1 #pragma once
2
3 #include "app.hpp"
4 #include "dbus_utility.hpp"
5 #include "error_messages.hpp"
6 #include "generated/enums/resource.hpp"
7 #include "query.hpp"
8 #include "registries/privilege_registry.hpp"
9 #include "utils/chassis_utils.hpp"
10
11 #include <boost/system/error_code.hpp>
12 #include <boost/url/format.hpp>
13 #include <sdbusplus/asio/property.hpp>
14 #include <sdbusplus/message/types.hpp>
15
16 #include <functional>
17 #include <memory>
18 #include <optional>
19 #include <string>
20 #include <string_view>
21
22 namespace redfish
23 {
24 constexpr std::array<std::string_view, 1> fanInterface = {
25 "xyz.openbmc_project.Inventory.Item.Fan"};
26
27 inline void
updateFanList(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const dbus::utility::MapperGetSubTreePathsResponse & fanPaths)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
getFanPaths(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & validChassisPath,const std::function<void (const dbus::utility::MapperGetSubTreePathsResponse & fanPaths)> & callback)52 inline void getFanPaths(
53 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
54 const std::string& validChassisPath,
55 const std::function<void(const dbus::utility::MapperGetSubTreePathsResponse&
56 fanPaths)>& callback)
57 {
58 sdbusplus::message::object_path endpointPath{validChassisPath};
59 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 {
72 BMCWEB_LOG_ERROR(
73 "DBUS response error for getAssociatedSubTreePaths {}",
74 ec.value());
75 messages::internalError(asyncResp->res);
76 }
77 return;
78 }
79 callback(subtreePaths);
80 });
81 }
82
doFanCollection(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::optional<std::string> & validChassisPath)83 inline 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
105 getFanPaths(asyncResp, *validChassisPath,
106 std::bind_front(updateFanList, asyncResp, chassisId));
107 }
108
109 inline void
handleFanCollectionHead(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId)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",
126 chassisId);
127 return;
128 }
129 asyncResp->res.addHeader(
130 boost::beast::http::field::link,
131 "</redfish/v1/JsonSchemas/FanCollection/FanCollection.json>; rel=describedby");
132 });
133 }
134
135 inline void
handleFanCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId)136 handleFanCollectionGet(App& app, const crow::Request& req,
137 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
138 const std::string& chassisId)
139 {
140 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
141 {
142 return;
143 }
144
145 redfish::chassis_utils::getValidChassisPath(
146 asyncResp, chassisId,
147 std::bind_front(doFanCollection, asyncResp, chassisId));
148 }
149
requestRoutesFanCollection(App & app)150 inline void requestRoutesFanCollection(App& app)
151 {
152 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/")
153 .privileges(redfish::privileges::headFanCollection)
154 .methods(boost::beast::http::verb::head)(
155 std::bind_front(handleFanCollectionHead, std::ref(app)));
156
157 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/")
158 .privileges(redfish::privileges::getFanCollection)
159 .methods(boost::beast::http::verb::get)(
160 std::bind_front(handleFanCollectionGet, std::ref(app)));
161 }
162
checkFanId(const std::string & fanPath,const std::string & fanId)163 inline bool checkFanId(const std::string& fanPath, const std::string& fanId)
164 {
165 std::string fanName = sdbusplus::message::object_path(fanPath).filename();
166
167 return !(fanName.empty() || fanName != fanId);
168 }
169
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)170 inline void handleFanPath(
171 const std::string& fanId,
172 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
173 const dbus::utility::MapperGetSubTreePathsResponse& fanPaths,
174 const std::function<void(const std::string& fanPath,
175 const std::string& service)>& callback)
176 {
177 for (const auto& fanPath : fanPaths)
178 {
179 if (!checkFanId(fanPath, fanId))
180 {
181 continue;
182 }
183 dbus::utility::getDbusObject(
184 fanPath, fanInterface,
185 [fanPath, asyncResp,
186 callback](const boost::system::error_code& ec,
187 const dbus::utility::MapperGetObject& object) {
188 if (ec || object.empty())
189 {
190 BMCWEB_LOG_ERROR("DBUS response error on getDbusObject {}",
191 ec.value());
192 messages::internalError(asyncResp->res);
193 return;
194 }
195 callback(fanPath, object.begin()->first);
196 });
197
198 return;
199 }
200 BMCWEB_LOG_WARNING("Fan not found {}", fanId);
201 messages::resourceNotFound(asyncResp->res, "Fan", fanId);
202 }
203
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)204 inline void getValidFanPath(
205 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
206 const std::string& validChassisPath, const std::string& fanId,
207 const std::function<void(const std::string& fanPath,
208 const std::string& service)>& callback)
209 {
210 getFanPaths(
211 asyncResp, validChassisPath,
212 [fanId, asyncResp, callback](
213 const dbus::utility::MapperGetSubTreePathsResponse& fanPaths) {
214 handleFanPath(fanId, asyncResp, fanPaths, callback);
215 });
216 }
217
addFanCommonProperties(crow::Response & resp,const std::string & chassisId,const std::string & fanId)218 inline void addFanCommonProperties(crow::Response& resp,
219 const std::string& chassisId,
220 const std::string& fanId)
221 {
222 resp.addHeader(boost::beast::http::field::link,
223 "</redfish/v1/JsonSchemas/Fan/Fan.json>; rel=describedby");
224 resp.jsonValue["@odata.type"] = "#Fan.v1_3_0.Fan";
225 resp.jsonValue["Name"] = "Fan";
226 resp.jsonValue["Id"] = fanId;
227 resp.jsonValue["@odata.id"] = boost::urls::format(
228 "/redfish/v1/Chassis/{}/ThermalSubsystem/Fans/{}", chassisId, fanId);
229 resp.jsonValue["Status"]["State"] = resource::State::Enabled;
230 resp.jsonValue["Status"]["Health"] = resource::Health::OK;
231 }
232
getFanHealth(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & fanPath,const std::string & service)233 inline void getFanHealth(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
234 const std::string& fanPath, const std::string& service)
235 {
236 sdbusplus::asio::getProperty<bool>(
237 *crow::connections::systemBus, service, fanPath,
238 "xyz.openbmc_project.State.Decorator.OperationalStatus", "Functional",
239 [asyncResp](const boost::system::error_code& ec, const bool value) {
240 if (ec)
241 {
242 if (ec.value() != EBADR)
243 {
244 BMCWEB_LOG_ERROR("DBUS response error for Health {}",
245 ec.value());
246 messages::internalError(asyncResp->res);
247 }
248 return;
249 }
250
251 if (!value)
252 {
253 asyncResp->res.jsonValue["Status"]["Health"] =
254 resource::Health::Critical;
255 }
256 });
257 }
258
getFanState(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & fanPath,const std::string & service)259 inline void getFanState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
260 const std::string& fanPath, const std::string& service)
261 {
262 sdbusplus::asio::getProperty<bool>(
263 *crow::connections::systemBus, service, fanPath,
264 "xyz.openbmc_project.Inventory.Item", "Present",
265 [asyncResp](const boost::system::error_code& ec, const bool value) {
266 if (ec)
267 {
268 if (ec.value() != EBADR)
269 {
270 BMCWEB_LOG_ERROR("DBUS response error for State {}",
271 ec.value());
272 messages::internalError(asyncResp->res);
273 }
274 return;
275 }
276
277 if (!value)
278 {
279 asyncResp->res.jsonValue["Status"]["State"] =
280 resource::State::Absent;
281 }
282 });
283 }
284
getFanAsset(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & fanPath,const std::string & service)285 inline void getFanAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
286 const std::string& fanPath, const std::string& service)
287 {
288 sdbusplus::asio::getAllProperties(
289 *crow::connections::systemBus, service, fanPath,
290 "xyz.openbmc_project.Inventory.Decorator.Asset",
291 [fanPath, asyncResp{asyncResp}](
292 const boost::system::error_code& ec,
293 const dbus::utility::DBusPropertiesMap& assetList) {
294 if (ec)
295 {
296 if (ec.value() != EBADR)
297 {
298 BMCWEB_LOG_ERROR("DBUS response error for Properties{}",
299 ec.value());
300 messages::internalError(asyncResp->res);
301 }
302 return;
303 }
304 const std::string* manufacturer = nullptr;
305 const std::string* model = nullptr;
306 const std::string* partNumber = nullptr;
307 const std::string* serialNumber = nullptr;
308 const std::string* sparePartNumber = nullptr;
309
310 const bool success = sdbusplus::unpackPropertiesNoThrow(
311 dbus_utils::UnpackErrorPrinter(), assetList, "Manufacturer",
312 manufacturer, "Model", model, "PartNumber", partNumber,
313 "SerialNumber", serialNumber, "SparePartNumber",
314 sparePartNumber);
315 if (!success)
316 {
317 messages::internalError(asyncResp->res);
318 return;
319 }
320 if (manufacturer != nullptr)
321 {
322 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
323 }
324 if (model != nullptr)
325 {
326 asyncResp->res.jsonValue["Model"] = *model;
327 }
328 if (partNumber != nullptr)
329 {
330 asyncResp->res.jsonValue["PartNumber"] = *partNumber;
331 }
332 if (serialNumber != nullptr)
333 {
334 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber;
335 }
336 if (sparePartNumber != nullptr && !sparePartNumber->empty())
337 {
338 asyncResp->res.jsonValue["SparePartNumber"] = *sparePartNumber;
339 }
340 });
341 }
342
getFanLocation(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & fanPath,const std::string & service)343 inline void getFanLocation(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
344 const std::string& fanPath,
345 const std::string& service)
346 {
347 sdbusplus::asio::getProperty<std::string>(
348 *crow::connections::systemBus, service, fanPath,
349 "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode",
350 [asyncResp](const boost::system::error_code& ec,
351 const std::string& property) {
352 if (ec)
353 {
354 if (ec.value() != EBADR)
355 {
356 BMCWEB_LOG_ERROR("DBUS response error for Location{}",
357 ec.value());
358 messages::internalError(asyncResp->res);
359 }
360 return;
361 }
362 asyncResp->res
363 .jsonValue["Location"]["PartLocation"]["ServiceLabel"] =
364 property;
365 });
366 }
367
368 inline void
afterGetValidFanPath(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & fanId,const std::string & fanPath,const std::string & service)369 afterGetValidFanPath(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
370 const std::string& chassisId, const std::string& fanId,
371 const std::string& fanPath, const std::string& service)
372 {
373 addFanCommonProperties(asyncResp->res, chassisId, fanId);
374 getFanState(asyncResp, fanPath, service);
375 getFanHealth(asyncResp, fanPath, service);
376 getFanAsset(asyncResp, fanPath, service);
377 getFanLocation(asyncResp, fanPath, service);
378 }
379
doFanGet(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & fanId,const std::optional<std::string> & validChassisPath)380 inline void doFanGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
381 const std::string& chassisId, const std::string& fanId,
382 const std::optional<std::string>& validChassisPath)
383 {
384 if (!validChassisPath)
385 {
386 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
387 return;
388 }
389
390 getValidFanPath(
391 asyncResp, *validChassisPath, fanId,
392 std::bind_front(afterGetValidFanPath, asyncResp, chassisId, fanId));
393 }
394
handleFanHead(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & fanId)395 inline void handleFanHead(App& app, const crow::Request& req,
396 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
397 const std::string& chassisId,
398 const std::string& fanId)
399 {
400 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
401 {
402 return;
403 }
404
405 redfish::chassis_utils::getValidChassisPath(
406 asyncResp, chassisId,
407 [asyncResp, chassisId,
408 fanId](const std::optional<std::string>& validChassisPath) {
409 if (!validChassisPath)
410 {
411 messages::resourceNotFound(asyncResp->res, "Chassis",
412 chassisId);
413 return;
414 }
415 getValidFanPath(
416 asyncResp, *validChassisPath, fanId,
417 [asyncResp](const std::string&, const std::string&) {
418 asyncResp->res.addHeader(
419 boost::beast::http::field::link,
420 "</redfish/v1/JsonSchemas/Fan/Fan.json>; rel=describedby");
421 });
422 });
423 }
424
handleFanGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & fanId)425 inline void handleFanGet(App& app, const crow::Request& req,
426 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
427 const std::string& chassisId, const std::string& fanId)
428 {
429 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
430 {
431 return;
432 }
433
434 redfish::chassis_utils::getValidChassisPath(
435 asyncResp, chassisId,
436 std::bind_front(doFanGet, asyncResp, chassisId, fanId));
437 }
438
requestRoutesFan(App & app)439 inline void requestRoutesFan(App& app)
440 {
441 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/")
442 .privileges(redfish::privileges::headFan)
443 .methods(boost::beast::http::verb::head)(
444 std::bind_front(handleFanHead, std::ref(app)));
445
446 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/")
447 .privileges(redfish::privileges::getFan)
448 .methods(boost::beast::http::verb::get)(
449 std::bind_front(handleFanGet, std::ref(app)));
450 }
451
452 } // namespace redfish
453