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/system/error_code.hpp> 11 #include <boost/url/format.hpp> 12 #include <sdbusplus/asio/property.hpp> 13 #include <sdbusplus/message/types.hpp> 14 15 #include <functional> 16 #include <memory> 17 #include <optional> 18 #include <string> 19 #include <string_view> 20 21 namespace redfish 22 { 23 constexpr std::array<std::string_view, 1> fanInterface = { 24 "xyz.openbmc_project.Inventory.Item.Fan"}; 25 26 inline 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 51 inline 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 82 inline 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 108 inline 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 133 inline 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 148 inline 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 161 inline 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 168 static 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 202 inline 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 216 inline 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); 227 resp.jsonValue["Status"]["State"] = "Enabled"; 228 resp.jsonValue["Status"]["Health"] = "OK"; 229 } 230 231 inline 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 256 inline 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 }); 279 } 280 281 inline void 282 afterGetValidFanPath(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 283 const std::string& chassisId, const std::string& fanId, 284 const std::string& fanPath, const std::string& service) 285 { 286 addFanCommonProperties(asyncResp->res, chassisId, fanId); 287 getFanState(asyncResp, fanPath, service); 288 getFanHealth(asyncResp, fanPath, service); 289 } 290 291 inline void doFanGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 292 const std::string& chassisId, const std::string& fanId, 293 const std::optional<std::string>& validChassisPath) 294 { 295 if (!validChassisPath) 296 { 297 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 298 return; 299 } 300 301 getValidFanPath( 302 asyncResp, *validChassisPath, fanId, 303 std::bind_front(afterGetValidFanPath, asyncResp, chassisId, fanId)); 304 } 305 306 inline void handleFanHead(App& app, const crow::Request& req, 307 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 308 const std::string& chassisId, 309 const std::string& fanId) 310 { 311 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 312 { 313 return; 314 } 315 316 redfish::chassis_utils::getValidChassisPath( 317 asyncResp, chassisId, 318 [asyncResp, chassisId, 319 fanId](const std::optional<std::string>& validChassisPath) { 320 if (!validChassisPath) 321 { 322 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 323 return; 324 } 325 getValidFanPath(asyncResp, *validChassisPath, fanId, 326 [asyncResp](const std::string&, const std::string&) { 327 asyncResp->res.addHeader( 328 boost::beast::http::field::link, 329 "</redfish/v1/JsonSchemas/Fan/Fan.json>; rel=describedby"); 330 }); 331 }); 332 } 333 334 inline void handleFanGet(App& app, const crow::Request& req, 335 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 336 const std::string& chassisId, const std::string& fanId) 337 { 338 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 339 { 340 return; 341 } 342 343 redfish::chassis_utils::getValidChassisPath( 344 asyncResp, chassisId, 345 std::bind_front(doFanGet, asyncResp, chassisId, fanId)); 346 } 347 348 inline void requestRoutesFan(App& app) 349 { 350 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/") 351 .privileges(redfish::privileges::headFan) 352 .methods(boost::beast::http::verb::head)( 353 std::bind_front(handleFanHead, std::ref(app))); 354 355 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/") 356 .privileges(redfish::privileges::getFan) 357 .methods(boost::beast::http::verb::get)( 358 std::bind_front(handleFanGet, std::ref(app))); 359 } 360 361 } // namespace redfish 362