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 sdbusplus::asio::getProperty<bool>( 237 *crow::connections::systemBus, 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 sdbusplus::asio::getProperty<bool>( 263 *crow::connections::systemBus, service, fanPath, 264 "xyz.openbmc_project.Inventory.Item", "Present", 265 [asyncResp](const boost::system::error_code& ec, const bool value) { 266 if (ec) 267 { 268 if (ec.value() != EBADR) 269 { 270 BMCWEB_LOG_ERROR("DBUS response error for State {}", 271 ec.value()); 272 messages::internalError(asyncResp->res); 273 } 274 return; 275 } 276 277 if (!value) 278 { 279 asyncResp->res.jsonValue["Status"]["State"] = 280 resource::State::Absent; 281 } 282 }); 283 } 284 285 inline void getFanAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 286 const std::string& fanPath, const std::string& service) 287 { 288 sdbusplus::asio::getAllProperties( 289 *crow::connections::systemBus, service, fanPath, 290 "xyz.openbmc_project.Inventory.Decorator.Asset", 291 [fanPath, asyncResp{asyncResp}]( 292 const boost::system::error_code& ec, 293 const dbus::utility::DBusPropertiesMap& assetList) { 294 if (ec) 295 { 296 if (ec.value() != EBADR) 297 { 298 BMCWEB_LOG_ERROR("DBUS response error for Properties{}", 299 ec.value()); 300 messages::internalError(asyncResp->res); 301 } 302 return; 303 } 304 const std::string* manufacturer = nullptr; 305 const std::string* model = nullptr; 306 const std::string* partNumber = nullptr; 307 const std::string* serialNumber = nullptr; 308 const std::string* sparePartNumber = nullptr; 309 310 const bool success = sdbusplus::unpackPropertiesNoThrow( 311 dbus_utils::UnpackErrorPrinter(), assetList, "Manufacturer", 312 manufacturer, "Model", model, "PartNumber", partNumber, 313 "SerialNumber", serialNumber, "SparePartNumber", 314 sparePartNumber); 315 if (!success) 316 { 317 messages::internalError(asyncResp->res); 318 return; 319 } 320 if (manufacturer != nullptr) 321 { 322 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer; 323 } 324 if (model != nullptr) 325 { 326 asyncResp->res.jsonValue["Model"] = *model; 327 } 328 if (partNumber != nullptr) 329 { 330 asyncResp->res.jsonValue["PartNumber"] = *partNumber; 331 } 332 if (serialNumber != nullptr) 333 { 334 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber; 335 } 336 if (sparePartNumber != nullptr && !sparePartNumber->empty()) 337 { 338 asyncResp->res.jsonValue["SparePartNumber"] = *sparePartNumber; 339 } 340 }); 341 } 342 343 inline void getFanLocation(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 344 const std::string& fanPath, 345 const std::string& service) 346 { 347 sdbusplus::asio::getProperty<std::string>( 348 *crow::connections::systemBus, service, fanPath, 349 "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode", 350 [asyncResp](const boost::system::error_code& ec, 351 const std::string& property) { 352 if (ec) 353 { 354 if (ec.value() != EBADR) 355 { 356 BMCWEB_LOG_ERROR("DBUS response error for Location{}", 357 ec.value()); 358 messages::internalError(asyncResp->res); 359 } 360 return; 361 } 362 asyncResp->res 363 .jsonValue["Location"]["PartLocation"]["ServiceLabel"] = 364 property; 365 }); 366 } 367 368 inline void 369 afterGetValidFanPath(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 370 const std::string& chassisId, const std::string& fanId, 371 const std::string& fanPath, const std::string& service) 372 { 373 addFanCommonProperties(asyncResp->res, chassisId, fanId); 374 getFanState(asyncResp, fanPath, service); 375 getFanHealth(asyncResp, fanPath, service); 376 getFanAsset(asyncResp, fanPath, service); 377 getFanLocation(asyncResp, fanPath, service); 378 } 379 380 inline void doFanGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 381 const std::string& chassisId, const std::string& fanId, 382 const std::optional<std::string>& validChassisPath) 383 { 384 if (!validChassisPath) 385 { 386 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 387 return; 388 } 389 390 getValidFanPath( 391 asyncResp, *validChassisPath, fanId, 392 std::bind_front(afterGetValidFanPath, asyncResp, chassisId, fanId)); 393 } 394 395 inline void handleFanHead(App& app, const crow::Request& req, 396 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 397 const std::string& chassisId, 398 const std::string& fanId) 399 { 400 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 401 { 402 return; 403 } 404 405 redfish::chassis_utils::getValidChassisPath( 406 asyncResp, chassisId, 407 [asyncResp, chassisId, 408 fanId](const std::optional<std::string>& validChassisPath) { 409 if (!validChassisPath) 410 { 411 messages::resourceNotFound(asyncResp->res, "Chassis", 412 chassisId); 413 return; 414 } 415 getValidFanPath( 416 asyncResp, *validChassisPath, fanId, 417 [asyncResp](const std::string&, const std::string&) { 418 asyncResp->res.addHeader( 419 boost::beast::http::field::link, 420 "</redfish/v1/JsonSchemas/Fan/Fan.json>; rel=describedby"); 421 }); 422 }); 423 } 424 425 inline void handleFanGet(App& app, const crow::Request& req, 426 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 427 const std::string& chassisId, const std::string& fanId) 428 { 429 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 430 { 431 return; 432 } 433 434 redfish::chassis_utils::getValidChassisPath( 435 asyncResp, chassisId, 436 std::bind_front(doFanGet, asyncResp, chassisId, fanId)); 437 } 438 439 inline void requestRoutesFan(App& app) 440 { 441 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/") 442 .privileges(redfish::privileges::headFan) 443 .methods(boost::beast::http::verb::head)( 444 std::bind_front(handleFanHead, std::ref(app))); 445 446 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/") 447 .privileges(redfish::privileges::getFan) 448 .methods(boost::beast::http::verb::get)( 449 std::bind_front(handleFanGet, std::ref(app))); 450 } 451 452 } // namespace redfish 453