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