blob: 40368e719912ac43ee95cb3058c03d80e107956d [file] [log] [blame]
Ed Tanous40e9b922024-09-10 13:50:16 -07001// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright OpenBMC Authors
George Liu9516f412022-09-29 17:21:41 +08003#pragma once
4
5#include "app.hpp"
Ed Tanousd7857202025-01-28 15:32:26 -08006#include "async_resp.hpp"
George Liu9516f412022-09-29 17:21:41 +08007#include "dbus_utility.hpp"
8#include "error_messages.hpp"
Ed Tanous539d8c62024-06-19 14:38:27 -07009#include "generated/enums/resource.hpp"
Ed Tanousd7857202025-01-28 15:32:26 -080010#include "http_request.hpp"
Myung Bae1da1ff52024-08-20 11:34:59 -050011#include "led.hpp"
Ed Tanousd7857202025-01-28 15:32:26 -080012#include "logging.hpp"
George Liu9516f412022-09-29 17:21:41 +080013#include "query.hpp"
14#include "registries/privilege_registry.hpp"
Myung Baef7e62c12025-09-07 14:02:08 -050015#include "utils/asset_utils.hpp"
George Liu9516f412022-09-29 17:21:41 +080016#include "utils/chassis_utils.hpp"
Ed Tanous177612a2025-02-14 15:16:09 -080017#include "utils/dbus_utils.hpp"
Myung Bae1da1ff52024-08-20 11:34:59 -050018#include "utils/json_utils.hpp"
George Liu9516f412022-09-29 17:21:41 +080019
Ed Tanousd7857202025-01-28 15:32:26 -080020#include <asm-generic/errno.h>
21
22#include <boost/beast/http/field.hpp>
23#include <boost/beast/http/verb.hpp>
Albert Zhang9f1ae5a2021-06-10 14:41:48 +080024#include <boost/system/error_code.hpp>
George Liu9516f412022-09-29 17:21:41 +080025#include <boost/url/format.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080026#include <nlohmann/json.hpp>
27#include <sdbusplus/unpack_properties.hpp>
George Liu9516f412022-09-29 17:21:41 +080028
Ed Tanousd7857202025-01-28 15:32:26 -080029#include <array>
George Liu9516f412022-09-29 17:21:41 +080030#include <functional>
31#include <memory>
32#include <optional>
33#include <string>
34#include <string_view>
Ed Tanousd7857202025-01-28 15:32:26 -080035#include <utility>
George Liu9516f412022-09-29 17:21:41 +080036
37namespace redfish
38{
39constexpr std::array<std::string_view, 1> fanInterface = {
40 "xyz.openbmc_project.Inventory.Item.Fan"};
41
Patrick Williams504af5a2025-02-03 14:29:03 -050042inline void updateFanList(
43 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
44 const std::string& chassisId,
45 const dbus::utility::MapperGetSubTreePathsResponse& fanPaths)
George Liu9516f412022-09-29 17:21:41 +080046{
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
67inline void getFanPaths(
68 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
Ed Tanousd547d8d2024-03-16 18:04:41 -070069 const std::string& validChassisPath,
George Liu9516f412022-09-29 17:21:41 +080070 const std::function<void(const dbus::utility::MapperGetSubTreePathsResponse&
71 fanPaths)>& callback)
72{
Ed Tanousd547d8d2024-03-16 18:04:41 -070073 sdbusplus::message::object_path endpointPath{validChassisPath};
George Liu9516f412022-09-29 17:21:41 +080074 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) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -040083 if (ec)
George Liu9516f412022-09-29 17:21:41 +080084 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -040085 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;
George Liu9516f412022-09-29 17:21:41 +080093 }
Patrick Williamsbd79bce2024-08-16 15:22:20 -040094 callback(subtreePaths);
95 });
George Liu9516f412022-09-29 17:21:41 +080096}
97
98inline 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
Ed Tanousd547d8d2024-03-16 18:04:41 -0700120 getFanPaths(asyncResp, *validChassisPath,
George Liu9516f412022-09-29 17:21:41 +0800121 std::bind_front(updateFanList, asyncResp, chassisId));
122}
123
Patrick Williams504af5a2025-02-03 14:29:03 -0500124inline void handleFanCollectionHead(
125 App& app, const crow::Request& req,
126 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
127 const std::string& chassisId)
George Liu9516f412022-09-29 17:21:41 +0800128{
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) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400138 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 });
George Liu9516f412022-09-29 17:21:41 +0800148}
149
Patrick Williams504af5a2025-02-03 14:29:03 -0500150inline void handleFanCollectionGet(
151 App& app, const crow::Request& req,
152 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
153 const std::string& chassisId)
George Liu9516f412022-09-29 17:21:41 +0800154{
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
George Liue4e54232022-09-30 09:08:11 +0800165inline 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
Ed Tanous4ff0f1f2024-09-04 17:27:37 -0700172inline void handleFanPath(
George Liue4e54232022-09-30 09:08:11 +0800173 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) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400190 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 });
George Liue4e54232022-09-30 09:08:11 +0800199
200 return;
201 }
Ed Tanous62598e32023-07-17 17:06:25 -0700202 BMCWEB_LOG_WARNING("Fan not found {}", fanId);
George Liue4e54232022-09-30 09:08:11 +0800203 messages::resourceNotFound(asyncResp->res, "Fan", fanId);
204}
205
Myung Bae1da1ff52024-08-20 11:34:59 -0500206inline void getValidFanObject(
George Liue4e54232022-09-30 09:08:11 +0800207 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) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400216 handleFanPath(fanId, asyncResp, fanPaths, callback);
217 });
George Liue4e54232022-09-30 09:08:11 +0800218}
219
220inline 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);
Ed Tanous539d8c62024-06-19 14:38:27 -0700231 resp.jsonValue["Status"]["State"] = resource::State::Enabled;
232 resp.jsonValue["Status"]["Health"] = resource::Health::OK;
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800233}
234
235inline void getFanHealth(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
236 const std::string& fanPath, const std::string& service)
237{
Ed Tanousdeae6a72024-11-11 21:58:57 -0800238 dbus::utility::getProperty<bool>(
239 service, fanPath,
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800240 "xyz.openbmc_project.State.Decorator.OperationalStatus", "Functional",
241 [asyncResp](const boost::system::error_code& ec, const bool value) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400242 if (ec)
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800243 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400244 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;
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800251 }
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800252
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400253 if (!value)
254 {
255 asyncResp->res.jsonValue["Status"]["Health"] =
256 resource::Health::Critical;
257 }
258 });
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800259}
260
261inline void getFanState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
262 const std::string& fanPath, const std::string& service)
263{
Ed Tanousdeae6a72024-11-11 21:58:57 -0800264 dbus::utility::getProperty<bool>(
265 service, fanPath, "xyz.openbmc_project.Inventory.Item", "Present",
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800266 [asyncResp](const boost::system::error_code& ec, const bool value) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400267 if (ec)
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800268 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400269 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;
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800276 }
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800277
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400278 if (!value)
279 {
280 asyncResp->res.jsonValue["Status"]["State"] =
281 resource::State::Absent;
282 }
283 });
George Liue4e54232022-09-30 09:08:11 +0800284}
285
Ed Tanousfc3edfd2023-07-20 12:41:30 -0700286inline void getFanLocation(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
George Liu4a2e4852022-10-04 16:13:44 +0800287 const std::string& fanPath,
288 const std::string& service)
289{
Ed Tanousdeae6a72024-11-11 21:58:57 -0800290 dbus::utility::getProperty<std::string>(
291 service, fanPath,
George Liu4a2e4852022-10-04 16:13:44 +0800292 "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode",
Ed Tanousfc3edfd2023-07-20 12:41:30 -0700293 [asyncResp](const boost::system::error_code& ec,
294 const std::string& property) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400295 if (ec)
George Liu4a2e4852022-10-04 16:13:44 +0800296 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400297 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;
George Liu4a2e4852022-10-04 16:13:44 +0800304 }
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400305 asyncResp->res
306 .jsonValue["Location"]["PartLocation"]["ServiceLabel"] =
307 property;
308 });
George Liu4a2e4852022-10-04 16:13:44 +0800309}
310
Myung Bae1da1ff52024-08-20 11:34:59 -0500311inline void afterGetValidFanObject(
Patrick Williams504af5a2025-02-03 14:29:03 -0500312 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)
George Liue4e54232022-09-30 09:08:11 +0800315{
George Liue4e54232022-09-30 09:08:11 +0800316 addFanCommonProperties(asyncResp->res, chassisId, fanId);
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800317 getFanState(asyncResp, fanPath, service);
318 getFanHealth(asyncResp, fanPath, service);
Myung Baef7e62c12025-09-07 14:02:08 -0500319 asset_utils::getAssetInfo(asyncResp, service, fanPath, ""_json_pointer,
320 true);
George Liu4a2e4852022-10-04 16:13:44 +0800321 getFanLocation(asyncResp, fanPath, service);
Myung Bae1da1ff52024-08-20 11:34:59 -0500322 getLocationIndicatorActive(asyncResp, fanPath);
George Liue4e54232022-09-30 09:08:11 +0800323}
324
325inline 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
Myung Bae1da1ff52024-08-20 11:34:59 -0500335 getValidFanObject(
George Liue4e54232022-09-30 09:08:11 +0800336 asyncResp, *validChassisPath, fanId,
Myung Bae1da1ff52024-08-20 11:34:59 -0500337 std::bind_front(afterGetValidFanObject, asyncResp, chassisId, fanId));
George Liue4e54232022-09-30 09:08:11 +0800338}
339
340inline 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) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400354 if (!validChassisPath)
355 {
356 messages::resourceNotFound(asyncResp->res, "Chassis",
357 chassisId);
358 return;
359 }
Myung Bae1da1ff52024-08-20 11:34:59 -0500360 getValidFanObject(
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400361 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 });
George Liue4e54232022-09-30 09:08:11 +0800367 });
George Liue4e54232022-09-30 09:08:11 +0800368}
369
370inline 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
Myung Bae1da1ff52024-08-20 11:34:59 -0500384inline 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
417inline 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
George Liue4e54232022-09-30 09:08:11 +0800451inline void requestRoutesFan(App& app)
452{
Christopher Meis99353a92025-09-26 08:54:40 +0200453 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
George Liue4e54232022-09-30 09:08:11 +0800463 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)));
Myung Bae1da1ff52024-08-20 11:34:59 -0500472
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)));
George Liue4e54232022-09-30 09:08:11 +0800477}
478
George Liu9516f412022-09-29 17:21:41 +0800479} // namespace redfish