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