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