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 getDriveAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 295 const std::string& connectionName, 296 const std::string& path) 297 { 298 dbus::utility::getAllProperties( 299 connectionName, path, "xyz.openbmc_project.Inventory.Decorator.Asset", 300 [asyncResp](const boost::system::error_code& ec, 301 const std::vector< 302 std::pair<std::string, dbus::utility::DbusVariantType>>& 303 propertiesList) { 304 if (ec) 305 { 306 // this interface isn't necessary 307 return; 308 } 309 310 const std::string* partNumber = nullptr; 311 const std::string* serialNumber = nullptr; 312 const std::string* manufacturer = nullptr; 313 const std::string* model = nullptr; 314 315 const bool success = sdbusplus::unpackPropertiesNoThrow( 316 dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber", 317 partNumber, "SerialNumber", serialNumber, "Manufacturer", 318 manufacturer, "Model", model); 319 320 if (!success) 321 { 322 messages::internalError(asyncResp->res); 323 return; 324 } 325 326 if (partNumber != nullptr) 327 { 328 asyncResp->res.jsonValue["PartNumber"] = *partNumber; 329 } 330 331 if (serialNumber != nullptr) 332 { 333 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber; 334 } 335 336 if (manufacturer != nullptr) 337 { 338 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer; 339 } 340 341 if (model != nullptr) 342 { 343 asyncResp->res.jsonValue["Model"] = *model; 344 } 345 }); 346 } 347 348 inline void getDrivePresent(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 349 const std::string& connectionName, 350 const std::string& path) 351 { 352 dbus::utility::getProperty<bool>( 353 connectionName, path, "xyz.openbmc_project.Inventory.Item", "Present", 354 [asyncResp, 355 path](const boost::system::error_code& ec, const bool isPresent) { 356 // this interface isn't necessary, only check it if 357 // we get a good return 358 if (ec) 359 { 360 return; 361 } 362 363 if (!isPresent) 364 { 365 asyncResp->res.jsonValue["Status"]["State"] = 366 resource::State::Absent; 367 } 368 }); 369 } 370 371 inline void getDriveState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 372 const std::string& connectionName, 373 const std::string& path) 374 { 375 dbus::utility::getProperty<bool>( 376 connectionName, path, "xyz.openbmc_project.State.Drive", "Rebuilding", 377 [asyncResp](const boost::system::error_code& ec, const bool updating) { 378 // this interface isn't necessary, only check it 379 // if we get a good return 380 if (ec) 381 { 382 return; 383 } 384 385 // updating and disabled in the backend shouldn't be 386 // able to be set at the same time, so we don't need 387 // to check for the race condition of these two 388 // calls 389 if (updating) 390 { 391 asyncResp->res.jsonValue["Status"]["State"] = 392 resource::State::Updating; 393 } 394 }); 395 } 396 397 inline std::optional<drive::MediaType> convertDriveType(std::string_view type) 398 { 399 if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.HDD") 400 { 401 return drive::MediaType::HDD; 402 } 403 if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.SSD") 404 { 405 return drive::MediaType::SSD; 406 } 407 if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.Unknown") 408 { 409 return std::nullopt; 410 } 411 412 return drive::MediaType::Invalid; 413 } 414 415 inline std::optional<protocol::Protocol> convertDriveProtocol( 416 std::string_view proto) 417 { 418 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SAS") 419 { 420 return protocol::Protocol::SAS; 421 } 422 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SATA") 423 { 424 return protocol::Protocol::SATA; 425 } 426 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.NVMe") 427 { 428 return protocol::Protocol::NVMe; 429 } 430 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.FC") 431 { 432 return protocol::Protocol::FC; 433 } 434 if (proto == 435 "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.Unknown") 436 { 437 return std::nullopt; 438 } 439 440 return protocol::Protocol::Invalid; 441 } 442 443 inline void getDriveItemProperties( 444 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 445 const std::string& connectionName, const std::string& path) 446 { 447 dbus::utility::getAllProperties( 448 connectionName, path, "xyz.openbmc_project.Inventory.Item.Drive", 449 [asyncResp](const boost::system::error_code& ec, 450 const std::vector< 451 std::pair<std::string, dbus::utility::DbusVariantType>>& 452 propertiesList) { 453 if (ec) 454 { 455 // this interface isn't required 456 return; 457 } 458 const std::string* encryptionStatus = nullptr; 459 const bool* isLocked = nullptr; 460 for (const std::pair<std::string, dbus::utility::DbusVariantType>& 461 property : propertiesList) 462 { 463 const std::string& propertyName = property.first; 464 if (propertyName == "Type") 465 { 466 const std::string* value = 467 std::get_if<std::string>(&property.second); 468 if (value == nullptr) 469 { 470 // illegal property 471 BMCWEB_LOG_ERROR("Illegal property: Type"); 472 messages::internalError(asyncResp->res); 473 return; 474 } 475 476 std::optional<drive::MediaType> mediaType = 477 convertDriveType(*value); 478 if (!mediaType) 479 { 480 BMCWEB_LOG_WARNING("UnknownDriveType Interface: {}", 481 *value); 482 continue; 483 } 484 if (*mediaType == drive::MediaType::Invalid) 485 { 486 messages::internalError(asyncResp->res); 487 return; 488 } 489 490 asyncResp->res.jsonValue["MediaType"] = *mediaType; 491 } 492 else if (propertyName == "Capacity") 493 { 494 const uint64_t* capacity = 495 std::get_if<uint64_t>(&property.second); 496 if (capacity == nullptr) 497 { 498 BMCWEB_LOG_ERROR("Illegal property: Capacity"); 499 messages::internalError(asyncResp->res); 500 return; 501 } 502 if (*capacity == 0) 503 { 504 // drive capacity not known 505 continue; 506 } 507 508 asyncResp->res.jsonValue["CapacityBytes"] = *capacity; 509 } 510 else if (propertyName == "Protocol") 511 { 512 const std::string* value = 513 std::get_if<std::string>(&property.second); 514 if (value == nullptr) 515 { 516 BMCWEB_LOG_ERROR("Illegal property: Protocol"); 517 messages::internalError(asyncResp->res); 518 return; 519 } 520 521 std::optional<protocol::Protocol> proto = 522 convertDriveProtocol(*value); 523 if (!proto) 524 { 525 BMCWEB_LOG_WARNING( 526 "Unknown DrivePrototype Interface: {}", *value); 527 continue; 528 } 529 if (*proto == protocol::Protocol::Invalid) 530 { 531 messages::internalError(asyncResp->res); 532 return; 533 } 534 asyncResp->res.jsonValue["Protocol"] = *proto; 535 } 536 else if (propertyName == "PredictedMediaLifeLeftPercent") 537 { 538 const uint8_t* lifeLeft = 539 std::get_if<uint8_t>(&property.second); 540 if (lifeLeft == nullptr) 541 { 542 BMCWEB_LOG_ERROR( 543 "Illegal property: PredictedMediaLifeLeftPercent"); 544 messages::internalError(asyncResp->res); 545 return; 546 } 547 // 255 means reading the value is not supported 548 if (*lifeLeft != 255) 549 { 550 asyncResp->res 551 .jsonValue["PredictedMediaLifeLeftPercent"] = 552 *lifeLeft; 553 } 554 } 555 else if (propertyName == "EncryptionStatus") 556 { 557 encryptionStatus = 558 std::get_if<std::string>(&property.second); 559 if (encryptionStatus == nullptr) 560 { 561 BMCWEB_LOG_ERROR("Illegal property: EncryptionStatus"); 562 messages::internalError(asyncResp->res); 563 return; 564 } 565 } 566 else if (propertyName == "Locked") 567 { 568 isLocked = std::get_if<bool>(&property.second); 569 if (isLocked == nullptr) 570 { 571 BMCWEB_LOG_ERROR("Illegal property: Locked"); 572 messages::internalError(asyncResp->res); 573 return; 574 } 575 } 576 } 577 578 if (encryptionStatus == nullptr || isLocked == nullptr || 579 *encryptionStatus == 580 "xyz.openbmc_project.Inventory.Item.Drive.DriveEncryptionState.Unknown") 581 { 582 return; 583 } 584 if (*encryptionStatus != 585 "xyz.openbmc_project.Inventory.Item.Drive.DriveEncryptionState.Encrypted") 586 { 587 //"The drive is not currently encrypted." 588 asyncResp->res.jsonValue["EncryptionStatus"] = 589 drive::EncryptionStatus::Unencrypted; 590 return; 591 } 592 if (*isLocked) 593 { 594 //"The drive is currently encrypted and the data is not 595 // accessible to the user." 596 asyncResp->res.jsonValue["EncryptionStatus"] = 597 drive::EncryptionStatus::Locked; 598 return; 599 } 600 // if not locked 601 // "The drive is currently encrypted but the data is accessible 602 // to the user in unencrypted form." 603 asyncResp->res.jsonValue["EncryptionStatus"] = 604 drive::EncryptionStatus::Unlocked; 605 }); 606 } 607 608 inline void addAllDriveInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 609 const std::string& connectionName, 610 const std::string& path, 611 const std::vector<std::string>& interfaces) 612 { 613 for (const std::string& interface : interfaces) 614 { 615 if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset") 616 { 617 getDriveAsset(asyncResp, connectionName, path); 618 } 619 else if (interface == "xyz.openbmc_project.Inventory.Item") 620 { 621 getDrivePresent(asyncResp, connectionName, path); 622 } 623 else if (interface == "xyz.openbmc_project.State.Drive") 624 { 625 getDriveState(asyncResp, connectionName, path); 626 } 627 else if (interface == "xyz.openbmc_project.Inventory.Item.Drive") 628 { 629 getDriveItemProperties(asyncResp, connectionName, path); 630 } 631 } 632 } 633 634 inline void afterGetSubtreeSystemsStorageDrive( 635 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 636 const std::string& driveId, const boost::system::error_code& ec, 637 const dbus::utility::MapperGetSubTreeResponse& subtree) 638 { 639 if (ec) 640 { 641 BMCWEB_LOG_ERROR("Drive mapper call error"); 642 messages::internalError(asyncResp->res); 643 return; 644 } 645 646 auto drive = std::ranges::find_if( 647 subtree, 648 [&driveId](const std::pair<std::string, 649 dbus::utility::MapperServiceMap>& object) { 650 return sdbusplus::message::object_path(object.first).filename() == 651 driveId; 652 }); 653 654 if (drive == subtree.end()) 655 { 656 messages::resourceNotFound(asyncResp->res, "Drive", driveId); 657 return; 658 } 659 660 const std::string& path = drive->first; 661 const dbus::utility::MapperServiceMap& connectionNames = drive->second; 662 663 asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive"; 664 asyncResp->res.jsonValue["@odata.id"] = 665 boost::urls::format("/redfish/v1/Systems/{}/Storage/1/Drives/{}", 666 BMCWEB_REDFISH_SYSTEM_URI_NAME, driveId); 667 asyncResp->res.jsonValue["Name"] = driveId; 668 asyncResp->res.jsonValue["Id"] = driveId; 669 670 if (connectionNames.size() != 1) 671 { 672 BMCWEB_LOG_ERROR("Connection size {}, not equal to 1", 673 connectionNames.size()); 674 messages::internalError(asyncResp->res); 675 return; 676 } 677 678 getMainChassisId( 679 asyncResp, [](const std::string& chassisId, 680 const std::shared_ptr<bmcweb::AsyncResp>& aRsp) { 681 aRsp->res.jsonValue["Links"]["Chassis"]["@odata.id"] = 682 boost::urls::format("/redfish/v1/Chassis/{}", chassisId); 683 }); 684 685 // default it to Enabled 686 asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled; 687 688 addAllDriveInfo(asyncResp, connectionNames[0].first, path, 689 connectionNames[0].second); 690 } 691 692 inline void handleSystemsStorageDriveGet( 693 App& app, const crow::Request& req, 694 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 695 const std::string& systemName, const std::string& driveId) 696 { 697 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 698 { 699 return; 700 } 701 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 702 { 703 // Option currently returns no systems. TBD 704 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 705 systemName); 706 return; 707 } 708 709 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 710 { 711 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 712 systemName); 713 return; 714 } 715 716 constexpr std::array<std::string_view, 1> interfaces = { 717 "xyz.openbmc_project.Inventory.Item.Drive"}; 718 dbus::utility::getSubTree( 719 "/xyz/openbmc_project/inventory", 0, interfaces, 720 std::bind_front(afterGetSubtreeSystemsStorageDrive, asyncResp, 721 driveId)); 722 } 723 724 inline void requestRoutesDrive(App& app) 725 { 726 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Drives/<str>/") 727 .privileges(redfish::privileges::getDrive) 728 .methods(boost::beast::http::verb::get)( 729 std::bind_front(handleSystemsStorageDriveGet, std::ref(app))); 730 } 731 732 inline void afterChassisDriveCollectionSubtreeGet( 733 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 734 const std::string& chassisId, const boost::system::error_code& ec, 735 const dbus::utility::MapperGetSubTreeResponse& subtree) 736 { 737 if (ec) 738 { 739 if (ec == boost::system::errc::host_unreachable) 740 { 741 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 742 return; 743 } 744 messages::internalError(asyncResp->res); 745 return; 746 } 747 748 // Iterate over all retrieved ObjectPaths. 749 for (const auto& [path, connectionNames] : subtree) 750 { 751 sdbusplus::message::object_path objPath(path); 752 if (objPath.filename() != chassisId) 753 { 754 continue; 755 } 756 757 if (connectionNames.empty()) 758 { 759 BMCWEB_LOG_ERROR("Got 0 Connection names"); 760 continue; 761 } 762 763 asyncResp->res.jsonValue["@odata.type"] = 764 "#DriveCollection.DriveCollection"; 765 asyncResp->res.jsonValue["@odata.id"] = 766 boost::urls::format("/redfish/v1/Chassis/{}/Drives", chassisId); 767 asyncResp->res.jsonValue["Name"] = "Drive Collection"; 768 769 // Association lambda 770 dbus::utility::getAssociationEndPoints( 771 path + "/drive", 772 [asyncResp, chassisId](const boost::system::error_code& ec3, 773 const dbus::utility::MapperEndPoints& resp) { 774 if (ec3) 775 { 776 BMCWEB_LOG_ERROR("Error in chassis Drive association "); 777 } 778 nlohmann::json& members = asyncResp->res.jsonValue["Members"]; 779 // important if array is empty 780 members = nlohmann::json::array(); 781 782 std::vector<std::string> leafNames; 783 for (const auto& drive : resp) 784 { 785 sdbusplus::message::object_path drivePath(drive); 786 leafNames.push_back(drivePath.filename()); 787 } 788 789 std::ranges::sort(leafNames, AlphanumLess<std::string>()); 790 791 for (const auto& leafName : leafNames) 792 { 793 nlohmann::json::object_t member; 794 member["@odata.id"] = 795 boost::urls::format("/redfish/v1/Chassis/{}/Drives/{}", 796 chassisId, leafName); 797 members.emplace_back(std::move(member)); 798 // navigation links will be registered in next patch set 799 } 800 asyncResp->res.jsonValue["Members@odata.count"] = resp.size(); 801 }); // end association lambda 802 803 } // end Iterate over all retrieved ObjectPaths 804 } 805 /** 806 * Chassis drives, this URL will show all the DriveCollection 807 * information 808 */ 809 inline void chassisDriveCollectionGet( 810 crow::App& app, const crow::Request& req, 811 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 812 const std::string& chassisId) 813 { 814 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 815 { 816 return; 817 } 818 819 // mapper call lambda 820 dbus::utility::getSubTree( 821 "/xyz/openbmc_project/inventory", 0, chassisInterfaces, 822 std::bind_front(afterChassisDriveCollectionSubtreeGet, asyncResp, 823 chassisId)); 824 } 825 826 inline void requestRoutesChassisDrive(App& app) 827 { 828 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/") 829 .privileges(redfish::privileges::getDriveCollection) 830 .methods(boost::beast::http::verb::get)( 831 std::bind_front(chassisDriveCollectionGet, std::ref(app))); 832 } 833 834 inline void buildDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 835 const std::string& chassisId, 836 const std::string& driveName, 837 const boost::system::error_code& ec, 838 const dbus::utility::MapperGetSubTreeResponse& subtree) 839 { 840 if (ec) 841 { 842 BMCWEB_LOG_DEBUG("DBUS response error {}", ec); 843 messages::internalError(asyncResp->res); 844 return; 845 } 846 847 // Iterate over all retrieved ObjectPaths. 848 for (const auto& [path, connectionNames] : subtree) 849 { 850 sdbusplus::message::object_path objPath(path); 851 if (objPath.filename() != driveName) 852 { 853 continue; 854 } 855 856 if (connectionNames.empty()) 857 { 858 BMCWEB_LOG_ERROR("Got 0 Connection names"); 859 continue; 860 } 861 862 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 863 "/redfish/v1/Chassis/{}/Drives/{}", chassisId, driveName); 864 865 asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive"; 866 asyncResp->res.jsonValue["Name"] = driveName; 867 asyncResp->res.jsonValue["Id"] = driveName; 868 // default it to Enabled 869 asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled; 870 871 nlohmann::json::object_t linkChassisNav; 872 linkChassisNav["@odata.id"] = 873 boost::urls::format("/redfish/v1/Chassis/{}", chassisId); 874 asyncResp->res.jsonValue["Links"]["Chassis"] = linkChassisNav; 875 876 addAllDriveInfo(asyncResp, connectionNames[0].first, path, 877 connectionNames[0].second); 878 } 879 } 880 881 inline void matchAndFillDrive( 882 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 883 const std::string& chassisId, const std::string& driveName, 884 const std::vector<std::string>& resp) 885 { 886 for (const std::string& drivePath : resp) 887 { 888 sdbusplus::message::object_path path(drivePath); 889 std::string leaf = path.filename(); 890 if (leaf != driveName) 891 { 892 continue; 893 } 894 // mapper call drive 895 constexpr std::array<std::string_view, 1> driveInterface = { 896 "xyz.openbmc_project.Inventory.Item.Drive"}; 897 dbus::utility::getSubTree( 898 "/xyz/openbmc_project/inventory", 0, driveInterface, 899 [asyncResp, chassisId, driveName]( 900 const boost::system::error_code& ec, 901 const dbus::utility::MapperGetSubTreeResponse& subtree) { 902 buildDrive(asyncResp, chassisId, driveName, ec, subtree); 903 }); 904 } 905 } 906 907 inline void handleChassisDriveGet( 908 crow::App& app, const crow::Request& req, 909 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 910 const std::string& chassisId, const std::string& driveName) 911 { 912 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 913 { 914 return; 915 } 916 917 // mapper call chassis 918 dbus::utility::getSubTree( 919 "/xyz/openbmc_project/inventory", 0, chassisInterfaces, 920 [asyncResp, chassisId, 921 driveName](const boost::system::error_code& ec, 922 const dbus::utility::MapperGetSubTreeResponse& subtree) { 923 if (ec) 924 { 925 messages::internalError(asyncResp->res); 926 return; 927 } 928 929 // Iterate over all retrieved ObjectPaths. 930 for (const auto& [path, connectionNames] : subtree) 931 { 932 sdbusplus::message::object_path objPath(path); 933 if (objPath.filename() != chassisId) 934 { 935 continue; 936 } 937 938 if (connectionNames.empty()) 939 { 940 BMCWEB_LOG_ERROR("Got 0 Connection names"); 941 continue; 942 } 943 944 dbus::utility::getAssociationEndPoints( 945 path + "/drive", 946 [asyncResp, chassisId, 947 driveName](const boost::system::error_code& ec3, 948 const dbus::utility::MapperEndPoints& resp) { 949 if (ec3) 950 { 951 return; // no drives = no failures 952 } 953 matchAndFillDrive(asyncResp, chassisId, driveName, 954 resp); 955 }); 956 return; 957 } 958 // Couldn't find an object with that name. return an error 959 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 960 }); 961 } 962 963 /** 964 * This URL will show the drive interface for the specific drive in the chassis 965 */ 966 inline void requestRoutesChassisDriveName(App& app) 967 { 968 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/<str>/") 969 .privileges(redfish::privileges::getChassis) 970 .methods(boost::beast::http::verb::get)( 971 std::bind_front(handleChassisDriveGet, std::ref(app))); 972 } 973 974 inline void getStorageControllerAsset( 975 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 976 const boost::system::error_code& ec, 977 const std::vector<std::pair<std::string, dbus::utility::DbusVariantType>>& 978 propertiesList) 979 { 980 if (ec) 981 { 982 // this interface isn't necessary 983 BMCWEB_LOG_DEBUG("Failed to get StorageControllerAsset"); 984 return; 985 } 986 987 const std::string* partNumber = nullptr; 988 const std::string* serialNumber = nullptr; 989 const std::string* manufacturer = nullptr; 990 const std::string* model = nullptr; 991 if (!sdbusplus::unpackPropertiesNoThrow( 992 dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber", 993 partNumber, "SerialNumber", serialNumber, "Manufacturer", 994 manufacturer, "Model", model)) 995 { 996 messages::internalError(asyncResp->res); 997 return; 998 } 999 1000 if (partNumber != nullptr) 1001 { 1002 asyncResp->res.jsonValue["PartNumber"] = *partNumber; 1003 } 1004 1005 if (serialNumber != nullptr) 1006 { 1007 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber; 1008 } 1009 1010 if (manufacturer != nullptr) 1011 { 1012 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer; 1013 } 1014 1015 if (model != nullptr) 1016 { 1017 asyncResp->res.jsonValue["Model"] = *model; 1018 } 1019 } 1020 1021 inline void populateStorageController( 1022 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1023 const std::string& controllerId, const std::string& connectionName, 1024 const std::string& path) 1025 { 1026 asyncResp->res.jsonValue["@odata.type"] = 1027 "#StorageController.v1_6_0.StorageController"; 1028 asyncResp->res.jsonValue["@odata.id"] = 1029 boost::urls::format("/redfish/v1/Systems/{}/Storage/1/Controllers/{}", 1030 BMCWEB_REDFISH_SYSTEM_URI_NAME, controllerId); 1031 asyncResp->res.jsonValue["Name"] = controllerId; 1032 asyncResp->res.jsonValue["Id"] = controllerId; 1033 asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled; 1034 1035 dbus::utility::getProperty<bool>( 1036 connectionName, path, "xyz.openbmc_project.Inventory.Item", "Present", 1037 [asyncResp](const boost::system::error_code& ec, bool isPresent) { 1038 // this interface isn't necessary, only check it 1039 // if we get a good return 1040 if (ec) 1041 { 1042 BMCWEB_LOG_DEBUG("Failed to get Present property"); 1043 return; 1044 } 1045 if (!isPresent) 1046 { 1047 asyncResp->res.jsonValue["Status"]["State"] = 1048 resource::State::Absent; 1049 } 1050 }); 1051 1052 dbus::utility::getAllProperties( 1053 connectionName, path, "xyz.openbmc_project.Inventory.Decorator.Asset", 1054 [asyncResp](const boost::system::error_code& ec, 1055 const std::vector< 1056 std::pair<std::string, dbus::utility::DbusVariantType>>& 1057 propertiesList) { 1058 getStorageControllerAsset(asyncResp, ec, propertiesList); 1059 }); 1060 } 1061 1062 inline void getStorageControllerHandler( 1063 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1064 const std::string& controllerId, const boost::system::error_code& ec, 1065 const dbus::utility::MapperGetSubTreeResponse& subtree) 1066 { 1067 if (ec || subtree.empty()) 1068 { 1069 // doesn't have to be there 1070 BMCWEB_LOG_DEBUG("Failed to handle StorageController"); 1071 return; 1072 } 1073 1074 for (const auto& [path, interfaceDict] : subtree) 1075 { 1076 sdbusplus::message::object_path object(path); 1077 std::string id = object.filename(); 1078 if (id.empty()) 1079 { 1080 BMCWEB_LOG_ERROR("Failed to find filename in {}", path); 1081 return; 1082 } 1083 if (id != controllerId) 1084 { 1085 continue; 1086 } 1087 1088 if (interfaceDict.size() != 1) 1089 { 1090 BMCWEB_LOG_ERROR("Connection size {}, greater than 1", 1091 interfaceDict.size()); 1092 messages::internalError(asyncResp->res); 1093 return; 1094 } 1095 1096 const std::string& connectionName = interfaceDict.front().first; 1097 populateStorageController(asyncResp, controllerId, connectionName, 1098 path); 1099 } 1100 } 1101 1102 inline void populateStorageControllerCollection( 1103 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1104 const boost::system::error_code& ec, 1105 const dbus::utility::MapperGetSubTreePathsResponse& controllerList) 1106 { 1107 nlohmann::json::array_t members; 1108 if (ec || controllerList.empty()) 1109 { 1110 asyncResp->res.jsonValue["Members"] = std::move(members); 1111 asyncResp->res.jsonValue["Members@odata.count"] = 0; 1112 BMCWEB_LOG_DEBUG("Failed to find any StorageController"); 1113 return; 1114 } 1115 1116 for (const std::string& path : controllerList) 1117 { 1118 std::string id = sdbusplus::message::object_path(path).filename(); 1119 if (id.empty()) 1120 { 1121 BMCWEB_LOG_ERROR("Failed to find filename in {}", path); 1122 return; 1123 } 1124 nlohmann::json::object_t member; 1125 member["@odata.id"] = boost::urls::format( 1126 "/redfish/v1/Systems/{}/Storage/1/Controllers/{}", 1127 BMCWEB_REDFISH_SYSTEM_URI_NAME, id); 1128 members.emplace_back(member); 1129 } 1130 asyncResp->res.jsonValue["Members@odata.count"] = members.size(); 1131 asyncResp->res.jsonValue["Members"] = std::move(members); 1132 } 1133 1134 inline void handleSystemsStorageControllerCollectionGet( 1135 App& app, const crow::Request& req, 1136 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1137 const std::string& systemName) 1138 { 1139 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1140 { 1141 BMCWEB_LOG_DEBUG( 1142 "Failed to setup Redfish Route for StorageController Collection"); 1143 return; 1144 } 1145 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 1146 { 1147 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1148 systemName); 1149 BMCWEB_LOG_DEBUG("Failed to find ComputerSystem of {}", systemName); 1150 return; 1151 } 1152 1153 asyncResp->res.jsonValue["@odata.type"] = 1154 "#StorageControllerCollection.StorageControllerCollection"; 1155 asyncResp->res.jsonValue["@odata.id"] = 1156 std::format("/redfish/v1/Systems/{}/Storage/1/Controllers", 1157 BMCWEB_REDFISH_SYSTEM_URI_NAME); 1158 asyncResp->res.jsonValue["Name"] = "Storage Controller Collection"; 1159 1160 constexpr std::array<std::string_view, 1> interfaces = { 1161 "xyz.openbmc_project.Inventory.Item.StorageController"}; 1162 dbus::utility::getSubTreePaths( 1163 "/xyz/openbmc_project/inventory", 0, interfaces, 1164 [asyncResp](const boost::system::error_code& ec, 1165 const dbus::utility::MapperGetSubTreePathsResponse& 1166 controllerList) { 1167 populateStorageControllerCollection(asyncResp, ec, controllerList); 1168 }); 1169 } 1170 1171 inline void handleSystemsStorageControllerGet( 1172 App& app, const crow::Request& req, 1173 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1174 const std::string& systemName, const std::string& controllerId) 1175 { 1176 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1177 { 1178 BMCWEB_LOG_DEBUG("Failed to setup Redfish Route for StorageController"); 1179 return; 1180 } 1181 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 1182 { 1183 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1184 systemName); 1185 BMCWEB_LOG_DEBUG("Failed to find ComputerSystem of {}", systemName); 1186 return; 1187 } 1188 constexpr std::array<std::string_view, 1> interfaces = { 1189 "xyz.openbmc_project.Inventory.Item.StorageController"}; 1190 dbus::utility::getSubTree( 1191 "/xyz/openbmc_project/inventory", 0, interfaces, 1192 [asyncResp, 1193 controllerId](const boost::system::error_code& ec, 1194 const dbus::utility::MapperGetSubTreeResponse& subtree) { 1195 getStorageControllerHandler(asyncResp, controllerId, ec, subtree); 1196 }); 1197 } 1198 1199 inline void requestRoutesStorageControllerCollection(App& app) 1200 { 1201 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Controllers/") 1202 .privileges(redfish::privileges::getStorageControllerCollection) 1203 .methods(boost::beast::http::verb::get)(std::bind_front( 1204 handleSystemsStorageControllerCollectionGet, std::ref(app))); 1205 } 1206 1207 inline void requestRoutesStorageController(App& app) 1208 { 1209 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Controllers/<str>") 1210 .privileges(redfish::privileges::getStorageController) 1211 .methods(boost::beast::http::verb::get)( 1212 std::bind_front(handleSystemsStorageControllerGet, std::ref(app))); 1213 } 1214 1215 } // namespace redfish 1216