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