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