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