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