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