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