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