xref: /openbmc/bmcweb/redfish-core/lib/power_supply.hpp (revision 8a7c4b475469c8811dffe265992b903060aad65f)
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 inline void
22     updatePowerSupplyList(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
23                           const std::string& chassisId,
24                           const std::string& powerSupplyPath)
25 {
26     std::string powerSupplyName =
27         sdbusplus::message::object_path(powerSupplyPath).filename();
28     if (powerSupplyName.empty())
29     {
30         return;
31     }
32 
33     nlohmann::json item = nlohmann::json::object();
34     item["@odata.id"] = boost::urls::format(
35         "/redfish/v1/Chassis/{}/PowerSubsystem/PowerSupplies/{}", chassisId,
36         powerSupplyName);
37 
38     nlohmann::json& powerSupplyList = asyncResp->res.jsonValue["Members"];
39     powerSupplyList.emplace_back(std::move(item));
40     asyncResp->res.jsonValue["Members@odata.count"] = powerSupplyList.size();
41 }
42 
43 inline void
44     doPowerSupplyCollection(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
45                             const std::string& chassisId,
46                             const std::optional<std::string>& validChassisPath)
47 {
48     if (!validChassisPath)
49     {
50         messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
51         return;
52     }
53 
54     asyncResp->res.addHeader(
55         boost::beast::http::field::link,
56         "</redfish/v1/JsonSchemas/PowerSupplyCollection/PowerSupplyCollection.json>; rel=describedby");
57     asyncResp->res.jsonValue["@odata.type"] =
58         "#PowerSupplyCollection.PowerSupplyCollection";
59     asyncResp->res.jsonValue["Name"] = "Power Supply Collection";
60     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
61         "/redfish/v1/Chassis/{}/PowerSubsystem/PowerSupplies", chassisId);
62     asyncResp->res.jsonValue["Description"] =
63         "The collection of PowerSupply resource instances.";
64     asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
65     asyncResp->res.jsonValue["Members@odata.count"] = 0;
66 
67     std::string powerPath = *validChassisPath + "/powered_by";
68     dbus::utility::getAssociationEndPoints(
69         powerPath, [asyncResp, chassisId](
70                        const boost::system::error_code& ec,
71                        const dbus::utility::MapperEndPoints& endpoints) {
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 
82             for (const auto& endpoint : endpoints)
83             {
84                 updatePowerSupplyList(asyncResp, chassisId, endpoint);
85             }
86         });
87 }
88 
89 inline void handlePowerSupplyCollectionHead(
90     App& app, const crow::Request& req,
91     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
92     const std::string& chassisId)
93 {
94     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
95     {
96         return;
97     }
98 
99     redfish::chassis_utils::getValidChassisPath(
100         asyncResp, chassisId,
101         [asyncResp,
102          chassisId](const std::optional<std::string>& validChassisPath) {
103         if (!validChassisPath)
104         {
105             messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
106             return;
107         }
108         asyncResp->res.addHeader(
109             boost::beast::http::field::link,
110             "</redfish/v1/JsonSchemas/PowerSupplyCollection/PowerSupplyCollection.json>; rel=describedby");
111         });
112 }
113 
114 inline void handlePowerSupplyCollectionGet(
115     App& app, const crow::Request& req,
116     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
117     const std::string& chassisId)
118 {
119     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
120     {
121         return;
122     }
123 
124     redfish::chassis_utils::getValidChassisPath(
125         asyncResp, chassisId,
126         std::bind_front(doPowerSupplyCollection, asyncResp, chassisId));
127 }
128 
129 inline void requestRoutesPowerSupplyCollection(App& app)
130 {
131     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/")
132         .privileges(redfish::privileges::headPowerSupplyCollection)
133         .methods(boost::beast::http::verb::head)(
134             std::bind_front(handlePowerSupplyCollectionHead, std::ref(app)));
135 
136     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/")
137         .privileges(redfish::privileges::getPowerSupplyCollection)
138         .methods(boost::beast::http::verb::get)(
139             std::bind_front(handlePowerSupplyCollectionGet, std::ref(app)));
140 }
141 
142 inline bool checkPowerSupplyId(const std::string& powerSupplyPath,
143                                const std::string& powerSupplyId)
144 {
145     std::string powerSupplyName =
146         sdbusplus::message::object_path(powerSupplyPath).filename();
147 
148     return !(powerSupplyName.empty() || powerSupplyName != powerSupplyId);
149 }
150 
151 inline void getValidPowerSupplyPath(
152     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
153     const std::string& validChassisPath, const std::string& powerSupplyId,
154     std::function<void(const std::string& powerSupplyPath)>&& callback)
155 {
156     std::string powerPath = validChassisPath + "/powered_by";
157     dbus::utility::getAssociationEndPoints(
158         powerPath, [asyncResp, powerSupplyId, callback{std::move(callback)}](
159                        const boost::system::error_code& ec,
160                        const dbus::utility::MapperEndPoints& endpoints) {
161             if (ec)
162             {
163                 if (ec.value() != EBADR)
164                 {
165                     BMCWEB_LOG_ERROR
166                         << "DBUS response error for getAssociationEndPoints"
167                         << ec.value();
168                     messages::internalError(asyncResp->res);
169                     return;
170                 }
171                 messages::resourceNotFound(asyncResp->res, "PowerSupplies",
172                                            powerSupplyId);
173                 return;
174             }
175 
176             for (const auto& endpoint : endpoints)
177             {
178                 if (checkPowerSupplyId(endpoint, powerSupplyId))
179                 {
180                     callback(endpoint);
181                     return;
182                 }
183             }
184 
185             if (!endpoints.empty())
186             {
187                 BMCWEB_LOG_WARNING << "Power supply not found: "
188                                    << powerSupplyId;
189                 messages::resourceNotFound(asyncResp->res, "PowerSupplies",
190                                            powerSupplyId);
191                 return;
192             }
193         });
194 }
195 
196 inline void
197     getPowerSupplyState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
198                         const std::string& service, const std::string& path)
199 {
200     sdbusplus::asio::getProperty<bool>(
201         *crow::connections::systemBus, service, path,
202         "xyz.openbmc_project.Inventory.Item", "Present",
203         [asyncResp](const boost::system::error_code& ec, const bool value) {
204         if (ec)
205         {
206             if (ec.value() != EBADR)
207             {
208                 BMCWEB_LOG_ERROR << "DBUS response error for State "
209                                  << ec.value();
210                 messages::internalError(asyncResp->res);
211             }
212             return;
213         }
214 
215         if (!value)
216         {
217             asyncResp->res.jsonValue["Status"]["State"] = "Absent";
218         }
219         });
220 }
221 
222 inline void
223     getPowerSupplyHealth(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
224                          const std::string& service, const std::string& path)
225 {
226     sdbusplus::asio::getProperty<bool>(
227         *crow::connections::systemBus, service, path,
228         "xyz.openbmc_project.State.Decorator.OperationalStatus", "Functional",
229         [asyncResp](const boost::system::error_code& ec, const bool value) {
230         if (ec)
231         {
232             if (ec.value() != EBADR)
233             {
234                 BMCWEB_LOG_ERROR << "DBUS response error for Health "
235                                  << ec.value();
236                 messages::internalError(asyncResp->res);
237             }
238             return;
239         }
240 
241         if (!value)
242         {
243             asyncResp->res.jsonValue["Status"]["Health"] = "Critical";
244         }
245         });
246 }
247 
248 inline void
249     getPowerSupplyAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
250                         const std::string& service, const std::string& path)
251 {
252     sdbusplus::asio::getAllProperties(
253         *crow::connections::systemBus, service, path,
254         "xyz.openbmc_project.Inventory.Decorator.Asset",
255         [asyncResp](const boost::system::error_code& ec,
256                     const dbus::utility::DBusPropertiesMap& propertiesList) {
257         if (ec)
258         {
259             if (ec.value() != EBADR)
260             {
261                 BMCWEB_LOG_ERROR << "DBUS response error for Asset "
262                                  << ec.value();
263                 messages::internalError(asyncResp->res);
264             }
265             return;
266         }
267 
268         const std::string* partNumber = nullptr;
269         const std::string* serialNumber = nullptr;
270         const std::string* manufacturer = nullptr;
271         const std::string* model = nullptr;
272         const std::string* sparePartNumber = nullptr;
273 
274         const bool success = sdbusplus::unpackPropertiesNoThrow(
275             dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber",
276             partNumber, "SerialNumber", serialNumber, "Manufacturer",
277             manufacturer, "Model", model, "SparePartNumber", sparePartNumber);
278 
279         if (!success)
280         {
281             messages::internalError(asyncResp->res);
282             return;
283         }
284 
285         if (partNumber != nullptr)
286         {
287             asyncResp->res.jsonValue["PartNumber"] = *partNumber;
288         }
289 
290         if (serialNumber != nullptr)
291         {
292             asyncResp->res.jsonValue["SerialNumber"] = *serialNumber;
293         }
294 
295         if (manufacturer != nullptr)
296         {
297             asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
298         }
299 
300         if (model != nullptr)
301         {
302             asyncResp->res.jsonValue["Model"] = *model;
303         }
304 
305         // SparePartNumber is optional on D-Bus so skip if it is empty
306         if (sparePartNumber != nullptr && !sparePartNumber->empty())
307         {
308             asyncResp->res.jsonValue["SparePartNumber"] = *sparePartNumber;
309         }
310         });
311 }
312 
313 inline void getPowerSupplyFirmwareVersion(
314     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
315     const std::string& service, const std::string& path)
316 {
317     sdbusplus::asio::getProperty<std::string>(
318         *crow::connections::systemBus, service, path,
319         "xyz.openbmc_project.Software.Version", "Version",
320         [asyncResp](const boost::system::error_code& ec,
321                     const std::string& value) {
322         if (ec)
323         {
324             if (ec.value() != EBADR)
325             {
326                 BMCWEB_LOG_ERROR << "DBUS response error for FirmwareVersion "
327                                  << ec.value();
328                 messages::internalError(asyncResp->res);
329             }
330             return;
331         }
332         asyncResp->res.jsonValue["FirmwareVersion"] = value;
333         });
334 }
335 
336 inline void
337     getPowerSupplyLocation(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
338                            const std::string& service, const std::string& path)
339 {
340     sdbusplus::asio::getProperty<std::string>(
341         *crow::connections::systemBus, service, path,
342         "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode",
343         [asyncResp](const boost::system::error_code& ec,
344                     const std::string& value) {
345         if (ec)
346         {
347             if (ec.value() != EBADR)
348             {
349                 BMCWEB_LOG_ERROR << "DBUS response error for Location "
350                                  << ec.value();
351                 messages::internalError(asyncResp->res);
352             }
353             return;
354         }
355         asyncResp->res.jsonValue["Location"]["PartLocation"]["ServiceLabel"] =
356             value;
357         });
358 }
359 
360 inline void
361     doPowerSupplyGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
362                      const std::string& chassisId,
363                      const std::string& powerSupplyId,
364                      const std::optional<std::string>& validChassisPath)
365 {
366     if (!validChassisPath)
367     {
368         messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
369         return;
370     }
371 
372     // Get the correct Path and Service that match the input parameters
373     getValidPowerSupplyPath(asyncResp, *validChassisPath, powerSupplyId,
374                             [asyncResp, chassisId, powerSupplyId](
375                                 const std::string& powerSupplyPath) {
376         asyncResp->res.addHeader(
377             boost::beast::http::field::link,
378             "</redfish/v1/JsonSchemas/PowerSupply/PowerSupply.json>; rel=describedby");
379         asyncResp->res.jsonValue["@odata.type"] =
380             "#PowerSupply.v1_5_0.PowerSupply";
381         asyncResp->res.jsonValue["Name"] = "Power Supply";
382         asyncResp->res.jsonValue["Id"] = powerSupplyId;
383         asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
384             "/redfish/v1/Chassis/{}/PowerSubsystem/PowerSupplies/{}", chassisId,
385             powerSupplyId);
386 
387         asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
388         asyncResp->res.jsonValue["Status"]["Health"] = "OK";
389 
390         constexpr std::array<std::string_view, 1> interfaces = {
391             "xyz.openbmc_project.Inventory.Item.PowerSupply"};
392         dbus::utility::getDbusObject(
393             powerSupplyPath, interfaces,
394             [asyncResp,
395              powerSupplyPath](const boost::system::error_code& ec,
396                               const dbus::utility::MapperGetObject& object) {
397             if (ec || object.empty())
398             {
399                 messages::internalError(asyncResp->res);
400                 return;
401             }
402 
403             getPowerSupplyState(asyncResp, object.begin()->first,
404                                 powerSupplyPath);
405             getPowerSupplyHealth(asyncResp, object.begin()->first,
406                                  powerSupplyPath);
407             getPowerSupplyAsset(asyncResp, object.begin()->first,
408                                 powerSupplyPath);
409             getPowerSupplyFirmwareVersion(asyncResp, object.begin()->first,
410                                           powerSupplyPath);
411             getPowerSupplyLocation(asyncResp, object.begin()->first,
412                                    powerSupplyPath);
413             });
414     });
415 }
416 
417 inline void
418     handlePowerSupplyHead(App& app, const crow::Request& req,
419                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
420                           const std::string& chassisId,
421                           const std::string& powerSupplyId)
422 {
423     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
424     {
425         return;
426     }
427 
428     redfish::chassis_utils::getValidChassisPath(
429         asyncResp, chassisId,
430         [asyncResp, chassisId,
431          powerSupplyId](const std::optional<std::string>& validChassisPath) {
432         if (!validChassisPath)
433         {
434             messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
435             return;
436         }
437 
438         // Get the correct Path and Service that match the input parameters
439         getValidPowerSupplyPath(asyncResp, *validChassisPath, powerSupplyId,
440                                 [asyncResp](const std::string&) {
441             asyncResp->res.addHeader(
442                 boost::beast::http::field::link,
443                 "</redfish/v1/JsonSchemas/PowerSupply/PowerSupply.json>; rel=describedby");
444         });
445         });
446 }
447 
448 inline void
449     handlePowerSupplyGet(App& app, const crow::Request& req,
450                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
451                          const std::string& chassisId,
452                          const std::string& powerSupplyId)
453 {
454     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
455     {
456         return;
457     }
458 
459     redfish::chassis_utils::getValidChassisPath(
460         asyncResp, chassisId,
461         std::bind_front(doPowerSupplyGet, asyncResp, chassisId, powerSupplyId));
462 }
463 
464 inline void requestRoutesPowerSupply(App& app)
465 {
466     BMCWEB_ROUTE(
467         app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/<str>/")
468         .privileges(redfish::privileges::headPowerSupply)
469         .methods(boost::beast::http::verb::head)(
470             std::bind_front(handlePowerSupplyHead, std::ref(app)));
471 
472     BMCWEB_ROUTE(
473         app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/<str>/")
474         .privileges(redfish::privileges::getPowerSupply)
475         .methods(boost::beast::http::verb::get)(
476             std::bind_front(handlePowerSupplyGet, std::ref(app)));
477 }
478 
479 } // namespace redfish
480