1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3 #pragma once 4 5 #include "app.hpp" 6 #include "dbus_utility.hpp" 7 #include "error_messages.hpp" 8 #include "generated/enums/resource.hpp" 9 #include "query.hpp" 10 #include "registries/privilege_registry.hpp" 11 #include "utils/chassis_utils.hpp" 12 13 #include <boost/system/error_code.hpp> 14 #include <boost/url/format.hpp> 15 #include <sdbusplus/asio/property.hpp> 16 #include <sdbusplus/message/types.hpp> 17 18 #include <functional> 19 #include <memory> 20 #include <optional> 21 #include <string> 22 #include <string_view> 23 24 namespace redfish 25 { 26 constexpr std::array<std::string_view, 1> fanInterface = { 27 "xyz.openbmc_project.Inventory.Item.Fan"}; 28 29 inline void 30 updateFanList(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 31 const std::string& chassisId, 32 const dbus::utility::MapperGetSubTreePathsResponse& fanPaths) 33 { 34 nlohmann::json& fanList = asyncResp->res.jsonValue["Members"]; 35 for (const std::string& fanPath : fanPaths) 36 { 37 std::string fanName = 38 sdbusplus::message::object_path(fanPath).filename(); 39 if (fanName.empty()) 40 { 41 continue; 42 } 43 44 nlohmann::json item = nlohmann::json::object(); 45 item["@odata.id"] = boost::urls::format( 46 "/redfish/v1/Chassis/{}/ThermalSubsystem/Fans/{}", chassisId, 47 fanName); 48 49 fanList.emplace_back(std::move(item)); 50 } 51 asyncResp->res.jsonValue["Members@odata.count"] = fanList.size(); 52 } 53 54 inline void getFanPaths( 55 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 56 const std::string& validChassisPath, 57 const std::function<void(const dbus::utility::MapperGetSubTreePathsResponse& 58 fanPaths)>& callback) 59 { 60 sdbusplus::message::object_path endpointPath{validChassisPath}; 61 endpointPath /= "cooled_by"; 62 63 dbus::utility::getAssociatedSubTreePaths( 64 endpointPath, 65 sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0, 66 fanInterface, 67 [asyncResp, callback]( 68 const boost::system::error_code& ec, 69 const dbus::utility::MapperGetSubTreePathsResponse& subtreePaths) { 70 if (ec) 71 { 72 if (ec.value() != EBADR) 73 { 74 BMCWEB_LOG_ERROR( 75 "DBUS response error for getAssociatedSubTreePaths {}", 76 ec.value()); 77 messages::internalError(asyncResp->res); 78 } 79 return; 80 } 81 callback(subtreePaths); 82 }); 83 } 84 85 inline void doFanCollection(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 86 const std::string& chassisId, 87 const std::optional<std::string>& validChassisPath) 88 { 89 if (!validChassisPath) 90 { 91 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 92 return; 93 } 94 95 asyncResp->res.addHeader( 96 boost::beast::http::field::link, 97 "</redfish/v1/JsonSchemas/FanCollection/FanCollection.json>; rel=describedby"); 98 asyncResp->res.jsonValue["@odata.type"] = "#FanCollection.FanCollection"; 99 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 100 "/redfish/v1/Chassis/{}/ThermalSubsystem/Fans", chassisId); 101 asyncResp->res.jsonValue["Name"] = "Fan Collection"; 102 asyncResp->res.jsonValue["Description"] = 103 "The collection of Fan resource instances " + chassisId; 104 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 105 asyncResp->res.jsonValue["Members@odata.count"] = 0; 106 107 getFanPaths(asyncResp, *validChassisPath, 108 std::bind_front(updateFanList, asyncResp, chassisId)); 109 } 110 111 inline void 112 handleFanCollectionHead(App& app, const crow::Request& req, 113 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 114 const std::string& chassisId) 115 { 116 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 117 { 118 return; 119 } 120 121 redfish::chassis_utils::getValidChassisPath( 122 asyncResp, chassisId, 123 [asyncResp, 124 chassisId](const std::optional<std::string>& validChassisPath) { 125 if (!validChassisPath) 126 { 127 messages::resourceNotFound(asyncResp->res, "Chassis", 128 chassisId); 129 return; 130 } 131 asyncResp->res.addHeader( 132 boost::beast::http::field::link, 133 "</redfish/v1/JsonSchemas/FanCollection/FanCollection.json>; rel=describedby"); 134 }); 135 } 136 137 inline void 138 handleFanCollectionGet(App& app, const crow::Request& req, 139 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 140 const std::string& chassisId) 141 { 142 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 143 { 144 return; 145 } 146 147 redfish::chassis_utils::getValidChassisPath( 148 asyncResp, chassisId, 149 std::bind_front(doFanCollection, asyncResp, chassisId)); 150 } 151 152 inline void requestRoutesFanCollection(App& app) 153 { 154 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/") 155 .privileges(redfish::privileges::headFanCollection) 156 .methods(boost::beast::http::verb::head)( 157 std::bind_front(handleFanCollectionHead, std::ref(app))); 158 159 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/") 160 .privileges(redfish::privileges::getFanCollection) 161 .methods(boost::beast::http::verb::get)( 162 std::bind_front(handleFanCollectionGet, std::ref(app))); 163 } 164 165 inline bool checkFanId(const std::string& fanPath, const std::string& fanId) 166 { 167 std::string fanName = sdbusplus::message::object_path(fanPath).filename(); 168 169 return !(fanName.empty() || fanName != fanId); 170 } 171 172 inline void handleFanPath( 173 const std::string& fanId, 174 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 175 const dbus::utility::MapperGetSubTreePathsResponse& fanPaths, 176 const std::function<void(const std::string& fanPath, 177 const std::string& service)>& callback) 178 { 179 for (const auto& fanPath : fanPaths) 180 { 181 if (!checkFanId(fanPath, fanId)) 182 { 183 continue; 184 } 185 dbus::utility::getDbusObject( 186 fanPath, fanInterface, 187 [fanPath, asyncResp, 188 callback](const boost::system::error_code& ec, 189 const dbus::utility::MapperGetObject& object) { 190 if (ec || object.empty()) 191 { 192 BMCWEB_LOG_ERROR("DBUS response error on getDbusObject {}", 193 ec.value()); 194 messages::internalError(asyncResp->res); 195 return; 196 } 197 callback(fanPath, object.begin()->first); 198 }); 199 200 return; 201 } 202 BMCWEB_LOG_WARNING("Fan not found {}", fanId); 203 messages::resourceNotFound(asyncResp->res, "Fan", fanId); 204 } 205 206 inline void getValidFanPath( 207 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 208 const std::string& validChassisPath, const std::string& fanId, 209 const std::function<void(const std::string& fanPath, 210 const std::string& service)>& callback) 211 { 212 getFanPaths( 213 asyncResp, validChassisPath, 214 [fanId, asyncResp, callback]( 215 const dbus::utility::MapperGetSubTreePathsResponse& fanPaths) { 216 handleFanPath(fanId, asyncResp, fanPaths, callback); 217 }); 218 } 219 220 inline void addFanCommonProperties(crow::Response& resp, 221 const std::string& chassisId, 222 const std::string& fanId) 223 { 224 resp.addHeader(boost::beast::http::field::link, 225 "</redfish/v1/JsonSchemas/Fan/Fan.json>; rel=describedby"); 226 resp.jsonValue["@odata.type"] = "#Fan.v1_3_0.Fan"; 227 resp.jsonValue["Name"] = "Fan"; 228 resp.jsonValue["Id"] = fanId; 229 resp.jsonValue["@odata.id"] = boost::urls::format( 230 "/redfish/v1/Chassis/{}/ThermalSubsystem/Fans/{}", chassisId, fanId); 231 resp.jsonValue["Status"]["State"] = resource::State::Enabled; 232 resp.jsonValue["Status"]["Health"] = resource::Health::OK; 233 } 234 235 inline void getFanHealth(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 236 const std::string& fanPath, const std::string& service) 237 { 238 dbus::utility::getProperty<bool>( 239 service, fanPath, 240 "xyz.openbmc_project.State.Decorator.OperationalStatus", "Functional", 241 [asyncResp](const boost::system::error_code& ec, const bool value) { 242 if (ec) 243 { 244 if (ec.value() != EBADR) 245 { 246 BMCWEB_LOG_ERROR("DBUS response error for Health {}", 247 ec.value()); 248 messages::internalError(asyncResp->res); 249 } 250 return; 251 } 252 253 if (!value) 254 { 255 asyncResp->res.jsonValue["Status"]["Health"] = 256 resource::Health::Critical; 257 } 258 }); 259 } 260 261 inline void getFanState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 262 const std::string& fanPath, const std::string& service) 263 { 264 dbus::utility::getProperty<bool>( 265 service, fanPath, "xyz.openbmc_project.Inventory.Item", "Present", 266 [asyncResp](const boost::system::error_code& ec, const bool value) { 267 if (ec) 268 { 269 if (ec.value() != EBADR) 270 { 271 BMCWEB_LOG_ERROR("DBUS response error for State {}", 272 ec.value()); 273 messages::internalError(asyncResp->res); 274 } 275 return; 276 } 277 278 if (!value) 279 { 280 asyncResp->res.jsonValue["Status"]["State"] = 281 resource::State::Absent; 282 } 283 }); 284 } 285 286 inline void getFanAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 287 const std::string& fanPath, const std::string& service) 288 { 289 dbus::utility::getAllProperties( 290 service, fanPath, "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 dbus::utility::getProperty<std::string>( 348 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