blob: 9319dfa987a2e8d9c573d0f1966cf65345333033 [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
165inline 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
George Liue4e54232022-09-30 09:08:11 +0800178inline 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
Ed Tanous4ff0f1f2024-09-04 17:27:37 -0700185inline void handleFanPath(
George Liue4e54232022-09-30 09:08:11 +0800186 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) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400203 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 });
George Liue4e54232022-09-30 09:08:11 +0800212
213 return;
214 }
Ed Tanous62598e32023-07-17 17:06:25 -0700215 BMCWEB_LOG_WARNING("Fan not found {}", fanId);
George Liue4e54232022-09-30 09:08:11 +0800216 messages::resourceNotFound(asyncResp->res, "Fan", fanId);
217}
218
Myung Bae1da1ff52024-08-20 11:34:59 -0500219inline void getValidFanObject(
George Liue4e54232022-09-30 09:08:11 +0800220 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) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400229 handleFanPath(fanId, asyncResp, fanPaths, callback);
230 });
George Liue4e54232022-09-30 09:08:11 +0800231}
232
233inline 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);
Ed Tanous539d8c62024-06-19 14:38:27 -0700244 resp.jsonValue["Status"]["State"] = resource::State::Enabled;
245 resp.jsonValue["Status"]["Health"] = resource::Health::OK;
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800246}
247
248inline void getFanHealth(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
249 const std::string& fanPath, const std::string& service)
250{
Ed Tanousdeae6a72024-11-11 21:58:57 -0800251 dbus::utility::getProperty<bool>(
252 service, fanPath,
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800253 "xyz.openbmc_project.State.Decorator.OperationalStatus", "Functional",
254 [asyncResp](const boost::system::error_code& ec, const bool value) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400255 if (ec)
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800256 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400257 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;
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800264 }
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800265
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400266 if (!value)
267 {
268 asyncResp->res.jsonValue["Status"]["Health"] =
269 resource::Health::Critical;
270 }
271 });
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800272}
273
274inline void getFanState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
275 const std::string& fanPath, const std::string& service)
276{
Ed Tanousdeae6a72024-11-11 21:58:57 -0800277 dbus::utility::getProperty<bool>(
278 service, fanPath, "xyz.openbmc_project.Inventory.Item", "Present",
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800279 [asyncResp](const boost::system::error_code& ec, const bool value) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400280 if (ec)
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800281 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400282 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;
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800289 }
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800290
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400291 if (!value)
292 {
293 asyncResp->res.jsonValue["Status"]["State"] =
294 resource::State::Absent;
295 }
296 });
George Liue4e54232022-09-30 09:08:11 +0800297}
298
Ed Tanousfc3edfd2023-07-20 12:41:30 -0700299inline void getFanLocation(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
George Liu4a2e4852022-10-04 16:13:44 +0800300 const std::string& fanPath,
301 const std::string& service)
302{
Ed Tanousdeae6a72024-11-11 21:58:57 -0800303 dbus::utility::getProperty<std::string>(
304 service, fanPath,
George Liu4a2e4852022-10-04 16:13:44 +0800305 "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode",
Ed Tanousfc3edfd2023-07-20 12:41:30 -0700306 [asyncResp](const boost::system::error_code& ec,
307 const std::string& property) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400308 if (ec)
George Liu4a2e4852022-10-04 16:13:44 +0800309 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400310 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;
George Liu4a2e4852022-10-04 16:13:44 +0800317 }
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400318 asyncResp->res
319 .jsonValue["Location"]["PartLocation"]["ServiceLabel"] =
320 property;
321 });
George Liu4a2e4852022-10-04 16:13:44 +0800322}
323
Myung Bae1da1ff52024-08-20 11:34:59 -0500324inline void afterGetValidFanObject(
Patrick Williams504af5a2025-02-03 14:29:03 -0500325 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)
George Liue4e54232022-09-30 09:08:11 +0800328{
George Liue4e54232022-09-30 09:08:11 +0800329 addFanCommonProperties(asyncResp->res, chassisId, fanId);
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800330 getFanState(asyncResp, fanPath, service);
331 getFanHealth(asyncResp, fanPath, service);
Myung Baef7e62c12025-09-07 14:02:08 -0500332 asset_utils::getAssetInfo(asyncResp, service, fanPath, ""_json_pointer,
333 true);
George Liu4a2e4852022-10-04 16:13:44 +0800334 getFanLocation(asyncResp, fanPath, service);
Myung Bae1da1ff52024-08-20 11:34:59 -0500335 getLocationIndicatorActive(asyncResp, fanPath);
George Liue4e54232022-09-30 09:08:11 +0800336}
337
338inline 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
Myung Bae1da1ff52024-08-20 11:34:59 -0500348 getValidFanObject(
George Liue4e54232022-09-30 09:08:11 +0800349 asyncResp, *validChassisPath, fanId,
Myung Bae1da1ff52024-08-20 11:34:59 -0500350 std::bind_front(afterGetValidFanObject, asyncResp, chassisId, fanId));
George Liue4e54232022-09-30 09:08:11 +0800351}
352
353inline 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) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400367 if (!validChassisPath)
368 {
369 messages::resourceNotFound(asyncResp->res, "Chassis",
370 chassisId);
371 return;
372 }
Myung Bae1da1ff52024-08-20 11:34:59 -0500373 getValidFanObject(
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400374 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 });
George Liue4e54232022-09-30 09:08:11 +0800380 });
George Liue4e54232022-09-30 09:08:11 +0800381}
382
383inline 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
Myung Bae1da1ff52024-08-20 11:34:59 -0500397inline 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
430inline 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
George Liue4e54232022-09-30 09:08:11 +0800464inline 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)));
Myung Bae1da1ff52024-08-20 11:34:59 -0500475
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)));
George Liue4e54232022-09-30 09:08:11 +0800480}
481
George Liu9516f412022-09-29 17:21:41 +0800482} // namespace redfish