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