blob: 0acb762a0458897f2b173db67f6f579c2e64a06c [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"
6#include "query.hpp"
7#include "registries/privilege_registry.hpp"
8#include "utils/chassis_utils.hpp"
9
Albert Zhang9f1ae5a2021-06-10 14:41:48 +080010#include <boost/system/error_code.hpp>
George Liu9516f412022-09-29 17:21:41 +080011#include <boost/url/format.hpp>
Albert Zhang9f1ae5a2021-06-10 14:41:48 +080012#include <sdbusplus/asio/property.hpp>
George Liu9516f412022-09-29 17:21:41 +080013#include <sdbusplus/message/types.hpp>
14
15#include <functional>
16#include <memory>
17#include <optional>
18#include <string>
19#include <string_view>
20
21namespace redfish
22{
23constexpr std::array<std::string_view, 1> fanInterface = {
24 "xyz.openbmc_project.Inventory.Item.Fan"};
25
26inline void
27 updateFanList(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
28 const std::string& chassisId,
29 const dbus::utility::MapperGetSubTreePathsResponse& fanPaths)
30{
31 nlohmann::json& fanList = asyncResp->res.jsonValue["Members"];
32 for (const std::string& fanPath : fanPaths)
33 {
34 std::string fanName =
35 sdbusplus::message::object_path(fanPath).filename();
36 if (fanName.empty())
37 {
38 continue;
39 }
40
41 nlohmann::json item = nlohmann::json::object();
42 item["@odata.id"] = boost::urls::format(
43 "/redfish/v1/Chassis/{}/ThermalSubsystem/Fans/{}", chassisId,
44 fanName);
45
46 fanList.emplace_back(std::move(item));
47 }
48 asyncResp->res.jsonValue["Members@odata.count"] = fanList.size();
49}
50
51inline void getFanPaths(
52 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
53 const std::optional<std::string>& validChassisPath,
54 const std::function<void(const dbus::utility::MapperGetSubTreePathsResponse&
55 fanPaths)>& callback)
56{
57 sdbusplus::message::object_path endpointPath{*validChassisPath};
58 endpointPath /= "cooled_by";
59
60 dbus::utility::getAssociatedSubTreePaths(
61 endpointPath,
62 sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0,
63 fanInterface,
64 [asyncResp, callback](
65 const boost::system::error_code& ec,
66 const dbus::utility::MapperGetSubTreePathsResponse& subtreePaths) {
67 if (ec)
68 {
69 if (ec.value() != EBADR)
70 {
71 BMCWEB_LOG_ERROR
72 << "DBUS response error for getAssociatedSubTreePaths "
73 << ec.value();
74 messages::internalError(asyncResp->res);
75 }
76 return;
77 }
78 callback(subtreePaths);
79 });
80}
81
82inline void doFanCollection(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
83 const std::string& chassisId,
84 const std::optional<std::string>& validChassisPath)
85{
86 if (!validChassisPath)
87 {
88 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
89 return;
90 }
91
92 asyncResp->res.addHeader(
93 boost::beast::http::field::link,
94 "</redfish/v1/JsonSchemas/FanCollection/FanCollection.json>; rel=describedby");
95 asyncResp->res.jsonValue["@odata.type"] = "#FanCollection.FanCollection";
96 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
97 "/redfish/v1/Chassis/{}/ThermalSubsystem/Fans", chassisId);
98 asyncResp->res.jsonValue["Name"] = "Fan Collection";
99 asyncResp->res.jsonValue["Description"] =
100 "The collection of Fan resource instances " + chassisId;
101 asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
102 asyncResp->res.jsonValue["Members@odata.count"] = 0;
103
104 getFanPaths(asyncResp, validChassisPath,
105 std::bind_front(updateFanList, asyncResp, chassisId));
106}
107
108inline void
109 handleFanCollectionHead(App& app, const crow::Request& req,
110 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
111 const std::string& chassisId)
112{
113 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
114 {
115 return;
116 }
117
118 redfish::chassis_utils::getValidChassisPath(
119 asyncResp, chassisId,
120 [asyncResp,
121 chassisId](const std::optional<std::string>& validChassisPath) {
122 if (!validChassisPath)
123 {
124 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
125 return;
126 }
127 asyncResp->res.addHeader(
128 boost::beast::http::field::link,
129 "</redfish/v1/JsonSchemas/FanCollection/FanCollection.json>; rel=describedby");
130 });
131}
132
133inline void
134 handleFanCollectionGet(App& app, const crow::Request& req,
135 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
136 const std::string& chassisId)
137{
138 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
139 {
140 return;
141 }
142
143 redfish::chassis_utils::getValidChassisPath(
144 asyncResp, chassisId,
145 std::bind_front(doFanCollection, asyncResp, chassisId));
146}
147
148inline void requestRoutesFanCollection(App& app)
149{
150 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/")
151 .privileges(redfish::privileges::headFanCollection)
152 .methods(boost::beast::http::verb::head)(
153 std::bind_front(handleFanCollectionHead, std::ref(app)));
154
155 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/")
156 .privileges(redfish::privileges::getFanCollection)
157 .methods(boost::beast::http::verb::get)(
158 std::bind_front(handleFanCollectionGet, std::ref(app)));
159}
160
George Liue4e54232022-09-30 09:08:11 +0800161inline bool checkFanId(const std::string& fanPath, const std::string& fanId)
162{
163 std::string fanName = sdbusplus::message::object_path(fanPath).filename();
164
165 return !(fanName.empty() || fanName != fanId);
166}
167
168static inline void handleFanPath(
169 const std::string& fanId,
170 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
171 const dbus::utility::MapperGetSubTreePathsResponse& fanPaths,
172 const std::function<void(const std::string& fanPath,
173 const std::string& service)>& callback)
174{
175 for (const auto& fanPath : fanPaths)
176 {
177 if (!checkFanId(fanPath, fanId))
178 {
179 continue;
180 }
181 dbus::utility::getDbusObject(
182 fanPath, fanInterface,
183 [fanPath, asyncResp,
184 callback](const boost::system::error_code& ec,
185 const dbus::utility::MapperGetObject& object) {
186 if (ec || object.empty())
187 {
188 BMCWEB_LOG_ERROR << "DBUS response error on getDbusObject "
189 << ec.value();
190 messages::internalError(asyncResp->res);
191 return;
192 }
193 callback(fanPath, object.begin()->first);
194 });
195
196 return;
197 }
198 BMCWEB_LOG_WARNING << "Fan not found " << fanId;
199 messages::resourceNotFound(asyncResp->res, "Fan", fanId);
200}
201
202inline void getValidFanPath(
203 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
204 const std::string& validChassisPath, const std::string& fanId,
205 const std::function<void(const std::string& fanPath,
206 const std::string& service)>& callback)
207{
208 getFanPaths(
209 asyncResp, validChassisPath,
210 [fanId, asyncResp, callback](
211 const dbus::utility::MapperGetSubTreePathsResponse& fanPaths) {
212 handleFanPath(fanId, asyncResp, fanPaths, callback);
213 });
214}
215
216inline void addFanCommonProperties(crow::Response& resp,
217 const std::string& chassisId,
218 const std::string& fanId)
219{
220 resp.addHeader(boost::beast::http::field::link,
221 "</redfish/v1/JsonSchemas/Fan/Fan.json>; rel=describedby");
222 resp.jsonValue["@odata.type"] = "#Fan.v1_3_0.Fan";
223 resp.jsonValue["Name"] = "Fan";
224 resp.jsonValue["Id"] = fanId;
225 resp.jsonValue["@odata.id"] = boost::urls::format(
226 "/redfish/v1/Chassis/{}/ThermalSubsystem/Fans/{}", chassisId, fanId);
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800227 resp.jsonValue["Status"]["State"] = "Enabled";
228 resp.jsonValue["Status"]["Health"] = "OK";
229}
230
231inline void getFanHealth(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
232 const std::string& fanPath, const std::string& service)
233{
234 sdbusplus::asio::getProperty<bool>(
235 *crow::connections::systemBus, service, fanPath,
236 "xyz.openbmc_project.State.Decorator.OperationalStatus", "Functional",
237 [asyncResp](const boost::system::error_code& ec, const bool value) {
238 if (ec)
239 {
240 if (ec.value() != EBADR)
241 {
242 BMCWEB_LOG_ERROR << "DBUS response error for Health "
243 << ec.value();
244 messages::internalError(asyncResp->res);
245 }
246 return;
247 }
248
249 if (!value)
250 {
251 asyncResp->res.jsonValue["Status"]["Health"] = "Critical";
252 }
253 });
254}
255
256inline void getFanState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
257 const std::string& fanPath, const std::string& service)
258{
259 sdbusplus::asio::getProperty<bool>(
260 *crow::connections::systemBus, service, fanPath,
261 "xyz.openbmc_project.Inventory.Item", "Present",
262 [asyncResp](const boost::system::error_code& ec, const bool value) {
263 if (ec)
264 {
265 if (ec.value() != EBADR)
266 {
267 BMCWEB_LOG_ERROR << "DBUS response error for State "
268 << ec.value();
269 messages::internalError(asyncResp->res);
270 }
271 return;
272 }
273
274 if (!value)
275 {
276 asyncResp->res.jsonValue["Status"]["State"] = "Absent";
277 }
278 });
George Liue4e54232022-09-30 09:08:11 +0800279}
280
George Liu090ae7b2022-10-04 15:45:25 +0800281inline void getFanAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
282 const std::string& fanPath, const std::string& service)
283{
284 sdbusplus::asio::getAllProperties(
285 *crow::connections::systemBus, service, fanPath,
286 "xyz.openbmc_project.Inventory.Decorator.Asset",
287 [fanPath, asyncResp{asyncResp}](
288 const boost::system::error_code& ec,
289 const dbus::utility::DBusPropertiesMap& assetList) {
290 if (ec)
291 {
292 if (ec.value() != EBADR)
293 {
294 BMCWEB_LOG_ERROR << "DBUS response error for Properties"
295 << ec.value();
296 messages::internalError(asyncResp->res);
297 }
298 return;
299 }
300 const std::string* manufacturer = nullptr;
301 const std::string* model = nullptr;
302 const std::string* partNumber = nullptr;
303 const std::string* serialNumber = nullptr;
304 const std::string* sparePartNumber = nullptr;
305
306 const bool success = sdbusplus::unpackPropertiesNoThrow(
307 dbus_utils::UnpackErrorPrinter(), assetList, "Manufacturer",
308 manufacturer, "Model", model, "PartNumber", partNumber,
309 "SerialNumber", serialNumber, "SparePartNumber", sparePartNumber);
310 if (!success)
311 {
312 messages::internalError(asyncResp->res);
313 return;
314 }
315 if (manufacturer != nullptr)
316 {
317 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
318 }
319 if (model != nullptr)
320 {
321 asyncResp->res.jsonValue["Model"] = *model;
322 }
323 if (partNumber != nullptr)
324 {
325 asyncResp->res.jsonValue["PartNumber"] = *partNumber;
326 }
327 if (serialNumber != nullptr)
328 {
329 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber;
330 }
331 if (sparePartNumber != nullptr && !sparePartNumber->empty())
332 {
333 asyncResp->res.jsonValue["SparePartNumber"] = *sparePartNumber;
334 }
335 });
336}
337
George Liue4e54232022-09-30 09:08:11 +0800338inline void
339 afterGetValidFanPath(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
340 const std::string& chassisId, const std::string& fanId,
341 const std::string& fanPath, const std::string& service)
342{
George Liue4e54232022-09-30 09:08:11 +0800343 addFanCommonProperties(asyncResp->res, chassisId, fanId);
Albert Zhang9f1ae5a2021-06-10 14:41:48 +0800344 getFanState(asyncResp, fanPath, service);
345 getFanHealth(asyncResp, fanPath, service);
George Liu090ae7b2022-10-04 15:45:25 +0800346 getFanAsset(asyncResp, fanPath, service);
George Liue4e54232022-09-30 09:08:11 +0800347}
348
349inline void doFanGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
350 const std::string& chassisId, const std::string& fanId,
351 const std::optional<std::string>& validChassisPath)
352{
353 if (!validChassisPath)
354 {
355 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
356 return;
357 }
358
359 getValidFanPath(
360 asyncResp, *validChassisPath, fanId,
361 std::bind_front(afterGetValidFanPath, asyncResp, chassisId, fanId));
362}
363
364inline void handleFanHead(App& app, const crow::Request& req,
365 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
366 const std::string& chassisId,
367 const std::string& fanId)
368{
369 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
370 {
371 return;
372 }
373
374 redfish::chassis_utils::getValidChassisPath(
375 asyncResp, chassisId,
376 [asyncResp, chassisId,
377 fanId](const std::optional<std::string>& validChassisPath) {
378 if (!validChassisPath)
379 {
380 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
381 return;
382 }
383 getValidFanPath(asyncResp, *validChassisPath, fanId,
384 [asyncResp](const std::string&, const std::string&) {
385 asyncResp->res.addHeader(
386 boost::beast::http::field::link,
387 "</redfish/v1/JsonSchemas/Fan/Fan.json>; rel=describedby");
388 });
389 });
390}
391
392inline void handleFanGet(App& app, const crow::Request& req,
393 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
394 const std::string& chassisId, const std::string& fanId)
395{
396 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
397 {
398 return;
399 }
400
401 redfish::chassis_utils::getValidChassisPath(
402 asyncResp, chassisId,
403 std::bind_front(doFanGet, asyncResp, chassisId, fanId));
404}
405
406inline void requestRoutesFan(App& app)
407{
408 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/")
409 .privileges(redfish::privileges::headFan)
410 .methods(boost::beast::http::verb::head)(
411 std::bind_front(handleFanHead, std::ref(app)));
412
413 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/")
414 .privileges(redfish::privileges::getFan)
415 .methods(boost::beast::http::verb::get)(
416 std::bind_front(handleFanGet, std::ref(app)));
417}
418
George Liu9516f412022-09-29 17:21:41 +0800419} // namespace redfish