xref: /openbmc/bmcweb/features/redfish/lib/power_supply.hpp (revision 3f95a2772fc4fd57459fdfffbee52d8831fc4f35)
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 
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 
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 
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 
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     const std::string reqpath = "/xyz/openbmc_project/inventory";
134 
135     dbus::utility::getAssociatedSubTreePathsById(
136         chassisId, reqpath, chassisInterfaces, "powered_by",
137         powerSupplyInterface,
138         [asyncResp, chassisId](
139             const boost::system::error_code& ec,
140             const dbus::utility::MapperGetSubTreePathsResponse& subtreePaths) {
141             doPowerSupplyCollection(asyncResp, chassisId, ec, subtreePaths);
142         });
143 }
144 
145 inline void requestRoutesPowerSupplyCollection(App& app)
146 {
147     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/")
148         .privileges(redfish::privileges::headPowerSupplyCollection)
149         .methods(boost::beast::http::verb::head)(
150             std::bind_front(handlePowerSupplyCollectionHead, std::ref(app)));
151 
152     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/")
153         .privileges(redfish::privileges::getPowerSupplyCollection)
154         .methods(boost::beast::http::verb::get)(
155             std::bind_front(handlePowerSupplyCollectionGet, std::ref(app)));
156 }
157 
158 inline void afterGetValidPowerSupplyPath(
159     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
160     const std::string& powerSupplyId, const boost::system::error_code& ec,
161     const dbus::utility::MapperGetSubTreeResponse& subtree,
162     const std::function<void(const std::string& powerSupplyPath,
163                              const std::string& service)>& callback)
164 {
165     if (ec)
166     {
167         if (ec.value() != EBADR)
168         {
169             BMCWEB_LOG_ERROR("DBUS response error{}", ec.value());
170             messages::internalError(asyncResp->res);
171         }
172         return;
173     }
174     for (const auto& [objectPath, service] : subtree)
175     {
176         sdbusplus::message::object_path path(objectPath);
177         if (path.filename() == powerSupplyId)
178         {
179             callback(path, service.begin()->first);
180             return;
181         }
182     }
183 
184     BMCWEB_LOG_WARNING("Power supply not found: {}", powerSupplyId);
185     messages::resourceNotFound(asyncResp->res, "PowerSupplies", powerSupplyId);
186 }
187 
188 inline void getValidPowerSupplyPath(
189     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
190     const std::string& chassisId, const std::string& powerSupplyId,
191     std::function<void(const std::string& powerSupplyPath,
192                        const std::string& service)>&& callback)
193 {
194     const std::string reqpath = "/xyz/openbmc_project/inventory";
195 
196     dbus::utility::getAssociatedSubTreeById(
197         chassisId, reqpath, chassisInterfaces, "powered_by",
198         powerSupplyInterface,
199         [asyncResp, chassisId, powerSupplyId, callback{std::move(callback)}](
200             const boost::system::error_code& ec,
201             const dbus::utility::MapperGetSubTreeResponse& subtree) {
202             afterGetValidPowerSupplyPath(asyncResp, powerSupplyId, ec, subtree,
203                                          callback);
204         });
205 }
206 
207 inline void getPowerSupplyState(
208     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
209     const std::string& service, const std::string& path)
210 {
211     dbus::utility::getProperty<bool>(
212         service, path, "xyz.openbmc_project.Inventory.Item", "Present",
213         [asyncResp](const boost::system::error_code& ec, const bool value) {
214             if (ec)
215             {
216                 if (ec.value() != EBADR)
217                 {
218                     BMCWEB_LOG_ERROR("DBUS response error for State {}",
219                                      ec.value());
220                     messages::internalError(asyncResp->res);
221                 }
222                 return;
223             }
224 
225             if (!value)
226             {
227                 asyncResp->res.jsonValue["Status"]["State"] =
228                     resource::State::Absent;
229             }
230         });
231 }
232 
233 inline void getPowerSupplyHealth(
234     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
235     const std::string& service, const std::string& path)
236 {
237     dbus::utility::getProperty<bool>(
238         service, path, "xyz.openbmc_project.State.Decorator.OperationalStatus",
239         "Functional",
240         [asyncResp](const boost::system::error_code& ec, const bool value) {
241             if (ec)
242             {
243                 if (ec.value() != EBADR)
244                 {
245                     BMCWEB_LOG_ERROR("DBUS response error for Health {}",
246                                      ec.value());
247                     messages::internalError(asyncResp->res);
248                 }
249                 return;
250             }
251 
252             if (!value)
253             {
254                 asyncResp->res.jsonValue["Status"]["Health"] =
255                     resource::Health::Critical;
256             }
257         });
258 }
259 
260 inline void getPowerSupplyAsset(
261     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
262     const std::string& service, const std::string& path)
263 {
264     dbus::utility::getAllProperties(
265         service, path, "xyz.openbmc_project.Inventory.Decorator.Asset",
266         [asyncResp](const boost::system::error_code& ec,
267                     const dbus::utility::DBusPropertiesMap& propertiesList) {
268             if (ec)
269             {
270                 if (ec.value() != EBADR)
271                 {
272                     BMCWEB_LOG_ERROR("DBUS response error for Asset {}",
273                                      ec.value());
274                     messages::internalError(asyncResp->res);
275                 }
276                 return;
277             }
278 
279             const std::string* partNumber = nullptr;
280             const std::string* serialNumber = nullptr;
281             const std::string* manufacturer = nullptr;
282             const std::string* model = nullptr;
283             const std::string* sparePartNumber = nullptr;
284             const std::string* buildDate = nullptr;
285 
286             const bool success = sdbusplus::unpackPropertiesNoThrow(
287                 dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber",
288                 partNumber, "SerialNumber", serialNumber, "Manufacturer",
289                 manufacturer, "Model", model, "SparePartNumber",
290                 sparePartNumber, "BuildDate", buildDate);
291 
292             if (!success)
293             {
294                 messages::internalError(asyncResp->res);
295                 return;
296             }
297 
298             if (partNumber != nullptr)
299             {
300                 asyncResp->res.jsonValue["PartNumber"] = *partNumber;
301             }
302 
303             if (serialNumber != nullptr)
304             {
305                 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber;
306             }
307 
308             if (manufacturer != nullptr)
309             {
310                 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
311             }
312 
313             if (model != nullptr)
314             {
315                 asyncResp->res.jsonValue["Model"] = *model;
316             }
317 
318             // SparePartNumber is optional on D-Bus so skip if it is empty
319             if (sparePartNumber != nullptr && !sparePartNumber->empty())
320             {
321                 asyncResp->res.jsonValue["SparePartNumber"] = *sparePartNumber;
322             }
323 
324             if (buildDate != nullptr)
325             {
326                 time_utils::productionDateReport(asyncResp->res, *buildDate);
327             }
328         });
329 }
330 
331 inline void getPowerSupplyFirmwareVersion(
332     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
333     const std::string& service, const std::string& path)
334 {
335     dbus::utility::getProperty<std::string>(
336         service, path, "xyz.openbmc_project.Software.Version", "Version",
337         [asyncResp](const boost::system::error_code& ec,
338                     const std::string& value) {
339             if (ec)
340             {
341                 if (ec.value() != EBADR)
342                 {
343                     BMCWEB_LOG_ERROR(
344                         "DBUS response error for FirmwareVersion {}",
345                         ec.value());
346                     messages::internalError(asyncResp->res);
347                 }
348                 return;
349             }
350             asyncResp->res.jsonValue["FirmwareVersion"] = value;
351         });
352 }
353 
354 inline void getPowerSupplyLocation(
355     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
356     const std::string& service, const std::string& path)
357 {
358     dbus::utility::getProperty<std::string>(
359         service, path, "xyz.openbmc_project.Inventory.Decorator.LocationCode",
360         "LocationCode",
361         [asyncResp](const boost::system::error_code& ec,
362                     const std::string& value) {
363             if (ec)
364             {
365                 if (ec.value() != EBADR)
366                 {
367                     BMCWEB_LOG_ERROR("DBUS response error for Location {}",
368                                      ec.value());
369                     messages::internalError(asyncResp->res);
370                 }
371                 return;
372             }
373             asyncResp->res
374                 .jsonValue["Location"]["PartLocation"]["ServiceLabel"] = value;
375         });
376 }
377 
378 inline void handleGetEfficiencyResponse(
379     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
380     const boost::system::error_code& ec, uint32_t value)
381 {
382     if (ec)
383     {
384         if (ec.value() != EBADR)
385         {
386             BMCWEB_LOG_ERROR("DBUS response error for DeratingFactor {}",
387                              ec.value());
388             messages::internalError(asyncResp->res);
389         }
390         return;
391     }
392     // The PDI default value is 0, if it hasn't been set leave off
393     if (value == 0)
394     {
395         return;
396     }
397 
398     nlohmann::json::array_t efficiencyRatings;
399     nlohmann::json::object_t efficiencyPercent;
400     efficiencyPercent["EfficiencyPercent"] = value;
401     efficiencyRatings.emplace_back(std::move(efficiencyPercent));
402     asyncResp->res.jsonValue["EfficiencyRatings"] =
403         std::move(efficiencyRatings);
404 }
405 
406 inline void handlePowerSupplyAttributesSubTreeResponse(
407     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
408     const boost::system::error_code& ec,
409     const dbus::utility::MapperGetSubTreeResponse& subtree)
410 {
411     if (ec)
412     {
413         if (ec.value() != EBADR)
414         {
415             BMCWEB_LOG_ERROR("DBUS response error for EfficiencyPercent {}",
416                              ec.value());
417             messages::internalError(asyncResp->res);
418         }
419         return;
420     }
421 
422     if (subtree.empty())
423     {
424         BMCWEB_LOG_DEBUG("Can't find Power Supply Attributes!");
425         return;
426     }
427 
428     if (subtree.size() != 1)
429     {
430         BMCWEB_LOG_ERROR(
431             "Unexpected number of paths returned by getSubTree: {}",
432             subtree.size());
433         messages::internalError(asyncResp->res);
434         return;
435     }
436 
437     const auto& [path, serviceMap] = *subtree.begin();
438     const auto& [service, interfaces] = *serviceMap.begin();
439     dbus::utility::getProperty<uint32_t>(
440         service, path, "xyz.openbmc_project.Control.PowerSupplyAttributes",
441         "DeratingFactor",
442         [asyncResp](const boost::system::error_code& ec1, uint32_t value) {
443             handleGetEfficiencyResponse(asyncResp, ec1, value);
444         });
445 }
446 
447 inline void getEfficiencyPercent(
448     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
449 {
450     constexpr std::array<std::string_view, 1> efficiencyIntf = {
451         "xyz.openbmc_project.Control.PowerSupplyAttributes"};
452 
453     dbus::utility::getSubTree(
454         "/xyz/openbmc_project", 0, efficiencyIntf,
455         [asyncResp](const boost::system::error_code& ec,
456                     const dbus::utility::MapperGetSubTreeResponse& subtree) {
457             handlePowerSupplyAttributesSubTreeResponse(asyncResp, ec, subtree);
458         });
459 }
460 
461 inline void doPowerSupplyGet(
462     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
463     const std::string& chassisId, const std::string& powerSupplyId,
464     const std::string& powerSupplyPath, const std::string& service)
465 {
466     asyncResp->res.addHeader(
467         boost::beast::http::field::link,
468         "</redfish/v1/JsonSchemas/PowerSupply/PowerSupply.json>; rel=describedby");
469     asyncResp->res.jsonValue["@odata.type"] = "#PowerSupply.v1_5_0.PowerSupply";
470     asyncResp->res.jsonValue["Name"] = "Power Supply";
471     asyncResp->res.jsonValue["Id"] = powerSupplyId;
472     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
473         "/redfish/v1/Chassis/{}/PowerSubsystem/PowerSupplies/{}", chassisId,
474         powerSupplyId);
475 
476     asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
477     asyncResp->res.jsonValue["Status"]["Health"] = resource::Health::OK;
478 
479     getPowerSupplyState(asyncResp, service, powerSupplyPath);
480     getPowerSupplyHealth(asyncResp, service, powerSupplyPath);
481     getPowerSupplyAsset(asyncResp, service, powerSupplyPath);
482     getPowerSupplyFirmwareVersion(asyncResp, service, powerSupplyPath);
483     getPowerSupplyLocation(asyncResp, service, powerSupplyPath);
484     getEfficiencyPercent(asyncResp);
485 }
486 
487 inline void handlePowerSupplyHead(
488     App& app, const crow::Request& req,
489     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
490     const std::string& chassisId, const std::string& powerSupplyId)
491 {
492     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
493     {
494         return;
495     }
496 
497     // Get the correct Path and Service that match the input parameters
498     getValidPowerSupplyPath(
499         asyncResp, chassisId, powerSupplyId,
500         [asyncResp](const std::string&, const std::string&) {
501             asyncResp->res.addHeader(
502                 boost::beast::http::field::link,
503                 "</redfish/v1/JsonSchemas/PowerSupply/PowerSupply.json>; rel=describedby");
504         });
505 }
506 
507 inline void handlePowerSupplyGet(
508     App& app, const crow::Request& req,
509     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
510     const std::string& chassisId, const std::string& powerSupplyId)
511 {
512     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
513     {
514         return;
515     }
516     getValidPowerSupplyPath(
517         asyncResp, chassisId, powerSupplyId,
518         std::bind_front(doPowerSupplyGet, asyncResp, chassisId, powerSupplyId));
519 }
520 
521 inline void requestRoutesPowerSupply(App& app)
522 {
523     BMCWEB_ROUTE(
524         app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/<str>/")
525         .privileges(redfish::privileges::headPowerSupply)
526         .methods(boost::beast::http::verb::head)(
527             std::bind_front(handlePowerSupplyHead, std::ref(app)));
528 
529     BMCWEB_ROUTE(
530         app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/<str>/")
531         .privileges(redfish::privileges::getPowerSupply)
532         .methods(boost::beast::http::verb::get)(
533             std::bind_front(handlePowerSupplyGet, std::ref(app)));
534 }
535 
536 } // namespace redfish
537