blob: d426709d33d5b5b82020e92c7accf2f6382d5466 [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"
11#include "logging.hpp"
George Liu9516f412022-09-29 17:21:41 +080012#include "query.hpp"
13#include "registries/privilege_registry.hpp"
14#include "utils/chassis_utils.hpp"
15
Ed Tanousd7857202025-01-28 15:32:26 -080016#include <asm-generic/errno.h>
17
18#include <boost/beast/http/field.hpp>
19#include <boost/beast/http/verb.hpp>
Albert Zhang9f1ae5a2021-06-10 14:41:48 +080020#include <boost/system/error_code.hpp>
George Liu9516f412022-09-29 17:21:41 +080021#include <boost/url/format.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080022#include <nlohmann/json.hpp>
23#include <sdbusplus/unpack_properties.hpp>
George Liu9516f412022-09-29 17:21:41 +080024
Ed Tanousd7857202025-01-28 15:32:26 -080025#include <array>
George Liu9516f412022-09-29 17:21:41 +080026#include <functional>
27#include <memory>
28#include <optional>
29#include <string>
30#include <string_view>
Ed Tanousd7857202025-01-28 15:32:26 -080031#include <utility>
George Liu9516f412022-09-29 17:21:41 +080032
33namespace redfish
34{
35constexpr std::array<std::string_view, 1> fanInterface = {
36 "xyz.openbmc_project.Inventory.Item.Fan"};
37
38inline void
39 updateFanList(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
63inline void getFanPaths(
64 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
Ed Tanousd547d8d2024-03-16 18:04:41 -070065 const std::string& validChassisPath,
George Liu9516f412022-09-29 17:21:41 +080066 const std::function<void(const dbus::utility::MapperGetSubTreePathsResponse&
67 fanPaths)>& callback)
68{
Ed Tanousd547d8d2024-03-16 18:04:41 -070069 sdbusplus::message::object_path endpointPath{validChassisPath};
George Liu9516f412022-09-29 17:21:41 +080070 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) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -040079 if (ec)
George Liu9516f412022-09-29 17:21:41 +080080 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -040081 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;
George Liu9516f412022-09-29 17:21:41 +080089 }
Patrick Williamsbd79bce2024-08-16 15:22:20 -040090 callback(subtreePaths);
91 });
George Liu9516f412022-09-29 17:21:41 +080092}
93
94inline 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
Ed Tanousd547d8d2024-03-16 18:04:41 -0700116 getFanPaths(asyncResp, *validChassisPath,
George Liu9516f412022-09-29 17:21:41 +0800117 std::bind_front(updateFanList, asyncResp, chassisId));
118}
119
120inline void
121 handleFanCollectionHead(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) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400134 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 });
George Liu9516f412022-09-29 17:21:41 +0800144}
145
146inline void
147 handleFanCollectionGet(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
161inline 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
George Liue4e54232022-09-30 09:08:11 +0800174inline 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
Ed Tanous4ff0f1f2024-09-04 17:27:37 -0700181inline void handleFanPath(
George Liue4e54232022-09-30 09:08:11 +0800182 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) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400199 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 });
George Liue4e54232022-09-30 09:08:11 +0800208
209 return;
210 }
Ed Tanous62598e32023-07-17 17:06:25 -0700211 BMCWEB_LOG_WARNING("Fan not found {}", fanId);
George Liue4e54232022-09-30 09:08:11 +0800212 messages::resourceNotFound(asyncResp->res, "Fan", fanId);
213}
214
215inline 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) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400225 handleFanPath(fanId, asyncResp, fanPaths, callback);
226 });
George Liue4e54232022-09-30 09:08:11 +0800227}
228
229inline 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);
Ed Tanous539d8c62024-06-19 14:38:27 -0700240 resp.jsonValue["Status"]["State"] = resource::State::Enabled;
241 resp.jsonValue["Status"]["Health"] = resource::Health::OK;
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800242}
243
244inline void getFanHealth(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
245 const std::string& fanPath, const std::string& service)
246{
Ed Tanousdeae6a72024-11-11 21:58:57 -0800247 dbus::utility::getProperty<bool>(
248 service, fanPath,
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800249 "xyz.openbmc_project.State.Decorator.OperationalStatus", "Functional",
250 [asyncResp](const boost::system::error_code& ec, const bool value) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400251 if (ec)
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800252 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400253 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;
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800260 }
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800261
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400262 if (!value)
263 {
264 asyncResp->res.jsonValue["Status"]["Health"] =
265 resource::Health::Critical;
266 }
267 });
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800268}
269
270inline void getFanState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
271 const std::string& fanPath, const std::string& service)
272{
Ed Tanousdeae6a72024-11-11 21:58:57 -0800273 dbus::utility::getProperty<bool>(
274 service, fanPath, "xyz.openbmc_project.Inventory.Item", "Present",
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800275 [asyncResp](const boost::system::error_code& ec, const bool value) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400276 if (ec)
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800277 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400278 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;
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800285 }
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800286
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400287 if (!value)
288 {
289 asyncResp->res.jsonValue["Status"]["State"] =
290 resource::State::Absent;
291 }
292 });
George Liue4e54232022-09-30 09:08:11 +0800293}
294
George Liu090ae7b2022-10-04 15:45:25 +0800295inline void getFanAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
296 const std::string& fanPath, const std::string& service)
297{
Ed Tanousdeae6a72024-11-11 21:58:57 -0800298 dbus::utility::getAllProperties(
299 service, fanPath, "xyz.openbmc_project.Inventory.Decorator.Asset",
George Liu090ae7b2022-10-04 15:45:25 +0800300 [fanPath, asyncResp{asyncResp}](
301 const boost::system::error_code& ec,
302 const dbus::utility::DBusPropertiesMap& assetList) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400303 if (ec)
George Liu090ae7b2022-10-04 15:45:25 +0800304 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400305 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;
George Liu090ae7b2022-10-04 15:45:25 +0800312 }
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400313 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;
George Liu090ae7b2022-10-04 15:45:25 +0800318
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400319 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 });
George Liu090ae7b2022-10-04 15:45:25 +0800350}
351
Ed Tanousfc3edfd2023-07-20 12:41:30 -0700352inline void getFanLocation(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
George Liu4a2e4852022-10-04 16:13:44 +0800353 const std::string& fanPath,
354 const std::string& service)
355{
Ed Tanousdeae6a72024-11-11 21:58:57 -0800356 dbus::utility::getProperty<std::string>(
357 service, fanPath,
George Liu4a2e4852022-10-04 16:13:44 +0800358 "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode",
Ed Tanousfc3edfd2023-07-20 12:41:30 -0700359 [asyncResp](const boost::system::error_code& ec,
360 const std::string& property) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400361 if (ec)
George Liu4a2e4852022-10-04 16:13:44 +0800362 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400363 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;
George Liu4a2e4852022-10-04 16:13:44 +0800370 }
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400371 asyncResp->res
372 .jsonValue["Location"]["PartLocation"]["ServiceLabel"] =
373 property;
374 });
George Liu4a2e4852022-10-04 16:13:44 +0800375}
376
George Liue4e54232022-09-30 09:08:11 +0800377inline void
378 afterGetValidFanPath(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{
George Liue4e54232022-09-30 09:08:11 +0800382 addFanCommonProperties(asyncResp->res, chassisId, fanId);
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800383 getFanState(asyncResp, fanPath, service);
384 getFanHealth(asyncResp, fanPath, service);
George Liu090ae7b2022-10-04 15:45:25 +0800385 getFanAsset(asyncResp, fanPath, service);
George Liu4a2e4852022-10-04 16:13:44 +0800386 getFanLocation(asyncResp, fanPath, service);
George Liue4e54232022-09-30 09:08:11 +0800387}
388
389inline 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
404inline 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) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400418 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 });
George Liue4e54232022-09-30 09:08:11 +0800431 });
George Liue4e54232022-09-30 09:08:11 +0800432}
433
434inline 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
448inline 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
George Liu9516f412022-09-29 17:21:41 +0800461} // namespace redfish