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 handleSystemsStorageGet( 191 App& app, const crow::Request& req, 192 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 193 const std::string& systemName, const std::string& storageId) 194 { 195 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 196 { 197 return; 198 } 199 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 200 { 201 // Option currently returns no systems. TBD 202 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 203 systemName); 204 return; 205 } 206 207 constexpr std::array<std::string_view, 1> interfaces = { 208 "xyz.openbmc_project.Inventory.Item.Storage"}; 209 dbus::utility::getSubTree( 210 "/xyz/openbmc_project/inventory", 0, interfaces, 211 std::bind_front(afterSystemsStorageGetSubtree, asyncResp, storageId)); 212 } 213 214 inline void afterSubtree(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 215 const std::string& storageId, 216 const boost::system::error_code& ec, 217 const dbus::utility::MapperGetSubTreeResponse& subtree) 218 { 219 if (ec) 220 { 221 BMCWEB_LOG_DEBUG("requestRoutesStorage DBUS response error"); 222 messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage", 223 storageId); 224 return; 225 } 226 auto storage = std::ranges::find_if( 227 subtree, 228 [&storageId](const std::pair<std::string, 229 dbus::utility::MapperServiceMap>& object) { 230 return sdbusplus::message::object_path(object.first).filename() == 231 storageId; 232 }); 233 if (storage == subtree.end()) 234 { 235 messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage", 236 storageId); 237 return; 238 } 239 240 asyncResp->res.jsonValue["@odata.type"] = "#Storage.v1_13_0.Storage"; 241 asyncResp->res.jsonValue["@odata.id"] = 242 boost::urls::format("/redfish/v1/Storage/{}", storageId); 243 asyncResp->res.jsonValue["Name"] = "Storage"; 244 asyncResp->res.jsonValue["Id"] = storageId; 245 asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled; 246 247 // Storage subsystem to Storage link. 248 nlohmann::json::array_t storageServices; 249 nlohmann::json::object_t storageService; 250 storageService["@odata.id"] = 251 boost::urls::format("/redfish/v1/Systems/{}/Storage/{}", 252 BMCWEB_REDFISH_SYSTEM_URI_NAME, storageId); 253 storageServices.emplace_back(storageService); 254 asyncResp->res.jsonValue["Links"]["StorageServices"] = 255 std::move(storageServices); 256 asyncResp->res.jsonValue["Links"]["StorageServices@odata.count"] = 1; 257 } 258 259 inline void 260 handleStorageGet(App& app, const crow::Request& req, 261 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 262 const std::string& storageId) 263 { 264 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 265 { 266 BMCWEB_LOG_DEBUG("requestRoutesStorage setUpRedfishRoute failed"); 267 return; 268 } 269 270 constexpr std::array<std::string_view, 1> interfaces = { 271 "xyz.openbmc_project.Inventory.Item.Storage"}; 272 dbus::utility::getSubTree( 273 "/xyz/openbmc_project/inventory", 0, interfaces, 274 std::bind_front(afterSubtree, asyncResp, storageId)); 275 } 276 277 inline void requestRoutesStorage(App& app) 278 { 279 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/<str>/") 280 .privileges(redfish::privileges::getStorage) 281 .methods(boost::beast::http::verb::get)( 282 std::bind_front(handleSystemsStorageGet, std::ref(app))); 283 284 BMCWEB_ROUTE(app, "/redfish/v1/Storage/<str>/") 285 .privileges(redfish::privileges::getStorage) 286 .methods(boost::beast::http::verb::get)( 287 std::bind_front(handleStorageGet, std::ref(app))); 288 } 289 290 inline void getDriveAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 291 const std::string& connectionName, 292 const std::string& path) 293 { 294 sdbusplus::asio::getAllProperties( 295 *crow::connections::systemBus, connectionName, path, 296 "xyz.openbmc_project.Inventory.Decorator.Asset", 297 [asyncResp](const boost::system::error_code& ec, 298 const std::vector< 299 std::pair<std::string, dbus::utility::DbusVariantType>>& 300 propertiesList) { 301 if (ec) 302 { 303 // this interface isn't necessary 304 return; 305 } 306 307 const std::string* partNumber = nullptr; 308 const std::string* serialNumber = nullptr; 309 const std::string* manufacturer = nullptr; 310 const std::string* model = nullptr; 311 312 const bool success = sdbusplus::unpackPropertiesNoThrow( 313 dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber", 314 partNumber, "SerialNumber", serialNumber, "Manufacturer", 315 manufacturer, "Model", model); 316 317 if (!success) 318 { 319 messages::internalError(asyncResp->res); 320 return; 321 } 322 323 if (partNumber != nullptr) 324 { 325 asyncResp->res.jsonValue["PartNumber"] = *partNumber; 326 } 327 328 if (serialNumber != nullptr) 329 { 330 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber; 331 } 332 333 if (manufacturer != nullptr) 334 { 335 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer; 336 } 337 338 if (model != nullptr) 339 { 340 asyncResp->res.jsonValue["Model"] = *model; 341 } 342 }); 343 } 344 345 inline void getDrivePresent(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 346 const std::string& connectionName, 347 const std::string& path) 348 { 349 sdbusplus::asio::getProperty<bool>( 350 *crow::connections::systemBus, connectionName, path, 351 "xyz.openbmc_project.Inventory.Item", "Present", 352 [asyncResp, 353 path](const boost::system::error_code& ec, const bool isPresent) { 354 // this interface isn't necessary, only check it if 355 // we get a good return 356 if (ec) 357 { 358 return; 359 } 360 361 if (!isPresent) 362 { 363 asyncResp->res.jsonValue["Status"]["State"] = 364 resource::State::Absent; 365 } 366 }); 367 } 368 369 inline void getDriveState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 370 const std::string& connectionName, 371 const std::string& path) 372 { 373 sdbusplus::asio::getProperty<bool>( 374 *crow::connections::systemBus, connectionName, path, 375 "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 sdbusplus::asio::getAllProperties( 447 *crow::connections::systemBus, connectionName, path, 448 "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 constexpr std::array<std::string_view, 2> interfaces = { 821 "xyz.openbmc_project.Inventory.Item.Board", 822 "xyz.openbmc_project.Inventory.Item.Chassis"}; 823 dbus::utility::getSubTree( 824 "/xyz/openbmc_project/inventory", 0, interfaces, 825 std::bind_front(afterChassisDriveCollectionSubtreeGet, asyncResp, 826 chassisId)); 827 } 828 829 inline void requestRoutesChassisDrive(App& app) 830 { 831 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/") 832 .privileges(redfish::privileges::getDriveCollection) 833 .methods(boost::beast::http::verb::get)( 834 std::bind_front(chassisDriveCollectionGet, std::ref(app))); 835 } 836 837 inline void buildDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 838 const std::string& chassisId, 839 const std::string& driveName, 840 const boost::system::error_code& ec, 841 const dbus::utility::MapperGetSubTreeResponse& subtree) 842 { 843 if (ec) 844 { 845 BMCWEB_LOG_DEBUG("DBUS response error {}", ec); 846 messages::internalError(asyncResp->res); 847 return; 848 } 849 850 // Iterate over all retrieved ObjectPaths. 851 for (const auto& [path, connectionNames] : subtree) 852 { 853 sdbusplus::message::object_path objPath(path); 854 if (objPath.filename() != driveName) 855 { 856 continue; 857 } 858 859 if (connectionNames.empty()) 860 { 861 BMCWEB_LOG_ERROR("Got 0 Connection names"); 862 continue; 863 } 864 865 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 866 "/redfish/v1/Chassis/{}/Drives/{}", chassisId, driveName); 867 868 asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive"; 869 asyncResp->res.jsonValue["Name"] = driveName; 870 asyncResp->res.jsonValue["Id"] = driveName; 871 // default it to Enabled 872 asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled; 873 874 nlohmann::json::object_t linkChassisNav; 875 linkChassisNav["@odata.id"] = 876 boost::urls::format("/redfish/v1/Chassis/{}", chassisId); 877 asyncResp->res.jsonValue["Links"]["Chassis"] = linkChassisNav; 878 879 addAllDriveInfo(asyncResp, connectionNames[0].first, path, 880 connectionNames[0].second); 881 } 882 } 883 884 inline void matchAndFillDrive( 885 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 886 const std::string& chassisId, 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 handleChassisDriveGet( 911 crow::App& app, const crow::Request& req, 912 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 913 const std::string& chassisId, const std::string& driveName) 914 { 915 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 916 { 917 return; 918 } 919 constexpr std::array<std::string_view, 2> interfaces = { 920 "xyz.openbmc_project.Inventory.Item.Board", 921 "xyz.openbmc_project.Inventory.Item.Chassis"}; 922 923 // mapper call chassis 924 dbus::utility::getSubTree( 925 "/xyz/openbmc_project/inventory", 0, interfaces, 926 [asyncResp, chassisId, 927 driveName](const boost::system::error_code& ec, 928 const dbus::utility::MapperGetSubTreeResponse& subtree) { 929 if (ec) 930 { 931 messages::internalError(asyncResp->res); 932 return; 933 } 934 935 // Iterate over all retrieved ObjectPaths. 936 for (const auto& [path, connectionNames] : subtree) 937 { 938 sdbusplus::message::object_path objPath(path); 939 if (objPath.filename() != chassisId) 940 { 941 continue; 942 } 943 944 if (connectionNames.empty()) 945 { 946 BMCWEB_LOG_ERROR("Got 0 Connection names"); 947 continue; 948 } 949 950 dbus::utility::getAssociationEndPoints( 951 path + "/drive", 952 [asyncResp, chassisId, 953 driveName](const boost::system::error_code& ec3, 954 const dbus::utility::MapperEndPoints& resp) { 955 if (ec3) 956 { 957 return; // no drives = no failures 958 } 959 matchAndFillDrive(asyncResp, chassisId, driveName, 960 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