1 /* 2 // Copyright (c) 2019 Intel Corporation 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 */ 16 #pragma once 17 18 #include "bmcweb_config.h" 19 20 #include "app.hpp" 21 #include "dbus_utility.hpp" 22 #include "generated/enums/drive.hpp" 23 #include "generated/enums/protocol.hpp" 24 #include "generated/enums/resource.hpp" 25 #include "human_sort.hpp" 26 #include "query.hpp" 27 #include "redfish_util.hpp" 28 #include "registries/privilege_registry.hpp" 29 #include "utils/collection.hpp" 30 #include "utils/dbus_utils.hpp" 31 32 #include <boost/system/error_code.hpp> 33 #include <boost/url/format.hpp> 34 #include <sdbusplus/asio/property.hpp> 35 #include <sdbusplus/unpack_properties.hpp> 36 37 #include <array> 38 #include <ranges> 39 #include <string_view> 40 41 namespace redfish 42 { 43 44 inline void handleSystemsStorageCollectionGet( 45 App& app, const crow::Request& req, 46 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 47 const std::string& systemName) 48 { 49 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 50 { 51 return; 52 } 53 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 54 { 55 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 56 systemName); 57 return; 58 } 59 60 asyncResp->res.jsonValue["@odata.type"] = 61 "#StorageCollection.StorageCollection"; 62 asyncResp->res.jsonValue["@odata.id"] = std::format( 63 "/redfish/v1/Systems/{}/Storage", BMCWEB_REDFISH_SYSTEM_URI_NAME); 64 asyncResp->res.jsonValue["Name"] = "Storage Collection"; 65 66 constexpr std::array<std::string_view, 1> interface{ 67 "xyz.openbmc_project.Inventory.Item.Storage"}; 68 collection_util::getCollectionMembers( 69 asyncResp, 70 boost::urls::format("/redfish/v1/Systems/{}/Storage", 71 BMCWEB_REDFISH_SYSTEM_URI_NAME), 72 interface, "/xyz/openbmc_project/inventory"); 73 } 74 75 inline void handleStorageCollectionGet( 76 App& app, const crow::Request& req, 77 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 78 { 79 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 80 { 81 return; 82 } 83 asyncResp->res.jsonValue["@odata.type"] = 84 "#StorageCollection.StorageCollection"; 85 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Storage"; 86 asyncResp->res.jsonValue["Name"] = "Storage Collection"; 87 constexpr std::array<std::string_view, 1> interface{ 88 "xyz.openbmc_project.Inventory.Item.Storage"}; 89 collection_util::getCollectionMembers( 90 asyncResp, boost::urls::format("/redfish/v1/Storage"), interface, 91 "/xyz/openbmc_project/inventory"); 92 } 93 94 inline void requestRoutesStorageCollection(App& app) 95 { 96 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/") 97 .privileges(redfish::privileges::getStorageCollection) 98 .methods(boost::beast::http::verb::get)( 99 std::bind_front(handleSystemsStorageCollectionGet, std::ref(app))); 100 BMCWEB_ROUTE(app, "/redfish/v1/Storage/") 101 .privileges(redfish::privileges::getStorageCollection) 102 .methods(boost::beast::http::verb::get)( 103 std::bind_front(handleStorageCollectionGet, std::ref(app))); 104 } 105 106 inline void afterChassisDriveCollectionSubtree( 107 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 108 const boost::system::error_code& ec, 109 const dbus::utility::MapperGetSubTreePathsResponse& driveList) 110 { 111 if (ec) 112 { 113 BMCWEB_LOG_ERROR("Drive mapper call error"); 114 messages::internalError(asyncResp->res); 115 return; 116 } 117 118 nlohmann::json& driveArray = asyncResp->res.jsonValue["Drives"]; 119 driveArray = nlohmann::json::array(); 120 auto& count = asyncResp->res.jsonValue["Drives@odata.count"]; 121 count = 0; 122 123 for (const std::string& drive : driveList) 124 { 125 sdbusplus::message::object_path object(drive); 126 if (object.filename().empty()) 127 { 128 BMCWEB_LOG_ERROR("Failed to find filename in {}", drive); 129 return; 130 } 131 132 nlohmann::json::object_t driveJson; 133 driveJson["@odata.id"] = boost::urls::format( 134 "/redfish/v1/Systems/{}/Storage/1/Drives/{}", 135 BMCWEB_REDFISH_SYSTEM_URI_NAME, object.filename()); 136 driveArray.emplace_back(std::move(driveJson)); 137 } 138 139 count = driveArray.size(); 140 } 141 inline void getDrives(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 142 { 143 const std::array<std::string_view, 1> interfaces = { 144 "xyz.openbmc_project.Inventory.Item.Drive"}; 145 dbus::utility::getSubTreePaths( 146 "/xyz/openbmc_project/inventory", 0, interfaces, 147 std::bind_front(afterChassisDriveCollectionSubtree, asyncResp)); 148 } 149 150 inline void afterSystemsStorageGetSubtree( 151 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 152 const std::string& storageId, const boost::system::error_code& ec, 153 const dbus::utility::MapperGetSubTreeResponse& subtree) 154 { 155 if (ec) 156 { 157 BMCWEB_LOG_DEBUG("requestRoutesStorage DBUS response error"); 158 messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage", 159 storageId); 160 return; 161 } 162 auto storage = std::ranges::find_if( 163 subtree, 164 [&storageId](const std::pair<std::string, 165 dbus::utility::MapperServiceMap>& object) { 166 return sdbusplus::message::object_path(object.first).filename() == 167 storageId; 168 }); 169 if (storage == subtree.end()) 170 { 171 messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage", 172 storageId); 173 return; 174 } 175 176 asyncResp->res.jsonValue["@odata.type"] = "#Storage.v1_13_0.Storage"; 177 asyncResp->res.jsonValue["@odata.id"] = 178 boost::urls::format("/redfish/v1/Systems/{}/Storage/{}", 179 BMCWEB_REDFISH_SYSTEM_URI_NAME, storageId); 180 asyncResp->res.jsonValue["Name"] = "Storage"; 181 asyncResp->res.jsonValue["Id"] = storageId; 182 asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled; 183 184 getDrives(asyncResp); 185 asyncResp->res.jsonValue["Controllers"]["@odata.id"] = 186 boost::urls::format("/redfish/v1/Systems/{}/Storage/{}/Controllers", 187 BMCWEB_REDFISH_SYSTEM_URI_NAME, storageId); 188 } 189 190 inline void 191 handleSystemsStorageGet(App& app, const crow::Request& req, 192 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 193 const std::string& systemName, 194 const std::string& storageId) 195 { 196 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 197 { 198 return; 199 } 200 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 201 { 202 // Option currently returns no systems. TBD 203 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 204 systemName); 205 return; 206 } 207 208 constexpr std::array<std::string_view, 1> interfaces = { 209 "xyz.openbmc_project.Inventory.Item.Storage"}; 210 dbus::utility::getSubTree( 211 "/xyz/openbmc_project/inventory", 0, interfaces, 212 std::bind_front(afterSystemsStorageGetSubtree, asyncResp, storageId)); 213 } 214 215 inline void afterSubtree(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 216 const std::string& storageId, 217 const boost::system::error_code& ec, 218 const dbus::utility::MapperGetSubTreeResponse& subtree) 219 { 220 if (ec) 221 { 222 BMCWEB_LOG_DEBUG("requestRoutesStorage DBUS response error"); 223 messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage", 224 storageId); 225 return; 226 } 227 auto storage = std::ranges::find_if( 228 subtree, 229 [&storageId](const std::pair<std::string, 230 dbus::utility::MapperServiceMap>& object) { 231 return sdbusplus::message::object_path(object.first).filename() == 232 storageId; 233 }); 234 if (storage == subtree.end()) 235 { 236 messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage", 237 storageId); 238 return; 239 } 240 241 asyncResp->res.jsonValue["@odata.type"] = "#Storage.v1_13_0.Storage"; 242 asyncResp->res.jsonValue["@odata.id"] = 243 boost::urls::format("/redfish/v1/Storage/{}", storageId); 244 asyncResp->res.jsonValue["Name"] = "Storage"; 245 asyncResp->res.jsonValue["Id"] = storageId; 246 asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled; 247 248 // Storage subsystem to Storage link. 249 nlohmann::json::array_t storageServices; 250 nlohmann::json::object_t storageService; 251 storageService["@odata.id"] = 252 boost::urls::format("/redfish/v1/Systems/{}/Storage/{}", 253 BMCWEB_REDFISH_SYSTEM_URI_NAME, storageId); 254 storageServices.emplace_back(storageService); 255 asyncResp->res.jsonValue["Links"]["StorageServices"] = 256 std::move(storageServices); 257 asyncResp->res.jsonValue["Links"]["StorageServices@odata.count"] = 1; 258 } 259 260 inline void 261 handleStorageGet(App& app, const crow::Request& req, 262 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 263 const std::string& storageId) 264 { 265 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 266 { 267 BMCWEB_LOG_DEBUG("requestRoutesStorage setUpRedfishRoute failed"); 268 return; 269 } 270 271 constexpr std::array<std::string_view, 1> interfaces = { 272 "xyz.openbmc_project.Inventory.Item.Storage"}; 273 dbus::utility::getSubTree( 274 "/xyz/openbmc_project/inventory", 0, interfaces, 275 std::bind_front(afterSubtree, asyncResp, storageId)); 276 } 277 278 inline void requestRoutesStorage(App& app) 279 { 280 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/<str>/") 281 .privileges(redfish::privileges::getStorage) 282 .methods(boost::beast::http::verb::get)( 283 std::bind_front(handleSystemsStorageGet, std::ref(app))); 284 285 BMCWEB_ROUTE(app, "/redfish/v1/Storage/<str>/") 286 .privileges(redfish::privileges::getStorage) 287 .methods(boost::beast::http::verb::get)( 288 std::bind_front(handleStorageGet, std::ref(app))); 289 } 290 291 inline void getDriveAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 292 const std::string& connectionName, 293 const std::string& path) 294 { 295 sdbusplus::asio::getAllProperties( 296 *crow::connections::systemBus, connectionName, path, 297 "xyz.openbmc_project.Inventory.Decorator.Asset", 298 [asyncResp](const boost::system::error_code& ec, 299 const std::vector< 300 std::pair<std::string, dbus::utility::DbusVariantType>>& 301 propertiesList) { 302 if (ec) 303 { 304 // this interface isn't necessary 305 return; 306 } 307 308 const std::string* partNumber = nullptr; 309 const std::string* serialNumber = nullptr; 310 const std::string* manufacturer = nullptr; 311 const std::string* model = nullptr; 312 313 const bool success = sdbusplus::unpackPropertiesNoThrow( 314 dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber", 315 partNumber, "SerialNumber", serialNumber, "Manufacturer", 316 manufacturer, "Model", model); 317 318 if (!success) 319 { 320 messages::internalError(asyncResp->res); 321 return; 322 } 323 324 if (partNumber != nullptr) 325 { 326 asyncResp->res.jsonValue["PartNumber"] = *partNumber; 327 } 328 329 if (serialNumber != nullptr) 330 { 331 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber; 332 } 333 334 if (manufacturer != nullptr) 335 { 336 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer; 337 } 338 339 if (model != nullptr) 340 { 341 asyncResp->res.jsonValue["Model"] = *model; 342 } 343 }); 344 } 345 346 inline void getDrivePresent(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 347 const std::string& connectionName, 348 const std::string& path) 349 { 350 sdbusplus::asio::getProperty<bool>( 351 *crow::connections::systemBus, connectionName, path, 352 "xyz.openbmc_project.Inventory.Item", "Present", 353 [asyncResp, path](const boost::system::error_code& ec, 354 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 sdbusplus::asio::getProperty<bool>( 375 *crow::connections::systemBus, connectionName, path, 376 "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> 416 convertDriveProtocol(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 444 getDriveItemProperties(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 445 const std::string& connectionName, 446 const std::string& path) 447 { 448 sdbusplus::asio::getAllProperties( 449 *crow::connections::systemBus, connectionName, path, 450 "xyz.openbmc_project.Inventory.Item.Drive", 451 [asyncResp](const boost::system::error_code& ec, 452 const std::vector< 453 std::pair<std::string, dbus::utility::DbusVariantType>>& 454 propertiesList) { 455 if (ec) 456 { 457 // this interface isn't required 458 return; 459 } 460 const std::string* encryptionStatus = nullptr; 461 const bool* isLocked = nullptr; 462 for (const std::pair<std::string, dbus::utility::DbusVariantType>& 463 property : propertiesList) 464 { 465 const std::string& propertyName = property.first; 466 if (propertyName == "Type") 467 { 468 const std::string* value = 469 std::get_if<std::string>(&property.second); 470 if (value == nullptr) 471 { 472 // illegal property 473 BMCWEB_LOG_ERROR("Illegal property: Type"); 474 messages::internalError(asyncResp->res); 475 return; 476 } 477 478 std::optional<drive::MediaType> mediaType = 479 convertDriveType(*value); 480 if (!mediaType) 481 { 482 BMCWEB_LOG_WARNING("UnknownDriveType Interface: {}", 483 *value); 484 continue; 485 } 486 if (*mediaType == drive::MediaType::Invalid) 487 { 488 messages::internalError(asyncResp->res); 489 return; 490 } 491 492 asyncResp->res.jsonValue["MediaType"] = *mediaType; 493 } 494 else if (propertyName == "Capacity") 495 { 496 const uint64_t* capacity = 497 std::get_if<uint64_t>(&property.second); 498 if (capacity == nullptr) 499 { 500 BMCWEB_LOG_ERROR("Illegal property: Capacity"); 501 messages::internalError(asyncResp->res); 502 return; 503 } 504 if (*capacity == 0) 505 { 506 // drive capacity not known 507 continue; 508 } 509 510 asyncResp->res.jsonValue["CapacityBytes"] = *capacity; 511 } 512 else if (propertyName == "Protocol") 513 { 514 const std::string* value = 515 std::get_if<std::string>(&property.second); 516 if (value == nullptr) 517 { 518 BMCWEB_LOG_ERROR("Illegal property: Protocol"); 519 messages::internalError(asyncResp->res); 520 return; 521 } 522 523 std::optional<protocol::Protocol> proto = 524 convertDriveProtocol(*value); 525 if (!proto) 526 { 527 BMCWEB_LOG_WARNING("Unknown DrivePrototype Interface: {}", 528 *value); 529 continue; 530 } 531 if (*proto == protocol::Protocol::Invalid) 532 { 533 messages::internalError(asyncResp->res); 534 return; 535 } 536 asyncResp->res.jsonValue["Protocol"] = *proto; 537 } 538 else if (propertyName == "PredictedMediaLifeLeftPercent") 539 { 540 const uint8_t* lifeLeft = 541 std::get_if<uint8_t>(&property.second); 542 if (lifeLeft == nullptr) 543 { 544 BMCWEB_LOG_ERROR( 545 "Illegal property: PredictedMediaLifeLeftPercent"); 546 messages::internalError(asyncResp->res); 547 return; 548 } 549 // 255 means reading the value is not supported 550 if (*lifeLeft != 255) 551 { 552 asyncResp->res.jsonValue["PredictedMediaLifeLeftPercent"] = 553 *lifeLeft; 554 } 555 } 556 else if (propertyName == "EncryptionStatus") 557 { 558 encryptionStatus = 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 static 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(asyncResp, 679 [](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"] = boost::urls::format( 795 "/redfish/v1/Chassis/{}/Drives/{}", 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 884 matchAndFillDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 885 const std::string& chassisId, 886 const std::string& driveName, 887 const std::vector<std::string>& resp) 888 { 889 for (const std::string& drivePath : resp) 890 { 891 sdbusplus::message::object_path path(drivePath); 892 std::string leaf = path.filename(); 893 if (leaf != driveName) 894 { 895 continue; 896 } 897 // mapper call drive 898 constexpr std::array<std::string_view, 1> driveInterface = { 899 "xyz.openbmc_project.Inventory.Item.Drive"}; 900 dbus::utility::getSubTree( 901 "/xyz/openbmc_project/inventory", 0, driveInterface, 902 [asyncResp, chassisId, driveName]( 903 const boost::system::error_code& ec, 904 const dbus::utility::MapperGetSubTreeResponse& subtree) { 905 buildDrive(asyncResp, chassisId, driveName, ec, subtree); 906 }); 907 } 908 } 909 910 inline void 911 handleChassisDriveGet(crow::App& app, const crow::Request& req, 912 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 913 const std::string& chassisId, 914 const std::string& driveName) 915 { 916 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 917 { 918 return; 919 } 920 constexpr std::array<std::string_view, 2> interfaces = { 921 "xyz.openbmc_project.Inventory.Item.Board", 922 "xyz.openbmc_project.Inventory.Item.Chassis"}; 923 924 // mapper call chassis 925 dbus::utility::getSubTree( 926 "/xyz/openbmc_project/inventory", 0, interfaces, 927 [asyncResp, chassisId, 928 driveName](const boost::system::error_code& ec, 929 const dbus::utility::MapperGetSubTreeResponse& subtree) { 930 if (ec) 931 { 932 messages::internalError(asyncResp->res); 933 return; 934 } 935 936 // Iterate over all retrieved ObjectPaths. 937 for (const auto& [path, connectionNames] : subtree) 938 { 939 sdbusplus::message::object_path objPath(path); 940 if (objPath.filename() != chassisId) 941 { 942 continue; 943 } 944 945 if (connectionNames.empty()) 946 { 947 BMCWEB_LOG_ERROR("Got 0 Connection names"); 948 continue; 949 } 950 951 dbus::utility::getAssociationEndPoints( 952 path + "/drive", 953 [asyncResp, chassisId, 954 driveName](const boost::system::error_code& ec3, 955 const dbus::utility::MapperEndPoints& resp) { 956 if (ec3) 957 { 958 return; // no drives = no failures 959 } 960 matchAndFillDrive(asyncResp, chassisId, driveName, resp); 961 }); 962 break; 963 } 964 }); 965 } 966 967 /** 968 * This URL will show the drive interface for the specific drive in the chassis 969 */ 970 inline void requestRoutesChassisDriveName(App& app) 971 { 972 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/<str>/") 973 .privileges(redfish::privileges::getChassis) 974 .methods(boost::beast::http::verb::get)( 975 std::bind_front(handleChassisDriveGet, std::ref(app))); 976 } 977 978 inline void getStorageControllerAsset( 979 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 980 const boost::system::error_code& ec, 981 const std::vector<std::pair<std::string, dbus::utility::DbusVariantType>>& 982 propertiesList) 983 { 984 if (ec) 985 { 986 // this interface isn't necessary 987 BMCWEB_LOG_DEBUG("Failed to get StorageControllerAsset"); 988 return; 989 } 990 991 const std::string* partNumber = nullptr; 992 const std::string* serialNumber = nullptr; 993 const std::string* manufacturer = nullptr; 994 const std::string* model = nullptr; 995 if (!sdbusplus::unpackPropertiesNoThrow( 996 dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber", 997 partNumber, "SerialNumber", serialNumber, "Manufacturer", 998 manufacturer, "Model", model)) 999 { 1000 messages::internalError(asyncResp->res); 1001 return; 1002 } 1003 1004 if (partNumber != nullptr) 1005 { 1006 asyncResp->res.jsonValue["PartNumber"] = *partNumber; 1007 } 1008 1009 if (serialNumber != nullptr) 1010 { 1011 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber; 1012 } 1013 1014 if (manufacturer != nullptr) 1015 { 1016 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer; 1017 } 1018 1019 if (model != nullptr) 1020 { 1021 asyncResp->res.jsonValue["Model"] = *model; 1022 } 1023 } 1024 1025 inline void populateStorageController( 1026 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1027 const std::string& controllerId, const std::string& connectionName, 1028 const std::string& path) 1029 { 1030 asyncResp->res.jsonValue["@odata.type"] = 1031 "#StorageController.v1_6_0.StorageController"; 1032 asyncResp->res.jsonValue["@odata.id"] = 1033 boost::urls::format("/redfish/v1/Systems/{}/Storage/1/Controllers/{}", 1034 BMCWEB_REDFISH_SYSTEM_URI_NAME, controllerId); 1035 asyncResp->res.jsonValue["Name"] = controllerId; 1036 asyncResp->res.jsonValue["Id"] = controllerId; 1037 asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled; 1038 1039 sdbusplus::asio::getProperty<bool>( 1040 *crow::connections::systemBus, connectionName, path, 1041 "xyz.openbmc_project.Inventory.Item", "Present", 1042 [asyncResp](const boost::system::error_code& ec, bool isPresent) { 1043 // this interface isn't necessary, only check it 1044 // if we get a good return 1045 if (ec) 1046 { 1047 BMCWEB_LOG_DEBUG("Failed to get Present property"); 1048 return; 1049 } 1050 if (!isPresent) 1051 { 1052 asyncResp->res.jsonValue["Status"]["State"] = 1053 resource::State::Absent; 1054 } 1055 }); 1056 1057 sdbusplus::asio::getAllProperties( 1058 *crow::connections::systemBus, connectionName, path, 1059 "xyz.openbmc_project.Inventory.Decorator.Asset", 1060 [asyncResp](const boost::system::error_code& ec, 1061 const std::vector< 1062 std::pair<std::string, dbus::utility::DbusVariantType>>& 1063 propertiesList) { 1064 getStorageControllerAsset(asyncResp, ec, propertiesList); 1065 }); 1066 } 1067 1068 inline void getStorageControllerHandler( 1069 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1070 const std::string& controllerId, const boost::system::error_code& ec, 1071 const dbus::utility::MapperGetSubTreeResponse& subtree) 1072 { 1073 if (ec || subtree.empty()) 1074 { 1075 // doesn't have to be there 1076 BMCWEB_LOG_DEBUG("Failed to handle StorageController"); 1077 return; 1078 } 1079 1080 for (const auto& [path, interfaceDict] : subtree) 1081 { 1082 sdbusplus::message::object_path object(path); 1083 std::string id = object.filename(); 1084 if (id.empty()) 1085 { 1086 BMCWEB_LOG_ERROR("Failed to find filename in {}", path); 1087 return; 1088 } 1089 if (id != controllerId) 1090 { 1091 continue; 1092 } 1093 1094 if (interfaceDict.size() != 1) 1095 { 1096 BMCWEB_LOG_ERROR("Connection size {}, greater than 1", 1097 interfaceDict.size()); 1098 messages::internalError(asyncResp->res); 1099 return; 1100 } 1101 1102 const std::string& connectionName = interfaceDict.front().first; 1103 populateStorageController(asyncResp, controllerId, connectionName, 1104 path); 1105 } 1106 } 1107 1108 inline void populateStorageControllerCollection( 1109 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1110 const boost::system::error_code& ec, 1111 const dbus::utility::MapperGetSubTreePathsResponse& controllerList) 1112 { 1113 nlohmann::json::array_t members; 1114 if (ec || controllerList.empty()) 1115 { 1116 asyncResp->res.jsonValue["Members"] = std::move(members); 1117 asyncResp->res.jsonValue["Members@odata.count"] = 0; 1118 BMCWEB_LOG_DEBUG("Failed to find any StorageController"); 1119 return; 1120 } 1121 1122 for (const std::string& path : controllerList) 1123 { 1124 std::string id = sdbusplus::message::object_path(path).filename(); 1125 if (id.empty()) 1126 { 1127 BMCWEB_LOG_ERROR("Failed to find filename in {}", path); 1128 return; 1129 } 1130 nlohmann::json::object_t member; 1131 member["@odata.id"] = boost::urls::format( 1132 "/redfish/v1/Systems/{}/Storage/1/Controllers/{}", 1133 BMCWEB_REDFISH_SYSTEM_URI_NAME, id); 1134 members.emplace_back(member); 1135 } 1136 asyncResp->res.jsonValue["Members@odata.count"] = members.size(); 1137 asyncResp->res.jsonValue["Members"] = std::move(members); 1138 } 1139 1140 inline void handleSystemsStorageControllerCollectionGet( 1141 App& app, const crow::Request& req, 1142 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1143 const std::string& systemName) 1144 { 1145 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1146 { 1147 BMCWEB_LOG_DEBUG( 1148 "Failed to setup Redfish Route for StorageController Collection"); 1149 return; 1150 } 1151 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 1152 { 1153 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1154 systemName); 1155 BMCWEB_LOG_DEBUG("Failed to find ComputerSystem of {}", systemName); 1156 return; 1157 } 1158 1159 asyncResp->res.jsonValue["@odata.type"] = 1160 "#StorageControllerCollection.StorageControllerCollection"; 1161 asyncResp->res.jsonValue["@odata.id"] = 1162 std::format("/redfish/v1/Systems/{}/Storage/1/Controllers", 1163 BMCWEB_REDFISH_SYSTEM_URI_NAME); 1164 asyncResp->res.jsonValue["Name"] = "Storage Controller Collection"; 1165 1166 constexpr std::array<std::string_view, 1> interfaces = { 1167 "xyz.openbmc_project.Inventory.Item.StorageController"}; 1168 dbus::utility::getSubTreePaths( 1169 "/xyz/openbmc_project/inventory", 0, interfaces, 1170 [asyncResp](const boost::system::error_code& ec, 1171 const dbus::utility::MapperGetSubTreePathsResponse& 1172 controllerList) { 1173 populateStorageControllerCollection(asyncResp, ec, controllerList); 1174 }); 1175 } 1176 1177 inline void handleSystemsStorageControllerGet( 1178 App& app, const crow::Request& req, 1179 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1180 const std::string& systemName, const std::string& controllerId) 1181 { 1182 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1183 { 1184 BMCWEB_LOG_DEBUG("Failed to setup Redfish Route for StorageController"); 1185 return; 1186 } 1187 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 1188 { 1189 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1190 systemName); 1191 BMCWEB_LOG_DEBUG("Failed to find ComputerSystem of {}", systemName); 1192 return; 1193 } 1194 constexpr std::array<std::string_view, 1> interfaces = { 1195 "xyz.openbmc_project.Inventory.Item.StorageController"}; 1196 dbus::utility::getSubTree( 1197 "/xyz/openbmc_project/inventory", 0, interfaces, 1198 [asyncResp, 1199 controllerId](const boost::system::error_code& ec, 1200 const dbus::utility::MapperGetSubTreeResponse& subtree) { 1201 getStorageControllerHandler(asyncResp, controllerId, ec, subtree); 1202 }); 1203 } 1204 1205 inline void requestRoutesStorageControllerCollection(App& app) 1206 { 1207 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Controllers/") 1208 .privileges(redfish::privileges::getStorageControllerCollection) 1209 .methods(boost::beast::http::verb::get)(std::bind_front( 1210 handleSystemsStorageControllerCollectionGet, std::ref(app))); 1211 } 1212 1213 inline void requestRoutesStorageController(App& app) 1214 { 1215 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Controllers/<str>") 1216 .privileges(redfish::privileges::getStorageController) 1217 .methods(boost::beast::http::verb::get)( 1218 std::bind_front(handleSystemsStorageControllerGet, std::ref(app))); 1219 } 1220 1221 } // namespace redfish 1222