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