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