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 "logging.hpp"
12 #include "query.hpp"
13 #include "registries/privilege_registry.hpp"
14 #include "utils/chassis_utils.hpp"
15
16 #include <asm-generic/errno.h>
17
18 #include <boost/beast/http/field.hpp>
19 #include <boost/beast/http/verb.hpp>
20 #include <boost/system/error_code.hpp>
21 #include <boost/url/format.hpp>
22 #include <nlohmann/json.hpp>
23 #include <sdbusplus/unpack_properties.hpp>
24
25 #include <array>
26 #include <functional>
27 #include <memory>
28 #include <optional>
29 #include <string>
30 #include <string_view>
31 #include <utility>
32
33 namespace redfish
34 {
35 constexpr std::array<std::string_view, 1> fanInterface = {
36 "xyz.openbmc_project.Inventory.Item.Fan"};
37
updateFanList(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const dbus::utility::MapperGetSubTreePathsResponse & fanPaths)38 inline void updateFanList(
39 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
getFanPaths(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & validChassisPath,const std::function<void (const dbus::utility::MapperGetSubTreePathsResponse & fanPaths)> & callback)63 inline void getFanPaths(
64 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
65 const std::string& validChassisPath,
66 const std::function<void(const dbus::utility::MapperGetSubTreePathsResponse&
67 fanPaths)>& callback)
68 {
69 sdbusplus::message::object_path endpointPath{validChassisPath};
70 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) {
79 if (ec)
80 {
81 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;
89 }
90 callback(subtreePaths);
91 });
92 }
93
doFanCollection(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::optional<std::string> & validChassisPath)94 inline 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
116 getFanPaths(asyncResp, *validChassisPath,
117 std::bind_front(updateFanList, asyncResp, chassisId));
118 }
119
handleFanCollectionHead(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId)120 inline void handleFanCollectionHead(
121 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) {
134 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 });
144 }
145
handleFanCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId)146 inline void handleFanCollectionGet(
147 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
requestRoutesFanCollection(App & app)161 inline 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
checkFanId(const std::string & fanPath,const std::string & fanId)174 inline 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
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)181 inline void handleFanPath(
182 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) {
199 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 });
208
209 return;
210 }
211 BMCWEB_LOG_WARNING("Fan not found {}", fanId);
212 messages::resourceNotFound(asyncResp->res, "Fan", fanId);
213 }
214
getValidFanPath(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)215 inline 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) {
225 handleFanPath(fanId, asyncResp, fanPaths, callback);
226 });
227 }
228
addFanCommonProperties(crow::Response & resp,const std::string & chassisId,const std::string & fanId)229 inline 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);
240 resp.jsonValue["Status"]["State"] = resource::State::Enabled;
241 resp.jsonValue["Status"]["Health"] = resource::Health::OK;
242 }
243
getFanHealth(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & fanPath,const std::string & service)244 inline void getFanHealth(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
245 const std::string& fanPath, const std::string& service)
246 {
247 dbus::utility::getProperty<bool>(
248 service, fanPath,
249 "xyz.openbmc_project.State.Decorator.OperationalStatus", "Functional",
250 [asyncResp](const boost::system::error_code& ec, const bool value) {
251 if (ec)
252 {
253 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;
260 }
261
262 if (!value)
263 {
264 asyncResp->res.jsonValue["Status"]["Health"] =
265 resource::Health::Critical;
266 }
267 });
268 }
269
getFanState(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & fanPath,const std::string & service)270 inline void getFanState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
271 const std::string& fanPath, const std::string& service)
272 {
273 dbus::utility::getProperty<bool>(
274 service, fanPath, "xyz.openbmc_project.Inventory.Item", "Present",
275 [asyncResp](const boost::system::error_code& ec, const bool value) {
276 if (ec)
277 {
278 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;
285 }
286
287 if (!value)
288 {
289 asyncResp->res.jsonValue["Status"]["State"] =
290 resource::State::Absent;
291 }
292 });
293 }
294
getFanAsset(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & fanPath,const std::string & service)295 inline void getFanAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
296 const std::string& fanPath, const std::string& service)
297 {
298 dbus::utility::getAllProperties(
299 service, fanPath, "xyz.openbmc_project.Inventory.Decorator.Asset",
300 [fanPath, asyncResp{asyncResp}](
301 const boost::system::error_code& ec,
302 const dbus::utility::DBusPropertiesMap& assetList) {
303 if (ec)
304 {
305 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;
312 }
313 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;
318
319 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 });
350 }
351
getFanLocation(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & fanPath,const std::string & service)352 inline void getFanLocation(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
353 const std::string& fanPath,
354 const std::string& service)
355 {
356 dbus::utility::getProperty<std::string>(
357 service, fanPath,
358 "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode",
359 [asyncResp](const boost::system::error_code& ec,
360 const std::string& property) {
361 if (ec)
362 {
363 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;
370 }
371 asyncResp->res
372 .jsonValue["Location"]["PartLocation"]["ServiceLabel"] =
373 property;
374 });
375 }
376
afterGetValidFanPath(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & fanId,const std::string & fanPath,const std::string & service)377 inline void afterGetValidFanPath(
378 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 {
382 addFanCommonProperties(asyncResp->res, chassisId, fanId);
383 getFanState(asyncResp, fanPath, service);
384 getFanHealth(asyncResp, fanPath, service);
385 getFanAsset(asyncResp, fanPath, service);
386 getFanLocation(asyncResp, fanPath, service);
387 }
388
doFanGet(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & fanId,const std::optional<std::string> & validChassisPath)389 inline 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
handleFanHead(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & fanId)404 inline 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) {
418 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 });
431 });
432 }
433
handleFanGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & fanId)434 inline 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
requestRoutesFan(App & app)448 inline 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
461 } // namespace redfish
462