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