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