1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3 // SPDX-FileCopyrightText: Copyright 2019 Intel Corporation 4 #pragma once 5 6 #include "bmcweb_config.h" 7 8 #include "app.hpp" 9 #include "async_resp.hpp" 10 #include "dbus_utility.hpp" 11 #include "error_messages.hpp" 12 #include "generated/enums/drive.hpp" 13 #include "generated/enums/protocol.hpp" 14 #include "generated/enums/resource.hpp" 15 #include "http_request.hpp" 16 #include "human_sort.hpp" 17 #include "logging.hpp" 18 #include "query.hpp" 19 #include "redfish_util.hpp" 20 #include "registries/privilege_registry.hpp" 21 #include "utils/chassis_utils.hpp" 22 #include "utils/collection.hpp" 23 #include "utils/dbus_utils.hpp" 24 25 #include <boost/beast/http/verb.hpp> 26 #include <boost/system/error_code.hpp> 27 #include <boost/url/format.hpp> 28 #include <sdbusplus/message/native_types.hpp> 29 #include <sdbusplus/unpack_properties.hpp> 30 31 #include <algorithm> 32 #include <array> 33 #include <cstdint> 34 #include <format> 35 #include <functional> 36 #include <memory> 37 #include <optional> 38 #include <ranges> 39 #include <string> 40 #include <string_view> 41 #include <utility> 42 #include <variant> 43 #include <vector> 44 45 namespace redfish 46 { 47 48 inline void handleSystemsStorageCollectionGet( 49 App& app, const crow::Request& req, 50 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 51 const std::string& systemName) 52 { 53 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 54 { 55 return; 56 } 57 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 58 { 59 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 60 systemName); 61 return; 62 } 63 64 asyncResp->res.jsonValue["@odata.type"] = 65 "#StorageCollection.StorageCollection"; 66 asyncResp->res.jsonValue["@odata.id"] = std::format( 67 "/redfish/v1/Systems/{}/Storage", BMCWEB_REDFISH_SYSTEM_URI_NAME); 68 asyncResp->res.jsonValue["Name"] = "Storage Collection"; 69 70 constexpr std::array<std::string_view, 1> interface{ 71 "xyz.openbmc_project.Inventory.Item.Storage"}; 72 collection_util::getCollectionMembers( 73 asyncResp, 74 boost::urls::format("/redfish/v1/Systems/{}/Storage", 75 BMCWEB_REDFISH_SYSTEM_URI_NAME), 76 interface, "/xyz/openbmc_project/inventory"); 77 } 78 79 inline void handleStorageCollectionGet( 80 App& app, const crow::Request& req, 81 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 82 { 83 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 84 { 85 return; 86 } 87 asyncResp->res.jsonValue["@odata.type"] = 88 "#StorageCollection.StorageCollection"; 89 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Storage"; 90 asyncResp->res.jsonValue["Name"] = "Storage Collection"; 91 constexpr std::array<std::string_view, 1> interface{ 92 "xyz.openbmc_project.Inventory.Item.Storage"}; 93 collection_util::getCollectionMembers( 94 asyncResp, boost::urls::format("/redfish/v1/Storage"), interface, 95 "/xyz/openbmc_project/inventory"); 96 } 97 98 inline void requestRoutesStorageCollection(App& app) 99 { 100 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/") 101 .privileges(redfish::privileges::getStorageCollection) 102 .methods(boost::beast::http::verb::get)( 103 std::bind_front(handleSystemsStorageCollectionGet, std::ref(app))); 104 BMCWEB_ROUTE(app, "/redfish/v1/Storage/") 105 .privileges(redfish::privileges::getStorageCollection) 106 .methods(boost::beast::http::verb::get)( 107 std::bind_front(handleStorageCollectionGet, std::ref(app))); 108 } 109 110 inline void afterChassisDriveCollectionSubtree( 111 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 112 const boost::system::error_code& ec, 113 const dbus::utility::MapperGetSubTreePathsResponse& driveList) 114 { 115 if (ec) 116 { 117 BMCWEB_LOG_ERROR("Drive mapper call error"); 118 messages::internalError(asyncResp->res); 119 return; 120 } 121 122 nlohmann::json& driveArray = asyncResp->res.jsonValue["Drives"]; 123 driveArray = nlohmann::json::array(); 124 auto& count = asyncResp->res.jsonValue["Drives@odata.count"]; 125 count = 0; 126 127 for (const std::string& drive : driveList) 128 { 129 sdbusplus::message::object_path object(drive); 130 if (object.filename().empty()) 131 { 132 BMCWEB_LOG_ERROR("Failed to find filename in {}", drive); 133 return; 134 } 135 136 nlohmann::json::object_t driveJson; 137 driveJson["@odata.id"] = boost::urls::format( 138 "/redfish/v1/Systems/{}/Storage/1/Drives/{}", 139 BMCWEB_REDFISH_SYSTEM_URI_NAME, object.filename()); 140 driveArray.emplace_back(std::move(driveJson)); 141 } 142 143 count = driveArray.size(); 144 } 145 inline void getDrives(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 146 { 147 const std::array<std::string_view, 1> interfaces = { 148 "xyz.openbmc_project.Inventory.Item.Drive"}; 149 dbus::utility::getSubTreePaths( 150 "/xyz/openbmc_project/inventory", 0, interfaces, 151 std::bind_front(afterChassisDriveCollectionSubtree, asyncResp)); 152 } 153 154 inline void afterSystemsStorageGetSubtree( 155 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 156 const std::string& storageId, const boost::system::error_code& ec, 157 const dbus::utility::MapperGetSubTreeResponse& subtree) 158 { 159 if (ec) 160 { 161 BMCWEB_LOG_DEBUG("requestRoutesStorage DBUS response error"); 162 messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage", 163 storageId); 164 return; 165 } 166 auto storage = std::ranges::find_if( 167 subtree, 168 [&storageId](const std::pair<std::string, 169 dbus::utility::MapperServiceMap>& object) { 170 return sdbusplus::message::object_path(object.first).filename() == 171 storageId; 172 }); 173 if (storage == subtree.end()) 174 { 175 messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage", 176 storageId); 177 return; 178 } 179 180 asyncResp->res.jsonValue["@odata.type"] = "#Storage.v1_13_0.Storage"; 181 asyncResp->res.jsonValue["@odata.id"] = 182 boost::urls::format("/redfish/v1/Systems/{}/Storage/{}", 183 BMCWEB_REDFISH_SYSTEM_URI_NAME, storageId); 184 asyncResp->res.jsonValue["Name"] = "Storage"; 185 asyncResp->res.jsonValue["Id"] = storageId; 186 asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled; 187 188 getDrives(asyncResp); 189 asyncResp->res.jsonValue["Controllers"]["@odata.id"] = 190 boost::urls::format("/redfish/v1/Systems/{}/Storage/{}/Controllers", 191 BMCWEB_REDFISH_SYSTEM_URI_NAME, storageId); 192 } 193 194 inline void handleSystemsStorageGet( 195 App& app, const crow::Request& req, 196 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 197 const std::string& systemName, const std::string& storageId) 198 { 199 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 200 { 201 return; 202 } 203 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 204 { 205 // Option currently returns no systems. TBD 206 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 207 systemName); 208 return; 209 } 210 211 constexpr std::array<std::string_view, 1> interfaces = { 212 "xyz.openbmc_project.Inventory.Item.Storage"}; 213 dbus::utility::getSubTree( 214 "/xyz/openbmc_project/inventory", 0, interfaces, 215 std::bind_front(afterSystemsStorageGetSubtree, asyncResp, storageId)); 216 } 217 218 inline void afterSubtree(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 219 const std::string& storageId, 220 const boost::system::error_code& ec, 221 const dbus::utility::MapperGetSubTreeResponse& subtree) 222 { 223 if (ec) 224 { 225 BMCWEB_LOG_DEBUG("requestRoutesStorage DBUS response error"); 226 messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage", 227 storageId); 228 return; 229 } 230 auto storage = std::ranges::find_if( 231 subtree, 232 [&storageId](const std::pair<std::string, 233 dbus::utility::MapperServiceMap>& object) { 234 return sdbusplus::message::object_path(object.first).filename() == 235 storageId; 236 }); 237 if (storage == subtree.end()) 238 { 239 messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage", 240 storageId); 241 return; 242 } 243 244 asyncResp->res.jsonValue["@odata.type"] = "#Storage.v1_13_0.Storage"; 245 asyncResp->res.jsonValue["@odata.id"] = 246 boost::urls::format("/redfish/v1/Storage/{}", storageId); 247 asyncResp->res.jsonValue["Name"] = "Storage"; 248 asyncResp->res.jsonValue["Id"] = storageId; 249 asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled; 250 251 // Storage subsystem to Storage link. 252 nlohmann::json::array_t storageServices; 253 nlohmann::json::object_t storageService; 254 storageService["@odata.id"] = 255 boost::urls::format("/redfish/v1/Systems/{}/Storage/{}", 256 BMCWEB_REDFISH_SYSTEM_URI_NAME, storageId); 257 storageServices.emplace_back(storageService); 258 asyncResp->res.jsonValue["Links"]["StorageServices"] = 259 std::move(storageServices); 260 asyncResp->res.jsonValue["Links"]["StorageServices@odata.count"] = 1; 261 } 262 263 inline void handleStorageGet( 264 App& app, const crow::Request& req, 265 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 266 const std::string& storageId) 267 { 268 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 269 { 270 BMCWEB_LOG_DEBUG("requestRoutesStorage setUpRedfishRoute failed"); 271 return; 272 } 273 274 constexpr std::array<std::string_view, 1> interfaces = { 275 "xyz.openbmc_project.Inventory.Item.Storage"}; 276 dbus::utility::getSubTree( 277 "/xyz/openbmc_project/inventory", 0, interfaces, 278 std::bind_front(afterSubtree, asyncResp, storageId)); 279 } 280 281 inline void requestRoutesStorage(App& app) 282 { 283 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/<str>/") 284 .privileges(redfish::privileges::getStorage) 285 .methods(boost::beast::http::verb::get)( 286 std::bind_front(handleSystemsStorageGet, std::ref(app))); 287 288 BMCWEB_ROUTE(app, "/redfish/v1/Storage/<str>/") 289 .privileges(redfish::privileges::getStorage) 290 .methods(boost::beast::http::verb::get)( 291 std::bind_front(handleStorageGet, std::ref(app))); 292 } 293 294 inline void getDrivePresent(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 295 const std::string& connectionName, 296 const std::string& path) 297 { 298 dbus::utility::getProperty<bool>( 299 connectionName, path, "xyz.openbmc_project.Inventory.Item", "Present", 300 [asyncResp, 301 path](const boost::system::error_code& ec, const bool isPresent) { 302 // this interface isn't necessary, only check it if 303 // we get a good return 304 if (ec) 305 { 306 return; 307 } 308 309 if (!isPresent) 310 { 311 asyncResp->res.jsonValue["Status"]["State"] = 312 resource::State::Absent; 313 } 314 }); 315 } 316 317 inline void getDriveState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 318 const std::string& connectionName, 319 const std::string& path) 320 { 321 dbus::utility::getProperty<bool>( 322 connectionName, path, "xyz.openbmc_project.State.Drive", "Rebuilding", 323 [asyncResp](const boost::system::error_code& ec, const bool updating) { 324 // this interface isn't necessary, only check it 325 // if we get a good return 326 if (ec) 327 { 328 return; 329 } 330 331 // updating and disabled in the backend shouldn't be 332 // able to be set at the same time, so we don't need 333 // to check for the race condition of these two 334 // calls 335 if (updating) 336 { 337 asyncResp->res.jsonValue["Status"]["State"] = 338 resource::State::Updating; 339 } 340 }); 341 } 342 343 inline std::optional<drive::MediaType> convertDriveType(std::string_view type) 344 { 345 if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.HDD") 346 { 347 return drive::MediaType::HDD; 348 } 349 if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.SSD") 350 { 351 return drive::MediaType::SSD; 352 } 353 if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.Unknown") 354 { 355 return std::nullopt; 356 } 357 358 return drive::MediaType::Invalid; 359 } 360 361 inline std::optional<protocol::Protocol> convertDriveProtocol( 362 std::string_view proto) 363 { 364 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SAS") 365 { 366 return protocol::Protocol::SAS; 367 } 368 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SATA") 369 { 370 return protocol::Protocol::SATA; 371 } 372 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.NVMe") 373 { 374 return protocol::Protocol::NVMe; 375 } 376 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.FC") 377 { 378 return protocol::Protocol::FC; 379 } 380 if (proto == 381 "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.Unknown") 382 { 383 return std::nullopt; 384 } 385 386 return protocol::Protocol::Invalid; 387 } 388 389 inline void getDriveItemProperties( 390 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 391 const std::string& connectionName, const std::string& path) 392 { 393 dbus::utility::getAllProperties( 394 connectionName, path, "xyz.openbmc_project.Inventory.Item.Drive", 395 [asyncResp](const boost::system::error_code& ec, 396 const std::vector< 397 std::pair<std::string, dbus::utility::DbusVariantType>>& 398 propertiesList) { 399 if (ec) 400 { 401 // this interface isn't required 402 return; 403 } 404 const std::string* encryptionStatus = nullptr; 405 const bool* isLocked = nullptr; 406 for (const std::pair<std::string, dbus::utility::DbusVariantType>& 407 property : propertiesList) 408 { 409 const std::string& propertyName = property.first; 410 if (propertyName == "Type") 411 { 412 const std::string* value = 413 std::get_if<std::string>(&property.second); 414 if (value == nullptr) 415 { 416 // illegal property 417 BMCWEB_LOG_ERROR("Illegal property: Type"); 418 messages::internalError(asyncResp->res); 419 return; 420 } 421 422 std::optional<drive::MediaType> mediaType = 423 convertDriveType(*value); 424 if (!mediaType) 425 { 426 BMCWEB_LOG_WARNING("UnknownDriveType Interface: {}", 427 *value); 428 continue; 429 } 430 if (*mediaType == drive::MediaType::Invalid) 431 { 432 messages::internalError(asyncResp->res); 433 return; 434 } 435 436 asyncResp->res.jsonValue["MediaType"] = *mediaType; 437 } 438 else if (propertyName == "Capacity") 439 { 440 const uint64_t* capacity = 441 std::get_if<uint64_t>(&property.second); 442 if (capacity == nullptr) 443 { 444 BMCWEB_LOG_ERROR("Illegal property: Capacity"); 445 messages::internalError(asyncResp->res); 446 return; 447 } 448 if (*capacity == 0) 449 { 450 // drive capacity not known 451 continue; 452 } 453 454 asyncResp->res.jsonValue["CapacityBytes"] = *capacity; 455 } 456 else if (propertyName == "Protocol") 457 { 458 const std::string* value = 459 std::get_if<std::string>(&property.second); 460 if (value == nullptr) 461 { 462 BMCWEB_LOG_ERROR("Illegal property: Protocol"); 463 messages::internalError(asyncResp->res); 464 return; 465 } 466 467 std::optional<protocol::Protocol> proto = 468 convertDriveProtocol(*value); 469 if (!proto) 470 { 471 BMCWEB_LOG_WARNING( 472 "Unknown DrivePrototype Interface: {}", *value); 473 continue; 474 } 475 if (*proto == protocol::Protocol::Invalid) 476 { 477 messages::internalError(asyncResp->res); 478 return; 479 } 480 asyncResp->res.jsonValue["Protocol"] = *proto; 481 } 482 else if (propertyName == "PredictedMediaLifeLeftPercent") 483 { 484 const uint8_t* lifeLeft = 485 std::get_if<uint8_t>(&property.second); 486 if (lifeLeft == nullptr) 487 { 488 BMCWEB_LOG_ERROR( 489 "Illegal property: PredictedMediaLifeLeftPercent"); 490 messages::internalError(asyncResp->res); 491 return; 492 } 493 // 255 means reading the value is not supported 494 if (*lifeLeft != 255) 495 { 496 asyncResp->res 497 .jsonValue["PredictedMediaLifeLeftPercent"] = 498 *lifeLeft; 499 } 500 } 501 else if (propertyName == "EncryptionStatus") 502 { 503 encryptionStatus = 504 std::get_if<std::string>(&property.second); 505 if (encryptionStatus == nullptr) 506 { 507 BMCWEB_LOG_ERROR("Illegal property: EncryptionStatus"); 508 messages::internalError(asyncResp->res); 509 return; 510 } 511 } 512 else if (propertyName == "Locked") 513 { 514 isLocked = std::get_if<bool>(&property.second); 515 if (isLocked == nullptr) 516 { 517 BMCWEB_LOG_ERROR("Illegal property: Locked"); 518 messages::internalError(asyncResp->res); 519 return; 520 } 521 } 522 } 523 524 if (encryptionStatus == nullptr || isLocked == nullptr || 525 *encryptionStatus == 526 "xyz.openbmc_project.Inventory.Item.Drive.DriveEncryptionState.Unknown") 527 { 528 return; 529 } 530 if (*encryptionStatus != 531 "xyz.openbmc_project.Inventory.Item.Drive.DriveEncryptionState.Encrypted") 532 { 533 //"The drive is not currently encrypted." 534 asyncResp->res.jsonValue["EncryptionStatus"] = 535 drive::EncryptionStatus::Unencrypted; 536 return; 537 } 538 if (*isLocked) 539 { 540 //"The drive is currently encrypted and the data is not 541 // accessible to the user." 542 asyncResp->res.jsonValue["EncryptionStatus"] = 543 drive::EncryptionStatus::Locked; 544 return; 545 } 546 // if not locked 547 // "The drive is currently encrypted but the data is accessible 548 // to the user in unencrypted form." 549 asyncResp->res.jsonValue["EncryptionStatus"] = 550 drive::EncryptionStatus::Unlocked; 551 }); 552 } 553 554 inline void addAllDriveInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 555 const std::string& connectionName, 556 const std::string& path, 557 const std::vector<std::string>& interfaces) 558 { 559 for (const std::string& interface : interfaces) 560 { 561 if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset") 562 { 563 asset_utils::getAssetInfo(asyncResp, connectionName, path, 564 ""_json_pointer, false); 565 } 566 else if (interface == "xyz.openbmc_project.Inventory.Item") 567 { 568 getDrivePresent(asyncResp, connectionName, path); 569 } 570 else if (interface == "xyz.openbmc_project.State.Drive") 571 { 572 getDriveState(asyncResp, connectionName, path); 573 } 574 else if (interface == "xyz.openbmc_project.Inventory.Item.Drive") 575 { 576 getDriveItemProperties(asyncResp, connectionName, path); 577 } 578 } 579 } 580 581 inline void afterGetSubtreeSystemsStorageDrive( 582 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 583 const std::string& driveId, const boost::system::error_code& ec, 584 const dbus::utility::MapperGetSubTreeResponse& subtree) 585 { 586 if (ec) 587 { 588 BMCWEB_LOG_ERROR("Drive mapper call error"); 589 messages::internalError(asyncResp->res); 590 return; 591 } 592 593 auto drive = std::ranges::find_if( 594 subtree, 595 [&driveId](const std::pair<std::string, 596 dbus::utility::MapperServiceMap>& object) { 597 return sdbusplus::message::object_path(object.first).filename() == 598 driveId; 599 }); 600 601 if (drive == subtree.end()) 602 { 603 messages::resourceNotFound(asyncResp->res, "Drive", driveId); 604 return; 605 } 606 607 const std::string& path = drive->first; 608 const dbus::utility::MapperServiceMap& connectionNames = drive->second; 609 610 asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive"; 611 asyncResp->res.jsonValue["@odata.id"] = 612 boost::urls::format("/redfish/v1/Systems/{}/Storage/1/Drives/{}", 613 BMCWEB_REDFISH_SYSTEM_URI_NAME, driveId); 614 asyncResp->res.jsonValue["Name"] = driveId; 615 asyncResp->res.jsonValue["Id"] = driveId; 616 617 if (connectionNames.size() != 1) 618 { 619 BMCWEB_LOG_ERROR("Connection size {}, not equal to 1", 620 connectionNames.size()); 621 messages::internalError(asyncResp->res); 622 return; 623 } 624 625 getMainChassisId( 626 asyncResp, [](const std::string& chassisId, 627 const std::shared_ptr<bmcweb::AsyncResp>& aRsp) { 628 aRsp->res.jsonValue["Links"]["Chassis"]["@odata.id"] = 629 boost::urls::format("/redfish/v1/Chassis/{}", chassisId); 630 }); 631 632 // default it to Enabled 633 asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled; 634 635 addAllDriveInfo(asyncResp, connectionNames[0].first, path, 636 connectionNames[0].second); 637 } 638 639 inline void handleSystemsStorageDriveGet( 640 App& app, const crow::Request& req, 641 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 642 const std::string& systemName, const std::string& driveId) 643 { 644 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 645 { 646 return; 647 } 648 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 649 { 650 // Option currently returns no systems. TBD 651 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 652 systemName); 653 return; 654 } 655 656 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 657 { 658 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 659 systemName); 660 return; 661 } 662 663 constexpr std::array<std::string_view, 1> interfaces = { 664 "xyz.openbmc_project.Inventory.Item.Drive"}; 665 dbus::utility::getSubTree( 666 "/xyz/openbmc_project/inventory", 0, interfaces, 667 std::bind_front(afterGetSubtreeSystemsStorageDrive, asyncResp, 668 driveId)); 669 } 670 671 inline void requestRoutesDrive(App& app) 672 { 673 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Drives/<str>/") 674 .privileges(redfish::privileges::getDrive) 675 .methods(boost::beast::http::verb::get)( 676 std::bind_front(handleSystemsStorageDriveGet, std::ref(app))); 677 } 678 679 inline void afterChassisDriveCollectionSubtreeGet( 680 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 681 const std::string& chassisId, const boost::system::error_code& ec, 682 const dbus::utility::MapperGetSubTreeResponse& subtree) 683 { 684 if (ec) 685 { 686 if (ec == boost::system::errc::host_unreachable) 687 { 688 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 689 return; 690 } 691 messages::internalError(asyncResp->res); 692 return; 693 } 694 695 // Iterate over all retrieved ObjectPaths. 696 for (const auto& [path, connectionNames] : subtree) 697 { 698 sdbusplus::message::object_path objPath(path); 699 if (objPath.filename() != chassisId) 700 { 701 continue; 702 } 703 704 if (connectionNames.empty()) 705 { 706 BMCWEB_LOG_ERROR("Got 0 Connection names"); 707 continue; 708 } 709 710 asyncResp->res.jsonValue["@odata.type"] = 711 "#DriveCollection.DriveCollection"; 712 asyncResp->res.jsonValue["@odata.id"] = 713 boost::urls::format("/redfish/v1/Chassis/{}/Drives", chassisId); 714 asyncResp->res.jsonValue["Name"] = "Drive Collection"; 715 716 // Association lambda 717 dbus::utility::getAssociationEndPoints( 718 path + "/drive", 719 [asyncResp, chassisId](const boost::system::error_code& ec3, 720 const dbus::utility::MapperEndPoints& resp) { 721 if (ec3) 722 { 723 BMCWEB_LOG_ERROR("Error in chassis Drive association "); 724 } 725 nlohmann::json& members = asyncResp->res.jsonValue["Members"]; 726 // important if array is empty 727 members = nlohmann::json::array(); 728 729 std::vector<std::string> leafNames; 730 for (const auto& drive : resp) 731 { 732 sdbusplus::message::object_path drivePath(drive); 733 leafNames.push_back(drivePath.filename()); 734 } 735 736 std::ranges::sort(leafNames, AlphanumLess<std::string>()); 737 738 for (const auto& leafName : leafNames) 739 { 740 nlohmann::json::object_t member; 741 member["@odata.id"] = 742 boost::urls::format("/redfish/v1/Chassis/{}/Drives/{}", 743 chassisId, leafName); 744 members.emplace_back(std::move(member)); 745 // navigation links will be registered in next patch set 746 } 747 asyncResp->res.jsonValue["Members@odata.count"] = resp.size(); 748 }); // end association lambda 749 750 } // end Iterate over all retrieved ObjectPaths 751 } 752 /** 753 * Chassis drives, this URL will show all the DriveCollection 754 * information 755 */ 756 inline void chassisDriveCollectionGet( 757 crow::App& app, const crow::Request& req, 758 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 759 const std::string& chassisId) 760 { 761 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 762 { 763 return; 764 } 765 766 // mapper call lambda 767 dbus::utility::getSubTree( 768 "/xyz/openbmc_project/inventory", 0, chassisInterfaces, 769 std::bind_front(afterChassisDriveCollectionSubtreeGet, asyncResp, 770 chassisId)); 771 } 772 773 inline void requestRoutesChassisDrive(App& app) 774 { 775 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/") 776 .privileges(redfish::privileges::getDriveCollection) 777 .methods(boost::beast::http::verb::get)( 778 std::bind_front(chassisDriveCollectionGet, std::ref(app))); 779 } 780 781 inline void buildDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 782 const std::string& chassisId, 783 const std::string& driveName, 784 const boost::system::error_code& ec, 785 const dbus::utility::MapperGetSubTreeResponse& subtree) 786 { 787 if (ec) 788 { 789 BMCWEB_LOG_DEBUG("DBUS response error {}", ec); 790 messages::internalError(asyncResp->res); 791 return; 792 } 793 794 // Iterate over all retrieved ObjectPaths. 795 for (const auto& [path, connectionNames] : subtree) 796 { 797 sdbusplus::message::object_path objPath(path); 798 if (objPath.filename() != driveName) 799 { 800 continue; 801 } 802 803 if (connectionNames.empty()) 804 { 805 BMCWEB_LOG_ERROR("Got 0 Connection names"); 806 continue; 807 } 808 809 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 810 "/redfish/v1/Chassis/{}/Drives/{}", chassisId, driveName); 811 812 asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive"; 813 asyncResp->res.jsonValue["Name"] = driveName; 814 asyncResp->res.jsonValue["Id"] = driveName; 815 // default it to Enabled 816 asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled; 817 818 nlohmann::json::object_t linkChassisNav; 819 linkChassisNav["@odata.id"] = 820 boost::urls::format("/redfish/v1/Chassis/{}", chassisId); 821 asyncResp->res.jsonValue["Links"]["Chassis"] = linkChassisNav; 822 823 addAllDriveInfo(asyncResp, connectionNames[0].first, path, 824 connectionNames[0].second); 825 } 826 } 827 828 inline void matchAndFillDrive( 829 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 830 const std::string& chassisId, const std::string& driveName, 831 const std::vector<std::string>& resp) 832 { 833 for (const std::string& drivePath : resp) 834 { 835 sdbusplus::message::object_path path(drivePath); 836 std::string leaf = path.filename(); 837 if (leaf != driveName) 838 { 839 continue; 840 } 841 // mapper call drive 842 constexpr std::array<std::string_view, 1> driveInterface = { 843 "xyz.openbmc_project.Inventory.Item.Drive"}; 844 dbus::utility::getSubTree( 845 "/xyz/openbmc_project/inventory", 0, driveInterface, 846 [asyncResp, chassisId, driveName]( 847 const boost::system::error_code& ec, 848 const dbus::utility::MapperGetSubTreeResponse& subtree) { 849 buildDrive(asyncResp, chassisId, driveName, ec, subtree); 850 }); 851 } 852 } 853 854 inline void handleChassisDriveGet( 855 crow::App& app, const crow::Request& req, 856 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 857 const std::string& chassisId, const std::string& driveName) 858 { 859 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 860 { 861 return; 862 } 863 864 // mapper call chassis 865 dbus::utility::getSubTree( 866 "/xyz/openbmc_project/inventory", 0, chassisInterfaces, 867 [asyncResp, chassisId, 868 driveName](const boost::system::error_code& ec, 869 const dbus::utility::MapperGetSubTreeResponse& subtree) { 870 if (ec) 871 { 872 messages::internalError(asyncResp->res); 873 return; 874 } 875 876 // Iterate over all retrieved ObjectPaths. 877 for (const auto& [path, connectionNames] : subtree) 878 { 879 sdbusplus::message::object_path objPath(path); 880 if (objPath.filename() != chassisId) 881 { 882 continue; 883 } 884 885 if (connectionNames.empty()) 886 { 887 BMCWEB_LOG_ERROR("Got 0 Connection names"); 888 continue; 889 } 890 891 dbus::utility::getAssociationEndPoints( 892 path + "/drive", 893 [asyncResp, chassisId, 894 driveName](const boost::system::error_code& ec3, 895 const dbus::utility::MapperEndPoints& resp) { 896 if (ec3) 897 { 898 return; // no drives = no failures 899 } 900 matchAndFillDrive(asyncResp, chassisId, driveName, 901 resp); 902 }); 903 return; 904 } 905 // Couldn't find an object with that name. return an error 906 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 907 }); 908 } 909 910 /** 911 * This URL will show the drive interface for the specific drive in the chassis 912 */ 913 inline void requestRoutesChassisDriveName(App& app) 914 { 915 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/<str>/") 916 .privileges(redfish::privileges::getChassis) 917 .methods(boost::beast::http::verb::get)( 918 std::bind_front(handleChassisDriveGet, std::ref(app))); 919 } 920 921 inline void populateStorageController( 922 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 923 const std::string& controllerId, const std::string& connectionName, 924 const std::string& path) 925 { 926 asyncResp->res.jsonValue["@odata.type"] = 927 "#StorageController.v1_6_0.StorageController"; 928 asyncResp->res.jsonValue["@odata.id"] = 929 boost::urls::format("/redfish/v1/Systems/{}/Storage/1/Controllers/{}", 930 BMCWEB_REDFISH_SYSTEM_URI_NAME, controllerId); 931 asyncResp->res.jsonValue["Name"] = controllerId; 932 asyncResp->res.jsonValue["Id"] = controllerId; 933 asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled; 934 935 dbus::utility::getProperty<bool>( 936 connectionName, path, "xyz.openbmc_project.Inventory.Item", "Present", 937 [asyncResp](const boost::system::error_code& ec, bool isPresent) { 938 // this interface isn't necessary, only check it 939 // if we get a good return 940 if (ec) 941 { 942 BMCWEB_LOG_DEBUG("Failed to get Present property"); 943 return; 944 } 945 if (!isPresent) 946 { 947 asyncResp->res.jsonValue["Status"]["State"] = 948 resource::State::Absent; 949 } 950 }); 951 952 asset_utils::getAssetInfo(asyncResp, connectionName, path, ""_json_pointer, 953 false); 954 } 955 956 inline void getStorageControllerHandler( 957 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 958 const std::string& controllerId, const boost::system::error_code& ec, 959 const dbus::utility::MapperGetSubTreeResponse& subtree) 960 { 961 if (ec || subtree.empty()) 962 { 963 // doesn't have to be there 964 BMCWEB_LOG_DEBUG("Failed to handle StorageController"); 965 return; 966 } 967 968 for (const auto& [path, interfaceDict] : subtree) 969 { 970 sdbusplus::message::object_path object(path); 971 std::string id = object.filename(); 972 if (id.empty()) 973 { 974 BMCWEB_LOG_ERROR("Failed to find filename in {}", path); 975 return; 976 } 977 if (id != controllerId) 978 { 979 continue; 980 } 981 982 if (interfaceDict.size() != 1) 983 { 984 BMCWEB_LOG_ERROR("Connection size {}, greater than 1", 985 interfaceDict.size()); 986 messages::internalError(asyncResp->res); 987 return; 988 } 989 990 const std::string& connectionName = interfaceDict.front().first; 991 populateStorageController(asyncResp, controllerId, connectionName, 992 path); 993 } 994 } 995 996 inline void populateStorageControllerCollection( 997 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 998 const boost::system::error_code& ec, 999 const dbus::utility::MapperGetSubTreePathsResponse& controllerList) 1000 { 1001 nlohmann::json::array_t members; 1002 if (ec || controllerList.empty()) 1003 { 1004 asyncResp->res.jsonValue["Members"] = std::move(members); 1005 asyncResp->res.jsonValue["Members@odata.count"] = 0; 1006 BMCWEB_LOG_DEBUG("Failed to find any StorageController"); 1007 return; 1008 } 1009 1010 for (const std::string& path : controllerList) 1011 { 1012 std::string id = sdbusplus::message::object_path(path).filename(); 1013 if (id.empty()) 1014 { 1015 BMCWEB_LOG_ERROR("Failed to find filename in {}", path); 1016 return; 1017 } 1018 nlohmann::json::object_t member; 1019 member["@odata.id"] = boost::urls::format( 1020 "/redfish/v1/Systems/{}/Storage/1/Controllers/{}", 1021 BMCWEB_REDFISH_SYSTEM_URI_NAME, id); 1022 members.emplace_back(member); 1023 } 1024 asyncResp->res.jsonValue["Members@odata.count"] = members.size(); 1025 asyncResp->res.jsonValue["Members"] = std::move(members); 1026 } 1027 1028 inline void handleSystemsStorageControllerCollectionGet( 1029 App& app, const crow::Request& req, 1030 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1031 const std::string& systemName) 1032 { 1033 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1034 { 1035 BMCWEB_LOG_DEBUG( 1036 "Failed to setup Redfish Route for StorageController Collection"); 1037 return; 1038 } 1039 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 1040 { 1041 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1042 systemName); 1043 BMCWEB_LOG_DEBUG("Failed to find ComputerSystem of {}", systemName); 1044 return; 1045 } 1046 1047 asyncResp->res.jsonValue["@odata.type"] = 1048 "#StorageControllerCollection.StorageControllerCollection"; 1049 asyncResp->res.jsonValue["@odata.id"] = 1050 std::format("/redfish/v1/Systems/{}/Storage/1/Controllers", 1051 BMCWEB_REDFISH_SYSTEM_URI_NAME); 1052 asyncResp->res.jsonValue["Name"] = "Storage Controller Collection"; 1053 1054 constexpr std::array<std::string_view, 1> interfaces = { 1055 "xyz.openbmc_project.Inventory.Item.StorageController"}; 1056 dbus::utility::getSubTreePaths( 1057 "/xyz/openbmc_project/inventory", 0, interfaces, 1058 [asyncResp](const boost::system::error_code& ec, 1059 const dbus::utility::MapperGetSubTreePathsResponse& 1060 controllerList) { 1061 populateStorageControllerCollection(asyncResp, ec, controllerList); 1062 }); 1063 } 1064 1065 inline void handleSystemsStorageControllerGet( 1066 App& app, const crow::Request& req, 1067 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1068 const std::string& systemName, const std::string& controllerId) 1069 { 1070 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1071 { 1072 BMCWEB_LOG_DEBUG("Failed to setup Redfish Route for StorageController"); 1073 return; 1074 } 1075 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 1076 { 1077 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1078 systemName); 1079 BMCWEB_LOG_DEBUG("Failed to find ComputerSystem of {}", systemName); 1080 return; 1081 } 1082 constexpr std::array<std::string_view, 1> interfaces = { 1083 "xyz.openbmc_project.Inventory.Item.StorageController"}; 1084 dbus::utility::getSubTree( 1085 "/xyz/openbmc_project/inventory", 0, interfaces, 1086 [asyncResp, 1087 controllerId](const boost::system::error_code& ec, 1088 const dbus::utility::MapperGetSubTreeResponse& subtree) { 1089 getStorageControllerHandler(asyncResp, controllerId, ec, subtree); 1090 }); 1091 } 1092 1093 inline void requestRoutesStorageControllerCollection(App& app) 1094 { 1095 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Controllers/") 1096 .privileges(redfish::privileges::getStorageControllerCollection) 1097 .methods(boost::beast::http::verb::get)(std::bind_front( 1098 handleSystemsStorageControllerCollectionGet, std::ref(app))); 1099 } 1100 1101 inline void requestRoutesStorageController(App& app) 1102 { 1103 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Controllers/<str>") 1104 .privileges(redfish::privileges::getStorageController) 1105 .methods(boost::beast::http::verb::get)( 1106 std::bind_front(handleSystemsStorageControllerGet, std::ref(app))); 1107 } 1108 1109 } // namespace redfish 1110