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 
12 #include <boost/system/error_code.hpp>
13 #include <boost/url/format.hpp>
14 
15 #include <memory>
16 #include <optional>
17 #include <string>
18 
19 namespace redfish
20 {
21 
22 static constexpr std::array<std::string_view, 1> powerSupplyInterface = {
23     "xyz.openbmc_project.Inventory.Item.PowerSupply"};
24 
25 inline void updatePowerSupplyList(
26     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
27     const std::string& chassisId,
28     const dbus::utility::MapperGetSubTreePathsResponse& powerSupplyPaths)
29 {
30     nlohmann::json& powerSupplyList = asyncResp->res.jsonValue["Members"];
31     for (const std::string& powerSupplyPath : powerSupplyPaths)
32     {
33         std::string powerSupplyName =
34             sdbusplus::message::object_path(powerSupplyPath).filename();
35         if (powerSupplyName.empty())
36         {
37             continue;
38         }
39 
40         nlohmann::json item = nlohmann::json::object();
41         item["@odata.id"] = boost::urls::format(
42             "/redfish/v1/Chassis/{}/PowerSubsystem/PowerSupplies/{}", chassisId,
43             powerSupplyName);
44 
45         powerSupplyList.emplace_back(std::move(item));
46     }
47     asyncResp->res.jsonValue["Members@odata.count"] = powerSupplyList.size();
48 }
49 
50 inline void
51     doPowerSupplyCollection(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
52                             const std::string& chassisId,
53                             const std::optional<std::string>& validChassisPath)
54 {
55     if (!validChassisPath)
56     {
57         messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
58         return;
59     }
60 
61     asyncResp->res.addHeader(
62         boost::beast::http::field::link,
63         "</redfish/v1/JsonSchemas/PowerSupplyCollection/PowerSupplyCollection.json>; rel=describedby");
64     asyncResp->res.jsonValue["@odata.type"] =
65         "#PowerSupplyCollection.PowerSupplyCollection";
66     asyncResp->res.jsonValue["Name"] = "Power Supply Collection";
67     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
68         "/redfish/v1/Chassis/{}/PowerSubsystem/PowerSupplies", chassisId);
69     asyncResp->res.jsonValue["Description"] =
70         "The collection of PowerSupply resource instances.";
71     asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
72     asyncResp->res.jsonValue["Members@odata.count"] = 0;
73 
74     std::string powerPath = *validChassisPath + "/powered_by";
75     dbus::utility::getAssociatedSubTreePaths(
76         powerPath,
77         sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0,
78         powerSupplyInterface,
79         [asyncResp, chassisId](
80             const boost::system::error_code& ec,
81             const dbus::utility::MapperGetSubTreePathsResponse& subtreePaths) {
82         if (ec)
83         {
84             if (ec.value() != EBADR)
85             {
86                 BMCWEB_LOG_ERROR("DBUS response error{}", ec.value());
87                 messages::internalError(asyncResp->res);
88             }
89             return;
90         }
91 
92         updatePowerSupplyList(asyncResp, chassisId, subtreePaths);
93     });
94 }
95 
96 inline void handlePowerSupplyCollectionHead(
97     App& app, const crow::Request& req,
98     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
99     const std::string& chassisId)
100 {
101     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
102     {
103         return;
104     }
105 
106     redfish::chassis_utils::getValidChassisPath(
107         asyncResp, chassisId,
108         [asyncResp,
109          chassisId](const std::optional<std::string>& validChassisPath) {
110         if (!validChassisPath)
111         {
112             messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
113             return;
114         }
115         asyncResp->res.addHeader(
116             boost::beast::http::field::link,
117             "</redfish/v1/JsonSchemas/PowerSupplyCollection/PowerSupplyCollection.json>; rel=describedby");
118     });
119 }
120 
121 inline void handlePowerSupplyCollectionGet(
122     App& app, const crow::Request& req,
123     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
124     const std::string& chassisId)
125 {
126     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
127     {
128         return;
129     }
130 
131     redfish::chassis_utils::getValidChassisPath(
132         asyncResp, chassisId,
133         std::bind_front(doPowerSupplyCollection, asyncResp, chassisId));
134 }
135 
136 inline void requestRoutesPowerSupplyCollection(App& app)
137 {
138     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/")
139         .privileges(redfish::privileges::headPowerSupplyCollection)
140         .methods(boost::beast::http::verb::head)(
141             std::bind_front(handlePowerSupplyCollectionHead, std::ref(app)));
142 
143     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/")
144         .privileges(redfish::privileges::getPowerSupplyCollection)
145         .methods(boost::beast::http::verb::get)(
146             std::bind_front(handlePowerSupplyCollectionGet, std::ref(app)));
147 }
148 
149 inline bool checkPowerSupplyId(const std::string& powerSupplyPath,
150                                const std::string& powerSupplyId)
151 {
152     std::string powerSupplyName =
153         sdbusplus::message::object_path(powerSupplyPath).filename();
154 
155     return !(powerSupplyName.empty() || powerSupplyName != powerSupplyId);
156 }
157 
158 inline void getValidPowerSupplyPath(
159     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
160     const std::string& validChassisPath, const std::string& powerSupplyId,
161     std::function<void(const std::string& powerSupplyPath)>&& callback)
162 {
163     std::string powerPath = validChassisPath + "/powered_by";
164     dbus::utility::getAssociatedSubTreePaths(
165         powerPath,
166         sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0,
167         powerSupplyInterface,
168         [asyncResp, powerSupplyId, callback{std::move(callback)}](
169             const boost::system::error_code& ec,
170             const dbus::utility::MapperGetSubTreePathsResponse& subtreePaths) {
171         if (ec)
172         {
173             if (ec.value() != EBADR)
174             {
175                 BMCWEB_LOG_ERROR(
176                     "DBUS response error for getAssociatedSubTreePaths{}",
177                     ec.value());
178                 messages::internalError(asyncResp->res);
179                 return;
180             }
181             messages::resourceNotFound(asyncResp->res, "PowerSupplies",
182                                        powerSupplyId);
183             return;
184         }
185 
186         for (const std::string& path : subtreePaths)
187         {
188             if (checkPowerSupplyId(path, powerSupplyId))
189             {
190                 callback(path);
191                 return;
192             }
193         }
194 
195         if (!subtreePaths.empty())
196         {
197             BMCWEB_LOG_WARNING("Power supply not found: {}", powerSupplyId);
198             messages::resourceNotFound(asyncResp->res, "PowerSupplies",
199                                        powerSupplyId);
200             return;
201         }
202     });
203 }
204 
205 inline void
206     getPowerSupplyState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
207                         const std::string& service, const std::string& path)
208 {
209     sdbusplus::asio::getProperty<bool>(
210         *crow::connections::systemBus, service, path,
211         "xyz.openbmc_project.Inventory.Item", "Present",
212         [asyncResp](const boost::system::error_code& ec, const bool value) {
213         if (ec)
214         {
215             if (ec.value() != EBADR)
216             {
217                 BMCWEB_LOG_ERROR("DBUS response error for State {}",
218                                  ec.value());
219                 messages::internalError(asyncResp->res);
220             }
221             return;
222         }
223 
224         if (!value)
225         {
226             asyncResp->res.jsonValue["Status"]["State"] =
227                 resource::State::Absent;
228         }
229     });
230 }
231 
232 inline void
233     getPowerSupplyHealth(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
234                          const std::string& service, const std::string& path)
235 {
236     sdbusplus::asio::getProperty<bool>(
237         *crow::connections::systemBus, service, path,
238         "xyz.openbmc_project.State.Decorator.OperationalStatus", "Functional",
239         [asyncResp](const boost::system::error_code& ec, const bool value) {
240         if (ec)
241         {
242             if (ec.value() != EBADR)
243             {
244                 BMCWEB_LOG_ERROR("DBUS response error for Health {}",
245                                  ec.value());
246                 messages::internalError(asyncResp->res);
247             }
248             return;
249         }
250 
251         if (!value)
252         {
253             asyncResp->res.jsonValue["Status"]["Health"] =
254                 resource::Health::Critical;
255         }
256     });
257 }
258 
259 inline void
260     getPowerSupplyAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
261                         const std::string& service, const std::string& path)
262 {
263     sdbusplus::asio::getAllProperties(
264         *crow::connections::systemBus, service, path,
265         "xyz.openbmc_project.Inventory.Decorator.Asset",
266         [asyncResp](const boost::system::error_code& ec,
267                     const dbus::utility::DBusPropertiesMap& propertiesList) {
268         if (ec)
269         {
270             if (ec.value() != EBADR)
271             {
272                 BMCWEB_LOG_ERROR("DBUS response error for Asset {}",
273                                  ec.value());
274                 messages::internalError(asyncResp->res);
275             }
276             return;
277         }
278 
279         const std::string* partNumber = nullptr;
280         const std::string* serialNumber = nullptr;
281         const std::string* manufacturer = nullptr;
282         const std::string* model = nullptr;
283         const std::string* sparePartNumber = nullptr;
284 
285         const bool success = sdbusplus::unpackPropertiesNoThrow(
286             dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber",
287             partNumber, "SerialNumber", serialNumber, "Manufacturer",
288             manufacturer, "Model", model, "SparePartNumber", sparePartNumber);
289 
290         if (!success)
291         {
292             messages::internalError(asyncResp->res);
293             return;
294         }
295 
296         if (partNumber != nullptr)
297         {
298             asyncResp->res.jsonValue["PartNumber"] = *partNumber;
299         }
300 
301         if (serialNumber != nullptr)
302         {
303             asyncResp->res.jsonValue["SerialNumber"] = *serialNumber;
304         }
305 
306         if (manufacturer != nullptr)
307         {
308             asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
309         }
310 
311         if (model != nullptr)
312         {
313             asyncResp->res.jsonValue["Model"] = *model;
314         }
315 
316         // SparePartNumber is optional on D-Bus so skip if it is empty
317         if (sparePartNumber != nullptr && !sparePartNumber->empty())
318         {
319             asyncResp->res.jsonValue["SparePartNumber"] = *sparePartNumber;
320         }
321     });
322 }
323 
324 inline void getPowerSupplyFirmwareVersion(
325     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
326     const std::string& service, const std::string& path)
327 {
328     sdbusplus::asio::getProperty<std::string>(
329         *crow::connections::systemBus, service, path,
330         "xyz.openbmc_project.Software.Version", "Version",
331         [asyncResp](const boost::system::error_code& ec,
332                     const std::string& value) {
333         if (ec)
334         {
335             if (ec.value() != EBADR)
336             {
337                 BMCWEB_LOG_ERROR("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
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.jsonValue["Location"]["PartLocation"]["ServiceLabel"] =
367             value;
368     });
369 }
370 
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 
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
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 
454 inline void
455     doPowerSupplyGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
456                      const std::string& chassisId,
457                      const std::string& powerSupplyId,
458                      const std::optional<std::string>& validChassisPath)
459 {
460     if (!validChassisPath)
461     {
462         messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
463         return;
464     }
465 
466     // Get the correct Path and Service that match the input parameters
467     getValidPowerSupplyPath(asyncResp, *validChassisPath, powerSupplyId,
468                             [asyncResp, chassisId, powerSupplyId](
469                                 const std::string& powerSupplyPath) {
470         asyncResp->res.addHeader(
471             boost::beast::http::field::link,
472             "</redfish/v1/JsonSchemas/PowerSupply/PowerSupply.json>; rel=describedby");
473         asyncResp->res.jsonValue["@odata.type"] =
474             "#PowerSupply.v1_5_0.PowerSupply";
475         asyncResp->res.jsonValue["Name"] = "Power Supply";
476         asyncResp->res.jsonValue["Id"] = powerSupplyId;
477         asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
478             "/redfish/v1/Chassis/{}/PowerSubsystem/PowerSupplies/{}", chassisId,
479             powerSupplyId);
480 
481         asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
482         asyncResp->res.jsonValue["Status"]["Health"] = resource::Health::OK;
483 
484         dbus::utility::getDbusObject(
485             powerSupplyPath, powerSupplyInterface,
486             [asyncResp,
487              powerSupplyPath](const boost::system::error_code& ec,
488                               const dbus::utility::MapperGetObject& object) {
489             if (ec || object.empty())
490             {
491                 messages::internalError(asyncResp->res);
492                 return;
493             }
494 
495             getPowerSupplyState(asyncResp, object.begin()->first,
496                                 powerSupplyPath);
497             getPowerSupplyHealth(asyncResp, object.begin()->first,
498                                  powerSupplyPath);
499             getPowerSupplyAsset(asyncResp, object.begin()->first,
500                                 powerSupplyPath);
501             getPowerSupplyFirmwareVersion(asyncResp, object.begin()->first,
502                                           powerSupplyPath);
503             getPowerSupplyLocation(asyncResp, object.begin()->first,
504                                    powerSupplyPath);
505         });
506 
507         getEfficiencyPercent(asyncResp);
508     });
509 }
510 
511 inline void
512     handlePowerSupplyHead(App& app, const crow::Request& req,
513                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
514                           const std::string& chassisId,
515                           const std::string& powerSupplyId)
516 {
517     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
518     {
519         return;
520     }
521 
522     redfish::chassis_utils::getValidChassisPath(
523         asyncResp, chassisId,
524         [asyncResp, chassisId,
525          powerSupplyId](const std::optional<std::string>& validChassisPath) {
526         if (!validChassisPath)
527         {
528             messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
529             return;
530         }
531 
532         // Get the correct Path and Service that match the input parameters
533         getValidPowerSupplyPath(asyncResp, *validChassisPath, powerSupplyId,
534                                 [asyncResp](const std::string&) {
535             asyncResp->res.addHeader(
536                 boost::beast::http::field::link,
537                 "</redfish/v1/JsonSchemas/PowerSupply/PowerSupply.json>; rel=describedby");
538         });
539     });
540 }
541 
542 inline void
543     handlePowerSupplyGet(App& app, const crow::Request& req,
544                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
545                          const std::string& chassisId,
546                          const std::string& powerSupplyId)
547 {
548     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
549     {
550         return;
551     }
552 
553     redfish::chassis_utils::getValidChassisPath(
554         asyncResp, chassisId,
555         std::bind_front(doPowerSupplyGet, asyncResp, chassisId, powerSupplyId));
556 }
557 
558 inline void requestRoutesPowerSupply(App& app)
559 {
560     BMCWEB_ROUTE(
561         app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/<str>/")
562         .privileges(redfish::privileges::headPowerSupply)
563         .methods(boost::beast::http::verb::head)(
564             std::bind_front(handlePowerSupplyHead, std::ref(app)));
565 
566     BMCWEB_ROUTE(
567         app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/<str>/")
568         .privileges(redfish::privileges::getPowerSupply)
569         .methods(boost::beast::http::verb::get)(
570             std::bind_front(handlePowerSupplyGet, std::ref(app)));
571 }
572 
573 } // namespace redfish
574