1 #pragma once 2 3 #include "app.hpp" 4 #include "dbus_utility.hpp" 5 #include "generated/enums/resource.hpp" 6 #include "query.hpp" 7 #include "registries/privilege_registry.hpp" 8 #include "utils/chassis_utils.hpp" 9 #include "utils/dbus_utils.hpp" 10 #include "utils/json_utils.hpp" 11 12 #include <boost/system/error_code.hpp> 13 #include <boost/url/format.hpp> 14 15 #include <memory> 16 #include <optional> 17 #include <string> 18 19 namespace redfish 20 { 21 22 static constexpr std::array<std::string_view, 1> powerSupplyInterface = { 23 "xyz.openbmc_project.Inventory.Item.PowerSupply"}; 24 25 inline void updatePowerSupplyList( 26 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 27 const std::string& chassisId, 28 const dbus::utility::MapperGetSubTreePathsResponse& powerSupplyPaths) 29 { 30 nlohmann::json& powerSupplyList = asyncResp->res.jsonValue["Members"]; 31 for (const std::string& powerSupplyPath : powerSupplyPaths) 32 { 33 std::string powerSupplyName = 34 sdbusplus::message::object_path(powerSupplyPath).filename(); 35 if (powerSupplyName.empty()) 36 { 37 continue; 38 } 39 40 nlohmann::json item = nlohmann::json::object(); 41 item["@odata.id"] = boost::urls::format( 42 "/redfish/v1/Chassis/{}/PowerSubsystem/PowerSupplies/{}", chassisId, 43 powerSupplyName); 44 45 powerSupplyList.emplace_back(std::move(item)); 46 } 47 asyncResp->res.jsonValue["Members@odata.count"] = powerSupplyList.size(); 48 } 49 50 inline void 51 doPowerSupplyCollection(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 52 const std::string& chassisId, 53 const std::optional<std::string>& validChassisPath) 54 { 55 if (!validChassisPath) 56 { 57 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 58 return; 59 } 60 61 asyncResp->res.addHeader( 62 boost::beast::http::field::link, 63 "</redfish/v1/JsonSchemas/PowerSupplyCollection/PowerSupplyCollection.json>; rel=describedby"); 64 asyncResp->res.jsonValue["@odata.type"] = 65 "#PowerSupplyCollection.PowerSupplyCollection"; 66 asyncResp->res.jsonValue["Name"] = "Power Supply Collection"; 67 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 68 "/redfish/v1/Chassis/{}/PowerSubsystem/PowerSupplies", chassisId); 69 asyncResp->res.jsonValue["Description"] = 70 "The collection of PowerSupply resource instances."; 71 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 72 asyncResp->res.jsonValue["Members@odata.count"] = 0; 73 74 std::string powerPath = *validChassisPath + "/powered_by"; 75 dbus::utility::getAssociatedSubTreePaths( 76 powerPath, 77 sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0, 78 powerSupplyInterface, 79 [asyncResp, chassisId]( 80 const boost::system::error_code& ec, 81 const dbus::utility::MapperGetSubTreePathsResponse& subtreePaths) { 82 if (ec) 83 { 84 if (ec.value() != EBADR) 85 { 86 BMCWEB_LOG_ERROR("DBUS response error{}", ec.value()); 87 messages::internalError(asyncResp->res); 88 } 89 return; 90 } 91 92 updatePowerSupplyList(asyncResp, chassisId, subtreePaths); 93 }); 94 } 95 96 inline void handlePowerSupplyCollectionHead( 97 App& app, const crow::Request& req, 98 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 99 const std::string& chassisId) 100 { 101 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 102 { 103 return; 104 } 105 106 redfish::chassis_utils::getValidChassisPath( 107 asyncResp, chassisId, 108 [asyncResp, 109 chassisId](const std::optional<std::string>& validChassisPath) { 110 if (!validChassisPath) 111 { 112 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 113 return; 114 } 115 asyncResp->res.addHeader( 116 boost::beast::http::field::link, 117 "</redfish/v1/JsonSchemas/PowerSupplyCollection/PowerSupplyCollection.json>; rel=describedby"); 118 }); 119 } 120 121 inline void handlePowerSupplyCollectionGet( 122 App& app, const crow::Request& req, 123 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 124 const std::string& chassisId) 125 { 126 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 127 { 128 return; 129 } 130 131 redfish::chassis_utils::getValidChassisPath( 132 asyncResp, chassisId, 133 std::bind_front(doPowerSupplyCollection, asyncResp, chassisId)); 134 } 135 136 inline void requestRoutesPowerSupplyCollection(App& app) 137 { 138 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/") 139 .privileges(redfish::privileges::headPowerSupplyCollection) 140 .methods(boost::beast::http::verb::head)( 141 std::bind_front(handlePowerSupplyCollectionHead, std::ref(app))); 142 143 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/") 144 .privileges(redfish::privileges::getPowerSupplyCollection) 145 .methods(boost::beast::http::verb::get)( 146 std::bind_front(handlePowerSupplyCollectionGet, std::ref(app))); 147 } 148 149 inline bool checkPowerSupplyId(const std::string& powerSupplyPath, 150 const std::string& powerSupplyId) 151 { 152 std::string powerSupplyName = 153 sdbusplus::message::object_path(powerSupplyPath).filename(); 154 155 return !(powerSupplyName.empty() || powerSupplyName != powerSupplyId); 156 } 157 158 inline void getValidPowerSupplyPath( 159 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 160 const std::string& validChassisPath, const std::string& powerSupplyId, 161 std::function<void(const std::string& powerSupplyPath)>&& callback) 162 { 163 std::string powerPath = validChassisPath + "/powered_by"; 164 dbus::utility::getAssociatedSubTreePaths( 165 powerPath, 166 sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0, 167 powerSupplyInterface, 168 [asyncResp, powerSupplyId, callback{std::move(callback)}]( 169 const boost::system::error_code& ec, 170 const dbus::utility::MapperGetSubTreePathsResponse& subtreePaths) { 171 if (ec) 172 { 173 if (ec.value() != EBADR) 174 { 175 BMCWEB_LOG_ERROR( 176 "DBUS response error for getAssociatedSubTreePaths{}", 177 ec.value()); 178 messages::internalError(asyncResp->res); 179 return; 180 } 181 messages::resourceNotFound(asyncResp->res, "PowerSupplies", 182 powerSupplyId); 183 return; 184 } 185 186 for (const std::string& path : subtreePaths) 187 { 188 if (checkPowerSupplyId(path, powerSupplyId)) 189 { 190 callback(path); 191 return; 192 } 193 } 194 195 if (!subtreePaths.empty()) 196 { 197 BMCWEB_LOG_WARNING("Power supply not found: {}", powerSupplyId); 198 messages::resourceNotFound(asyncResp->res, "PowerSupplies", 199 powerSupplyId); 200 return; 201 } 202 }); 203 } 204 205 inline void 206 getPowerSupplyState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 207 const std::string& service, const std::string& path) 208 { 209 sdbusplus::asio::getProperty<bool>( 210 *crow::connections::systemBus, service, path, 211 "xyz.openbmc_project.Inventory.Item", "Present", 212 [asyncResp](const boost::system::error_code& ec, const bool value) { 213 if (ec) 214 { 215 if (ec.value() != EBADR) 216 { 217 BMCWEB_LOG_ERROR("DBUS response error for State {}", 218 ec.value()); 219 messages::internalError(asyncResp->res); 220 } 221 return; 222 } 223 224 if (!value) 225 { 226 asyncResp->res.jsonValue["Status"]["State"] = 227 resource::State::Absent; 228 } 229 }); 230 } 231 232 inline void 233 getPowerSupplyHealth(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 234 const std::string& service, const std::string& path) 235 { 236 sdbusplus::asio::getProperty<bool>( 237 *crow::connections::systemBus, service, path, 238 "xyz.openbmc_project.State.Decorator.OperationalStatus", "Functional", 239 [asyncResp](const boost::system::error_code& ec, const bool value) { 240 if (ec) 241 { 242 if (ec.value() != EBADR) 243 { 244 BMCWEB_LOG_ERROR("DBUS response error for Health {}", 245 ec.value()); 246 messages::internalError(asyncResp->res); 247 } 248 return; 249 } 250 251 if (!value) 252 { 253 asyncResp->res.jsonValue["Status"]["Health"] = 254 resource::Health::Critical; 255 } 256 }); 257 } 258 259 inline void 260 getPowerSupplyAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 261 const std::string& service, const std::string& path) 262 { 263 sdbusplus::asio::getAllProperties( 264 *crow::connections::systemBus, service, path, 265 "xyz.openbmc_project.Inventory.Decorator.Asset", 266 [asyncResp](const boost::system::error_code& ec, 267 const dbus::utility::DBusPropertiesMap& propertiesList) { 268 if (ec) 269 { 270 if (ec.value() != EBADR) 271 { 272 BMCWEB_LOG_ERROR("DBUS response error for Asset {}", 273 ec.value()); 274 messages::internalError(asyncResp->res); 275 } 276 return; 277 } 278 279 const std::string* partNumber = nullptr; 280 const std::string* serialNumber = nullptr; 281 const std::string* manufacturer = nullptr; 282 const std::string* model = nullptr; 283 const std::string* sparePartNumber = nullptr; 284 285 const bool success = sdbusplus::unpackPropertiesNoThrow( 286 dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber", 287 partNumber, "SerialNumber", serialNumber, "Manufacturer", 288 manufacturer, "Model", model, "SparePartNumber", sparePartNumber); 289 290 if (!success) 291 { 292 messages::internalError(asyncResp->res); 293 return; 294 } 295 296 if (partNumber != nullptr) 297 { 298 asyncResp->res.jsonValue["PartNumber"] = *partNumber; 299 } 300 301 if (serialNumber != nullptr) 302 { 303 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber; 304 } 305 306 if (manufacturer != nullptr) 307 { 308 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer; 309 } 310 311 if (model != nullptr) 312 { 313 asyncResp->res.jsonValue["Model"] = *model; 314 } 315 316 // SparePartNumber is optional on D-Bus so skip if it is empty 317 if (sparePartNumber != nullptr && !sparePartNumber->empty()) 318 { 319 asyncResp->res.jsonValue["SparePartNumber"] = *sparePartNumber; 320 } 321 }); 322 } 323 324 inline void getPowerSupplyFirmwareVersion( 325 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 326 const std::string& service, const std::string& path) 327 { 328 sdbusplus::asio::getProperty<std::string>( 329 *crow::connections::systemBus, service, path, 330 "xyz.openbmc_project.Software.Version", "Version", 331 [asyncResp](const boost::system::error_code& ec, 332 const std::string& value) { 333 if (ec) 334 { 335 if (ec.value() != EBADR) 336 { 337 BMCWEB_LOG_ERROR("DBUS response error for FirmwareVersion {}", 338 ec.value()); 339 messages::internalError(asyncResp->res); 340 } 341 return; 342 } 343 asyncResp->res.jsonValue["FirmwareVersion"] = value; 344 }); 345 } 346 347 inline void 348 getPowerSupplyLocation(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 349 const std::string& service, const std::string& path) 350 { 351 sdbusplus::asio::getProperty<std::string>( 352 *crow::connections::systemBus, service, path, 353 "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode", 354 [asyncResp](const boost::system::error_code& ec, 355 const std::string& value) { 356 if (ec) 357 { 358 if (ec.value() != EBADR) 359 { 360 BMCWEB_LOG_ERROR("DBUS response error for Location {}", 361 ec.value()); 362 messages::internalError(asyncResp->res); 363 } 364 return; 365 } 366 asyncResp->res.jsonValue["Location"]["PartLocation"]["ServiceLabel"] = 367 value; 368 }); 369 } 370 371 inline void handleGetEfficiencyResponse( 372 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 373 const boost::system::error_code& ec, uint32_t value) 374 { 375 if (ec) 376 { 377 if (ec.value() != EBADR) 378 { 379 BMCWEB_LOG_ERROR("DBUS response error for DeratingFactor {}", 380 ec.value()); 381 messages::internalError(asyncResp->res); 382 } 383 return; 384 } 385 // The PDI default value is 0, if it hasn't been set leave off 386 if (value == 0) 387 { 388 return; 389 } 390 391 nlohmann::json::array_t efficiencyRatings; 392 nlohmann::json::object_t efficiencyPercent; 393 efficiencyPercent["EfficiencyPercent"] = value; 394 efficiencyRatings.emplace_back(std::move(efficiencyPercent)); 395 asyncResp->res.jsonValue["EfficiencyRatings"] = 396 std::move(efficiencyRatings); 397 } 398 399 inline void handlePowerSupplyAttributesSubTreeResponse( 400 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 401 const boost::system::error_code& ec, 402 const dbus::utility::MapperGetSubTreeResponse& subtree) 403 { 404 if (ec) 405 { 406 if (ec.value() != EBADR) 407 { 408 BMCWEB_LOG_ERROR("DBUS response error for EfficiencyPercent {}", 409 ec.value()); 410 messages::internalError(asyncResp->res); 411 } 412 return; 413 } 414 415 if (subtree.empty()) 416 { 417 BMCWEB_LOG_DEBUG("Can't find Power Supply Attributes!"); 418 return; 419 } 420 421 if (subtree.size() != 1) 422 { 423 BMCWEB_LOG_ERROR( 424 "Unexpected number of paths returned by getSubTree: {}", 425 subtree.size()); 426 messages::internalError(asyncResp->res); 427 return; 428 } 429 430 const auto& [path, serviceMap] = *subtree.begin(); 431 const auto& [service, interfaces] = *serviceMap.begin(); 432 sdbusplus::asio::getProperty<uint32_t>( 433 *crow::connections::systemBus, service, path, 434 "xyz.openbmc_project.Control.PowerSupplyAttributes", "DeratingFactor", 435 [asyncResp](const boost::system::error_code& ec1, uint32_t value) { 436 handleGetEfficiencyResponse(asyncResp, ec1, value); 437 }); 438 } 439 440 inline void 441 getEfficiencyPercent(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 442 { 443 constexpr std::array<std::string_view, 1> efficiencyIntf = { 444 "xyz.openbmc_project.Control.PowerSupplyAttributes"}; 445 446 dbus::utility::getSubTree( 447 "/xyz/openbmc_project", 0, efficiencyIntf, 448 [asyncResp](const boost::system::error_code& ec, 449 const dbus::utility::MapperGetSubTreeResponse& subtree) { 450 handlePowerSupplyAttributesSubTreeResponse(asyncResp, ec, subtree); 451 }); 452 } 453 454 inline void 455 doPowerSupplyGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 456 const std::string& chassisId, 457 const std::string& powerSupplyId, 458 const std::optional<std::string>& validChassisPath) 459 { 460 if (!validChassisPath) 461 { 462 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 463 return; 464 } 465 466 // Get the correct Path and Service that match the input parameters 467 getValidPowerSupplyPath(asyncResp, *validChassisPath, powerSupplyId, 468 [asyncResp, chassisId, powerSupplyId]( 469 const std::string& powerSupplyPath) { 470 asyncResp->res.addHeader( 471 boost::beast::http::field::link, 472 "</redfish/v1/JsonSchemas/PowerSupply/PowerSupply.json>; rel=describedby"); 473 asyncResp->res.jsonValue["@odata.type"] = 474 "#PowerSupply.v1_5_0.PowerSupply"; 475 asyncResp->res.jsonValue["Name"] = "Power Supply"; 476 asyncResp->res.jsonValue["Id"] = powerSupplyId; 477 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 478 "/redfish/v1/Chassis/{}/PowerSubsystem/PowerSupplies/{}", chassisId, 479 powerSupplyId); 480 481 asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled; 482 asyncResp->res.jsonValue["Status"]["Health"] = resource::Health::OK; 483 484 dbus::utility::getDbusObject( 485 powerSupplyPath, powerSupplyInterface, 486 [asyncResp, 487 powerSupplyPath](const boost::system::error_code& ec, 488 const dbus::utility::MapperGetObject& object) { 489 if (ec || object.empty()) 490 { 491 messages::internalError(asyncResp->res); 492 return; 493 } 494 495 getPowerSupplyState(asyncResp, object.begin()->first, 496 powerSupplyPath); 497 getPowerSupplyHealth(asyncResp, object.begin()->first, 498 powerSupplyPath); 499 getPowerSupplyAsset(asyncResp, object.begin()->first, 500 powerSupplyPath); 501 getPowerSupplyFirmwareVersion(asyncResp, object.begin()->first, 502 powerSupplyPath); 503 getPowerSupplyLocation(asyncResp, object.begin()->first, 504 powerSupplyPath); 505 }); 506 507 getEfficiencyPercent(asyncResp); 508 }); 509 } 510 511 inline void 512 handlePowerSupplyHead(App& app, const crow::Request& req, 513 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 514 const std::string& chassisId, 515 const std::string& powerSupplyId) 516 { 517 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 518 { 519 return; 520 } 521 522 redfish::chassis_utils::getValidChassisPath( 523 asyncResp, chassisId, 524 [asyncResp, chassisId, 525 powerSupplyId](const std::optional<std::string>& validChassisPath) { 526 if (!validChassisPath) 527 { 528 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 529 return; 530 } 531 532 // Get the correct Path and Service that match the input parameters 533 getValidPowerSupplyPath(asyncResp, *validChassisPath, powerSupplyId, 534 [asyncResp](const std::string&) { 535 asyncResp->res.addHeader( 536 boost::beast::http::field::link, 537 "</redfish/v1/JsonSchemas/PowerSupply/PowerSupply.json>; rel=describedby"); 538 }); 539 }); 540 } 541 542 inline void 543 handlePowerSupplyGet(App& app, const crow::Request& req, 544 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 545 const std::string& chassisId, 546 const std::string& powerSupplyId) 547 { 548 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 549 { 550 return; 551 } 552 553 redfish::chassis_utils::getValidChassisPath( 554 asyncResp, chassisId, 555 std::bind_front(doPowerSupplyGet, asyncResp, chassisId, powerSupplyId)); 556 } 557 558 inline void requestRoutesPowerSupply(App& app) 559 { 560 BMCWEB_ROUTE( 561 app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/<str>/") 562 .privileges(redfish::privileges::headPowerSupply) 563 .methods(boost::beast::http::verb::head)( 564 std::bind_front(handlePowerSupplyHead, std::ref(app))); 565 566 BMCWEB_ROUTE( 567 app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/<str>/") 568 .privileges(redfish::privileges::getPowerSupply) 569 .methods(boost::beast::http::verb::get)( 570 std::bind_front(handlePowerSupplyGet, std::ref(app))); 571 } 572 573 } // namespace redfish 574