1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3 #pragma once 4 5 #include "app.hpp" 6 #include "async_resp.hpp" 7 #include "dbus_utility.hpp" 8 #include "error_messages.hpp" 9 #include "generated/enums/resource.hpp" 10 #include "http_request.hpp" 11 #include "logging.hpp" 12 #include "query.hpp" 13 #include "registries/privilege_registry.hpp" 14 #include "utils/chassis_utils.hpp" 15 16 #include <asm-generic/errno.h> 17 18 #include <boost/beast/http/field.hpp> 19 #include <boost/beast/http/verb.hpp> 20 #include <boost/system/error_code.hpp> 21 #include <boost/url/format.hpp> 22 #include <nlohmann/json.hpp> 23 #include <sdbusplus/unpack_properties.hpp> 24 25 #include <array> 26 #include <functional> 27 #include <memory> 28 #include <optional> 29 #include <string> 30 #include <string_view> 31 #include <utility> 32 33 namespace redfish 34 { 35 constexpr std::array<std::string_view, 1> fanInterface = { 36 "xyz.openbmc_project.Inventory.Item.Fan"}; 37 38 inline void updateFanList( 39 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 40 const std::string& chassisId, 41 const dbus::utility::MapperGetSubTreePathsResponse& fanPaths) 42 { 43 nlohmann::json& fanList = asyncResp->res.jsonValue["Members"]; 44 for (const std::string& fanPath : fanPaths) 45 { 46 std::string fanName = 47 sdbusplus::message::object_path(fanPath).filename(); 48 if (fanName.empty()) 49 { 50 continue; 51 } 52 53 nlohmann::json item = nlohmann::json::object(); 54 item["@odata.id"] = boost::urls::format( 55 "/redfish/v1/Chassis/{}/ThermalSubsystem/Fans/{}", chassisId, 56 fanName); 57 58 fanList.emplace_back(std::move(item)); 59 } 60 asyncResp->res.jsonValue["Members@odata.count"] = fanList.size(); 61 } 62 63 inline void getFanPaths( 64 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 65 const std::string& validChassisPath, 66 const std::function<void(const dbus::utility::MapperGetSubTreePathsResponse& 67 fanPaths)>& callback) 68 { 69 sdbusplus::message::object_path endpointPath{validChassisPath}; 70 endpointPath /= "cooled_by"; 71 72 dbus::utility::getAssociatedSubTreePaths( 73 endpointPath, 74 sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0, 75 fanInterface, 76 [asyncResp, callback]( 77 const boost::system::error_code& ec, 78 const dbus::utility::MapperGetSubTreePathsResponse& subtreePaths) { 79 if (ec) 80 { 81 if (ec.value() != EBADR) 82 { 83 BMCWEB_LOG_ERROR( 84 "DBUS response error for getAssociatedSubTreePaths {}", 85 ec.value()); 86 messages::internalError(asyncResp->res); 87 } 88 return; 89 } 90 callback(subtreePaths); 91 }); 92 } 93 94 inline void doFanCollection(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 95 const std::string& chassisId, 96 const std::optional<std::string>& validChassisPath) 97 { 98 if (!validChassisPath) 99 { 100 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 101 return; 102 } 103 104 asyncResp->res.addHeader( 105 boost::beast::http::field::link, 106 "</redfish/v1/JsonSchemas/FanCollection/FanCollection.json>; rel=describedby"); 107 asyncResp->res.jsonValue["@odata.type"] = "#FanCollection.FanCollection"; 108 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 109 "/redfish/v1/Chassis/{}/ThermalSubsystem/Fans", chassisId); 110 asyncResp->res.jsonValue["Name"] = "Fan Collection"; 111 asyncResp->res.jsonValue["Description"] = 112 "The collection of Fan resource instances " + chassisId; 113 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 114 asyncResp->res.jsonValue["Members@odata.count"] = 0; 115 116 getFanPaths(asyncResp, *validChassisPath, 117 std::bind_front(updateFanList, asyncResp, chassisId)); 118 } 119 120 inline void handleFanCollectionHead( 121 App& app, const crow::Request& req, 122 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 123 const std::string& chassisId) 124 { 125 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 126 { 127 return; 128 } 129 130 redfish::chassis_utils::getValidChassisPath( 131 asyncResp, chassisId, 132 [asyncResp, 133 chassisId](const std::optional<std::string>& validChassisPath) { 134 if (!validChassisPath) 135 { 136 messages::resourceNotFound(asyncResp->res, "Chassis", 137 chassisId); 138 return; 139 } 140 asyncResp->res.addHeader( 141 boost::beast::http::field::link, 142 "</redfish/v1/JsonSchemas/FanCollection/FanCollection.json>; rel=describedby"); 143 }); 144 } 145 146 inline void handleFanCollectionGet( 147 App& app, const crow::Request& req, 148 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 149 const std::string& chassisId) 150 { 151 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 152 { 153 return; 154 } 155 156 redfish::chassis_utils::getValidChassisPath( 157 asyncResp, chassisId, 158 std::bind_front(doFanCollection, asyncResp, chassisId)); 159 } 160 161 inline void requestRoutesFanCollection(App& app) 162 { 163 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/") 164 .privileges(redfish::privileges::headFanCollection) 165 .methods(boost::beast::http::verb::head)( 166 std::bind_front(handleFanCollectionHead, std::ref(app))); 167 168 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/") 169 .privileges(redfish::privileges::getFanCollection) 170 .methods(boost::beast::http::verb::get)( 171 std::bind_front(handleFanCollectionGet, std::ref(app))); 172 } 173 174 inline bool checkFanId(const std::string& fanPath, const std::string& fanId) 175 { 176 std::string fanName = sdbusplus::message::object_path(fanPath).filename(); 177 178 return !(fanName.empty() || fanName != fanId); 179 } 180 181 inline void handleFanPath( 182 const std::string& fanId, 183 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 184 const dbus::utility::MapperGetSubTreePathsResponse& fanPaths, 185 const std::function<void(const std::string& fanPath, 186 const std::string& service)>& callback) 187 { 188 for (const auto& fanPath : fanPaths) 189 { 190 if (!checkFanId(fanPath, fanId)) 191 { 192 continue; 193 } 194 dbus::utility::getDbusObject( 195 fanPath, fanInterface, 196 [fanPath, asyncResp, 197 callback](const boost::system::error_code& ec, 198 const dbus::utility::MapperGetObject& object) { 199 if (ec || object.empty()) 200 { 201 BMCWEB_LOG_ERROR("DBUS response error on getDbusObject {}", 202 ec.value()); 203 messages::internalError(asyncResp->res); 204 return; 205 } 206 callback(fanPath, object.begin()->first); 207 }); 208 209 return; 210 } 211 BMCWEB_LOG_WARNING("Fan not found {}", fanId); 212 messages::resourceNotFound(asyncResp->res, "Fan", fanId); 213 } 214 215 inline void getValidFanPath( 216 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 217 const std::string& validChassisPath, const std::string& fanId, 218 const std::function<void(const std::string& fanPath, 219 const std::string& service)>& callback) 220 { 221 getFanPaths( 222 asyncResp, validChassisPath, 223 [fanId, asyncResp, callback]( 224 const dbus::utility::MapperGetSubTreePathsResponse& fanPaths) { 225 handleFanPath(fanId, asyncResp, fanPaths, callback); 226 }); 227 } 228 229 inline void addFanCommonProperties(crow::Response& resp, 230 const std::string& chassisId, 231 const std::string& fanId) 232 { 233 resp.addHeader(boost::beast::http::field::link, 234 "</redfish/v1/JsonSchemas/Fan/Fan.json>; rel=describedby"); 235 resp.jsonValue["@odata.type"] = "#Fan.v1_3_0.Fan"; 236 resp.jsonValue["Name"] = "Fan"; 237 resp.jsonValue["Id"] = fanId; 238 resp.jsonValue["@odata.id"] = boost::urls::format( 239 "/redfish/v1/Chassis/{}/ThermalSubsystem/Fans/{}", chassisId, fanId); 240 resp.jsonValue["Status"]["State"] = resource::State::Enabled; 241 resp.jsonValue["Status"]["Health"] = resource::Health::OK; 242 } 243 244 inline void getFanHealth(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 245 const std::string& fanPath, const std::string& service) 246 { 247 dbus::utility::getProperty<bool>( 248 service, fanPath, 249 "xyz.openbmc_project.State.Decorator.OperationalStatus", "Functional", 250 [asyncResp](const boost::system::error_code& ec, const bool value) { 251 if (ec) 252 { 253 if (ec.value() != EBADR) 254 { 255 BMCWEB_LOG_ERROR("DBUS response error for Health {}", 256 ec.value()); 257 messages::internalError(asyncResp->res); 258 } 259 return; 260 } 261 262 if (!value) 263 { 264 asyncResp->res.jsonValue["Status"]["Health"] = 265 resource::Health::Critical; 266 } 267 }); 268 } 269 270 inline void getFanState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 271 const std::string& fanPath, const std::string& service) 272 { 273 dbus::utility::getProperty<bool>( 274 service, fanPath, "xyz.openbmc_project.Inventory.Item", "Present", 275 [asyncResp](const boost::system::error_code& ec, const bool value) { 276 if (ec) 277 { 278 if (ec.value() != EBADR) 279 { 280 BMCWEB_LOG_ERROR("DBUS response error for State {}", 281 ec.value()); 282 messages::internalError(asyncResp->res); 283 } 284 return; 285 } 286 287 if (!value) 288 { 289 asyncResp->res.jsonValue["Status"]["State"] = 290 resource::State::Absent; 291 } 292 }); 293 } 294 295 inline void getFanAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 296 const std::string& fanPath, const std::string& service) 297 { 298 dbus::utility::getAllProperties( 299 service, fanPath, "xyz.openbmc_project.Inventory.Decorator.Asset", 300 [fanPath, asyncResp{asyncResp}]( 301 const boost::system::error_code& ec, 302 const dbus::utility::DBusPropertiesMap& assetList) { 303 if (ec) 304 { 305 if (ec.value() != EBADR) 306 { 307 BMCWEB_LOG_ERROR("DBUS response error for Properties{}", 308 ec.value()); 309 messages::internalError(asyncResp->res); 310 } 311 return; 312 } 313 const std::string* manufacturer = nullptr; 314 const std::string* model = nullptr; 315 const std::string* partNumber = nullptr; 316 const std::string* serialNumber = nullptr; 317 const std::string* sparePartNumber = nullptr; 318 319 const bool success = sdbusplus::unpackPropertiesNoThrow( 320 dbus_utils::UnpackErrorPrinter(), assetList, "Manufacturer", 321 manufacturer, "Model", model, "PartNumber", partNumber, 322 "SerialNumber", serialNumber, "SparePartNumber", 323 sparePartNumber); 324 if (!success) 325 { 326 messages::internalError(asyncResp->res); 327 return; 328 } 329 if (manufacturer != nullptr) 330 { 331 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer; 332 } 333 if (model != nullptr) 334 { 335 asyncResp->res.jsonValue["Model"] = *model; 336 } 337 if (partNumber != nullptr) 338 { 339 asyncResp->res.jsonValue["PartNumber"] = *partNumber; 340 } 341 if (serialNumber != nullptr) 342 { 343 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber; 344 } 345 if (sparePartNumber != nullptr && !sparePartNumber->empty()) 346 { 347 asyncResp->res.jsonValue["SparePartNumber"] = *sparePartNumber; 348 } 349 }); 350 } 351 352 inline void getFanLocation(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 353 const std::string& fanPath, 354 const std::string& service) 355 { 356 dbus::utility::getProperty<std::string>( 357 service, fanPath, 358 "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode", 359 [asyncResp](const boost::system::error_code& ec, 360 const std::string& property) { 361 if (ec) 362 { 363 if (ec.value() != EBADR) 364 { 365 BMCWEB_LOG_ERROR("DBUS response error for Location{}", 366 ec.value()); 367 messages::internalError(asyncResp->res); 368 } 369 return; 370 } 371 asyncResp->res 372 .jsonValue["Location"]["PartLocation"]["ServiceLabel"] = 373 property; 374 }); 375 } 376 377 inline void afterGetValidFanPath( 378 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 379 const std::string& chassisId, const std::string& fanId, 380 const std::string& fanPath, const std::string& service) 381 { 382 addFanCommonProperties(asyncResp->res, chassisId, fanId); 383 getFanState(asyncResp, fanPath, service); 384 getFanHealth(asyncResp, fanPath, service); 385 getFanAsset(asyncResp, fanPath, service); 386 getFanLocation(asyncResp, fanPath, service); 387 } 388 389 inline void doFanGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 390 const std::string& chassisId, const std::string& fanId, 391 const std::optional<std::string>& validChassisPath) 392 { 393 if (!validChassisPath) 394 { 395 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 396 return; 397 } 398 399 getValidFanPath( 400 asyncResp, *validChassisPath, fanId, 401 std::bind_front(afterGetValidFanPath, asyncResp, chassisId, fanId)); 402 } 403 404 inline void handleFanHead(App& app, const crow::Request& req, 405 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 406 const std::string& chassisId, 407 const std::string& fanId) 408 { 409 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 410 { 411 return; 412 } 413 414 redfish::chassis_utils::getValidChassisPath( 415 asyncResp, chassisId, 416 [asyncResp, chassisId, 417 fanId](const std::optional<std::string>& validChassisPath) { 418 if (!validChassisPath) 419 { 420 messages::resourceNotFound(asyncResp->res, "Chassis", 421 chassisId); 422 return; 423 } 424 getValidFanPath( 425 asyncResp, *validChassisPath, fanId, 426 [asyncResp](const std::string&, const std::string&) { 427 asyncResp->res.addHeader( 428 boost::beast::http::field::link, 429 "</redfish/v1/JsonSchemas/Fan/Fan.json>; rel=describedby"); 430 }); 431 }); 432 } 433 434 inline void handleFanGet(App& app, const crow::Request& req, 435 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 436 const std::string& chassisId, const std::string& fanId) 437 { 438 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 439 { 440 return; 441 } 442 443 redfish::chassis_utils::getValidChassisPath( 444 asyncResp, chassisId, 445 std::bind_front(doFanGet, asyncResp, chassisId, fanId)); 446 } 447 448 inline void requestRoutesFan(App& app) 449 { 450 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/") 451 .privileges(redfish::privileges::headFan) 452 .methods(boost::beast::http::verb::head)( 453 std::bind_front(handleFanHead, std::ref(app))); 454 455 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/") 456 .privileges(redfish::privileges::getFan) 457 .methods(boost::beast::http::verb::get)( 458 std::bind_front(handleFanGet, std::ref(app))); 459 } 460 461 } // namespace redfish 462