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