xref: /openbmc/bmcweb/redfish-core/lib/fan.hpp (revision 8f79c5b6)
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 getFanAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
282                         const std::string& fanPath, const std::string& service)
283 {
284     sdbusplus::asio::getAllProperties(
285         *crow::connections::systemBus, service, fanPath,
286         "xyz.openbmc_project.Inventory.Decorator.Asset",
287         [fanPath, asyncResp{asyncResp}](
288             const boost::system::error_code& ec,
289             const dbus::utility::DBusPropertiesMap& assetList) {
290         if (ec)
291         {
292             if (ec.value() != EBADR)
293             {
294                 BMCWEB_LOG_ERROR("DBUS response error for Properties{}",
295                                  ec.value());
296                 messages::internalError(asyncResp->res);
297             }
298             return;
299         }
300         const std::string* manufacturer = nullptr;
301         const std::string* model = nullptr;
302         const std::string* partNumber = nullptr;
303         const std::string* serialNumber = nullptr;
304         const std::string* sparePartNumber = nullptr;
305 
306         const bool success = sdbusplus::unpackPropertiesNoThrow(
307             dbus_utils::UnpackErrorPrinter(), assetList, "Manufacturer",
308             manufacturer, "Model", model, "PartNumber", partNumber,
309             "SerialNumber", serialNumber, "SparePartNumber", sparePartNumber);
310         if (!success)
311         {
312             messages::internalError(asyncResp->res);
313             return;
314         }
315         if (manufacturer != nullptr)
316         {
317             asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
318         }
319         if (model != nullptr)
320         {
321             asyncResp->res.jsonValue["Model"] = *model;
322         }
323         if (partNumber != nullptr)
324         {
325             asyncResp->res.jsonValue["PartNumber"] = *partNumber;
326         }
327         if (serialNumber != nullptr)
328         {
329             asyncResp->res.jsonValue["SerialNumber"] = *serialNumber;
330         }
331         if (sparePartNumber != nullptr && !sparePartNumber->empty())
332         {
333             asyncResp->res.jsonValue["SparePartNumber"] = *sparePartNumber;
334         }
335     });
336 }
337 
338 inline void getFanLocation(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
339                            const std::string& fanPath,
340                            const std::string& service)
341 {
342     sdbusplus::asio::getProperty<std::string>(
343         *crow::connections::systemBus, service, fanPath,
344         "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode",
345         [asyncResp](const boost::system::error_code& ec,
346                     const std::string& property) {
347         if (ec)
348         {
349             if (ec.value() != EBADR)
350             {
351                 BMCWEB_LOG_ERROR("DBUS response error for Location{}",
352                                  ec.value());
353                 messages::internalError(asyncResp->res);
354             }
355             return;
356         }
357         asyncResp->res.jsonValue["Location"]["PartLocation"]["ServiceLabel"] =
358             property;
359     });
360 }
361 
362 inline void
363     afterGetValidFanPath(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
364                          const std::string& chassisId, const std::string& fanId,
365                          const std::string& fanPath, const std::string& service)
366 {
367     addFanCommonProperties(asyncResp->res, chassisId, fanId);
368     getFanState(asyncResp, fanPath, service);
369     getFanHealth(asyncResp, fanPath, service);
370     getFanAsset(asyncResp, fanPath, service);
371     getFanLocation(asyncResp, fanPath, service);
372 }
373 
374 inline void doFanGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
375                      const std::string& chassisId, const std::string& fanId,
376                      const std::optional<std::string>& validChassisPath)
377 {
378     if (!validChassisPath)
379     {
380         messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
381         return;
382     }
383 
384     getValidFanPath(
385         asyncResp, *validChassisPath, fanId,
386         std::bind_front(afterGetValidFanPath, asyncResp, chassisId, fanId));
387 }
388 
389 inline void handleFanHead(App& app, const crow::Request& req,
390                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
391                           const std::string& chassisId,
392                           const std::string& fanId)
393 {
394     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
395     {
396         return;
397     }
398 
399     redfish::chassis_utils::getValidChassisPath(
400         asyncResp, chassisId,
401         [asyncResp, chassisId,
402          fanId](const std::optional<std::string>& validChassisPath) {
403         if (!validChassisPath)
404         {
405             messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
406             return;
407         }
408         getValidFanPath(asyncResp, *validChassisPath, fanId,
409                         [asyncResp](const std::string&, const std::string&) {
410             asyncResp->res.addHeader(
411                 boost::beast::http::field::link,
412                 "</redfish/v1/JsonSchemas/Fan/Fan.json>; rel=describedby");
413         });
414     });
415 }
416 
417 inline void handleFanGet(App& app, const crow::Request& req,
418                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
419                          const std::string& chassisId, const std::string& fanId)
420 {
421     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
422     {
423         return;
424     }
425 
426     redfish::chassis_utils::getValidChassisPath(
427         asyncResp, chassisId,
428         std::bind_front(doFanGet, asyncResp, chassisId, fanId));
429 }
430 
431 inline void requestRoutesFan(App& app)
432 {
433     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/")
434         .privileges(redfish::privileges::headFan)
435         .methods(boost::beast::http::verb::head)(
436             std::bind_front(handleFanHead, std::ref(app)));
437 
438     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/")
439         .privileges(redfish::privileges::getFan)
440         .methods(boost::beast::http::verb::get)(
441             std::bind_front(handleFanGet, std::ref(app)));
442 }
443 
444 } // namespace redfish
445