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