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 dbus::utility::getProperty<bool>( 202 service, path, "xyz.openbmc_project.Inventory.Item", "Present", 203 [asyncResp](const boost::system::error_code& ec, const bool value) { 204 if (ec) 205 { 206 if (ec.value() != EBADR) 207 { 208 BMCWEB_LOG_ERROR("DBUS response error for State {}", 209 ec.value()); 210 messages::internalError(asyncResp->res); 211 } 212 return; 213 } 214 215 if (!value) 216 { 217 asyncResp->res.jsonValue["Status"]["State"] = 218 resource::State::Absent; 219 } 220 }); 221 } 222 223 inline void 224 getPowerSupplyHealth(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 225 const std::string& service, const std::string& path) 226 { 227 dbus::utility::getProperty<bool>( 228 service, path, "xyz.openbmc_project.State.Decorator.OperationalStatus", 229 "Functional", 230 [asyncResp](const boost::system::error_code& ec, const bool value) { 231 if (ec) 232 { 233 if (ec.value() != EBADR) 234 { 235 BMCWEB_LOG_ERROR("DBUS response error for Health {}", 236 ec.value()); 237 messages::internalError(asyncResp->res); 238 } 239 return; 240 } 241 242 if (!value) 243 { 244 asyncResp->res.jsonValue["Status"]["Health"] = 245 resource::Health::Critical; 246 } 247 }); 248 } 249 250 inline void 251 getPowerSupplyAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 252 const std::string& service, const std::string& path) 253 { 254 dbus::utility::getAllProperties( 255 service, path, "xyz.openbmc_project.Inventory.Decorator.Asset", 256 [asyncResp](const boost::system::error_code& ec, 257 const dbus::utility::DBusPropertiesMap& propertiesList) { 258 if (ec) 259 { 260 if (ec.value() != EBADR) 261 { 262 BMCWEB_LOG_ERROR("DBUS response error for Asset {}", 263 ec.value()); 264 messages::internalError(asyncResp->res); 265 } 266 return; 267 } 268 269 const std::string* partNumber = nullptr; 270 const std::string* serialNumber = nullptr; 271 const std::string* manufacturer = nullptr; 272 const std::string* model = nullptr; 273 const std::string* sparePartNumber = nullptr; 274 const std::string* buildDate = nullptr; 275 276 const bool success = sdbusplus::unpackPropertiesNoThrow( 277 dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber", 278 partNumber, "SerialNumber", serialNumber, "Manufacturer", 279 manufacturer, "Model", model, "SparePartNumber", 280 sparePartNumber, "BuildDate", buildDate); 281 282 if (!success) 283 { 284 messages::internalError(asyncResp->res); 285 return; 286 } 287 288 if (partNumber != nullptr) 289 { 290 asyncResp->res.jsonValue["PartNumber"] = *partNumber; 291 } 292 293 if (serialNumber != nullptr) 294 { 295 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber; 296 } 297 298 if (manufacturer != nullptr) 299 { 300 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer; 301 } 302 303 if (model != nullptr) 304 { 305 asyncResp->res.jsonValue["Model"] = *model; 306 } 307 308 // SparePartNumber is optional on D-Bus so skip if it is empty 309 if (sparePartNumber != nullptr && !sparePartNumber->empty()) 310 { 311 asyncResp->res.jsonValue["SparePartNumber"] = *sparePartNumber; 312 } 313 314 if (buildDate != nullptr) 315 { 316 time_utils::productionDateReport(asyncResp->res, *buildDate); 317 } 318 }); 319 } 320 321 inline void getPowerSupplyFirmwareVersion( 322 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 323 const std::string& service, const std::string& path) 324 { 325 dbus::utility::getProperty<std::string>( 326 service, path, "xyz.openbmc_project.Software.Version", "Version", 327 [asyncResp](const boost::system::error_code& ec, 328 const std::string& value) { 329 if (ec) 330 { 331 if (ec.value() != EBADR) 332 { 333 BMCWEB_LOG_ERROR( 334 "DBUS response error for FirmwareVersion {}", 335 ec.value()); 336 messages::internalError(asyncResp->res); 337 } 338 return; 339 } 340 asyncResp->res.jsonValue["FirmwareVersion"] = value; 341 }); 342 } 343 344 inline void 345 getPowerSupplyLocation(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 346 const std::string& service, const std::string& path) 347 { 348 dbus::utility::getProperty<std::string>( 349 service, path, "xyz.openbmc_project.Inventory.Decorator.LocationCode", 350 "LocationCode", 351 [asyncResp](const boost::system::error_code& ec, 352 const std::string& value) { 353 if (ec) 354 { 355 if (ec.value() != EBADR) 356 { 357 BMCWEB_LOG_ERROR("DBUS response error for Location {}", 358 ec.value()); 359 messages::internalError(asyncResp->res); 360 } 361 return; 362 } 363 asyncResp->res 364 .jsonValue["Location"]["PartLocation"]["ServiceLabel"] = value; 365 }); 366 } 367 368 inline void handleGetEfficiencyResponse( 369 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 370 const boost::system::error_code& ec, uint32_t value) 371 { 372 if (ec) 373 { 374 if (ec.value() != EBADR) 375 { 376 BMCWEB_LOG_ERROR("DBUS response error for DeratingFactor {}", 377 ec.value()); 378 messages::internalError(asyncResp->res); 379 } 380 return; 381 } 382 // The PDI default value is 0, if it hasn't been set leave off 383 if (value == 0) 384 { 385 return; 386 } 387 388 nlohmann::json::array_t efficiencyRatings; 389 nlohmann::json::object_t efficiencyPercent; 390 efficiencyPercent["EfficiencyPercent"] = value; 391 efficiencyRatings.emplace_back(std::move(efficiencyPercent)); 392 asyncResp->res.jsonValue["EfficiencyRatings"] = 393 std::move(efficiencyRatings); 394 } 395 396 inline void handlePowerSupplyAttributesSubTreeResponse( 397 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 398 const boost::system::error_code& ec, 399 const dbus::utility::MapperGetSubTreeResponse& subtree) 400 { 401 if (ec) 402 { 403 if (ec.value() != EBADR) 404 { 405 BMCWEB_LOG_ERROR("DBUS response error for EfficiencyPercent {}", 406 ec.value()); 407 messages::internalError(asyncResp->res); 408 } 409 return; 410 } 411 412 if (subtree.empty()) 413 { 414 BMCWEB_LOG_DEBUG("Can't find Power Supply Attributes!"); 415 return; 416 } 417 418 if (subtree.size() != 1) 419 { 420 BMCWEB_LOG_ERROR( 421 "Unexpected number of paths returned by getSubTree: {}", 422 subtree.size()); 423 messages::internalError(asyncResp->res); 424 return; 425 } 426 427 const auto& [path, serviceMap] = *subtree.begin(); 428 const auto& [service, interfaces] = *serviceMap.begin(); 429 dbus::utility::getProperty<uint32_t>( 430 service, path, "xyz.openbmc_project.Control.PowerSupplyAttributes", 431 "DeratingFactor", 432 [asyncResp](const boost::system::error_code& ec1, uint32_t value) { 433 handleGetEfficiencyResponse(asyncResp, ec1, value); 434 }); 435 } 436 437 inline void 438 getEfficiencyPercent(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 439 { 440 constexpr std::array<std::string_view, 1> efficiencyIntf = { 441 "xyz.openbmc_project.Control.PowerSupplyAttributes"}; 442 443 dbus::utility::getSubTree( 444 "/xyz/openbmc_project", 0, efficiencyIntf, 445 [asyncResp](const boost::system::error_code& ec, 446 const dbus::utility::MapperGetSubTreeResponse& subtree) { 447 handlePowerSupplyAttributesSubTreeResponse(asyncResp, ec, subtree); 448 }); 449 } 450 451 inline void doPowerSupplyGet( 452 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 453 const std::string& chassisId, const std::string& powerSupplyId, 454 const std::string& powerSupplyPath, const std::string& service) 455 { 456 asyncResp->res.addHeader( 457 boost::beast::http::field::link, 458 "</redfish/v1/JsonSchemas/PowerSupply/PowerSupply.json>; rel=describedby"); 459 asyncResp->res.jsonValue["@odata.type"] = "#PowerSupply.v1_5_0.PowerSupply"; 460 asyncResp->res.jsonValue["Name"] = "Power Supply"; 461 asyncResp->res.jsonValue["Id"] = powerSupplyId; 462 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 463 "/redfish/v1/Chassis/{}/PowerSubsystem/PowerSupplies/{}", chassisId, 464 powerSupplyId); 465 466 asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled; 467 asyncResp->res.jsonValue["Status"]["Health"] = resource::Health::OK; 468 469 getPowerSupplyState(asyncResp, service, powerSupplyPath); 470 getPowerSupplyHealth(asyncResp, service, powerSupplyPath); 471 getPowerSupplyAsset(asyncResp, service, powerSupplyPath); 472 getPowerSupplyFirmwareVersion(asyncResp, service, powerSupplyPath); 473 getPowerSupplyLocation(asyncResp, service, powerSupplyPath); 474 getEfficiencyPercent(asyncResp); 475 } 476 477 inline void handlePowerSupplyHead( 478 App& app, const crow::Request& req, 479 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 480 const std::string& chassisId, const std::string& powerSupplyId) 481 { 482 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 483 { 484 return; 485 } 486 487 // Get the correct Path and Service that match the input parameters 488 getValidPowerSupplyPath( 489 asyncResp, chassisId, powerSupplyId, 490 [asyncResp](const std::string&, const std::string&) { 491 asyncResp->res.addHeader( 492 boost::beast::http::field::link, 493 "</redfish/v1/JsonSchemas/PowerSupply/PowerSupply.json>; rel=describedby"); 494 }); 495 } 496 497 inline void handlePowerSupplyGet( 498 App& app, const crow::Request& req, 499 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 500 const std::string& chassisId, const std::string& powerSupplyId) 501 { 502 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 503 { 504 return; 505 } 506 getValidPowerSupplyPath( 507 asyncResp, chassisId, powerSupplyId, 508 std::bind_front(doPowerSupplyGet, asyncResp, chassisId, powerSupplyId)); 509 } 510 511 inline void requestRoutesPowerSupply(App& app) 512 { 513 BMCWEB_ROUTE( 514 app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/<str>/") 515 .privileges(redfish::privileges::headPowerSupply) 516 .methods(boost::beast::http::verb::head)( 517 std::bind_front(handlePowerSupplyHead, std::ref(app))); 518 519 BMCWEB_ROUTE( 520 app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/<str>/") 521 .privileges(redfish::privileges::getPowerSupply) 522 .methods(boost::beast::http::verb::get)( 523 std::bind_front(handlePowerSupplyGet, std::ref(app))); 524 } 525 526 } // namespace redfish 527