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 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( 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