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