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