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