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 "led.hpp"
12 #include "logging.hpp"
13 #include "query.hpp"
14 #include "registries/privilege_registry.hpp"
15 #include "utils/asset_utils.hpp"
16 #include "utils/chassis_utils.hpp"
17 #include "utils/dbus_utils.hpp"
18 #include "utils/json_utils.hpp"
19
20 #include <asm-generic/errno.h>
21
22 #include <boost/beast/http/field.hpp>
23 #include <boost/beast/http/verb.hpp>
24 #include <boost/system/error_code.hpp>
25 #include <boost/url/format.hpp>
26 #include <nlohmann/json.hpp>
27 #include <sdbusplus/unpack_properties.hpp>
28
29 #include <array>
30 #include <functional>
31 #include <memory>
32 #include <optional>
33 #include <string>
34 #include <string_view>
35 #include <utility>
36
37 namespace redfish
38 {
39 constexpr std::array<std::string_view, 1> fanInterface = {
40 "xyz.openbmc_project.Inventory.Item.Fan"};
41
updateFanList(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const dbus::utility::MapperGetSubTreePathsResponse & fanPaths)42 inline void updateFanList(
43 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
44 const std::string& chassisId,
45 const dbus::utility::MapperGetSubTreePathsResponse& fanPaths)
46 {
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
getFanPaths(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & validChassisPath,const std::function<void (const dbus::utility::MapperGetSubTreePathsResponse & fanPaths)> & callback)67 inline void getFanPaths(
68 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
69 const std::string& validChassisPath,
70 const std::function<void(const dbus::utility::MapperGetSubTreePathsResponse&
71 fanPaths)>& callback)
72 {
73 sdbusplus::message::object_path endpointPath{validChassisPath};
74 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) {
83 if (ec)
84 {
85 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;
93 }
94 callback(subtreePaths);
95 });
96 }
97
doFanCollection(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::optional<std::string> & validChassisPath)98 inline 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
120 getFanPaths(asyncResp, *validChassisPath,
121 std::bind_front(updateFanList, asyncResp, chassisId));
122 }
123
handleFanCollectionHead(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId)124 inline void handleFanCollectionHead(
125 App& app, const crow::Request& req,
126 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
127 const std::string& chassisId)
128 {
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) {
138 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 });
148 }
149
handleFanCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId)150 inline void handleFanCollectionGet(
151 App& app, const crow::Request& req,
152 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
153 const std::string& chassisId)
154 {
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
checkFanId(const std::string & fanPath,const std::string & fanId)165 inline 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
handleFanPath(const std::string & fanId,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const dbus::utility::MapperGetSubTreePathsResponse & fanPaths,const std::function<void (const std::string & fanPath,const std::string & service)> & callback)172 inline void handleFanPath(
173 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) {
190 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 });
199
200 return;
201 }
202 BMCWEB_LOG_WARNING("Fan not found {}", fanId);
203 messages::resourceNotFound(asyncResp->res, "Fan", fanId);
204 }
205
getValidFanObject(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & validChassisPath,const std::string & fanId,const std::function<void (const std::string & fanPath,const std::string & service)> & callback)206 inline void getValidFanObject(
207 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) {
216 handleFanPath(fanId, asyncResp, fanPaths, callback);
217 });
218 }
219
addFanCommonProperties(crow::Response & resp,const std::string & chassisId,const std::string & fanId)220 inline 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);
231 resp.jsonValue["Status"]["State"] = resource::State::Enabled;
232 resp.jsonValue["Status"]["Health"] = resource::Health::OK;
233 }
234
getFanHealth(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & fanPath,const std::string & service)235 inline void getFanHealth(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
236 const std::string& fanPath, const std::string& service)
237 {
238 dbus::utility::getProperty<bool>(
239 service, fanPath,
240 "xyz.openbmc_project.State.Decorator.OperationalStatus", "Functional",
241 [asyncResp](const boost::system::error_code& ec, const bool value) {
242 if (ec)
243 {
244 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;
251 }
252
253 if (!value)
254 {
255 asyncResp->res.jsonValue["Status"]["Health"] =
256 resource::Health::Critical;
257 }
258 });
259 }
260
getFanState(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & fanPath,const std::string & service)261 inline void getFanState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
262 const std::string& fanPath, const std::string& service)
263 {
264 dbus::utility::getProperty<bool>(
265 service, fanPath, "xyz.openbmc_project.Inventory.Item", "Present",
266 [asyncResp](const boost::system::error_code& ec, const bool value) {
267 if (ec)
268 {
269 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;
276 }
277
278 if (!value)
279 {
280 asyncResp->res.jsonValue["Status"]["State"] =
281 resource::State::Absent;
282 }
283 });
284 }
285
getFanLocation(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & fanPath,const std::string & service)286 inline void getFanLocation(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
287 const std::string& fanPath,
288 const std::string& service)
289 {
290 dbus::utility::getProperty<std::string>(
291 service, fanPath,
292 "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode",
293 [asyncResp](const boost::system::error_code& ec,
294 const std::string& property) {
295 if (ec)
296 {
297 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;
304 }
305 asyncResp->res
306 .jsonValue["Location"]["PartLocation"]["ServiceLabel"] =
307 property;
308 });
309 }
310
afterGetValidFanObject(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & fanId,const std::string & fanPath,const std::string & service)311 inline void afterGetValidFanObject(
312 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)
315 {
316 addFanCommonProperties(asyncResp->res, chassisId, fanId);
317 getFanState(asyncResp, fanPath, service);
318 getFanHealth(asyncResp, fanPath, service);
319 asset_utils::getAssetInfo(asyncResp, service, fanPath, ""_json_pointer,
320 true);
321 getFanLocation(asyncResp, fanPath, service);
322 getLocationIndicatorActive(asyncResp, fanPath);
323 }
324
doFanGet(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & fanId,const std::optional<std::string> & validChassisPath)325 inline 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
335 getValidFanObject(
336 asyncResp, *validChassisPath, fanId,
337 std::bind_front(afterGetValidFanObject, asyncResp, chassisId, fanId));
338 }
339
handleFanHead(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & fanId)340 inline 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) {
354 if (!validChassisPath)
355 {
356 messages::resourceNotFound(asyncResp->res, "Chassis",
357 chassisId);
358 return;
359 }
360 getValidFanObject(
361 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 });
367 });
368 }
369
handleFanGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & fanId)370 inline 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
handleSetFanPathById(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & fanId,bool locationIndicatorActive,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreePathsResponse & fanPaths)384 inline 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
handleFanPatch(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & fanId)417 inline 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
requestRoutesFan(App & app)451 inline void requestRoutesFan(App& app)
452 {
453 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
463 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)));
472
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)));
477 }
478
479 } // namespace redfish
480