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", chassisId); 126 return; 127 } 128 asyncResp->res.addHeader( 129 boost::beast::http::field::link, 130 "</redfish/v1/JsonSchemas/FanCollection/FanCollection.json>; rel=describedby"); 131 }); 132 } 133 134 inline void 135 handleFanCollectionGet(App& app, const crow::Request& req, 136 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 137 const std::string& chassisId) 138 { 139 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 140 { 141 return; 142 } 143 144 redfish::chassis_utils::getValidChassisPath( 145 asyncResp, chassisId, 146 std::bind_front(doFanCollection, asyncResp, chassisId)); 147 } 148 149 inline void requestRoutesFanCollection(App& app) 150 { 151 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/") 152 .privileges(redfish::privileges::headFanCollection) 153 .methods(boost::beast::http::verb::head)( 154 std::bind_front(handleFanCollectionHead, std::ref(app))); 155 156 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/") 157 .privileges(redfish::privileges::getFanCollection) 158 .methods(boost::beast::http::verb::get)( 159 std::bind_front(handleFanCollectionGet, std::ref(app))); 160 } 161 162 inline bool checkFanId(const std::string& fanPath, const std::string& fanId) 163 { 164 std::string fanName = sdbusplus::message::object_path(fanPath).filename(); 165 166 return !(fanName.empty() || fanName != fanId); 167 } 168 169 static inline void handleFanPath( 170 const std::string& fanId, 171 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 172 const dbus::utility::MapperGetSubTreePathsResponse& fanPaths, 173 const std::function<void(const std::string& fanPath, 174 const std::string& service)>& callback) 175 { 176 for (const auto& fanPath : fanPaths) 177 { 178 if (!checkFanId(fanPath, fanId)) 179 { 180 continue; 181 } 182 dbus::utility::getDbusObject( 183 fanPath, fanInterface, 184 [fanPath, asyncResp, 185 callback](const boost::system::error_code& ec, 186 const dbus::utility::MapperGetObject& object) { 187 if (ec || object.empty()) 188 { 189 BMCWEB_LOG_ERROR("DBUS response error on getDbusObject {}", 190 ec.value()); 191 messages::internalError(asyncResp->res); 192 return; 193 } 194 callback(fanPath, object.begin()->first); 195 }); 196 197 return; 198 } 199 BMCWEB_LOG_WARNING("Fan not found {}", fanId); 200 messages::resourceNotFound(asyncResp->res, "Fan", fanId); 201 } 202 203 inline void getValidFanPath( 204 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 205 const std::string& validChassisPath, const std::string& fanId, 206 const std::function<void(const std::string& fanPath, 207 const std::string& service)>& callback) 208 { 209 getFanPaths( 210 asyncResp, validChassisPath, 211 [fanId, asyncResp, callback]( 212 const dbus::utility::MapperGetSubTreePathsResponse& fanPaths) { 213 handleFanPath(fanId, asyncResp, fanPaths, callback); 214 }); 215 } 216 217 inline void addFanCommonProperties(crow::Response& resp, 218 const std::string& chassisId, 219 const std::string& fanId) 220 { 221 resp.addHeader(boost::beast::http::field::link, 222 "</redfish/v1/JsonSchemas/Fan/Fan.json>; rel=describedby"); 223 resp.jsonValue["@odata.type"] = "#Fan.v1_3_0.Fan"; 224 resp.jsonValue["Name"] = "Fan"; 225 resp.jsonValue["Id"] = fanId; 226 resp.jsonValue["@odata.id"] = boost::urls::format( 227 "/redfish/v1/Chassis/{}/ThermalSubsystem/Fans/{}", chassisId, fanId); 228 resp.jsonValue["Status"]["State"] = resource::State::Enabled; 229 resp.jsonValue["Status"]["Health"] = resource::Health::OK; 230 } 231 232 inline void getFanHealth(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 233 const std::string& fanPath, const std::string& service) 234 { 235 sdbusplus::asio::getProperty<bool>( 236 *crow::connections::systemBus, service, fanPath, 237 "xyz.openbmc_project.State.Decorator.OperationalStatus", "Functional", 238 [asyncResp](const boost::system::error_code& ec, const bool value) { 239 if (ec) 240 { 241 if (ec.value() != EBADR) 242 { 243 BMCWEB_LOG_ERROR("DBUS response error for Health {}", 244 ec.value()); 245 messages::internalError(asyncResp->res); 246 } 247 return; 248 } 249 250 if (!value) 251 { 252 asyncResp->res.jsonValue["Status"]["Health"] = 253 resource::Health::Critical; 254 } 255 }); 256 } 257 258 inline void getFanState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 259 const std::string& fanPath, const std::string& service) 260 { 261 sdbusplus::asio::getProperty<bool>( 262 *crow::connections::systemBus, service, fanPath, 263 "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 sdbusplus::asio::getAllProperties( 288 *crow::connections::systemBus, service, fanPath, 289 "xyz.openbmc_project.Inventory.Decorator.Asset", 290 [fanPath, asyncResp{asyncResp}]( 291 const boost::system::error_code& ec, 292 const dbus::utility::DBusPropertiesMap& assetList) { 293 if (ec) 294 { 295 if (ec.value() != EBADR) 296 { 297 BMCWEB_LOG_ERROR("DBUS response error for Properties{}", 298 ec.value()); 299 messages::internalError(asyncResp->res); 300 } 301 return; 302 } 303 const std::string* manufacturer = nullptr; 304 const std::string* model = nullptr; 305 const std::string* partNumber = nullptr; 306 const std::string* serialNumber = nullptr; 307 const std::string* sparePartNumber = nullptr; 308 309 const bool success = sdbusplus::unpackPropertiesNoThrow( 310 dbus_utils::UnpackErrorPrinter(), assetList, "Manufacturer", 311 manufacturer, "Model", model, "PartNumber", partNumber, 312 "SerialNumber", serialNumber, "SparePartNumber", 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 sdbusplus::asio::getProperty<std::string>( 346 *crow::connections::systemBus, 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.jsonValue["Location"]["PartLocation"]["ServiceLabel"] = 361 property; 362 }); 363 } 364 365 inline void 366 afterGetValidFanPath(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 367 const std::string& chassisId, const std::string& fanId, 368 const std::string& fanPath, const std::string& service) 369 { 370 addFanCommonProperties(asyncResp->res, chassisId, fanId); 371 getFanState(asyncResp, fanPath, service); 372 getFanHealth(asyncResp, fanPath, service); 373 getFanAsset(asyncResp, fanPath, service); 374 getFanLocation(asyncResp, fanPath, service); 375 } 376 377 inline void doFanGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 378 const std::string& chassisId, const std::string& fanId, 379 const std::optional<std::string>& validChassisPath) 380 { 381 if (!validChassisPath) 382 { 383 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 384 return; 385 } 386 387 getValidFanPath( 388 asyncResp, *validChassisPath, fanId, 389 std::bind_front(afterGetValidFanPath, asyncResp, chassisId, fanId)); 390 } 391 392 inline void handleFanHead(App& app, const crow::Request& req, 393 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 394 const std::string& chassisId, 395 const std::string& fanId) 396 { 397 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 398 { 399 return; 400 } 401 402 redfish::chassis_utils::getValidChassisPath( 403 asyncResp, chassisId, 404 [asyncResp, chassisId, 405 fanId](const std::optional<std::string>& validChassisPath) { 406 if (!validChassisPath) 407 { 408 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 409 return; 410 } 411 getValidFanPath(asyncResp, *validChassisPath, fanId, 412 [asyncResp](const std::string&, const std::string&) { 413 asyncResp->res.addHeader( 414 boost::beast::http::field::link, 415 "</redfish/v1/JsonSchemas/Fan/Fan.json>; rel=describedby"); 416 }); 417 }); 418 } 419 420 inline void handleFanGet(App& app, const crow::Request& req, 421 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 422 const std::string& chassisId, const std::string& fanId) 423 { 424 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 425 { 426 return; 427 } 428 429 redfish::chassis_utils::getValidChassisPath( 430 asyncResp, chassisId, 431 std::bind_front(doFanGet, asyncResp, chassisId, fanId)); 432 } 433 434 inline void requestRoutesFan(App& app) 435 { 436 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/") 437 .privileges(redfish::privileges::headFan) 438 .methods(boost::beast::http::verb::head)( 439 std::bind_front(handleFanHead, std::ref(app))); 440 441 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/") 442 .privileges(redfish::privileges::getFan) 443 .methods(boost::beast::http::verb::get)( 444 std::bind_front(handleFanGet, std::ref(app))); 445 } 446 447 } // namespace redfish 448