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 doPowerSupplyCollection(
51     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
52     const std::string& chassisId, const boost::system::error_code& ec,
53     const dbus::utility::MapperGetSubTreePathsResponse& subtreePaths)
54 {
55     if (ec)
56     {
57         if (ec.value() != EBADR)
58         {
59             BMCWEB_LOG_ERROR("DBUS response error{}", ec.value());
60             messages::internalError(asyncResp->res);
61         }
62         return;
63     }
64     asyncResp->res.addHeader(
65         boost::beast::http::field::link,
66         "</redfish/v1/JsonSchemas/PowerSupplyCollection/PowerSupplyCollection.json>; rel=describedby");
67     asyncResp->res.jsonValue["@odata.type"] =
68         "#PowerSupplyCollection.PowerSupplyCollection";
69     asyncResp->res.jsonValue["Name"] = "Power Supply Collection";
70     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
71         "/redfish/v1/Chassis/{}/PowerSubsystem/PowerSupplies", chassisId);
72     asyncResp->res.jsonValue["Description"] =
73         "The collection of PowerSupply resource instances.";
74     asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
75     asyncResp->res.jsonValue["Members@odata.count"] = 0;
76 
77     updatePowerSupplyList(asyncResp, chassisId, subtreePaths);
78 }
79 
80 inline void handlePowerSupplyCollectionHead(
81     App& app, const crow::Request& req,
82     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
83     const std::string& chassisId)
84 {
85     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
86     {
87         return;
88     }
89 
90     redfish::chassis_utils::getValidChassisPath(
91         asyncResp, chassisId,
92         [asyncResp,
93          chassisId](const std::optional<std::string>& validChassisPath) {
94             if (!validChassisPath)
95             {
96                 messages::resourceNotFound(asyncResp->res, "Chassis",
97                                            chassisId);
98                 return;
99             }
100             asyncResp->res.addHeader(
101                 boost::beast::http::field::link,
102                 "</redfish/v1/JsonSchemas/PowerSupplyCollection/PowerSupplyCollection.json>; rel=describedby");
103         });
104 }
105 
106 inline void handlePowerSupplyCollectionGet(
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     constexpr std::array<std::string_view, 2> chasisInterfaces = {
117         "xyz.openbmc_project.Inventory.Item.Board",
118         "xyz.openbmc_project.Inventory.Item.Chassis"};
119     const std::string reqpath = "/xyz/openbmc_project/inventory";
120 
121     dbus::utility::getAssociatedSubTreePathsById(
122         chassisId, reqpath, chasisInterfaces, "powered_by",
123         powerSupplyInterface,
124         [asyncResp, chassisId](
125             const boost::system::error_code& ec,
126             const dbus::utility::MapperGetSubTreePathsResponse& subtreePaths) {
127             doPowerSupplyCollection(asyncResp, chassisId, ec, subtreePaths);
128         });
129 }
130 
131 inline void requestRoutesPowerSupplyCollection(App& app)
132 {
133     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/")
134         .privileges(redfish::privileges::headPowerSupplyCollection)
135         .methods(boost::beast::http::verb::head)(
136             std::bind_front(handlePowerSupplyCollectionHead, std::ref(app)));
137 
138     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/")
139         .privileges(redfish::privileges::getPowerSupplyCollection)
140         .methods(boost::beast::http::verb::get)(
141             std::bind_front(handlePowerSupplyCollectionGet, std::ref(app)));
142 }
143 
144 inline void afterGetValidPowerSupplyPath(
145     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
146     const std::string& powerSupplyId, const boost::system::error_code& ec,
147     const dbus::utility::MapperGetSubTreeResponse& subtree,
148     const std::function<void(const std::string& powerSupplyPath,
149                              const std::string& service)>& callback)
150 {
151     if (ec)
152     {
153         if (ec.value() != EBADR)
154         {
155             BMCWEB_LOG_ERROR("DBUS response error{}", ec.value());
156             messages::internalError(asyncResp->res);
157         }
158         return;
159     }
160     for (const auto& [objectPath, service] : subtree)
161     {
162         sdbusplus::message::object_path path(objectPath);
163         if (path == powerSupplyId)
164         {
165             callback(path, service.begin()->first);
166             return;
167         }
168     }
169 
170     BMCWEB_LOG_WARNING("Power supply not found: {}", powerSupplyId);
171     messages::resourceNotFound(asyncResp->res, "PowerSupplies", powerSupplyId);
172 }
173 
174 inline void getValidPowerSupplyPath(
175     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
176     const std::string& chassisId, const std::string& powerSupplyId,
177     std::function<void(const std::string& powerSupplyPath,
178                        const std::string& service)>&& callback)
179 {
180     constexpr std::array<std::string_view, 2> chasisInterfaces = {
181         "xyz.openbmc_project.Inventory.Item.Board",
182         "xyz.openbmc_project.Inventory.Item.Chassis"};
183     const std::string reqpath = "/xyz/openbmc_project/inventory";
184 
185     dbus::utility::getAssociatedSubTreeById(
186         chassisId, reqpath, chasisInterfaces, "powered_by",
187         powerSupplyInterface,
188         [asyncResp, chassisId, powerSupplyId, callback{std::move(callback)}](
189             const boost::system::error_code& ec,
190             const dbus::utility::MapperGetSubTreeResponse& subtree) {
191             afterGetValidPowerSupplyPath(asyncResp, powerSupplyId, ec, subtree,
192                                          callback);
193         });
194 }
195 
196 inline void
197     getPowerSupplyState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
198                         const std::string& service, const std::string& path)
199 {
200     sdbusplus::asio::getProperty<bool>(
201         *crow::connections::systemBus, service, path,
202         "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
224     getPowerSupplyHealth(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
225                          const std::string& service, const std::string& path)
226 {
227     sdbusplus::asio::getProperty<bool>(
228         *crow::connections::systemBus, service, path,
229         "xyz.openbmc_project.State.Decorator.OperationalStatus", "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
251     getPowerSupplyAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
252                         const std::string& service, const std::string& path)
253 {
254     sdbusplus::asio::getAllProperties(
255         *crow::connections::systemBus, service, path,
256         "xyz.openbmc_project.Inventory.Decorator.Asset",
257         [asyncResp](const boost::system::error_code& ec,
258                     const dbus::utility::DBusPropertiesMap& propertiesList) {
259             if (ec)
260             {
261                 if (ec.value() != EBADR)
262                 {
263                     BMCWEB_LOG_ERROR("DBUS response error for Asset {}",
264                                      ec.value());
265                     messages::internalError(asyncResp->res);
266                 }
267                 return;
268             }
269 
270             const std::string* partNumber = nullptr;
271             const std::string* serialNumber = nullptr;
272             const std::string* manufacturer = nullptr;
273             const std::string* model = nullptr;
274             const std::string* sparePartNumber = 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);
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 }
315 
316 inline void getPowerSupplyFirmwareVersion(
317     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
318     const std::string& service, const std::string& path)
319 {
320     sdbusplus::asio::getProperty<std::string>(
321         *crow::connections::systemBus, service, path,
322         "xyz.openbmc_project.Software.Version", "Version",
323         [asyncResp](const boost::system::error_code& ec,
324                     const std::string& value) {
325             if (ec)
326             {
327                 if (ec.value() != EBADR)
328                 {
329                     BMCWEB_LOG_ERROR(
330                         "DBUS response error for FirmwareVersion {}",
331                         ec.value());
332                     messages::internalError(asyncResp->res);
333                 }
334                 return;
335             }
336             asyncResp->res.jsonValue["FirmwareVersion"] = value;
337         });
338 }
339 
340 inline void
341     getPowerSupplyLocation(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
342                            const std::string& service, const std::string& path)
343 {
344     sdbusplus::asio::getProperty<std::string>(
345         *crow::connections::systemBus, service, path,
346         "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode",
347         [asyncResp](const boost::system::error_code& ec,
348                     const std::string& value) {
349             if (ec)
350             {
351                 if (ec.value() != EBADR)
352                 {
353                     BMCWEB_LOG_ERROR("DBUS response error for Location {}",
354                                      ec.value());
355                     messages::internalError(asyncResp->res);
356                 }
357                 return;
358             }
359             asyncResp->res
360                 .jsonValue["Location"]["PartLocation"]["ServiceLabel"] = value;
361         });
362 }
363 
364 inline void handleGetEfficiencyResponse(
365     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
366     const boost::system::error_code& ec, uint32_t value)
367 {
368     if (ec)
369     {
370         if (ec.value() != EBADR)
371         {
372             BMCWEB_LOG_ERROR("DBUS response error for DeratingFactor {}",
373                              ec.value());
374             messages::internalError(asyncResp->res);
375         }
376         return;
377     }
378     // The PDI default value is 0, if it hasn't been set leave off
379     if (value == 0)
380     {
381         return;
382     }
383 
384     nlohmann::json::array_t efficiencyRatings;
385     nlohmann::json::object_t efficiencyPercent;
386     efficiencyPercent["EfficiencyPercent"] = value;
387     efficiencyRatings.emplace_back(std::move(efficiencyPercent));
388     asyncResp->res.jsonValue["EfficiencyRatings"] =
389         std::move(efficiencyRatings);
390 }
391 
392 inline void handlePowerSupplyAttributesSubTreeResponse(
393     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
394     const boost::system::error_code& ec,
395     const dbus::utility::MapperGetSubTreeResponse& subtree)
396 {
397     if (ec)
398     {
399         if (ec.value() != EBADR)
400         {
401             BMCWEB_LOG_ERROR("DBUS response error for EfficiencyPercent {}",
402                              ec.value());
403             messages::internalError(asyncResp->res);
404         }
405         return;
406     }
407 
408     if (subtree.empty())
409     {
410         BMCWEB_LOG_DEBUG("Can't find Power Supply Attributes!");
411         return;
412     }
413 
414     if (subtree.size() != 1)
415     {
416         BMCWEB_LOG_ERROR(
417             "Unexpected number of paths returned by getSubTree: {}",
418             subtree.size());
419         messages::internalError(asyncResp->res);
420         return;
421     }
422 
423     const auto& [path, serviceMap] = *subtree.begin();
424     const auto& [service, interfaces] = *serviceMap.begin();
425     sdbusplus::asio::getProperty<uint32_t>(
426         *crow::connections::systemBus, service, path,
427         "xyz.openbmc_project.Control.PowerSupplyAttributes", "DeratingFactor",
428         [asyncResp](const boost::system::error_code& ec1, uint32_t value) {
429             handleGetEfficiencyResponse(asyncResp, ec1, value);
430         });
431 }
432 
433 inline void
434     getEfficiencyPercent(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
435 {
436     constexpr std::array<std::string_view, 1> efficiencyIntf = {
437         "xyz.openbmc_project.Control.PowerSupplyAttributes"};
438 
439     dbus::utility::getSubTree(
440         "/xyz/openbmc_project", 0, efficiencyIntf,
441         [asyncResp](const boost::system::error_code& ec,
442                     const dbus::utility::MapperGetSubTreeResponse& subtree) {
443             handlePowerSupplyAttributesSubTreeResponse(asyncResp, ec, subtree);
444         });
445 }
446 
447 inline void doPowerSupplyGet(
448     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
449     const std::string& chassisId, const std::string& powerSupplyId,
450     const std::string& powerSupplyPath, const std::string& service)
451 {
452     asyncResp->res.addHeader(
453         boost::beast::http::field::link,
454         "</redfish/v1/JsonSchemas/PowerSupply/PowerSupply.json>; rel=describedby");
455     asyncResp->res.jsonValue["@odata.type"] = "#PowerSupply.v1_5_0.PowerSupply";
456     asyncResp->res.jsonValue["Name"] = "Power Supply";
457     asyncResp->res.jsonValue["Id"] = powerSupplyId;
458     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
459         "/redfish/v1/Chassis/{}/PowerSubsystem/PowerSupplies/{}", chassisId,
460         powerSupplyId);
461 
462     asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
463     asyncResp->res.jsonValue["Status"]["Health"] = resource::Health::OK;
464 
465     getPowerSupplyState(asyncResp, service, powerSupplyPath);
466     getPowerSupplyHealth(asyncResp, service, powerSupplyPath);
467     getPowerSupplyAsset(asyncResp, service, powerSupplyPath);
468     getPowerSupplyFirmwareVersion(asyncResp, service, powerSupplyPath);
469     getPowerSupplyLocation(asyncResp, service, powerSupplyPath);
470     getEfficiencyPercent(asyncResp);
471 }
472 
473 inline void handlePowerSupplyHead(
474     App& app, const crow::Request& req,
475     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
476     const std::string& chassisId, const std::string& powerSupplyId)
477 {
478     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
479     {
480         return;
481     }
482 
483     // Get the correct Path and Service that match the input parameters
484     getValidPowerSupplyPath(
485         asyncResp, chassisId, powerSupplyId,
486         [asyncResp](const std::string&, const std::string&) {
487             asyncResp->res.addHeader(
488                 boost::beast::http::field::link,
489                 "</redfish/v1/JsonSchemas/PowerSupply/PowerSupply.json>; rel=describedby");
490         });
491 }
492 
493 inline void handlePowerSupplyGet(
494     App& app, const crow::Request& req,
495     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
496     const std::string& chassisId, const std::string& powerSupplyId)
497 {
498     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
499     {
500         return;
501     }
502     getValidPowerSupplyPath(
503         asyncResp, chassisId, powerSupplyId,
504         std::bind_front(doPowerSupplyGet, asyncResp, chassisId, powerSupplyId));
505 }
506 
507 inline void requestRoutesPowerSupply(App& app)
508 {
509     BMCWEB_ROUTE(
510         app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/<str>/")
511         .privileges(redfish::privileges::headPowerSupply)
512         .methods(boost::beast::http::verb::head)(
513             std::bind_front(handlePowerSupplyHead, std::ref(app)));
514 
515     BMCWEB_ROUTE(
516         app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/<str>/")
517         .privileges(redfish::privileges::getPowerSupply)
518         .methods(boost::beast::http::verb::get)(
519             std::bind_front(handlePowerSupplyGet, std::ref(app)));
520 }
521 
522 } // namespace redfish
523