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 
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
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 
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 
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 
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 
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 
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
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
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
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 
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
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 
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 
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
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 
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 
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 
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 
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