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