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