xref: /openbmc/bmcweb/redfish-core/lib/fan.hpp (revision 80e6e25e7d721fa03fcc2b194881d8d8a64fe416)
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",
126                                            chassisId);
127                 return;
128             }
129             asyncResp->res.addHeader(
130                 boost::beast::http::field::link,
131                 "</redfish/v1/JsonSchemas/FanCollection/FanCollection.json>; rel=describedby");
132         });
133 }
134 
135 inline void
136     handleFanCollectionGet(App& app, const crow::Request& req,
137                            const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
138                            const std::string& chassisId)
139 {
140     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
141     {
142         return;
143     }
144 
145     redfish::chassis_utils::getValidChassisPath(
146         asyncResp, chassisId,
147         std::bind_front(doFanCollection, asyncResp, chassisId));
148 }
149 
150 inline void requestRoutesFanCollection(App& app)
151 {
152     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/")
153         .privileges(redfish::privileges::headFanCollection)
154         .methods(boost::beast::http::verb::head)(
155             std::bind_front(handleFanCollectionHead, std::ref(app)));
156 
157     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/")
158         .privileges(redfish::privileges::getFanCollection)
159         .methods(boost::beast::http::verb::get)(
160             std::bind_front(handleFanCollectionGet, std::ref(app)));
161 }
162 
163 inline bool checkFanId(const std::string& fanPath, const std::string& fanId)
164 {
165     std::string fanName = sdbusplus::message::object_path(fanPath).filename();
166 
167     return !(fanName.empty() || fanName != fanId);
168 }
169 
170 inline void handleFanPath(
171     const std::string& fanId,
172     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
173     const dbus::utility::MapperGetSubTreePathsResponse& fanPaths,
174     const std::function<void(const std::string& fanPath,
175                              const std::string& service)>& callback)
176 {
177     for (const auto& fanPath : fanPaths)
178     {
179         if (!checkFanId(fanPath, fanId))
180         {
181             continue;
182         }
183         dbus::utility::getDbusObject(
184             fanPath, fanInterface,
185             [fanPath, asyncResp,
186              callback](const boost::system::error_code& ec,
187                        const dbus::utility::MapperGetObject& object) {
188                 if (ec || object.empty())
189                 {
190                     BMCWEB_LOG_ERROR("DBUS response error on getDbusObject {}",
191                                      ec.value());
192                     messages::internalError(asyncResp->res);
193                     return;
194                 }
195                 callback(fanPath, object.begin()->first);
196             });
197 
198         return;
199     }
200     BMCWEB_LOG_WARNING("Fan not found {}", fanId);
201     messages::resourceNotFound(asyncResp->res, "Fan", fanId);
202 }
203 
204 inline void getValidFanPath(
205     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
206     const std::string& validChassisPath, const std::string& fanId,
207     const std::function<void(const std::string& fanPath,
208                              const std::string& service)>& callback)
209 {
210     getFanPaths(
211         asyncResp, validChassisPath,
212         [fanId, asyncResp, callback](
213             const dbus::utility::MapperGetSubTreePathsResponse& fanPaths) {
214             handleFanPath(fanId, asyncResp, fanPaths, callback);
215         });
216 }
217 
218 inline void addFanCommonProperties(crow::Response& resp,
219                                    const std::string& chassisId,
220                                    const std::string& fanId)
221 {
222     resp.addHeader(boost::beast::http::field::link,
223                    "</redfish/v1/JsonSchemas/Fan/Fan.json>; rel=describedby");
224     resp.jsonValue["@odata.type"] = "#Fan.v1_3_0.Fan";
225     resp.jsonValue["Name"] = "Fan";
226     resp.jsonValue["Id"] = fanId;
227     resp.jsonValue["@odata.id"] = boost::urls::format(
228         "/redfish/v1/Chassis/{}/ThermalSubsystem/Fans/{}", chassisId, fanId);
229     resp.jsonValue["Status"]["State"] = resource::State::Enabled;
230     resp.jsonValue["Status"]["Health"] = resource::Health::OK;
231 }
232 
233 inline void getFanHealth(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
234                          const std::string& fanPath, const std::string& service)
235 {
236     dbus::utility::getProperty<bool>(
237         service, fanPath,
238         "xyz.openbmc_project.State.Decorator.OperationalStatus", "Functional",
239         [asyncResp](const boost::system::error_code& ec, const bool value) {
240             if (ec)
241             {
242                 if (ec.value() != EBADR)
243                 {
244                     BMCWEB_LOG_ERROR("DBUS response error for Health {}",
245                                      ec.value());
246                     messages::internalError(asyncResp->res);
247                 }
248                 return;
249             }
250 
251             if (!value)
252             {
253                 asyncResp->res.jsonValue["Status"]["Health"] =
254                     resource::Health::Critical;
255             }
256         });
257 }
258 
259 inline void getFanState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
260                         const std::string& fanPath, const std::string& service)
261 {
262     dbus::utility::getProperty<bool>(
263         service, fanPath, "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     dbus::utility::getAllProperties(
288         service, fanPath, "xyz.openbmc_project.Inventory.Decorator.Asset",
289         [fanPath, asyncResp{asyncResp}](
290             const boost::system::error_code& ec,
291             const dbus::utility::DBusPropertiesMap& assetList) {
292             if (ec)
293             {
294                 if (ec.value() != EBADR)
295                 {
296                     BMCWEB_LOG_ERROR("DBUS response error for Properties{}",
297                                      ec.value());
298                     messages::internalError(asyncResp->res);
299                 }
300                 return;
301             }
302             const std::string* manufacturer = nullptr;
303             const std::string* model = nullptr;
304             const std::string* partNumber = nullptr;
305             const std::string* serialNumber = nullptr;
306             const std::string* sparePartNumber = nullptr;
307 
308             const bool success = sdbusplus::unpackPropertiesNoThrow(
309                 dbus_utils::UnpackErrorPrinter(), assetList, "Manufacturer",
310                 manufacturer, "Model", model, "PartNumber", partNumber,
311                 "SerialNumber", serialNumber, "SparePartNumber",
312                 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     dbus::utility::getProperty<std::string>(
346         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
361                 .jsonValue["Location"]["PartLocation"]["ServiceLabel"] =
362                 property;
363         });
364 }
365 
366 inline void
367     afterGetValidFanPath(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
368                          const std::string& chassisId, const std::string& fanId,
369                          const std::string& fanPath, const std::string& service)
370 {
371     addFanCommonProperties(asyncResp->res, chassisId, fanId);
372     getFanState(asyncResp, fanPath, service);
373     getFanHealth(asyncResp, fanPath, service);
374     getFanAsset(asyncResp, fanPath, service);
375     getFanLocation(asyncResp, fanPath, service);
376 }
377 
378 inline void doFanGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
379                      const std::string& chassisId, const std::string& fanId,
380                      const std::optional<std::string>& validChassisPath)
381 {
382     if (!validChassisPath)
383     {
384         messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
385         return;
386     }
387 
388     getValidFanPath(
389         asyncResp, *validChassisPath, fanId,
390         std::bind_front(afterGetValidFanPath, asyncResp, chassisId, fanId));
391 }
392 
393 inline void handleFanHead(App& app, const crow::Request& req,
394                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
395                           const std::string& chassisId,
396                           const std::string& fanId)
397 {
398     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
399     {
400         return;
401     }
402 
403     redfish::chassis_utils::getValidChassisPath(
404         asyncResp, chassisId,
405         [asyncResp, chassisId,
406          fanId](const std::optional<std::string>& validChassisPath) {
407             if (!validChassisPath)
408             {
409                 messages::resourceNotFound(asyncResp->res, "Chassis",
410                                            chassisId);
411                 return;
412             }
413             getValidFanPath(
414                 asyncResp, *validChassisPath, fanId,
415                 [asyncResp](const std::string&, const std::string&) {
416                     asyncResp->res.addHeader(
417                         boost::beast::http::field::link,
418                         "</redfish/v1/JsonSchemas/Fan/Fan.json>; rel=describedby");
419                 });
420         });
421 }
422 
423 inline void handleFanGet(App& app, const crow::Request& req,
424                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
425                          const std::string& chassisId, const std::string& fanId)
426 {
427     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
428     {
429         return;
430     }
431 
432     redfish::chassis_utils::getValidChassisPath(
433         asyncResp, chassisId,
434         std::bind_front(doFanGet, asyncResp, chassisId, fanId));
435 }
436 
437 inline void requestRoutesFan(App& app)
438 {
439     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/")
440         .privileges(redfish::privileges::headFan)
441         .methods(boost::beast::http::verb::head)(
442             std::bind_front(handleFanHead, std::ref(app)));
443 
444     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/")
445         .privileges(redfish::privileges::getFan)
446         .methods(boost::beast::http::verb::get)(
447             std::bind_front(handleFanGet, std::ref(app)));
448 }
449 
450 } // namespace redfish
451