1 #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 10 #include <boost/url/format.hpp> 11 #include <sdbusplus/message/types.hpp> 12 13 #include <functional> 14 #include <memory> 15 #include <optional> 16 #include <string> 17 #include <string_view> 18 19 namespace redfish 20 { 21 constexpr std::array<std::string_view, 1> fanInterface = { 22 "xyz.openbmc_project.Inventory.Item.Fan"}; 23 24 inline void 25 updateFanList(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 26 const std::string& chassisId, 27 const dbus::utility::MapperGetSubTreePathsResponse& fanPaths) 28 { 29 nlohmann::json& fanList = asyncResp->res.jsonValue["Members"]; 30 for (const std::string& fanPath : fanPaths) 31 { 32 std::string fanName = 33 sdbusplus::message::object_path(fanPath).filename(); 34 if (fanName.empty()) 35 { 36 continue; 37 } 38 39 nlohmann::json item = nlohmann::json::object(); 40 item["@odata.id"] = boost::urls::format( 41 "/redfish/v1/Chassis/{}/ThermalSubsystem/Fans/{}", chassisId, 42 fanName); 43 44 fanList.emplace_back(std::move(item)); 45 } 46 asyncResp->res.jsonValue["Members@odata.count"] = fanList.size(); 47 } 48 49 inline void getFanPaths( 50 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 51 const std::optional<std::string>& validChassisPath, 52 const std::function<void(const dbus::utility::MapperGetSubTreePathsResponse& 53 fanPaths)>& callback) 54 { 55 sdbusplus::message::object_path endpointPath{*validChassisPath}; 56 endpointPath /= "cooled_by"; 57 58 dbus::utility::getAssociatedSubTreePaths( 59 endpointPath, 60 sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0, 61 fanInterface, 62 [asyncResp, callback]( 63 const boost::system::error_code& ec, 64 const dbus::utility::MapperGetSubTreePathsResponse& subtreePaths) { 65 if (ec) 66 { 67 if (ec.value() != EBADR) 68 { 69 BMCWEB_LOG_ERROR 70 << "DBUS response error for getAssociatedSubTreePaths " 71 << ec.value(); 72 messages::internalError(asyncResp->res); 73 } 74 return; 75 } 76 callback(subtreePaths); 77 }); 78 } 79 80 inline void doFanCollection(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 81 const std::string& chassisId, 82 const std::optional<std::string>& validChassisPath) 83 { 84 if (!validChassisPath) 85 { 86 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 87 return; 88 } 89 90 asyncResp->res.addHeader( 91 boost::beast::http::field::link, 92 "</redfish/v1/JsonSchemas/FanCollection/FanCollection.json>; rel=describedby"); 93 asyncResp->res.jsonValue["@odata.type"] = "#FanCollection.FanCollection"; 94 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 95 "/redfish/v1/Chassis/{}/ThermalSubsystem/Fans", chassisId); 96 asyncResp->res.jsonValue["Name"] = "Fan Collection"; 97 asyncResp->res.jsonValue["Description"] = 98 "The collection of Fan resource instances " + chassisId; 99 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 100 asyncResp->res.jsonValue["Members@odata.count"] = 0; 101 102 getFanPaths(asyncResp, validChassisPath, 103 std::bind_front(updateFanList, asyncResp, chassisId)); 104 } 105 106 inline void 107 handleFanCollectionHead(App& app, const crow::Request& req, 108 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 109 const std::string& chassisId) 110 { 111 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 112 { 113 return; 114 } 115 116 redfish::chassis_utils::getValidChassisPath( 117 asyncResp, chassisId, 118 [asyncResp, 119 chassisId](const std::optional<std::string>& validChassisPath) { 120 if (!validChassisPath) 121 { 122 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 123 return; 124 } 125 asyncResp->res.addHeader( 126 boost::beast::http::field::link, 127 "</redfish/v1/JsonSchemas/FanCollection/FanCollection.json>; rel=describedby"); 128 }); 129 } 130 131 inline void 132 handleFanCollectionGet(App& app, const crow::Request& req, 133 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 134 const std::string& chassisId) 135 { 136 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 137 { 138 return; 139 } 140 141 redfish::chassis_utils::getValidChassisPath( 142 asyncResp, chassisId, 143 std::bind_front(doFanCollection, asyncResp, chassisId)); 144 } 145 146 inline void requestRoutesFanCollection(App& app) 147 { 148 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/") 149 .privileges(redfish::privileges::headFanCollection) 150 .methods(boost::beast::http::verb::head)( 151 std::bind_front(handleFanCollectionHead, std::ref(app))); 152 153 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/") 154 .privileges(redfish::privileges::getFanCollection) 155 .methods(boost::beast::http::verb::get)( 156 std::bind_front(handleFanCollectionGet, std::ref(app))); 157 } 158 159 inline bool checkFanId(const std::string& fanPath, const std::string& fanId) 160 { 161 std::string fanName = sdbusplus::message::object_path(fanPath).filename(); 162 163 return !(fanName.empty() || fanName != fanId); 164 } 165 166 static inline void handleFanPath( 167 const std::string& fanId, 168 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 169 const dbus::utility::MapperGetSubTreePathsResponse& fanPaths, 170 const std::function<void(const std::string& fanPath, 171 const std::string& service)>& callback) 172 { 173 for (const auto& fanPath : fanPaths) 174 { 175 if (!checkFanId(fanPath, fanId)) 176 { 177 continue; 178 } 179 dbus::utility::getDbusObject( 180 fanPath, fanInterface, 181 [fanPath, asyncResp, 182 callback](const boost::system::error_code& ec, 183 const dbus::utility::MapperGetObject& object) { 184 if (ec || object.empty()) 185 { 186 BMCWEB_LOG_ERROR << "DBUS response error on getDbusObject " 187 << ec.value(); 188 messages::internalError(asyncResp->res); 189 return; 190 } 191 callback(fanPath, object.begin()->first); 192 }); 193 194 return; 195 } 196 BMCWEB_LOG_WARNING << "Fan not found " << fanId; 197 messages::resourceNotFound(asyncResp->res, "Fan", fanId); 198 } 199 200 inline void getValidFanPath( 201 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 202 const std::string& validChassisPath, const std::string& fanId, 203 const std::function<void(const std::string& fanPath, 204 const std::string& service)>& callback) 205 { 206 getFanPaths( 207 asyncResp, validChassisPath, 208 [fanId, asyncResp, callback]( 209 const dbus::utility::MapperGetSubTreePathsResponse& fanPaths) { 210 handleFanPath(fanId, asyncResp, fanPaths, callback); 211 }); 212 } 213 214 inline void addFanCommonProperties(crow::Response& resp, 215 const std::string& chassisId, 216 const std::string& fanId) 217 { 218 resp.addHeader(boost::beast::http::field::link, 219 "</redfish/v1/JsonSchemas/Fan/Fan.json>; rel=describedby"); 220 resp.jsonValue["@odata.type"] = "#Fan.v1_3_0.Fan"; 221 resp.jsonValue["Name"] = "Fan"; 222 resp.jsonValue["Id"] = fanId; 223 resp.jsonValue["@odata.id"] = boost::urls::format( 224 "/redfish/v1/Chassis/{}/ThermalSubsystem/Fans/{}", chassisId, fanId); 225 } 226 227 inline void 228 afterGetValidFanPath(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 229 const std::string& chassisId, const std::string& fanId, 230 const std::string& fanPath, const std::string& service) 231 { 232 BMCWEB_LOG_DEBUG << "fanPath = " << fanPath << " service = " << service; 233 addFanCommonProperties(asyncResp->res, chassisId, fanId); 234 } 235 236 inline void doFanGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 237 const std::string& chassisId, const std::string& fanId, 238 const std::optional<std::string>& validChassisPath) 239 { 240 if (!validChassisPath) 241 { 242 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 243 return; 244 } 245 246 getValidFanPath( 247 asyncResp, *validChassisPath, fanId, 248 std::bind_front(afterGetValidFanPath, asyncResp, chassisId, fanId)); 249 } 250 251 inline void handleFanHead(App& app, const crow::Request& req, 252 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 253 const std::string& chassisId, 254 const std::string& fanId) 255 { 256 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 257 { 258 return; 259 } 260 261 redfish::chassis_utils::getValidChassisPath( 262 asyncResp, chassisId, 263 [asyncResp, chassisId, 264 fanId](const std::optional<std::string>& validChassisPath) { 265 if (!validChassisPath) 266 { 267 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 268 return; 269 } 270 getValidFanPath(asyncResp, *validChassisPath, fanId, 271 [asyncResp](const std::string&, const std::string&) { 272 asyncResp->res.addHeader( 273 boost::beast::http::field::link, 274 "</redfish/v1/JsonSchemas/Fan/Fan.json>; rel=describedby"); 275 }); 276 }); 277 } 278 279 inline void handleFanGet(App& app, const crow::Request& req, 280 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 281 const std::string& chassisId, const std::string& fanId) 282 { 283 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 284 { 285 return; 286 } 287 288 redfish::chassis_utils::getValidChassisPath( 289 asyncResp, chassisId, 290 std::bind_front(doFanGet, asyncResp, chassisId, fanId)); 291 } 292 293 inline void requestRoutesFan(App& app) 294 { 295 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/") 296 .privileges(redfish::privileges::headFan) 297 .methods(boost::beast::http::verb::head)( 298 std::bind_front(handleFanHead, std::ref(app))); 299 300 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/") 301 .privileges(redfish::privileges::getFan) 302 .methods(boost::beast::http::verb::get)( 303 std::bind_front(handleFanGet, std::ref(app))); 304 } 305 306 } // namespace redfish 307