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