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