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 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 338 inline void getFanLocation(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 339 const std::string& fanPath, 340 const std::string& service) 341 { 342 sdbusplus::asio::getProperty<std::string>( 343 *crow::connections::systemBus, service, fanPath, 344 "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode", 345 [asyncResp](const boost::system::error_code& ec, 346 const std::string& property) { 347 if (ec) 348 { 349 if (ec.value() != EBADR) 350 { 351 BMCWEB_LOG_ERROR("DBUS response error for Location{}", 352 ec.value()); 353 messages::internalError(asyncResp->res); 354 } 355 return; 356 } 357 asyncResp->res.jsonValue["Location"]["PartLocation"]["ServiceLabel"] = 358 property; 359 }); 360 } 361 362 inline void 363 afterGetValidFanPath(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 364 const std::string& chassisId, const std::string& fanId, 365 const std::string& fanPath, const std::string& service) 366 { 367 addFanCommonProperties(asyncResp->res, chassisId, fanId); 368 getFanState(asyncResp, fanPath, service); 369 getFanHealth(asyncResp, fanPath, service); 370 getFanAsset(asyncResp, fanPath, service); 371 getFanLocation(asyncResp, fanPath, service); 372 } 373 374 inline void doFanGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 375 const std::string& chassisId, const std::string& fanId, 376 const std::optional<std::string>& validChassisPath) 377 { 378 if (!validChassisPath) 379 { 380 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 381 return; 382 } 383 384 getValidFanPath( 385 asyncResp, *validChassisPath, fanId, 386 std::bind_front(afterGetValidFanPath, asyncResp, chassisId, fanId)); 387 } 388 389 inline void handleFanHead(App& app, const crow::Request& req, 390 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 391 const std::string& chassisId, 392 const std::string& fanId) 393 { 394 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 395 { 396 return; 397 } 398 399 redfish::chassis_utils::getValidChassisPath( 400 asyncResp, chassisId, 401 [asyncResp, chassisId, 402 fanId](const std::optional<std::string>& validChassisPath) { 403 if (!validChassisPath) 404 { 405 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 406 return; 407 } 408 getValidFanPath(asyncResp, *validChassisPath, fanId, 409 [asyncResp](const std::string&, const std::string&) { 410 asyncResp->res.addHeader( 411 boost::beast::http::field::link, 412 "</redfish/v1/JsonSchemas/Fan/Fan.json>; rel=describedby"); 413 }); 414 }); 415 } 416 417 inline void handleFanGet(App& app, const crow::Request& req, 418 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 419 const std::string& chassisId, const std::string& fanId) 420 { 421 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 422 { 423 return; 424 } 425 426 redfish::chassis_utils::getValidChassisPath( 427 asyncResp, chassisId, 428 std::bind_front(doFanGet, asyncResp, chassisId, fanId)); 429 } 430 431 inline void requestRoutesFan(App& app) 432 { 433 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/") 434 .privileges(redfish::privileges::headFan) 435 .methods(boost::beast::http::verb::head)( 436 std::bind_front(handleFanHead, std::ref(app))); 437 438 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/") 439 .privileges(redfish::privileges::getFan) 440 .methods(boost::beast::http::verb::get)( 441 std::bind_front(handleFanGet, std::ref(app))); 442 } 443 444 } // namespace redfish 445