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