xref: /openbmc/bmcweb/redfish-core/lib/fan.hpp (revision 99353a927d091ca89c590c6056808a4c0788bd63)
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 
updateFanList(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const dbus::utility::MapperGetSubTreePathsResponse & fanPaths)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 
getFanPaths(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & validChassisPath,const std::function<void (const dbus::utility::MapperGetSubTreePathsResponse & fanPaths)> & callback)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 
doFanCollection(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::optional<std::string> & validChassisPath)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 
handleFanCollectionHead(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId)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 
handleFanCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId)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 
checkFanId(const std::string & fanPath,const std::string & fanId)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 
handleFanPath(const std::string & fanId,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const dbus::utility::MapperGetSubTreePathsResponse & fanPaths,const std::function<void (const std::string & fanPath,const std::string & service)> & callback)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 
getValidFanObject(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & validChassisPath,const std::string & fanId,const std::function<void (const std::string & fanPath,const std::string & service)> & callback)206 inline void getValidFanObject(
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 
addFanCommonProperties(crow::Response & resp,const std::string & chassisId,const std::string & fanId)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 
getFanHealth(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & fanPath,const std::string & service)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 
getFanState(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & fanPath,const std::string & service)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 
getFanLocation(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & fanPath,const std::string & service)286 inline void getFanLocation(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
287                            const std::string& fanPath,
288                            const std::string& service)
289 {
290     dbus::utility::getProperty<std::string>(
291         service, fanPath,
292         "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode",
293         [asyncResp](const boost::system::error_code& ec,
294                     const std::string& property) {
295             if (ec)
296             {
297                 if (ec.value() != EBADR)
298                 {
299                     BMCWEB_LOG_ERROR("DBUS response error for Location{}",
300                                      ec.value());
301                     messages::internalError(asyncResp->res);
302                 }
303                 return;
304             }
305             asyncResp->res
306                 .jsonValue["Location"]["PartLocation"]["ServiceLabel"] =
307                 property;
308         });
309 }
310 
afterGetValidFanObject(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & fanId,const std::string & fanPath,const std::string & service)311 inline void afterGetValidFanObject(
312     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
313     const std::string& chassisId, const std::string& fanId,
314     const std::string& fanPath, const std::string& service)
315 {
316     addFanCommonProperties(asyncResp->res, chassisId, fanId);
317     getFanState(asyncResp, fanPath, service);
318     getFanHealth(asyncResp, fanPath, service);
319     asset_utils::getAssetInfo(asyncResp, service, fanPath, ""_json_pointer,
320                               true);
321     getFanLocation(asyncResp, fanPath, service);
322     getLocationIndicatorActive(asyncResp, fanPath);
323 }
324 
doFanGet(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & fanId,const std::optional<std::string> & validChassisPath)325 inline void doFanGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
326                      const std::string& chassisId, const std::string& fanId,
327                      const std::optional<std::string>& validChassisPath)
328 {
329     if (!validChassisPath)
330     {
331         messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
332         return;
333     }
334 
335     getValidFanObject(
336         asyncResp, *validChassisPath, fanId,
337         std::bind_front(afterGetValidFanObject, asyncResp, chassisId, fanId));
338 }
339 
handleFanHead(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & fanId)340 inline void handleFanHead(App& app, const crow::Request& req,
341                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
342                           const std::string& chassisId,
343                           const std::string& fanId)
344 {
345     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
346     {
347         return;
348     }
349 
350     redfish::chassis_utils::getValidChassisPath(
351         asyncResp, chassisId,
352         [asyncResp, chassisId,
353          fanId](const std::optional<std::string>& validChassisPath) {
354             if (!validChassisPath)
355             {
356                 messages::resourceNotFound(asyncResp->res, "Chassis",
357                                            chassisId);
358                 return;
359             }
360             getValidFanObject(
361                 asyncResp, *validChassisPath, fanId,
362                 [asyncResp](const std::string&, const std::string&) {
363                     asyncResp->res.addHeader(
364                         boost::beast::http::field::link,
365                         "</redfish/v1/JsonSchemas/Fan/Fan.json>; rel=describedby");
366                 });
367         });
368 }
369 
handleFanGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & fanId)370 inline void handleFanGet(App& app, const crow::Request& req,
371                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
372                          const std::string& chassisId, const std::string& fanId)
373 {
374     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
375     {
376         return;
377     }
378 
379     redfish::chassis_utils::getValidChassisPath(
380         asyncResp, chassisId,
381         std::bind_front(doFanGet, asyncResp, chassisId, fanId));
382 }
383 
handleSetFanPathById(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & fanId,bool locationIndicatorActive,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreePathsResponse & fanPaths)384 inline void handleSetFanPathById(
385     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
386     const std::string& chassisId, const std::string& fanId,
387     bool locationIndicatorActive, const boost::system::error_code& ec,
388     const dbus::utility::MapperGetSubTreePathsResponse& fanPaths)
389 {
390     if (ec)
391     {
392         if (ec.value() == boost::system::errc::io_error)
393         {
394             BMCWEB_LOG_WARNING("Chassis {} not found", chassisId);
395             messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
396             return;
397         }
398 
399         BMCWEB_LOG_ERROR("DBUS response error {}", ec.value());
400         messages::internalError(asyncResp->res);
401         return;
402     }
403 
404     for (const auto& fanPath : fanPaths)
405     {
406         if (checkFanId(fanPath, fanId))
407         {
408             setLocationIndicatorActive(asyncResp, fanPath,
409                                        locationIndicatorActive);
410             return;
411         }
412     }
413     BMCWEB_LOG_WARNING("Fan {} not found", fanId);
414     messages::resourceNotFound(asyncResp->res, "Fan", fanId);
415 }
416 
handleFanPatch(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & fanId)417 inline void handleFanPatch(App& app, const crow::Request& req,
418                            const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
419                            const std::string& chassisId,
420                            const std::string& fanId)
421 {
422     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
423     {
424         return;
425     }
426 
427     std::optional<bool> locationIndicatorActive;
428     if (!json_util::readJsonPatch(req, asyncResp->res,
429                                   "LocationIndicatorActive",
430                                   locationIndicatorActive))
431     {
432         return;
433     }
434 
435     if (locationIndicatorActive)
436     {
437         dbus::utility::getAssociatedSubTreePathsById(
438             chassisId, "/xyz/openbmc_project/inventory", chassisInterfaces,
439             "cooled_by", fanInterface,
440             [asyncResp, chassisId, fanId, locationIndicatorActive](
441                 const boost::system::error_code& ec,
442                 const dbus::utility::MapperGetSubTreePathsResponse&
443                     subtreePaths) {
444                 handleSetFanPathById(asyncResp, chassisId, fanId,
445                                      *locationIndicatorActive, ec,
446                                      subtreePaths);
447             });
448     }
449 }
450 
requestRoutesFan(App & app)451 inline void requestRoutesFan(App& app)
452 {
453     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/")
454         .privileges(redfish::privileges::headFanCollection)
455         .methods(boost::beast::http::verb::head)(
456             std::bind_front(handleFanCollectionHead, std::ref(app)));
457 
458     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/")
459         .privileges(redfish::privileges::getFanCollection)
460         .methods(boost::beast::http::verb::get)(
461             std::bind_front(handleFanCollectionGet, std::ref(app)));
462 
463     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/")
464         .privileges(redfish::privileges::headFan)
465         .methods(boost::beast::http::verb::head)(
466             std::bind_front(handleFanHead, std::ref(app)));
467 
468     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/")
469         .privileges(redfish::privileges::getFan)
470         .methods(boost::beast::http::verb::get)(
471             std::bind_front(handleFanGet, std::ref(app)));
472 
473     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/")
474         .privileges(redfish::privileges::patchFan)
475         .methods(boost::beast::http::verb::patch)(
476             std::bind_front(handleFanPatch, std::ref(app)));
477 }
478 
479 } // namespace redfish
480