1 #pragma once
2 
3 #include "app.hpp"
4 #include "dbus_utility.hpp"
5 #include "query.hpp"
6 #include "registries/privilege_registry.hpp"
7 #include "utils/chassis_utils.hpp"
8 
9 #include <boost/system/error_code.hpp>
10 #include <boost/url/format.hpp>
11 
12 #include <memory>
13 #include <optional>
14 #include <string>
15 
16 namespace redfish
17 {
18 
19 inline void
20     updatePowerSupplyList(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
21                           const std::string& chassisId,
22                           const std::string& powerSupplyPath)
23 {
24     std::string powerSupplyName =
25         sdbusplus::message::object_path(powerSupplyPath).filename();
26     if (powerSupplyName.empty())
27     {
28         return;
29     }
30 
31     nlohmann::json item = nlohmann::json::object();
32     item["@odata.id"] = boost::urls::format(
33         "/redfish/v1/Chassis/{}/PowerSubsystem/PowerSupplies/{}", chassisId,
34         powerSupplyName);
35 
36     nlohmann::json& powerSupplyList = asyncResp->res.jsonValue["Members"];
37     powerSupplyList.emplace_back(std::move(item));
38     asyncResp->res.jsonValue["Members@odata.count"] = powerSupplyList.size();
39 }
40 
41 inline void
42     doPowerSupplyCollection(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
43                             const std::string& chassisId,
44                             const std::optional<std::string>& validChassisPath)
45 {
46     if (!validChassisPath)
47     {
48         messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
49         return;
50     }
51 
52     asyncResp->res.addHeader(
53         boost::beast::http::field::link,
54         "</redfish/v1/JsonSchemas/PowerSupplyCollection/PowerSupplyCollection.json>; rel=describedby");
55     asyncResp->res.jsonValue["@odata.type"] =
56         "#PowerSupplyCollection.PowerSupplyCollection";
57     asyncResp->res.jsonValue["Name"] = "Power Supply Collection";
58     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
59         "/redfish/v1/Chassis/{}/PowerSubsystem/PowerSupplies", chassisId);
60     asyncResp->res.jsonValue["Description"] =
61         "The collection of PowerSupply resource instances.";
62     asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
63     asyncResp->res.jsonValue["Members@odata.count"] = 0;
64 
65     std::string powerPath = *validChassisPath + "/powered_by";
66     dbus::utility::getAssociationEndPoints(
67         powerPath, [asyncResp, chassisId](
68                        const boost::system::error_code& ec,
69                        const dbus::utility::MapperEndPoints& endpoints) {
70             if (ec)
71             {
72                 if (ec.value() != EBADR)
73                 {
74                     BMCWEB_LOG_ERROR << "DBUS response error" << ec.value();
75                     messages::internalError(asyncResp->res);
76                 }
77                 return;
78             }
79 
80             for (const auto& endpoint : endpoints)
81             {
82                 updatePowerSupplyList(asyncResp, chassisId, endpoint);
83             }
84         });
85 }
86 
87 inline void handlePowerSupplyCollectionHead(
88     App& app, const crow::Request& req,
89     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
90     const std::string& chassisId)
91 {
92     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
93     {
94         return;
95     }
96 
97     redfish::chassis_utils::getValidChassisPath(
98         asyncResp, chassisId,
99         [asyncResp,
100          chassisId](const std::optional<std::string>& validChassisPath) {
101         if (!validChassisPath)
102         {
103             messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
104             return;
105         }
106         asyncResp->res.addHeader(
107             boost::beast::http::field::link,
108             "</redfish/v1/JsonSchemas/PowerSupplyCollection/PowerSupplyCollection.json>; rel=describedby");
109         });
110 }
111 
112 inline void handlePowerSupplyCollectionGet(
113     App& app, const crow::Request& req,
114     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
115     const std::string& chassisId)
116 {
117     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
118     {
119         return;
120     }
121 
122     redfish::chassis_utils::getValidChassisPath(
123         asyncResp, chassisId,
124         std::bind_front(doPowerSupplyCollection, asyncResp, chassisId));
125 }
126 
127 inline void requestRoutesPowerSupplyCollection(App& app)
128 {
129     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/")
130         .privileges(redfish::privileges::headPowerSupplyCollection)
131         .methods(boost::beast::http::verb::head)(
132             std::bind_front(handlePowerSupplyCollectionHead, std::ref(app)));
133 
134     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/")
135         .privileges(redfish::privileges::getPowerSupplyCollection)
136         .methods(boost::beast::http::verb::get)(
137             std::bind_front(handlePowerSupplyCollectionGet, std::ref(app)));
138 }
139 
140 inline bool checkPowerSupplyId(const std::string& powerSupplyPath,
141                                const std::string& powerSupplyId)
142 {
143     std::string powerSupplyName =
144         sdbusplus::message::object_path(powerSupplyPath).filename();
145 
146     return !(powerSupplyName.empty() || powerSupplyName != powerSupplyId);
147 }
148 
149 inline void getValidPowerSupplyPath(
150     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
151     const std::string& validChassisPath, const std::string& powerSupplyId,
152     std::function<void(const std::string& powerSupplyPath)>&& callback)
153 {
154     std::string powerPath = validChassisPath + "/powered_by";
155     dbus::utility::getAssociationEndPoints(
156         powerPath, [asyncResp, powerSupplyId, callback{std::move(callback)}](
157                        const boost::system::error_code& ec,
158                        const dbus::utility::MapperEndPoints& endpoints) {
159             if (ec)
160             {
161                 if (ec.value() != EBADR)
162                 {
163                     BMCWEB_LOG_ERROR
164                         << "DBUS response error for getAssociationEndPoints"
165                         << ec.value();
166                     messages::internalError(asyncResp->res);
167                     return;
168                 }
169                 messages::resourceNotFound(asyncResp->res, "PowerSupplies",
170                                            powerSupplyId);
171                 return;
172             }
173 
174             for (const auto& endpoint : endpoints)
175             {
176                 if (checkPowerSupplyId(endpoint, powerSupplyId))
177                 {
178                     callback(endpoint);
179                     return;
180                 }
181             }
182 
183             if (!endpoints.empty())
184             {
185                 BMCWEB_LOG_WARNING << "Power supply not found: "
186                                    << powerSupplyId;
187                 messages::resourceNotFound(asyncResp->res, "PowerSupplies",
188                                            powerSupplyId);
189                 return;
190             }
191         });
192 }
193 
194 inline void
195     getPowerSupplyState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
196                         const std::string& service, const std::string& path)
197 {
198     sdbusplus::asio::getProperty<bool>(
199         *crow::connections::systemBus, service, path,
200         "xyz.openbmc_project.Inventory.Item", "Present",
201         [asyncResp](const boost::system::error_code& ec, const bool value) {
202         if (ec)
203         {
204             if (ec.value() != EBADR)
205             {
206                 BMCWEB_LOG_ERROR << "DBUS response error for State "
207                                  << ec.value();
208                 messages::internalError(asyncResp->res);
209             }
210             return;
211         }
212 
213         if (!value)
214         {
215             asyncResp->res.jsonValue["Status"]["State"] = "Absent";
216         }
217         });
218 }
219 
220 inline void
221     getPowerSupplyHealth(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
222                          const std::string& service, const std::string& path)
223 {
224     sdbusplus::asio::getProperty<bool>(
225         *crow::connections::systemBus, service, path,
226         "xyz.openbmc_project.State.Decorator.OperationalStatus", "Functional",
227         [asyncResp](const boost::system::error_code& ec, const bool value) {
228         if (ec)
229         {
230             if (ec.value() != EBADR)
231             {
232                 BMCWEB_LOG_ERROR << "DBUS response error for Health "
233                                  << ec.value();
234                 messages::internalError(asyncResp->res);
235             }
236             return;
237         }
238 
239         if (!value)
240         {
241             asyncResp->res.jsonValue["Status"]["Health"] = "Critical";
242         }
243         });
244 }
245 
246 inline void
247     doPowerSupplyGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
248                      const std::string& chassisId,
249                      const std::string& powerSupplyId,
250                      const std::optional<std::string>& validChassisPath)
251 {
252     if (!validChassisPath)
253     {
254         messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
255         return;
256     }
257 
258     // Get the correct Path and Service that match the input parameters
259     getValidPowerSupplyPath(asyncResp, *validChassisPath, powerSupplyId,
260                             [asyncResp, chassisId, powerSupplyId](
261                                 const std::string& powerSupplyPath) {
262         asyncResp->res.addHeader(
263             boost::beast::http::field::link,
264             "</redfish/v1/JsonSchemas/PowerSupply/PowerSupply.json>; rel=describedby");
265         asyncResp->res.jsonValue["@odata.type"] =
266             "#PowerSupply.v1_5_0.PowerSupply";
267         asyncResp->res.jsonValue["Name"] = "Power Supply";
268         asyncResp->res.jsonValue["Id"] = powerSupplyId;
269         asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
270             "/redfish/v1/Chassis/{}/PowerSubsystem/PowerSupplies/{}", chassisId,
271             powerSupplyId);
272 
273         asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
274         asyncResp->res.jsonValue["Status"]["Health"] = "OK";
275 
276         constexpr std::array<std::string_view, 1> interfaces = {
277             "xyz.openbmc_project.Inventory.Item.PowerSupply"};
278         dbus::utility::getDbusObject(
279             powerSupplyPath, interfaces,
280             [asyncResp,
281              powerSupplyPath](const boost::system::error_code& ec,
282                               const dbus::utility::MapperGetObject& object) {
283             if (ec || object.empty())
284             {
285                 messages::internalError(asyncResp->res);
286                 return;
287             }
288 
289             getPowerSupplyState(asyncResp, object.begin()->first,
290                                 powerSupplyPath);
291             getPowerSupplyHealth(asyncResp, object.begin()->first,
292                                  powerSupplyPath);
293             });
294     });
295 }
296 
297 inline void
298     handlePowerSupplyHead(App& app, const crow::Request& req,
299                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
300                           const std::string& chassisId,
301                           const std::string& powerSupplyId)
302 {
303     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
304     {
305         return;
306     }
307 
308     redfish::chassis_utils::getValidChassisPath(
309         asyncResp, chassisId,
310         [asyncResp, chassisId,
311          powerSupplyId](const std::optional<std::string>& validChassisPath) {
312         if (!validChassisPath)
313         {
314             messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
315             return;
316         }
317 
318         // Get the correct Path and Service that match the input parameters
319         getValidPowerSupplyPath(asyncResp, *validChassisPath, powerSupplyId,
320                                 [asyncResp](const std::string&) {
321             asyncResp->res.addHeader(
322                 boost::beast::http::field::link,
323                 "</redfish/v1/JsonSchemas/PowerSupply/PowerSupply.json>; rel=describedby");
324         });
325         });
326 }
327 
328 inline void
329     handlePowerSupplyGet(App& app, const crow::Request& req,
330                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
331                          const std::string& chassisId,
332                          const std::string& powerSupplyId)
333 {
334     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
335     {
336         return;
337     }
338 
339     redfish::chassis_utils::getValidChassisPath(
340         asyncResp, chassisId,
341         std::bind_front(doPowerSupplyGet, asyncResp, chassisId, powerSupplyId));
342 }
343 
344 inline void requestRoutesPowerSupply(App& app)
345 {
346     BMCWEB_ROUTE(
347         app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/<str>/")
348         .privileges(redfish::privileges::headPowerSupply)
349         .methods(boost::beast::http::verb::head)(
350             std::bind_front(handlePowerSupplyHead, std::ref(app)));
351 
352     BMCWEB_ROUTE(
353         app, "/redfish/v1/Chassis/<str>/PowerSubsystem/PowerSupplies/<str>/")
354         .privileges(redfish::privileges::getPowerSupply)
355         .methods(boost::beast::http::verb::get)(
356             std::bind_front(handlePowerSupplyGet, std::ref(app)));
357 }
358 
359 } // namespace redfish
360