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