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