xref: /openbmc/bmcweb/features/redfish/lib/power_supply.hpp (revision 788fe6cfaef30f10e9ba9c6ad06b26d799225030)
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
369     doPowerSupplyGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
370                      const std::string& chassisId,
371                      const std::string& powerSupplyId,
372                      const std::optional<std::string>& validChassisPath)
373 {
374     if (!validChassisPath)
375     {
376         messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
377         return;
378     }
379 
380     // Get the correct Path and Service that match the input parameters
381     getValidPowerSupplyPath(asyncResp, *validChassisPath, powerSupplyId,
382                             [asyncResp, chassisId, powerSupplyId](
383                                 const std::string& powerSupplyPath) {
384         asyncResp->res.addHeader(
385             boost::beast::http::field::link,
386             "</redfish/v1/JsonSchemas/PowerSupply/PowerSupply.json>; rel=describedby");
387         asyncResp->res.jsonValue["@odata.type"] =
388             "#PowerSupply.v1_5_0.PowerSupply";
389         asyncResp->res.jsonValue["Name"] = "Power Supply";
390         asyncResp->res.jsonValue["Id"] = powerSupplyId;
391         asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
392             "/redfish/v1/Chassis/{}/PowerSubsystem/PowerSupplies/{}", chassisId,
393             powerSupplyId);
394 
395         asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
396         asyncResp->res.jsonValue["Status"]["Health"] = "OK";
397 
398         dbus::utility::getDbusObject(
399             powerSupplyPath, powerSupplyInterface,
400             [asyncResp,
401              powerSupplyPath](const boost::system::error_code& ec,
402                               const dbus::utility::MapperGetObject& object) {
403             if (ec || object.empty())
404             {
405                 messages::internalError(asyncResp->res);
406                 return;
407             }
408 
409             getPowerSupplyState(asyncResp, object.begin()->first,
410                                 powerSupplyPath);
411             getPowerSupplyHealth(asyncResp, object.begin()->first,
412                                  powerSupplyPath);
413             getPowerSupplyAsset(asyncResp, object.begin()->first,
414                                 powerSupplyPath);
415             getPowerSupplyFirmwareVersion(asyncResp, object.begin()->first,
416                                           powerSupplyPath);
417             getPowerSupplyLocation(asyncResp, object.begin()->first,
418                                    powerSupplyPath);
419             });
420     });
421 }
422 
423 inline void
424     handlePowerSupplyHead(App& app, const crow::Request& req,
425                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
426                           const std::string& chassisId,
427                           const std::string& powerSupplyId)
428 {
429     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
430     {
431         return;
432     }
433 
434     redfish::chassis_utils::getValidChassisPath(
435         asyncResp, chassisId,
436         [asyncResp, chassisId,
437          powerSupplyId](const std::optional<std::string>& validChassisPath) {
438         if (!validChassisPath)
439         {
440             messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
441             return;
442         }
443 
444         // Get the correct Path and Service that match the input parameters
445         getValidPowerSupplyPath(asyncResp, *validChassisPath, powerSupplyId,
446                                 [asyncResp](const std::string&) {
447             asyncResp->res.addHeader(
448                 boost::beast::http::field::link,
449                 "</redfish/v1/JsonSchemas/PowerSupply/PowerSupply.json>; rel=describedby");
450         });
451         });
452 }
453 
454 inline void
455     handlePowerSupplyGet(App& app, const crow::Request& req,
456                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
457                          const std::string& chassisId,
458                          const std::string& powerSupplyId)
459 {
460     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
461     {
462         return;
463     }
464 
465     redfish::chassis_utils::getValidChassisPath(
466         asyncResp, chassisId,
467         std::bind_front(doPowerSupplyGet, asyncResp, chassisId, powerSupplyId));
468 }
469 
470 inline void requestRoutesPowerSupply(App& app)
471 {
472     BMCWEB_ROUTE(
473         app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/<str>/")
474         .privileges(redfish::privileges::headPowerSupply)
475         .methods(boost::beast::http::verb::head)(
476             std::bind_front(handlePowerSupplyHead, std::ref(app)));
477 
478     BMCWEB_ROUTE(
479         app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/<str>/")
480         .privileges(redfish::privileges::getPowerSupply)
481         .methods(boost::beast::http::verb::get)(
482             std::bind_front(handlePowerSupplyGet, std::ref(app)));
483 }
484 
485 } // namespace redfish
486