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 #include "utils/time_utils.hpp" 12 13 #include <boost/system/error_code.hpp> 14 #include <boost/url/format.hpp> 15 16 #include <memory> 17 #include <optional> 18 #include <string> 19 20 namespace redfish 21 { 22 23 static constexpr std::array<std::string_view, 1> powerSupplyInterface = { 24 "xyz.openbmc_project.Inventory.Item.PowerSupply"}; 25 26 inline void updatePowerSupplyList( 27 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 28 const std::string& chassisId, 29 const dbus::utility::MapperGetSubTreePathsResponse& powerSupplyPaths) 30 { 31 nlohmann::json& powerSupplyList = asyncResp->res.jsonValue["Members"]; 32 for (const std::string& powerSupplyPath : powerSupplyPaths) 33 { 34 std::string powerSupplyName = 35 sdbusplus::message::object_path(powerSupplyPath).filename(); 36 if (powerSupplyName.empty()) 37 { 38 continue; 39 } 40 41 nlohmann::json item = nlohmann::json::object(); 42 item["@odata.id"] = boost::urls::format( 43 "/redfish/v1/Chassis/{}/PowerSubsystem/PowerSupplies/{}", chassisId, 44 powerSupplyName); 45 46 powerSupplyList.emplace_back(std::move(item)); 47 } 48 asyncResp->res.jsonValue["Members@odata.count"] = powerSupplyList.size(); 49 } 50 51 inline void doPowerSupplyCollection( 52 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 53 const std::string& chassisId, const boost::system::error_code& ec, 54 const dbus::utility::MapperGetSubTreePathsResponse& subtreePaths) 55 { 56 if (ec) 57 { 58 if (ec.value() != EBADR) 59 { 60 BMCWEB_LOG_ERROR("DBUS response error{}", ec.value()); 61 messages::internalError(asyncResp->res); 62 } 63 return; 64 } 65 asyncResp->res.addHeader( 66 boost::beast::http::field::link, 67 "</redfish/v1/JsonSchemas/PowerSupplyCollection/PowerSupplyCollection.json>; rel=describedby"); 68 asyncResp->res.jsonValue["@odata.type"] = 69 "#PowerSupplyCollection.PowerSupplyCollection"; 70 asyncResp->res.jsonValue["Name"] = "Power Supply Collection"; 71 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 72 "/redfish/v1/Chassis/{}/PowerSubsystem/PowerSupplies", chassisId); 73 asyncResp->res.jsonValue["Description"] = 74 "The collection of PowerSupply resource instances."; 75 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 76 asyncResp->res.jsonValue["Members@odata.count"] = 0; 77 78 updatePowerSupplyList(asyncResp, chassisId, subtreePaths); 79 } 80 81 inline void handlePowerSupplyCollectionHead( 82 App& app, const crow::Request& req, 83 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 84 const std::string& chassisId) 85 { 86 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 87 { 88 return; 89 } 90 91 redfish::chassis_utils::getValidChassisPath( 92 asyncResp, chassisId, 93 [asyncResp, 94 chassisId](const std::optional<std::string>& validChassisPath) { 95 if (!validChassisPath) 96 { 97 messages::resourceNotFound(asyncResp->res, "Chassis", 98 chassisId); 99 return; 100 } 101 asyncResp->res.addHeader( 102 boost::beast::http::field::link, 103 "</redfish/v1/JsonSchemas/PowerSupplyCollection/PowerSupplyCollection.json>; rel=describedby"); 104 }); 105 } 106 107 inline void handlePowerSupplyCollectionGet( 108 App& app, const crow::Request& req, 109 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 110 const std::string& chassisId) 111 { 112 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 113 { 114 return; 115 } 116 117 constexpr std::array<std::string_view, 2> chasisInterfaces = { 118 "xyz.openbmc_project.Inventory.Item.Board", 119 "xyz.openbmc_project.Inventory.Item.Chassis"}; 120 const std::string reqpath = "/xyz/openbmc_project/inventory"; 121 122 dbus::utility::getAssociatedSubTreePathsById( 123 chassisId, reqpath, chasisInterfaces, "powered_by", 124 powerSupplyInterface, 125 [asyncResp, chassisId]( 126 const boost::system::error_code& ec, 127 const dbus::utility::MapperGetSubTreePathsResponse& subtreePaths) { 128 doPowerSupplyCollection(asyncResp, chassisId, ec, subtreePaths); 129 }); 130 } 131 132 inline void requestRoutesPowerSupplyCollection(App& app) 133 { 134 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/") 135 .privileges(redfish::privileges::headPowerSupplyCollection) 136 .methods(boost::beast::http::verb::head)( 137 std::bind_front(handlePowerSupplyCollectionHead, std::ref(app))); 138 139 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/") 140 .privileges(redfish::privileges::getPowerSupplyCollection) 141 .methods(boost::beast::http::verb::get)( 142 std::bind_front(handlePowerSupplyCollectionGet, std::ref(app))); 143 } 144 145 inline void afterGetValidPowerSupplyPath( 146 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 147 const std::string& powerSupplyId, const boost::system::error_code& ec, 148 const dbus::utility::MapperGetSubTreeResponse& subtree, 149 const std::function<void(const std::string& powerSupplyPath, 150 const std::string& service)>& callback) 151 { 152 if (ec) 153 { 154 if (ec.value() != EBADR) 155 { 156 BMCWEB_LOG_ERROR("DBUS response error{}", ec.value()); 157 messages::internalError(asyncResp->res); 158 } 159 return; 160 } 161 for (const auto& [objectPath, service] : subtree) 162 { 163 sdbusplus::message::object_path path(objectPath); 164 if (path.filename() == powerSupplyId) 165 { 166 callback(path, service.begin()->first); 167 return; 168 } 169 } 170 171 BMCWEB_LOG_WARNING("Power supply not found: {}", powerSupplyId); 172 messages::resourceNotFound(asyncResp->res, "PowerSupplies", powerSupplyId); 173 } 174 175 inline void getValidPowerSupplyPath( 176 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 177 const std::string& chassisId, const std::string& powerSupplyId, 178 std::function<void(const std::string& powerSupplyPath, 179 const std::string& service)>&& callback) 180 { 181 constexpr std::array<std::string_view, 2> chasisInterfaces = { 182 "xyz.openbmc_project.Inventory.Item.Board", 183 "xyz.openbmc_project.Inventory.Item.Chassis"}; 184 const std::string reqpath = "/xyz/openbmc_project/inventory"; 185 186 dbus::utility::getAssociatedSubTreeById( 187 chassisId, reqpath, chasisInterfaces, "powered_by", 188 powerSupplyInterface, 189 [asyncResp, chassisId, powerSupplyId, callback{std::move(callback)}]( 190 const boost::system::error_code& ec, 191 const dbus::utility::MapperGetSubTreeResponse& subtree) { 192 afterGetValidPowerSupplyPath(asyncResp, powerSupplyId, ec, subtree, 193 callback); 194 }); 195 } 196 197 inline void 198 getPowerSupplyState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 199 const std::string& service, const std::string& path) 200 { 201 sdbusplus::asio::getProperty<bool>( 202 *crow::connections::systemBus, service, path, 203 "xyz.openbmc_project.Inventory.Item", "Present", 204 [asyncResp](const boost::system::error_code& ec, const bool value) { 205 if (ec) 206 { 207 if (ec.value() != EBADR) 208 { 209 BMCWEB_LOG_ERROR("DBUS response error for State {}", 210 ec.value()); 211 messages::internalError(asyncResp->res); 212 } 213 return; 214 } 215 216 if (!value) 217 { 218 asyncResp->res.jsonValue["Status"]["State"] = 219 resource::State::Absent; 220 } 221 }); 222 } 223 224 inline void 225 getPowerSupplyHealth(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 226 const std::string& service, const std::string& path) 227 { 228 sdbusplus::asio::getProperty<bool>( 229 *crow::connections::systemBus, service, path, 230 "xyz.openbmc_project.State.Decorator.OperationalStatus", "Functional", 231 [asyncResp](const boost::system::error_code& ec, const bool value) { 232 if (ec) 233 { 234 if (ec.value() != EBADR) 235 { 236 BMCWEB_LOG_ERROR("DBUS response error for Health {}", 237 ec.value()); 238 messages::internalError(asyncResp->res); 239 } 240 return; 241 } 242 243 if (!value) 244 { 245 asyncResp->res.jsonValue["Status"]["Health"] = 246 resource::Health::Critical; 247 } 248 }); 249 } 250 251 inline void 252 getPowerSupplyAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 253 const std::string& service, const std::string& path) 254 { 255 sdbusplus::asio::getAllProperties( 256 *crow::connections::systemBus, service, path, 257 "xyz.openbmc_project.Inventory.Decorator.Asset", 258 [asyncResp](const boost::system::error_code& ec, 259 const dbus::utility::DBusPropertiesMap& propertiesList) { 260 if (ec) 261 { 262 if (ec.value() != EBADR) 263 { 264 BMCWEB_LOG_ERROR("DBUS response error for Asset {}", 265 ec.value()); 266 messages::internalError(asyncResp->res); 267 } 268 return; 269 } 270 271 const std::string* partNumber = nullptr; 272 const std::string* serialNumber = nullptr; 273 const std::string* manufacturer = nullptr; 274 const std::string* model = nullptr; 275 const std::string* sparePartNumber = nullptr; 276 const std::string* buildDate = nullptr; 277 278 const bool success = sdbusplus::unpackPropertiesNoThrow( 279 dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber", 280 partNumber, "SerialNumber", serialNumber, "Manufacturer", 281 manufacturer, "Model", model, "SparePartNumber", 282 sparePartNumber, "BuildDate", buildDate); 283 284 if (!success) 285 { 286 messages::internalError(asyncResp->res); 287 return; 288 } 289 290 if (partNumber != nullptr) 291 { 292 asyncResp->res.jsonValue["PartNumber"] = *partNumber; 293 } 294 295 if (serialNumber != nullptr) 296 { 297 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber; 298 } 299 300 if (manufacturer != nullptr) 301 { 302 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer; 303 } 304 305 if (model != nullptr) 306 { 307 asyncResp->res.jsonValue["Model"] = *model; 308 } 309 310 // SparePartNumber is optional on D-Bus so skip if it is empty 311 if (sparePartNumber != nullptr && !sparePartNumber->empty()) 312 { 313 asyncResp->res.jsonValue["SparePartNumber"] = *sparePartNumber; 314 } 315 316 if (buildDate != nullptr) 317 { 318 time_utils::productionDateReport(asyncResp->res, *buildDate); 319 } 320 }); 321 } 322 323 inline void getPowerSupplyFirmwareVersion( 324 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 325 const std::string& service, const std::string& path) 326 { 327 sdbusplus::asio::getProperty<std::string>( 328 *crow::connections::systemBus, service, path, 329 "xyz.openbmc_project.Software.Version", "Version", 330 [asyncResp](const boost::system::error_code& ec, 331 const std::string& value) { 332 if (ec) 333 { 334 if (ec.value() != EBADR) 335 { 336 BMCWEB_LOG_ERROR( 337 "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 367 .jsonValue["Location"]["PartLocation"]["ServiceLabel"] = 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 doPowerSupplyGet( 455 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 456 const std::string& chassisId, const std::string& powerSupplyId, 457 const std::string& powerSupplyPath, const std::string& service) 458 { 459 asyncResp->res.addHeader( 460 boost::beast::http::field::link, 461 "</redfish/v1/JsonSchemas/PowerSupply/PowerSupply.json>; rel=describedby"); 462 asyncResp->res.jsonValue["@odata.type"] = "#PowerSupply.v1_5_0.PowerSupply"; 463 asyncResp->res.jsonValue["Name"] = "Power Supply"; 464 asyncResp->res.jsonValue["Id"] = powerSupplyId; 465 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 466 "/redfish/v1/Chassis/{}/PowerSubsystem/PowerSupplies/{}", chassisId, 467 powerSupplyId); 468 469 asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled; 470 asyncResp->res.jsonValue["Status"]["Health"] = resource::Health::OK; 471 472 getPowerSupplyState(asyncResp, service, powerSupplyPath); 473 getPowerSupplyHealth(asyncResp, service, powerSupplyPath); 474 getPowerSupplyAsset(asyncResp, service, powerSupplyPath); 475 getPowerSupplyFirmwareVersion(asyncResp, service, powerSupplyPath); 476 getPowerSupplyLocation(asyncResp, service, powerSupplyPath); 477 getEfficiencyPercent(asyncResp); 478 } 479 480 inline void handlePowerSupplyHead( 481 App& app, const crow::Request& req, 482 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 483 const std::string& chassisId, const std::string& powerSupplyId) 484 { 485 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 486 { 487 return; 488 } 489 490 // Get the correct Path and Service that match the input parameters 491 getValidPowerSupplyPath( 492 asyncResp, chassisId, powerSupplyId, 493 [asyncResp](const std::string&, const std::string&) { 494 asyncResp->res.addHeader( 495 boost::beast::http::field::link, 496 "</redfish/v1/JsonSchemas/PowerSupply/PowerSupply.json>; rel=describedby"); 497 }); 498 } 499 500 inline void handlePowerSupplyGet( 501 App& app, const crow::Request& req, 502 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 503 const std::string& chassisId, const std::string& powerSupplyId) 504 { 505 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 506 { 507 return; 508 } 509 getValidPowerSupplyPath( 510 asyncResp, chassisId, powerSupplyId, 511 std::bind_front(doPowerSupplyGet, asyncResp, chassisId, powerSupplyId)); 512 } 513 514 inline void requestRoutesPowerSupply(App& app) 515 { 516 BMCWEB_ROUTE( 517 app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/<str>/") 518 .privileges(redfish::privileges::headPowerSupply) 519 .methods(boost::beast::http::verb::head)( 520 std::bind_front(handlePowerSupplyHead, std::ref(app))); 521 522 BMCWEB_ROUTE( 523 app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/<str>/") 524 .privileges(redfish::privileges::getPowerSupply) 525 .methods(boost::beast::http::verb::get)( 526 std::bind_front(handlePowerSupplyGet, std::ref(app))); 527 } 528 529 } // namespace redfish 530