xref: /openbmc/bmcweb/features/redfish/lib/fan.hpp (revision e4e54232c271f3c4b92531b1de78329d08a05fff)
19516f41fSGeorge Liu #pragma once
29516f41fSGeorge Liu 
39516f41fSGeorge Liu #include "app.hpp"
49516f41fSGeorge Liu #include "dbus_utility.hpp"
59516f41fSGeorge Liu #include "error_messages.hpp"
69516f41fSGeorge Liu #include "query.hpp"
79516f41fSGeorge Liu #include "registries/privilege_registry.hpp"
89516f41fSGeorge Liu #include "utils/chassis_utils.hpp"
99516f41fSGeorge Liu 
109516f41fSGeorge Liu #include <boost/url/format.hpp>
119516f41fSGeorge Liu #include <sdbusplus/message/types.hpp>
129516f41fSGeorge Liu 
139516f41fSGeorge Liu #include <functional>
149516f41fSGeorge Liu #include <memory>
159516f41fSGeorge Liu #include <optional>
169516f41fSGeorge Liu #include <string>
179516f41fSGeorge Liu #include <string_view>
189516f41fSGeorge Liu 
199516f41fSGeorge Liu namespace redfish
209516f41fSGeorge Liu {
219516f41fSGeorge Liu constexpr std::array<std::string_view, 1> fanInterface = {
229516f41fSGeorge Liu     "xyz.openbmc_project.Inventory.Item.Fan"};
239516f41fSGeorge Liu 
249516f41fSGeorge Liu inline void
259516f41fSGeorge Liu     updateFanList(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
269516f41fSGeorge Liu                   const std::string& chassisId,
279516f41fSGeorge Liu                   const dbus::utility::MapperGetSubTreePathsResponse& fanPaths)
289516f41fSGeorge Liu {
299516f41fSGeorge Liu     nlohmann::json& fanList = asyncResp->res.jsonValue["Members"];
309516f41fSGeorge Liu     for (const std::string& fanPath : fanPaths)
319516f41fSGeorge Liu     {
329516f41fSGeorge Liu         std::string fanName =
339516f41fSGeorge Liu             sdbusplus::message::object_path(fanPath).filename();
349516f41fSGeorge Liu         if (fanName.empty())
359516f41fSGeorge Liu         {
369516f41fSGeorge Liu             continue;
379516f41fSGeorge Liu         }
389516f41fSGeorge Liu 
399516f41fSGeorge Liu         nlohmann::json item = nlohmann::json::object();
409516f41fSGeorge Liu         item["@odata.id"] = boost::urls::format(
419516f41fSGeorge Liu             "/redfish/v1/Chassis/{}/ThermalSubsystem/Fans/{}", chassisId,
429516f41fSGeorge Liu             fanName);
439516f41fSGeorge Liu 
449516f41fSGeorge Liu         fanList.emplace_back(std::move(item));
459516f41fSGeorge Liu     }
469516f41fSGeorge Liu     asyncResp->res.jsonValue["Members@odata.count"] = fanList.size();
479516f41fSGeorge Liu }
489516f41fSGeorge Liu 
499516f41fSGeorge Liu inline void getFanPaths(
509516f41fSGeorge Liu     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
519516f41fSGeorge Liu     const std::optional<std::string>& validChassisPath,
529516f41fSGeorge Liu     const std::function<void(const dbus::utility::MapperGetSubTreePathsResponse&
539516f41fSGeorge Liu                                  fanPaths)>& callback)
549516f41fSGeorge Liu {
559516f41fSGeorge Liu     sdbusplus::message::object_path endpointPath{*validChassisPath};
569516f41fSGeorge Liu     endpointPath /= "cooled_by";
579516f41fSGeorge Liu 
589516f41fSGeorge Liu     dbus::utility::getAssociatedSubTreePaths(
599516f41fSGeorge Liu         endpointPath,
609516f41fSGeorge Liu         sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0,
619516f41fSGeorge Liu         fanInterface,
629516f41fSGeorge Liu         [asyncResp, callback](
639516f41fSGeorge Liu             const boost::system::error_code& ec,
649516f41fSGeorge Liu             const dbus::utility::MapperGetSubTreePathsResponse& subtreePaths) {
659516f41fSGeorge Liu         if (ec)
669516f41fSGeorge Liu         {
679516f41fSGeorge Liu             if (ec.value() != EBADR)
689516f41fSGeorge Liu             {
699516f41fSGeorge Liu                 BMCWEB_LOG_ERROR
709516f41fSGeorge Liu                     << "DBUS response error for getAssociatedSubTreePaths "
719516f41fSGeorge Liu                     << ec.value();
729516f41fSGeorge Liu                 messages::internalError(asyncResp->res);
739516f41fSGeorge Liu             }
749516f41fSGeorge Liu             return;
759516f41fSGeorge Liu         }
769516f41fSGeorge Liu         callback(subtreePaths);
779516f41fSGeorge Liu         });
789516f41fSGeorge Liu }
799516f41fSGeorge Liu 
809516f41fSGeorge Liu inline void doFanCollection(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
819516f41fSGeorge Liu                             const std::string& chassisId,
829516f41fSGeorge Liu                             const std::optional<std::string>& validChassisPath)
839516f41fSGeorge Liu {
849516f41fSGeorge Liu     if (!validChassisPath)
859516f41fSGeorge Liu     {
869516f41fSGeorge Liu         messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
879516f41fSGeorge Liu         return;
889516f41fSGeorge Liu     }
899516f41fSGeorge Liu 
909516f41fSGeorge Liu     asyncResp->res.addHeader(
919516f41fSGeorge Liu         boost::beast::http::field::link,
929516f41fSGeorge Liu         "</redfish/v1/JsonSchemas/FanCollection/FanCollection.json>; rel=describedby");
939516f41fSGeorge Liu     asyncResp->res.jsonValue["@odata.type"] = "#FanCollection.FanCollection";
949516f41fSGeorge Liu     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
959516f41fSGeorge Liu         "/redfish/v1/Chassis/{}/ThermalSubsystem/Fans", chassisId);
969516f41fSGeorge Liu     asyncResp->res.jsonValue["Name"] = "Fan Collection";
979516f41fSGeorge Liu     asyncResp->res.jsonValue["Description"] =
989516f41fSGeorge Liu         "The collection of Fan resource instances " + chassisId;
999516f41fSGeorge Liu     asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
1009516f41fSGeorge Liu     asyncResp->res.jsonValue["Members@odata.count"] = 0;
1019516f41fSGeorge Liu 
1029516f41fSGeorge Liu     getFanPaths(asyncResp, validChassisPath,
1039516f41fSGeorge Liu                 std::bind_front(updateFanList, asyncResp, chassisId));
1049516f41fSGeorge Liu }
1059516f41fSGeorge Liu 
1069516f41fSGeorge Liu inline void
1079516f41fSGeorge Liu     handleFanCollectionHead(App& app, const crow::Request& req,
1089516f41fSGeorge Liu                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1099516f41fSGeorge Liu                             const std::string& chassisId)
1109516f41fSGeorge Liu {
1119516f41fSGeorge Liu     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1129516f41fSGeorge Liu     {
1139516f41fSGeorge Liu         return;
1149516f41fSGeorge Liu     }
1159516f41fSGeorge Liu 
1169516f41fSGeorge Liu     redfish::chassis_utils::getValidChassisPath(
1179516f41fSGeorge Liu         asyncResp, chassisId,
1189516f41fSGeorge Liu         [asyncResp,
1199516f41fSGeorge Liu          chassisId](const std::optional<std::string>& validChassisPath) {
1209516f41fSGeorge Liu         if (!validChassisPath)
1219516f41fSGeorge Liu         {
1229516f41fSGeorge Liu             messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
1239516f41fSGeorge Liu             return;
1249516f41fSGeorge Liu         }
1259516f41fSGeorge Liu         asyncResp->res.addHeader(
1269516f41fSGeorge Liu             boost::beast::http::field::link,
1279516f41fSGeorge Liu             "</redfish/v1/JsonSchemas/FanCollection/FanCollection.json>; rel=describedby");
1289516f41fSGeorge Liu         });
1299516f41fSGeorge Liu }
1309516f41fSGeorge Liu 
1319516f41fSGeorge Liu inline void
1329516f41fSGeorge Liu     handleFanCollectionGet(App& app, const crow::Request& req,
1339516f41fSGeorge Liu                            const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1349516f41fSGeorge Liu                            const std::string& chassisId)
1359516f41fSGeorge Liu {
1369516f41fSGeorge Liu     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1379516f41fSGeorge Liu     {
1389516f41fSGeorge Liu         return;
1399516f41fSGeorge Liu     }
1409516f41fSGeorge Liu 
1419516f41fSGeorge Liu     redfish::chassis_utils::getValidChassisPath(
1429516f41fSGeorge Liu         asyncResp, chassisId,
1439516f41fSGeorge Liu         std::bind_front(doFanCollection, asyncResp, chassisId));
1449516f41fSGeorge Liu }
1459516f41fSGeorge Liu 
1469516f41fSGeorge Liu inline void requestRoutesFanCollection(App& app)
1479516f41fSGeorge Liu {
1489516f41fSGeorge Liu     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/")
1499516f41fSGeorge Liu         .privileges(redfish::privileges::headFanCollection)
1509516f41fSGeorge Liu         .methods(boost::beast::http::verb::head)(
1519516f41fSGeorge Liu             std::bind_front(handleFanCollectionHead, std::ref(app)));
1529516f41fSGeorge Liu 
1539516f41fSGeorge Liu     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/")
1549516f41fSGeorge Liu         .privileges(redfish::privileges::getFanCollection)
1559516f41fSGeorge Liu         .methods(boost::beast::http::verb::get)(
1569516f41fSGeorge Liu             std::bind_front(handleFanCollectionGet, std::ref(app)));
1579516f41fSGeorge Liu }
1589516f41fSGeorge Liu 
159*e4e54232SGeorge Liu inline bool checkFanId(const std::string& fanPath, const std::string& fanId)
160*e4e54232SGeorge Liu {
161*e4e54232SGeorge Liu     std::string fanName = sdbusplus::message::object_path(fanPath).filename();
162*e4e54232SGeorge Liu 
163*e4e54232SGeorge Liu     return !(fanName.empty() || fanName != fanId);
164*e4e54232SGeorge Liu }
165*e4e54232SGeorge Liu 
166*e4e54232SGeorge Liu static inline void handleFanPath(
167*e4e54232SGeorge Liu     const std::string& fanId,
168*e4e54232SGeorge Liu     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
169*e4e54232SGeorge Liu     const dbus::utility::MapperGetSubTreePathsResponse& fanPaths,
170*e4e54232SGeorge Liu     const std::function<void(const std::string& fanPath,
171*e4e54232SGeorge Liu                              const std::string& service)>& callback)
172*e4e54232SGeorge Liu {
173*e4e54232SGeorge Liu     for (const auto& fanPath : fanPaths)
174*e4e54232SGeorge Liu     {
175*e4e54232SGeorge Liu         if (!checkFanId(fanPath, fanId))
176*e4e54232SGeorge Liu         {
177*e4e54232SGeorge Liu             continue;
178*e4e54232SGeorge Liu         }
179*e4e54232SGeorge Liu         dbus::utility::getDbusObject(
180*e4e54232SGeorge Liu             fanPath, fanInterface,
181*e4e54232SGeorge Liu             [fanPath, asyncResp,
182*e4e54232SGeorge Liu              callback](const boost::system::error_code& ec,
183*e4e54232SGeorge Liu                        const dbus::utility::MapperGetObject& object) {
184*e4e54232SGeorge Liu             if (ec || object.empty())
185*e4e54232SGeorge Liu             {
186*e4e54232SGeorge Liu                 BMCWEB_LOG_ERROR << "DBUS response error on getDbusObject "
187*e4e54232SGeorge Liu                                  << ec.value();
188*e4e54232SGeorge Liu                 messages::internalError(asyncResp->res);
189*e4e54232SGeorge Liu                 return;
190*e4e54232SGeorge Liu             }
191*e4e54232SGeorge Liu             callback(fanPath, object.begin()->first);
192*e4e54232SGeorge Liu             });
193*e4e54232SGeorge Liu 
194*e4e54232SGeorge Liu         return;
195*e4e54232SGeorge Liu     }
196*e4e54232SGeorge Liu     BMCWEB_LOG_WARNING << "Fan not found " << fanId;
197*e4e54232SGeorge Liu     messages::resourceNotFound(asyncResp->res, "Fan", fanId);
198*e4e54232SGeorge Liu }
199*e4e54232SGeorge Liu 
200*e4e54232SGeorge Liu inline void getValidFanPath(
201*e4e54232SGeorge Liu     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
202*e4e54232SGeorge Liu     const std::string& validChassisPath, const std::string& fanId,
203*e4e54232SGeorge Liu     const std::function<void(const std::string& fanPath,
204*e4e54232SGeorge Liu                              const std::string& service)>& callback)
205*e4e54232SGeorge Liu {
206*e4e54232SGeorge Liu     getFanPaths(
207*e4e54232SGeorge Liu         asyncResp, validChassisPath,
208*e4e54232SGeorge Liu         [fanId, asyncResp, callback](
209*e4e54232SGeorge Liu             const dbus::utility::MapperGetSubTreePathsResponse& fanPaths) {
210*e4e54232SGeorge Liu         handleFanPath(fanId, asyncResp, fanPaths, callback);
211*e4e54232SGeorge Liu         });
212*e4e54232SGeorge Liu }
213*e4e54232SGeorge Liu 
214*e4e54232SGeorge Liu inline void addFanCommonProperties(crow::Response& resp,
215*e4e54232SGeorge Liu                                    const std::string& chassisId,
216*e4e54232SGeorge Liu                                    const std::string& fanId)
217*e4e54232SGeorge Liu {
218*e4e54232SGeorge Liu     resp.addHeader(boost::beast::http::field::link,
219*e4e54232SGeorge Liu                    "</redfish/v1/JsonSchemas/Fan/Fan.json>; rel=describedby");
220*e4e54232SGeorge Liu     resp.jsonValue["@odata.type"] = "#Fan.v1_3_0.Fan";
221*e4e54232SGeorge Liu     resp.jsonValue["Name"] = "Fan";
222*e4e54232SGeorge Liu     resp.jsonValue["Id"] = fanId;
223*e4e54232SGeorge Liu     resp.jsonValue["@odata.id"] = boost::urls::format(
224*e4e54232SGeorge Liu         "/redfish/v1/Chassis/{}/ThermalSubsystem/Fans/{}", chassisId, fanId);
225*e4e54232SGeorge Liu }
226*e4e54232SGeorge Liu 
227*e4e54232SGeorge Liu inline void
228*e4e54232SGeorge Liu     afterGetValidFanPath(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
229*e4e54232SGeorge Liu                          const std::string& chassisId, const std::string& fanId,
230*e4e54232SGeorge Liu                          const std::string& fanPath, const std::string& service)
231*e4e54232SGeorge Liu {
232*e4e54232SGeorge Liu     BMCWEB_LOG_DEBUG << "fanPath = " << fanPath << " service = " << service;
233*e4e54232SGeorge Liu     addFanCommonProperties(asyncResp->res, chassisId, fanId);
234*e4e54232SGeorge Liu }
235*e4e54232SGeorge Liu 
236*e4e54232SGeorge Liu inline void doFanGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
237*e4e54232SGeorge Liu                      const std::string& chassisId, const std::string& fanId,
238*e4e54232SGeorge Liu                      const std::optional<std::string>& validChassisPath)
239*e4e54232SGeorge Liu {
240*e4e54232SGeorge Liu     if (!validChassisPath)
241*e4e54232SGeorge Liu     {
242*e4e54232SGeorge Liu         messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
243*e4e54232SGeorge Liu         return;
244*e4e54232SGeorge Liu     }
245*e4e54232SGeorge Liu 
246*e4e54232SGeorge Liu     getValidFanPath(
247*e4e54232SGeorge Liu         asyncResp, *validChassisPath, fanId,
248*e4e54232SGeorge Liu         std::bind_front(afterGetValidFanPath, asyncResp, chassisId, fanId));
249*e4e54232SGeorge Liu }
250*e4e54232SGeorge Liu 
251*e4e54232SGeorge Liu inline void handleFanHead(App& app, const crow::Request& req,
252*e4e54232SGeorge Liu                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
253*e4e54232SGeorge Liu                           const std::string& chassisId,
254*e4e54232SGeorge Liu                           const std::string& fanId)
255*e4e54232SGeorge Liu {
256*e4e54232SGeorge Liu     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
257*e4e54232SGeorge Liu     {
258*e4e54232SGeorge Liu         return;
259*e4e54232SGeorge Liu     }
260*e4e54232SGeorge Liu 
261*e4e54232SGeorge Liu     redfish::chassis_utils::getValidChassisPath(
262*e4e54232SGeorge Liu         asyncResp, chassisId,
263*e4e54232SGeorge Liu         [asyncResp, chassisId,
264*e4e54232SGeorge Liu          fanId](const std::optional<std::string>& validChassisPath) {
265*e4e54232SGeorge Liu         if (!validChassisPath)
266*e4e54232SGeorge Liu         {
267*e4e54232SGeorge Liu             messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
268*e4e54232SGeorge Liu             return;
269*e4e54232SGeorge Liu         }
270*e4e54232SGeorge Liu         getValidFanPath(asyncResp, *validChassisPath, fanId,
271*e4e54232SGeorge Liu                         [asyncResp](const std::string&, const std::string&) {
272*e4e54232SGeorge Liu             asyncResp->res.addHeader(
273*e4e54232SGeorge Liu                 boost::beast::http::field::link,
274*e4e54232SGeorge Liu                 "</redfish/v1/JsonSchemas/Fan/Fan.json>; rel=describedby");
275*e4e54232SGeorge Liu         });
276*e4e54232SGeorge Liu         });
277*e4e54232SGeorge Liu }
278*e4e54232SGeorge Liu 
279*e4e54232SGeorge Liu inline void handleFanGet(App& app, const crow::Request& req,
280*e4e54232SGeorge Liu                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
281*e4e54232SGeorge Liu                          const std::string& chassisId, const std::string& fanId)
282*e4e54232SGeorge Liu {
283*e4e54232SGeorge Liu     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
284*e4e54232SGeorge Liu     {
285*e4e54232SGeorge Liu         return;
286*e4e54232SGeorge Liu     }
287*e4e54232SGeorge Liu 
288*e4e54232SGeorge Liu     redfish::chassis_utils::getValidChassisPath(
289*e4e54232SGeorge Liu         asyncResp, chassisId,
290*e4e54232SGeorge Liu         std::bind_front(doFanGet, asyncResp, chassisId, fanId));
291*e4e54232SGeorge Liu }
292*e4e54232SGeorge Liu 
293*e4e54232SGeorge Liu inline void requestRoutesFan(App& app)
294*e4e54232SGeorge Liu {
295*e4e54232SGeorge Liu     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/")
296*e4e54232SGeorge Liu         .privileges(redfish::privileges::headFan)
297*e4e54232SGeorge Liu         .methods(boost::beast::http::verb::head)(
298*e4e54232SGeorge Liu             std::bind_front(handleFanHead, std::ref(app)));
299*e4e54232SGeorge Liu 
300*e4e54232SGeorge Liu     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/")
301*e4e54232SGeorge Liu         .privileges(redfish::privileges::getFan)
302*e4e54232SGeorge Liu         .methods(boost::beast::http::verb::get)(
303*e4e54232SGeorge Liu             std::bind_front(handleFanGet, std::ref(app)));
304*e4e54232SGeorge Liu }
305*e4e54232SGeorge Liu 
3069516f41fSGeorge Liu } // namespace redfish
307