blob: 7245287f5d25cf5a231e173e7b5ae58950edac30 [file] [log] [blame]
George Liu9516f412022-09-29 17:21:41 +08001#pragma once
2
3#include "app.hpp"
4#include "dbus_utility.hpp"
5#include "error_messages.hpp"
Ed Tanous539d8c62024-06-19 14:38:27 -07006#include "generated/enums/resource.hpp"
George Liu9516f412022-09-29 17:21:41 +08007#include "query.hpp"
8#include "registries/privilege_registry.hpp"
9#include "utils/chassis_utils.hpp"
10
Albert Zhang9f1ae5a2021-06-10 14:41:48 +080011#include <boost/system/error_code.hpp>
George Liu9516f412022-09-29 17:21:41 +080012#include <boost/url/format.hpp>
Albert Zhang9f1ae5a2021-06-10 14:41:48 +080013#include <sdbusplus/asio/property.hpp>
George Liu9516f412022-09-29 17:21:41 +080014#include <sdbusplus/message/types.hpp>
15
16#include <functional>
17#include <memory>
18#include <optional>
19#include <string>
20#include <string_view>
21
22namespace redfish
23{
24constexpr std::array<std::string_view, 1> fanInterface = {
25 "xyz.openbmc_project.Inventory.Item.Fan"};
26
27inline void
28 updateFanList(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
29 const std::string& chassisId,
30 const dbus::utility::MapperGetSubTreePathsResponse& fanPaths)
31{
32 nlohmann::json& fanList = asyncResp->res.jsonValue["Members"];
33 for (const std::string& fanPath : fanPaths)
34 {
35 std::string fanName =
36 sdbusplus::message::object_path(fanPath).filename();
37 if (fanName.empty())
38 {
39 continue;
40 }
41
42 nlohmann::json item = nlohmann::json::object();
43 item["@odata.id"] = boost::urls::format(
44 "/redfish/v1/Chassis/{}/ThermalSubsystem/Fans/{}", chassisId,
45 fanName);
46
47 fanList.emplace_back(std::move(item));
48 }
49 asyncResp->res.jsonValue["Members@odata.count"] = fanList.size();
50}
51
52inline void getFanPaths(
53 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
Ed Tanousd547d8d2024-03-16 18:04:41 -070054 const std::string& validChassisPath,
George Liu9516f412022-09-29 17:21:41 +080055 const std::function<void(const dbus::utility::MapperGetSubTreePathsResponse&
56 fanPaths)>& callback)
57{
Ed Tanousd547d8d2024-03-16 18:04:41 -070058 sdbusplus::message::object_path endpointPath{validChassisPath};
George Liu9516f412022-09-29 17:21:41 +080059 endpointPath /= "cooled_by";
60
61 dbus::utility::getAssociatedSubTreePaths(
62 endpointPath,
63 sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0,
64 fanInterface,
65 [asyncResp, callback](
66 const boost::system::error_code& ec,
67 const dbus::utility::MapperGetSubTreePathsResponse& subtreePaths) {
68 if (ec)
69 {
70 if (ec.value() != EBADR)
71 {
Ed Tanous62598e32023-07-17 17:06:25 -070072 BMCWEB_LOG_ERROR(
73 "DBUS response error for getAssociatedSubTreePaths {}",
74 ec.value());
George Liu9516f412022-09-29 17:21:41 +080075 messages::internalError(asyncResp->res);
76 }
77 return;
78 }
79 callback(subtreePaths);
Patrick Williams5a39f772023-10-20 11:20:21 -050080 });
George Liu9516f412022-09-29 17:21:41 +080081}
82
83inline void doFanCollection(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
84 const std::string& chassisId,
85 const std::optional<std::string>& validChassisPath)
86{
87 if (!validChassisPath)
88 {
89 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
90 return;
91 }
92
93 asyncResp->res.addHeader(
94 boost::beast::http::field::link,
95 "</redfish/v1/JsonSchemas/FanCollection/FanCollection.json>; rel=describedby");
96 asyncResp->res.jsonValue["@odata.type"] = "#FanCollection.FanCollection";
97 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
98 "/redfish/v1/Chassis/{}/ThermalSubsystem/Fans", chassisId);
99 asyncResp->res.jsonValue["Name"] = "Fan Collection";
100 asyncResp->res.jsonValue["Description"] =
101 "The collection of Fan resource instances " + chassisId;
102 asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
103 asyncResp->res.jsonValue["Members@odata.count"] = 0;
104
Ed Tanousd547d8d2024-03-16 18:04:41 -0700105 getFanPaths(asyncResp, *validChassisPath,
George Liu9516f412022-09-29 17:21:41 +0800106 std::bind_front(updateFanList, asyncResp, chassisId));
107}
108
109inline void
110 handleFanCollectionHead(App& app, const crow::Request& req,
111 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
112 const std::string& chassisId)
113{
114 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
115 {
116 return;
117 }
118
119 redfish::chassis_utils::getValidChassisPath(
120 asyncResp, chassisId,
121 [asyncResp,
122 chassisId](const std::optional<std::string>& validChassisPath) {
123 if (!validChassisPath)
124 {
125 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
126 return;
127 }
128 asyncResp->res.addHeader(
129 boost::beast::http::field::link,
130 "</redfish/v1/JsonSchemas/FanCollection/FanCollection.json>; rel=describedby");
Patrick Williams5a39f772023-10-20 11:20:21 -0500131 });
George Liu9516f412022-09-29 17:21:41 +0800132}
133
134inline void
135 handleFanCollectionGet(App& app, const crow::Request& req,
136 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
137 const std::string& chassisId)
138{
139 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
140 {
141 return;
142 }
143
144 redfish::chassis_utils::getValidChassisPath(
145 asyncResp, chassisId,
146 std::bind_front(doFanCollection, asyncResp, chassisId));
147}
148
149inline void requestRoutesFanCollection(App& app)
150{
151 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/")
152 .privileges(redfish::privileges::headFanCollection)
153 .methods(boost::beast::http::verb::head)(
154 std::bind_front(handleFanCollectionHead, std::ref(app)));
155
156 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/")
157 .privileges(redfish::privileges::getFanCollection)
158 .methods(boost::beast::http::verb::get)(
159 std::bind_front(handleFanCollectionGet, std::ref(app)));
160}
161
George Liue4e54232022-09-30 09:08:11 +0800162inline bool checkFanId(const std::string& fanPath, const std::string& fanId)
163{
164 std::string fanName = sdbusplus::message::object_path(fanPath).filename();
165
166 return !(fanName.empty() || fanName != fanId);
167}
168
169static inline void handleFanPath(
170 const std::string& fanId,
171 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
172 const dbus::utility::MapperGetSubTreePathsResponse& fanPaths,
173 const std::function<void(const std::string& fanPath,
174 const std::string& service)>& callback)
175{
176 for (const auto& fanPath : fanPaths)
177 {
178 if (!checkFanId(fanPath, fanId))
179 {
180 continue;
181 }
182 dbus::utility::getDbusObject(
183 fanPath, fanInterface,
184 [fanPath, asyncResp,
185 callback](const boost::system::error_code& ec,
186 const dbus::utility::MapperGetObject& object) {
187 if (ec || object.empty())
188 {
Ed Tanous62598e32023-07-17 17:06:25 -0700189 BMCWEB_LOG_ERROR("DBUS response error on getDbusObject {}",
190 ec.value());
George Liue4e54232022-09-30 09:08:11 +0800191 messages::internalError(asyncResp->res);
192 return;
193 }
194 callback(fanPath, object.begin()->first);
Patrick Williams5a39f772023-10-20 11:20:21 -0500195 });
George Liue4e54232022-09-30 09:08:11 +0800196
197 return;
198 }
Ed Tanous62598e32023-07-17 17:06:25 -0700199 BMCWEB_LOG_WARNING("Fan not found {}", fanId);
George Liue4e54232022-09-30 09:08:11 +0800200 messages::resourceNotFound(asyncResp->res, "Fan", fanId);
201}
202
203inline void getValidFanPath(
204 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
205 const std::string& validChassisPath, const std::string& fanId,
206 const std::function<void(const std::string& fanPath,
207 const std::string& service)>& callback)
208{
209 getFanPaths(
210 asyncResp, validChassisPath,
211 [fanId, asyncResp, callback](
212 const dbus::utility::MapperGetSubTreePathsResponse& fanPaths) {
213 handleFanPath(fanId, asyncResp, fanPaths, callback);
Patrick Williams5a39f772023-10-20 11:20:21 -0500214 });
George Liue4e54232022-09-30 09:08:11 +0800215}
216
217inline void addFanCommonProperties(crow::Response& resp,
218 const std::string& chassisId,
219 const std::string& fanId)
220{
221 resp.addHeader(boost::beast::http::field::link,
222 "</redfish/v1/JsonSchemas/Fan/Fan.json>; rel=describedby");
223 resp.jsonValue["@odata.type"] = "#Fan.v1_3_0.Fan";
224 resp.jsonValue["Name"] = "Fan";
225 resp.jsonValue["Id"] = fanId;
226 resp.jsonValue["@odata.id"] = boost::urls::format(
227 "/redfish/v1/Chassis/{}/ThermalSubsystem/Fans/{}", chassisId, fanId);
Ed Tanous539d8c62024-06-19 14:38:27 -0700228 resp.jsonValue["Status"]["State"] = resource::State::Enabled;
229 resp.jsonValue["Status"]["Health"] = resource::Health::OK;
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800230}
231
232inline void getFanHealth(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
233 const std::string& fanPath, const std::string& service)
234{
235 sdbusplus::asio::getProperty<bool>(
236 *crow::connections::systemBus, service, fanPath,
237 "xyz.openbmc_project.State.Decorator.OperationalStatus", "Functional",
238 [asyncResp](const boost::system::error_code& ec, const bool value) {
239 if (ec)
240 {
241 if (ec.value() != EBADR)
242 {
Ed Tanous62598e32023-07-17 17:06:25 -0700243 BMCWEB_LOG_ERROR("DBUS response error for Health {}",
244 ec.value());
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800245 messages::internalError(asyncResp->res);
246 }
247 return;
248 }
249
250 if (!value)
251 {
Ed Tanous539d8c62024-06-19 14:38:27 -0700252 asyncResp->res.jsonValue["Status"]["Health"] =
253 resource::Health::Critical;
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800254 }
Patrick Williams5a39f772023-10-20 11:20:21 -0500255 });
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800256}
257
258inline void getFanState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
259 const std::string& fanPath, const std::string& service)
260{
261 sdbusplus::asio::getProperty<bool>(
262 *crow::connections::systemBus, service, fanPath,
263 "xyz.openbmc_project.Inventory.Item", "Present",
264 [asyncResp](const boost::system::error_code& ec, const bool value) {
265 if (ec)
266 {
267 if (ec.value() != EBADR)
268 {
Ed Tanous62598e32023-07-17 17:06:25 -0700269 BMCWEB_LOG_ERROR("DBUS response error for State {}",
270 ec.value());
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800271 messages::internalError(asyncResp->res);
272 }
273 return;
274 }
275
276 if (!value)
277 {
Ed Tanous539d8c62024-06-19 14:38:27 -0700278 asyncResp->res.jsonValue["Status"]["State"] =
279 resource::State::Absent;
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800280 }
Patrick Williams5a39f772023-10-20 11:20:21 -0500281 });
George Liue4e54232022-09-30 09:08:11 +0800282}
283
George Liu090ae7b2022-10-04 15:45:25 +0800284inline void getFanAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
285 const std::string& fanPath, const std::string& service)
286{
287 sdbusplus::asio::getAllProperties(
288 *crow::connections::systemBus, service, fanPath,
289 "xyz.openbmc_project.Inventory.Decorator.Asset",
290 [fanPath, asyncResp{asyncResp}](
291 const boost::system::error_code& ec,
292 const dbus::utility::DBusPropertiesMap& assetList) {
293 if (ec)
294 {
295 if (ec.value() != EBADR)
296 {
Ed Tanous62598e32023-07-17 17:06:25 -0700297 BMCWEB_LOG_ERROR("DBUS response error for Properties{}",
298 ec.value());
George Liu090ae7b2022-10-04 15:45:25 +0800299 messages::internalError(asyncResp->res);
300 }
301 return;
302 }
303 const std::string* manufacturer = nullptr;
304 const std::string* model = nullptr;
305 const std::string* partNumber = nullptr;
306 const std::string* serialNumber = nullptr;
307 const std::string* sparePartNumber = nullptr;
308
309 const bool success = sdbusplus::unpackPropertiesNoThrow(
310 dbus_utils::UnpackErrorPrinter(), assetList, "Manufacturer",
311 manufacturer, "Model", model, "PartNumber", partNumber,
312 "SerialNumber", serialNumber, "SparePartNumber", sparePartNumber);
313 if (!success)
314 {
315 messages::internalError(asyncResp->res);
316 return;
317 }
318 if (manufacturer != nullptr)
319 {
320 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
321 }
322 if (model != nullptr)
323 {
324 asyncResp->res.jsonValue["Model"] = *model;
325 }
326 if (partNumber != nullptr)
327 {
328 asyncResp->res.jsonValue["PartNumber"] = *partNumber;
329 }
330 if (serialNumber != nullptr)
331 {
332 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber;
333 }
334 if (sparePartNumber != nullptr && !sparePartNumber->empty())
335 {
336 asyncResp->res.jsonValue["SparePartNumber"] = *sparePartNumber;
337 }
Patrick Williams5a39f772023-10-20 11:20:21 -0500338 });
George Liu090ae7b2022-10-04 15:45:25 +0800339}
340
Ed Tanousfc3edfd2023-07-20 12:41:30 -0700341inline void getFanLocation(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
George Liu4a2e4852022-10-04 16:13:44 +0800342 const std::string& fanPath,
343 const std::string& service)
344{
345 sdbusplus::asio::getProperty<std::string>(
346 *crow::connections::systemBus, service, fanPath,
347 "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode",
Ed Tanousfc3edfd2023-07-20 12:41:30 -0700348 [asyncResp](const boost::system::error_code& ec,
349 const std::string& property) {
George Liu4a2e4852022-10-04 16:13:44 +0800350 if (ec)
351 {
352 if (ec.value() != EBADR)
353 {
Ed Tanous62598e32023-07-17 17:06:25 -0700354 BMCWEB_LOG_ERROR("DBUS response error for Location{}",
355 ec.value());
Ed Tanousfc3edfd2023-07-20 12:41:30 -0700356 messages::internalError(asyncResp->res);
George Liu4a2e4852022-10-04 16:13:44 +0800357 }
358 return;
359 }
Ed Tanousfc3edfd2023-07-20 12:41:30 -0700360 asyncResp->res.jsonValue["Location"]["PartLocation"]["ServiceLabel"] =
George Liu4a2e4852022-10-04 16:13:44 +0800361 property;
Patrick Williams5a39f772023-10-20 11:20:21 -0500362 });
George Liu4a2e4852022-10-04 16:13:44 +0800363}
364
George Liue4e54232022-09-30 09:08:11 +0800365inline void
366 afterGetValidFanPath(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
367 const std::string& chassisId, const std::string& fanId,
368 const std::string& fanPath, const std::string& service)
369{
George Liue4e54232022-09-30 09:08:11 +0800370 addFanCommonProperties(asyncResp->res, chassisId, fanId);
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800371 getFanState(asyncResp, fanPath, service);
372 getFanHealth(asyncResp, fanPath, service);
George Liu090ae7b2022-10-04 15:45:25 +0800373 getFanAsset(asyncResp, fanPath, service);
George Liu4a2e4852022-10-04 16:13:44 +0800374 getFanLocation(asyncResp, fanPath, service);
George Liue4e54232022-09-30 09:08:11 +0800375}
376
377inline void doFanGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
378 const std::string& chassisId, const std::string& fanId,
379 const std::optional<std::string>& validChassisPath)
380{
381 if (!validChassisPath)
382 {
383 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
384 return;
385 }
386
387 getValidFanPath(
388 asyncResp, *validChassisPath, fanId,
389 std::bind_front(afterGetValidFanPath, asyncResp, chassisId, fanId));
390}
391
392inline void handleFanHead(App& app, const crow::Request& req,
393 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
394 const std::string& chassisId,
395 const std::string& fanId)
396{
397 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
398 {
399 return;
400 }
401
402 redfish::chassis_utils::getValidChassisPath(
403 asyncResp, chassisId,
404 [asyncResp, chassisId,
405 fanId](const std::optional<std::string>& validChassisPath) {
406 if (!validChassisPath)
407 {
408 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
409 return;
410 }
411 getValidFanPath(asyncResp, *validChassisPath, fanId,
412 [asyncResp](const std::string&, const std::string&) {
413 asyncResp->res.addHeader(
414 boost::beast::http::field::link,
415 "</redfish/v1/JsonSchemas/Fan/Fan.json>; rel=describedby");
416 });
Patrick Williams5a39f772023-10-20 11:20:21 -0500417 });
George Liue4e54232022-09-30 09:08:11 +0800418}
419
420inline void handleFanGet(App& app, const crow::Request& req,
421 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
422 const std::string& chassisId, const std::string& fanId)
423{
424 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
425 {
426 return;
427 }
428
429 redfish::chassis_utils::getValidChassisPath(
430 asyncResp, chassisId,
431 std::bind_front(doFanGet, asyncResp, chassisId, fanId));
432}
433
434inline void requestRoutesFan(App& app)
435{
436 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/")
437 .privileges(redfish::privileges::headFan)
438 .methods(boost::beast::http::verb::head)(
439 std::bind_front(handleFanHead, std::ref(app)));
440
441 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/")
442 .privileges(redfish::privileges::getFan)
443 .methods(boost::beast::http::verb::get)(
444 std::bind_front(handleFanGet, std::ref(app)));
445}
446
George Liu9516f412022-09-29 17:21:41 +0800447} // namespace redfish