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