1 #pragma once 2 3 #include "app.hpp" 4 #include "dbus_utility.hpp" 5 #include "error_messages.hpp" 6 #include "generated/enums/resource.hpp" 7 #include "query.hpp" 8 #include "registries/privilege_registry.hpp" 9 #include "utils/chassis_utils.hpp" 10 11 #include <boost/system/error_code.hpp> 12 #include <boost/url/format.hpp> 13 #include <sdbusplus/asio/property.hpp> 14 #include <sdbusplus/message/types.hpp> 15 16 #include <functional> 17 #include <memory> 18 #include <optional> 19 #include <string> 20 #include <string_view> 21 22 namespace redfish 23 { 24 constexpr std::array<std::string_view, 1> fanInterface = { 25 "xyz.openbmc_project.Inventory.Item.Fan"}; 26 27 inline void 28 updateFanList(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 29 const std::string& chassisId, 30 const dbus::utility::MapperGetSubTreePathsResponse& fanPaths) 31 { 32 nlohmann::json& fanList = asyncResp->res.jsonValue["Members"]; 33 for (const std::string& fanPath : fanPaths) 34 { 35 std::string fanName = 36 sdbusplus::message::object_path(fanPath).filename(); 37 if (fanName.empty()) 38 { 39 continue; 40 } 41 42 nlohmann::json item = nlohmann::json::object(); 43 item["@odata.id"] = boost::urls::format( 44 "/redfish/v1/Chassis/{}/ThermalSubsystem/Fans/{}", chassisId, 45 fanName); 46 47 fanList.emplace_back(std::move(item)); 48 } 49 asyncResp->res.jsonValue["Members@odata.count"] = fanList.size(); 50 } 51 52 inline void getFanPaths( 53 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 54 const std::string& validChassisPath, 55 const std::function<void(const dbus::utility::MapperGetSubTreePathsResponse& 56 fanPaths)>& callback) 57 { 58 sdbusplus::message::object_path endpointPath{validChassisPath}; 59 endpointPath /= "cooled_by"; 60 61 dbus::utility::getAssociatedSubTreePaths( 62 endpointPath, 63 sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0, 64 fanInterface, 65 [asyncResp, callback]( 66 const boost::system::error_code& ec, 67 const dbus::utility::MapperGetSubTreePathsResponse& subtreePaths) { 68 if (ec) 69 { 70 if (ec.value() != EBADR) 71 { 72 BMCWEB_LOG_ERROR( 73 "DBUS response error for getAssociatedSubTreePaths {}", 74 ec.value()); 75 messages::internalError(asyncResp->res); 76 } 77 return; 78 } 79 callback(subtreePaths); 80 }); 81 } 82 83 inline void doFanCollection(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 84 const std::string& chassisId, 85 const std::optional<std::string>& validChassisPath) 86 { 87 if (!validChassisPath) 88 { 89 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 90 return; 91 } 92 93 asyncResp->res.addHeader( 94 boost::beast::http::field::link, 95 "</redfish/v1/JsonSchemas/FanCollection/FanCollection.json>; rel=describedby"); 96 asyncResp->res.jsonValue["@odata.type"] = "#FanCollection.FanCollection"; 97 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 98 "/redfish/v1/Chassis/{}/ThermalSubsystem/Fans", chassisId); 99 asyncResp->res.jsonValue["Name"] = "Fan Collection"; 100 asyncResp->res.jsonValue["Description"] = 101 "The collection of Fan resource instances " + chassisId; 102 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 103 asyncResp->res.jsonValue["Members@odata.count"] = 0; 104 105 getFanPaths(asyncResp, *validChassisPath, 106 std::bind_front(updateFanList, asyncResp, chassisId)); 107 } 108 109 inline void 110 handleFanCollectionHead(App& app, const crow::Request& req, 111 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 112 const std::string& chassisId) 113 { 114 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 115 { 116 return; 117 } 118 119 redfish::chassis_utils::getValidChassisPath( 120 asyncResp, chassisId, 121 [asyncResp, 122 chassisId](const std::optional<std::string>& validChassisPath) { 123 if (!validChassisPath) 124 { 125 messages::resourceNotFound(asyncResp->res, "Chassis", 126 chassisId); 127 return; 128 } 129 asyncResp->res.addHeader( 130 boost::beast::http::field::link, 131 "</redfish/v1/JsonSchemas/FanCollection/FanCollection.json>; rel=describedby"); 132 }); 133 } 134 135 inline void 136 handleFanCollectionGet(App& app, const crow::Request& req, 137 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 138 const std::string& chassisId) 139 { 140 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 141 { 142 return; 143 } 144 145 redfish::chassis_utils::getValidChassisPath( 146 asyncResp, chassisId, 147 std::bind_front(doFanCollection, asyncResp, chassisId)); 148 } 149 150 inline void requestRoutesFanCollection(App& app) 151 { 152 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/") 153 .privileges(redfish::privileges::headFanCollection) 154 .methods(boost::beast::http::verb::head)( 155 std::bind_front(handleFanCollectionHead, std::ref(app))); 156 157 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/") 158 .privileges(redfish::privileges::getFanCollection) 159 .methods(boost::beast::http::verb::get)( 160 std::bind_front(handleFanCollectionGet, std::ref(app))); 161 } 162 163 inline bool checkFanId(const std::string& fanPath, const std::string& fanId) 164 { 165 std::string fanName = sdbusplus::message::object_path(fanPath).filename(); 166 167 return !(fanName.empty() || fanName != fanId); 168 } 169 170 inline void handleFanPath( 171 const std::string& fanId, 172 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 173 const dbus::utility::MapperGetSubTreePathsResponse& fanPaths, 174 const std::function<void(const std::string& fanPath, 175 const std::string& service)>& callback) 176 { 177 for (const auto& fanPath : fanPaths) 178 { 179 if (!checkFanId(fanPath, fanId)) 180 { 181 continue; 182 } 183 dbus::utility::getDbusObject( 184 fanPath, fanInterface, 185 [fanPath, asyncResp, 186 callback](const boost::system::error_code& ec, 187 const dbus::utility::MapperGetObject& object) { 188 if (ec || object.empty()) 189 { 190 BMCWEB_LOG_ERROR("DBUS response error on getDbusObject {}", 191 ec.value()); 192 messages::internalError(asyncResp->res); 193 return; 194 } 195 callback(fanPath, object.begin()->first); 196 }); 197 198 return; 199 } 200 BMCWEB_LOG_WARNING("Fan not found {}", fanId); 201 messages::resourceNotFound(asyncResp->res, "Fan", fanId); 202 } 203 204 inline void getValidFanPath( 205 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 206 const std::string& validChassisPath, const std::string& fanId, 207 const std::function<void(const std::string& fanPath, 208 const std::string& service)>& callback) 209 { 210 getFanPaths( 211 asyncResp, validChassisPath, 212 [fanId, asyncResp, callback]( 213 const dbus::utility::MapperGetSubTreePathsResponse& fanPaths) { 214 handleFanPath(fanId, asyncResp, fanPaths, callback); 215 }); 216 } 217 218 inline void addFanCommonProperties(crow::Response& resp, 219 const std::string& chassisId, 220 const std::string& fanId) 221 { 222 resp.addHeader(boost::beast::http::field::link, 223 "</redfish/v1/JsonSchemas/Fan/Fan.json>; rel=describedby"); 224 resp.jsonValue["@odata.type"] = "#Fan.v1_3_0.Fan"; 225 resp.jsonValue["Name"] = "Fan"; 226 resp.jsonValue["Id"] = fanId; 227 resp.jsonValue["@odata.id"] = boost::urls::format( 228 "/redfish/v1/Chassis/{}/ThermalSubsystem/Fans/{}", chassisId, fanId); 229 resp.jsonValue["Status"]["State"] = resource::State::Enabled; 230 resp.jsonValue["Status"]["Health"] = resource::Health::OK; 231 } 232 233 inline void getFanHealth(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 234 const std::string& fanPath, const std::string& service) 235 { 236 dbus::utility::getProperty<bool>( 237 service, fanPath, 238 "xyz.openbmc_project.State.Decorator.OperationalStatus", "Functional", 239 [asyncResp](const boost::system::error_code& ec, const bool value) { 240 if (ec) 241 { 242 if (ec.value() != EBADR) 243 { 244 BMCWEB_LOG_ERROR("DBUS response error for Health {}", 245 ec.value()); 246 messages::internalError(asyncResp->res); 247 } 248 return; 249 } 250 251 if (!value) 252 { 253 asyncResp->res.jsonValue["Status"]["Health"] = 254 resource::Health::Critical; 255 } 256 }); 257 } 258 259 inline void getFanState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 260 const std::string& fanPath, const std::string& service) 261 { 262 dbus::utility::getProperty<bool>( 263 service, fanPath, "xyz.openbmc_project.Inventory.Item", "Present", 264 [asyncResp](const boost::system::error_code& ec, const bool value) { 265 if (ec) 266 { 267 if (ec.value() != EBADR) 268 { 269 BMCWEB_LOG_ERROR("DBUS response error for State {}", 270 ec.value()); 271 messages::internalError(asyncResp->res); 272 } 273 return; 274 } 275 276 if (!value) 277 { 278 asyncResp->res.jsonValue["Status"]["State"] = 279 resource::State::Absent; 280 } 281 }); 282 } 283 284 inline void getFanAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 285 const std::string& fanPath, const std::string& service) 286 { 287 dbus::utility::getAllProperties( 288 service, fanPath, "xyz.openbmc_project.Inventory.Decorator.Asset", 289 [fanPath, asyncResp{asyncResp}]( 290 const boost::system::error_code& ec, 291 const dbus::utility::DBusPropertiesMap& assetList) { 292 if (ec) 293 { 294 if (ec.value() != EBADR) 295 { 296 BMCWEB_LOG_ERROR("DBUS response error for Properties{}", 297 ec.value()); 298 messages::internalError(asyncResp->res); 299 } 300 return; 301 } 302 const std::string* manufacturer = nullptr; 303 const std::string* model = nullptr; 304 const std::string* partNumber = nullptr; 305 const std::string* serialNumber = nullptr; 306 const std::string* sparePartNumber = nullptr; 307 308 const bool success = sdbusplus::unpackPropertiesNoThrow( 309 dbus_utils::UnpackErrorPrinter(), assetList, "Manufacturer", 310 manufacturer, "Model", model, "PartNumber", partNumber, 311 "SerialNumber", serialNumber, "SparePartNumber", 312 sparePartNumber); 313 if (!success) 314 { 315 messages::internalError(asyncResp->res); 316 return; 317 } 318 if (manufacturer != nullptr) 319 { 320 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer; 321 } 322 if (model != nullptr) 323 { 324 asyncResp->res.jsonValue["Model"] = *model; 325 } 326 if (partNumber != nullptr) 327 { 328 asyncResp->res.jsonValue["PartNumber"] = *partNumber; 329 } 330 if (serialNumber != nullptr) 331 { 332 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber; 333 } 334 if (sparePartNumber != nullptr && !sparePartNumber->empty()) 335 { 336 asyncResp->res.jsonValue["SparePartNumber"] = *sparePartNumber; 337 } 338 }); 339 } 340 341 inline void getFanLocation(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 342 const std::string& fanPath, 343 const std::string& service) 344 { 345 dbus::utility::getProperty<std::string>( 346 service, fanPath, 347 "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode", 348 [asyncResp](const boost::system::error_code& ec, 349 const std::string& property) { 350 if (ec) 351 { 352 if (ec.value() != EBADR) 353 { 354 BMCWEB_LOG_ERROR("DBUS response error for Location{}", 355 ec.value()); 356 messages::internalError(asyncResp->res); 357 } 358 return; 359 } 360 asyncResp->res 361 .jsonValue["Location"]["PartLocation"]["ServiceLabel"] = 362 property; 363 }); 364 } 365 366 inline void 367 afterGetValidFanPath(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 368 const std::string& chassisId, const std::string& fanId, 369 const std::string& fanPath, const std::string& service) 370 { 371 addFanCommonProperties(asyncResp->res, chassisId, fanId); 372 getFanState(asyncResp, fanPath, service); 373 getFanHealth(asyncResp, fanPath, service); 374 getFanAsset(asyncResp, fanPath, service); 375 getFanLocation(asyncResp, fanPath, service); 376 } 377 378 inline void doFanGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 379 const std::string& chassisId, const std::string& fanId, 380 const std::optional<std::string>& validChassisPath) 381 { 382 if (!validChassisPath) 383 { 384 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 385 return; 386 } 387 388 getValidFanPath( 389 asyncResp, *validChassisPath, fanId, 390 std::bind_front(afterGetValidFanPath, asyncResp, chassisId, fanId)); 391 } 392 393 inline void handleFanHead(App& app, const crow::Request& req, 394 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 395 const std::string& chassisId, 396 const std::string& fanId) 397 { 398 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 399 { 400 return; 401 } 402 403 redfish::chassis_utils::getValidChassisPath( 404 asyncResp, chassisId, 405 [asyncResp, chassisId, 406 fanId](const std::optional<std::string>& validChassisPath) { 407 if (!validChassisPath) 408 { 409 messages::resourceNotFound(asyncResp->res, "Chassis", 410 chassisId); 411 return; 412 } 413 getValidFanPath( 414 asyncResp, *validChassisPath, fanId, 415 [asyncResp](const std::string&, const std::string&) { 416 asyncResp->res.addHeader( 417 boost::beast::http::field::link, 418 "</redfish/v1/JsonSchemas/Fan/Fan.json>; rel=describedby"); 419 }); 420 }); 421 } 422 423 inline void handleFanGet(App& app, const crow::Request& req, 424 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 425 const std::string& chassisId, const std::string& fanId) 426 { 427 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 428 { 429 return; 430 } 431 432 redfish::chassis_utils::getValidChassisPath( 433 asyncResp, chassisId, 434 std::bind_front(doFanGet, asyncResp, chassisId, fanId)); 435 } 436 437 inline void requestRoutesFan(App& app) 438 { 439 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/") 440 .privileges(redfish::privileges::headFan) 441 .methods(boost::beast::http::verb::head)( 442 std::bind_front(handleFanHead, std::ref(app))); 443 444 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/") 445 .privileges(redfish::privileges::getFan) 446 .methods(boost::beast::http::verb::get)( 447 std::bind_front(handleFanGet, std::ref(app))); 448 } 449 450 } // namespace redfish 451