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