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 "human_sort.hpp" 25 #include "openbmc_dbus_rest.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"] = "Enabled"; 183 184 getDrives(asyncResp); 185 asyncResp->res.jsonValue["Controllers"]["@odata.id"] = 186 boost::urls::format("/redfish/v1/Systems/{}/Storage/{}/Controllers", 187 BMCWEB_REDFISH_SYSTEM_URI_NAME, storageId); 188 } 189 190 inline void 191 handleSystemsStorageGet(App& app, const crow::Request& req, 192 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 193 const std::string& systemName, 194 const std::string& storageId) 195 { 196 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 197 { 198 return; 199 } 200 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 201 { 202 // Option currently returns no systems. TBD 203 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 204 systemName); 205 return; 206 } 207 208 constexpr std::array<std::string_view, 1> interfaces = { 209 "xyz.openbmc_project.Inventory.Item.Storage"}; 210 dbus::utility::getSubTree( 211 "/xyz/openbmc_project/inventory", 0, interfaces, 212 std::bind_front(afterSystemsStorageGetSubtree, asyncResp, storageId)); 213 } 214 215 inline void afterSubtree(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 216 const std::string& storageId, 217 const boost::system::error_code& ec, 218 const dbus::utility::MapperGetSubTreeResponse& subtree) 219 { 220 if (ec) 221 { 222 BMCWEB_LOG_DEBUG("requestRoutesStorage DBUS response error"); 223 messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage", 224 storageId); 225 return; 226 } 227 auto storage = std::ranges::find_if( 228 subtree, 229 [&storageId](const std::pair<std::string, 230 dbus::utility::MapperServiceMap>& object) { 231 return sdbusplus::message::object_path(object.first).filename() == 232 storageId; 233 }); 234 if (storage == subtree.end()) 235 { 236 messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage", 237 storageId); 238 return; 239 } 240 241 asyncResp->res.jsonValue["@odata.type"] = "#Storage.v1_13_0.Storage"; 242 asyncResp->res.jsonValue["@odata.id"] = 243 boost::urls::format("/redfish/v1/Storage/{}", storageId); 244 asyncResp->res.jsonValue["Name"] = "Storage"; 245 asyncResp->res.jsonValue["Id"] = storageId; 246 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 247 248 // Storage subsystem to Storage link. 249 nlohmann::json::array_t storageServices; 250 nlohmann::json::object_t storageService; 251 storageService["@odata.id"] = 252 boost::urls::format("/redfish/v1/Systems/{}/Storage/{}", 253 BMCWEB_REDFISH_SYSTEM_URI_NAME, storageId); 254 storageServices.emplace_back(storageService); 255 asyncResp->res.jsonValue["Links"]["StorageServices"] = 256 std::move(storageServices); 257 asyncResp->res.jsonValue["Links"]["StorageServices@odata.count"] = 1; 258 } 259 260 inline void 261 handleStorageGet(App& app, const crow::Request& req, 262 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 263 const std::string& storageId) 264 { 265 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 266 { 267 BMCWEB_LOG_DEBUG("requestRoutesStorage setUpRedfishRoute failed"); 268 return; 269 } 270 271 constexpr std::array<std::string_view, 1> interfaces = { 272 "xyz.openbmc_project.Inventory.Item.Storage"}; 273 dbus::utility::getSubTree( 274 "/xyz/openbmc_project/inventory", 0, interfaces, 275 std::bind_front(afterSubtree, asyncResp, storageId)); 276 } 277 278 inline void requestRoutesStorage(App& app) 279 { 280 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/<str>/") 281 .privileges(redfish::privileges::getStorage) 282 .methods(boost::beast::http::verb::get)( 283 std::bind_front(handleSystemsStorageGet, std::ref(app))); 284 285 BMCWEB_ROUTE(app, "/redfish/v1/Storage/<str>/") 286 .privileges(redfish::privileges::getStorage) 287 .methods(boost::beast::http::verb::get)( 288 std::bind_front(handleStorageGet, std::ref(app))); 289 } 290 291 inline void getDriveAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 292 const std::string& connectionName, 293 const std::string& path) 294 { 295 sdbusplus::asio::getAllProperties( 296 *crow::connections::systemBus, connectionName, path, 297 "xyz.openbmc_project.Inventory.Decorator.Asset", 298 [asyncResp](const boost::system::error_code& ec, 299 const std::vector< 300 std::pair<std::string, dbus::utility::DbusVariantType>>& 301 propertiesList) { 302 if (ec) 303 { 304 // this interface isn't necessary 305 return; 306 } 307 308 const std::string* partNumber = nullptr; 309 const std::string* serialNumber = nullptr; 310 const std::string* manufacturer = nullptr; 311 const std::string* model = nullptr; 312 313 const bool success = sdbusplus::unpackPropertiesNoThrow( 314 dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber", 315 partNumber, "SerialNumber", serialNumber, "Manufacturer", 316 manufacturer, "Model", model); 317 318 if (!success) 319 { 320 messages::internalError(asyncResp->res); 321 return; 322 } 323 324 if (partNumber != nullptr) 325 { 326 asyncResp->res.jsonValue["PartNumber"] = *partNumber; 327 } 328 329 if (serialNumber != nullptr) 330 { 331 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber; 332 } 333 334 if (manufacturer != nullptr) 335 { 336 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer; 337 } 338 339 if (model != nullptr) 340 { 341 asyncResp->res.jsonValue["Model"] = *model; 342 } 343 }); 344 } 345 346 inline void getDrivePresent(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 347 const std::string& connectionName, 348 const std::string& path) 349 { 350 sdbusplus::asio::getProperty<bool>( 351 *crow::connections::systemBus, connectionName, path, 352 "xyz.openbmc_project.Inventory.Item", "Present", 353 [asyncResp, path](const boost::system::error_code& ec, 354 const bool isPresent) { 355 // this interface isn't necessary, only check it if 356 // we get a good return 357 if (ec) 358 { 359 return; 360 } 361 362 if (!isPresent) 363 { 364 asyncResp->res.jsonValue["Status"]["State"] = "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"] = "Updating"; 391 } 392 }); 393 } 394 395 inline std::optional<drive::MediaType> convertDriveType(std::string_view type) 396 { 397 if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.HDD") 398 { 399 return drive::MediaType::HDD; 400 } 401 if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.SSD") 402 { 403 return drive::MediaType::SSD; 404 } 405 if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.Unknown") 406 { 407 return std::nullopt; 408 } 409 410 return drive::MediaType::Invalid; 411 } 412 413 inline std::optional<protocol::Protocol> 414 convertDriveProtocol(std::string_view proto) 415 { 416 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SAS") 417 { 418 return protocol::Protocol::SAS; 419 } 420 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SATA") 421 { 422 return protocol::Protocol::SATA; 423 } 424 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.NVMe") 425 { 426 return protocol::Protocol::NVMe; 427 } 428 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.FC") 429 { 430 return protocol::Protocol::FC; 431 } 432 if (proto == 433 "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.Unknown") 434 { 435 return std::nullopt; 436 } 437 438 return protocol::Protocol::Invalid; 439 } 440 441 inline void 442 getDriveItemProperties(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 443 const std::string& connectionName, 444 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("Unknown DrivePrototype Interface: {}", 526 *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.jsonValue["PredictedMediaLifeLeftPercent"] = 551 *lifeLeft; 552 } 553 } 554 else if (propertyName == "EncryptionStatus") 555 { 556 encryptionStatus = std::get_if<std::string>(&property.second); 557 if (encryptionStatus == nullptr) 558 { 559 BMCWEB_LOG_ERROR("Illegal property: EncryptionStatus"); 560 messages::internalError(asyncResp->res); 561 return; 562 } 563 } 564 else if (propertyName == "Locked") 565 { 566 isLocked = std::get_if<bool>(&property.second); 567 if (isLocked == nullptr) 568 { 569 BMCWEB_LOG_ERROR("Illegal property: Locked"); 570 messages::internalError(asyncResp->res); 571 return; 572 } 573 } 574 } 575 576 if (encryptionStatus == nullptr || isLocked == nullptr || 577 *encryptionStatus == 578 "xyz.openbmc_project.Inventory.Item.Drive.DriveEncryptionState.Unknown") 579 { 580 return; 581 } 582 if (*encryptionStatus != 583 "xyz.openbmc_project.Inventory.Item.Drive.DriveEncryptionState.Encrypted") 584 { 585 //"The drive is not currently encrypted." 586 asyncResp->res.jsonValue["EncryptionStatus"] = 587 drive::EncryptionStatus::Unencrypted; 588 return; 589 } 590 if (*isLocked) 591 { 592 //"The drive is currently encrypted and the data is not 593 // accessible to the user." 594 asyncResp->res.jsonValue["EncryptionStatus"] = 595 drive::EncryptionStatus::Locked; 596 return; 597 } 598 // if not locked 599 // "The drive is currently encrypted but the data is accessible 600 // to the user in unencrypted form." 601 asyncResp->res.jsonValue["EncryptionStatus"] = 602 drive::EncryptionStatus::Unlocked; 603 }); 604 } 605 606 static void addAllDriveInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 607 const std::string& connectionName, 608 const std::string& path, 609 const std::vector<std::string>& interfaces) 610 { 611 for (const std::string& interface : interfaces) 612 { 613 if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset") 614 { 615 getDriveAsset(asyncResp, connectionName, path); 616 } 617 else if (interface == "xyz.openbmc_project.Inventory.Item") 618 { 619 getDrivePresent(asyncResp, connectionName, path); 620 } 621 else if (interface == "xyz.openbmc_project.State.Drive") 622 { 623 getDriveState(asyncResp, connectionName, path); 624 } 625 else if (interface == "xyz.openbmc_project.Inventory.Item.Drive") 626 { 627 getDriveItemProperties(asyncResp, connectionName, path); 628 } 629 } 630 } 631 632 inline void afterGetSubtreeSystemsStorageDrive( 633 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 634 const std::string& driveId, const boost::system::error_code& ec, 635 const dbus::utility::MapperGetSubTreeResponse& subtree) 636 { 637 if (ec) 638 { 639 BMCWEB_LOG_ERROR("Drive mapper call error"); 640 messages::internalError(asyncResp->res); 641 return; 642 } 643 644 auto drive = std::ranges::find_if( 645 subtree, 646 [&driveId](const std::pair<std::string, 647 dbus::utility::MapperServiceMap>& object) { 648 return sdbusplus::message::object_path(object.first).filename() == 649 driveId; 650 }); 651 652 if (drive == subtree.end()) 653 { 654 messages::resourceNotFound(asyncResp->res, "Drive", driveId); 655 return; 656 } 657 658 const std::string& path = drive->first; 659 const dbus::utility::MapperServiceMap& connectionNames = drive->second; 660 661 asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive"; 662 asyncResp->res.jsonValue["@odata.id"] = 663 boost::urls::format("/redfish/v1/Systems/{}/Storage/1/Drives/{}", 664 BMCWEB_REDFISH_SYSTEM_URI_NAME, driveId); 665 asyncResp->res.jsonValue["Name"] = driveId; 666 asyncResp->res.jsonValue["Id"] = driveId; 667 668 if (connectionNames.size() != 1) 669 { 670 BMCWEB_LOG_ERROR("Connection size {}, not equal to 1", 671 connectionNames.size()); 672 messages::internalError(asyncResp->res); 673 return; 674 } 675 676 getMainChassisId(asyncResp, 677 [](const std::string& chassisId, 678 const std::shared_ptr<bmcweb::AsyncResp>& aRsp) { 679 aRsp->res.jsonValue["Links"]["Chassis"]["@odata.id"] = 680 boost::urls::format("/redfish/v1/Chassis/{}", chassisId); 681 }); 682 683 // default it to Enabled 684 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 685 686 addAllDriveInfo(asyncResp, connectionNames[0].first, path, 687 connectionNames[0].second); 688 } 689 690 inline void handleSystemsStorageDriveGet( 691 App& app, const crow::Request& req, 692 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 693 const std::string& systemName, const std::string& driveId) 694 { 695 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 696 { 697 return; 698 } 699 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 700 { 701 // Option currently returns no systems. TBD 702 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 703 systemName); 704 return; 705 } 706 707 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 708 { 709 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 710 systemName); 711 return; 712 } 713 714 constexpr std::array<std::string_view, 1> interfaces = { 715 "xyz.openbmc_project.Inventory.Item.Drive"}; 716 dbus::utility::getSubTree( 717 "/xyz/openbmc_project/inventory", 0, interfaces, 718 std::bind_front(afterGetSubtreeSystemsStorageDrive, asyncResp, 719 driveId)); 720 } 721 722 inline void requestRoutesDrive(App& app) 723 { 724 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Drives/<str>/") 725 .privileges(redfish::privileges::getDrive) 726 .methods(boost::beast::http::verb::get)( 727 std::bind_front(handleSystemsStorageDriveGet, std::ref(app))); 728 } 729 730 inline void afterChassisDriveCollectionSubtreeGet( 731 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 732 const std::string& chassisId, const boost::system::error_code& ec, 733 const dbus::utility::MapperGetSubTreeResponse& subtree) 734 { 735 if (ec) 736 { 737 if (ec == boost::system::errc::host_unreachable) 738 { 739 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 740 return; 741 } 742 messages::internalError(asyncResp->res); 743 return; 744 } 745 746 // Iterate over all retrieved ObjectPaths. 747 for (const auto& [path, connectionNames] : subtree) 748 { 749 sdbusplus::message::object_path objPath(path); 750 if (objPath.filename() != chassisId) 751 { 752 continue; 753 } 754 755 if (connectionNames.empty()) 756 { 757 BMCWEB_LOG_ERROR("Got 0 Connection names"); 758 continue; 759 } 760 761 asyncResp->res.jsonValue["@odata.type"] = 762 "#DriveCollection.DriveCollection"; 763 asyncResp->res.jsonValue["@odata.id"] = 764 boost::urls::format("/redfish/v1/Chassis/{}/Drives", chassisId); 765 asyncResp->res.jsonValue["Name"] = "Drive Collection"; 766 767 // Association lambda 768 dbus::utility::getAssociationEndPoints( 769 path + "/drive", 770 [asyncResp, chassisId](const boost::system::error_code& ec3, 771 const dbus::utility::MapperEndPoints& resp) { 772 if (ec3) 773 { 774 BMCWEB_LOG_ERROR("Error in chassis Drive association "); 775 } 776 nlohmann::json& members = asyncResp->res.jsonValue["Members"]; 777 // important if array is empty 778 members = nlohmann::json::array(); 779 780 std::vector<std::string> leafNames; 781 for (const auto& drive : resp) 782 { 783 sdbusplus::message::object_path drivePath(drive); 784 leafNames.push_back(drivePath.filename()); 785 } 786 787 std::ranges::sort(leafNames, AlphanumLess<std::string>()); 788 789 for (const auto& leafName : leafNames) 790 { 791 nlohmann::json::object_t member; 792 member["@odata.id"] = boost::urls::format( 793 "/redfish/v1/Chassis/{}/Drives/{}", chassisId, leafName); 794 members.emplace_back(std::move(member)); 795 // navigation links will be registered in next patch set 796 } 797 asyncResp->res.jsonValue["Members@odata.count"] = resp.size(); 798 }); // end association lambda 799 800 } // end Iterate over all retrieved ObjectPaths 801 } 802 /** 803 * Chassis drives, this URL will show all the DriveCollection 804 * information 805 */ 806 inline void chassisDriveCollectionGet( 807 crow::App& app, const crow::Request& req, 808 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 809 const std::string& chassisId) 810 { 811 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 812 { 813 return; 814 } 815 816 // mapper call lambda 817 constexpr std::array<std::string_view, 2> interfaces = { 818 "xyz.openbmc_project.Inventory.Item.Board", 819 "xyz.openbmc_project.Inventory.Item.Chassis"}; 820 dbus::utility::getSubTree( 821 "/xyz/openbmc_project/inventory", 0, interfaces, 822 std::bind_front(afterChassisDriveCollectionSubtreeGet, asyncResp, 823 chassisId)); 824 } 825 826 inline void requestRoutesChassisDrive(App& app) 827 { 828 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/") 829 .privileges(redfish::privileges::getDriveCollection) 830 .methods(boost::beast::http::verb::get)( 831 std::bind_front(chassisDriveCollectionGet, std::ref(app))); 832 } 833 834 inline void buildDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 835 const std::string& chassisId, 836 const std::string& driveName, 837 const boost::system::error_code& ec, 838 const dbus::utility::MapperGetSubTreeResponse& subtree) 839 { 840 if (ec) 841 { 842 BMCWEB_LOG_DEBUG("DBUS response error {}", ec); 843 messages::internalError(asyncResp->res); 844 return; 845 } 846 847 // Iterate over all retrieved ObjectPaths. 848 for (const auto& [path, connectionNames] : subtree) 849 { 850 sdbusplus::message::object_path objPath(path); 851 if (objPath.filename() != driveName) 852 { 853 continue; 854 } 855 856 if (connectionNames.empty()) 857 { 858 BMCWEB_LOG_ERROR("Got 0 Connection names"); 859 continue; 860 } 861 862 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 863 "/redfish/v1/Chassis/{}/Drives/{}", chassisId, driveName); 864 865 asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive"; 866 asyncResp->res.jsonValue["Name"] = driveName; 867 asyncResp->res.jsonValue["Id"] = driveName; 868 // default it to Enabled 869 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 870 871 nlohmann::json::object_t linkChassisNav; 872 linkChassisNav["@odata.id"] = 873 boost::urls::format("/redfish/v1/Chassis/{}", chassisId); 874 asyncResp->res.jsonValue["Links"]["Chassis"] = linkChassisNav; 875 876 addAllDriveInfo(asyncResp, connectionNames[0].first, path, 877 connectionNames[0].second); 878 } 879 } 880 881 inline void 882 matchAndFillDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 883 const std::string& chassisId, 884 const std::string& driveName, 885 const std::vector<std::string>& resp) 886 { 887 for (const std::string& drivePath : resp) 888 { 889 sdbusplus::message::object_path path(drivePath); 890 std::string leaf = path.filename(); 891 if (leaf != driveName) 892 { 893 continue; 894 } 895 // mapper call drive 896 constexpr std::array<std::string_view, 1> driveInterface = { 897 "xyz.openbmc_project.Inventory.Item.Drive"}; 898 dbus::utility::getSubTree( 899 "/xyz/openbmc_project/inventory", 0, driveInterface, 900 [asyncResp, chassisId, driveName]( 901 const boost::system::error_code& ec, 902 const dbus::utility::MapperGetSubTreeResponse& subtree) { 903 buildDrive(asyncResp, chassisId, driveName, ec, subtree); 904 }); 905 } 906 } 907 908 inline void 909 handleChassisDriveGet(crow::App& app, const crow::Request& req, 910 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 911 const std::string& chassisId, 912 const std::string& driveName) 913 { 914 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 915 { 916 return; 917 } 918 constexpr std::array<std::string_view, 2> interfaces = { 919 "xyz.openbmc_project.Inventory.Item.Board", 920 "xyz.openbmc_project.Inventory.Item.Chassis"}; 921 922 // mapper call chassis 923 dbus::utility::getSubTree( 924 "/xyz/openbmc_project/inventory", 0, interfaces, 925 [asyncResp, chassisId, 926 driveName](const boost::system::error_code& ec, 927 const dbus::utility::MapperGetSubTreeResponse& subtree) { 928 if (ec) 929 { 930 messages::internalError(asyncResp->res); 931 return; 932 } 933 934 // Iterate over all retrieved ObjectPaths. 935 for (const auto& [path, connectionNames] : subtree) 936 { 937 sdbusplus::message::object_path objPath(path); 938 if (objPath.filename() != chassisId) 939 { 940 continue; 941 } 942 943 if (connectionNames.empty()) 944 { 945 BMCWEB_LOG_ERROR("Got 0 Connection names"); 946 continue; 947 } 948 949 dbus::utility::getAssociationEndPoints( 950 path + "/drive", 951 [asyncResp, chassisId, 952 driveName](const boost::system::error_code& ec3, 953 const dbus::utility::MapperEndPoints& resp) { 954 if (ec3) 955 { 956 return; // no drives = no failures 957 } 958 matchAndFillDrive(asyncResp, chassisId, driveName, resp); 959 }); 960 break; 961 } 962 }); 963 } 964 965 /** 966 * This URL will show the drive interface for the specific drive in the chassis 967 */ 968 inline void requestRoutesChassisDriveName(App& app) 969 { 970 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/<str>/") 971 .privileges(redfish::privileges::getChassis) 972 .methods(boost::beast::http::verb::get)( 973 std::bind_front(handleChassisDriveGet, std::ref(app))); 974 } 975 976 inline void getStorageControllerAsset( 977 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 978 const boost::system::error_code& ec, 979 const std::vector<std::pair<std::string, dbus::utility::DbusVariantType>>& 980 propertiesList) 981 { 982 if (ec) 983 { 984 // this interface isn't necessary 985 BMCWEB_LOG_DEBUG("Failed to get StorageControllerAsset"); 986 return; 987 } 988 989 const std::string* partNumber = nullptr; 990 const std::string* serialNumber = nullptr; 991 const std::string* manufacturer = nullptr; 992 const std::string* model = nullptr; 993 if (!sdbusplus::unpackPropertiesNoThrow( 994 dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber", 995 partNumber, "SerialNumber", serialNumber, "Manufacturer", 996 manufacturer, "Model", model)) 997 { 998 messages::internalError(asyncResp->res); 999 return; 1000 } 1001 1002 if (partNumber != nullptr) 1003 { 1004 asyncResp->res.jsonValue["PartNumber"] = *partNumber; 1005 } 1006 1007 if (serialNumber != nullptr) 1008 { 1009 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber; 1010 } 1011 1012 if (manufacturer != nullptr) 1013 { 1014 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer; 1015 } 1016 1017 if (model != nullptr) 1018 { 1019 asyncResp->res.jsonValue["Model"] = *model; 1020 } 1021 } 1022 1023 inline void populateStorageController( 1024 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1025 const std::string& controllerId, const std::string& connectionName, 1026 const std::string& path) 1027 { 1028 asyncResp->res.jsonValue["@odata.type"] = 1029 "#StorageController.v1_6_0.StorageController"; 1030 asyncResp->res.jsonValue["@odata.id"] = 1031 boost::urls::format("/redfish/v1/Systems/{}/Storage/1/Controllers/{}", 1032 BMCWEB_REDFISH_SYSTEM_URI_NAME, controllerId); 1033 asyncResp->res.jsonValue["Name"] = controllerId; 1034 asyncResp->res.jsonValue["Id"] = controllerId; 1035 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 1036 1037 sdbusplus::asio::getProperty<bool>( 1038 *crow::connections::systemBus, connectionName, path, 1039 "xyz.openbmc_project.Inventory.Item", "Present", 1040 [asyncResp](const boost::system::error_code& ec, bool isPresent) { 1041 // this interface isn't necessary, only check it 1042 // if we get a good return 1043 if (ec) 1044 { 1045 BMCWEB_LOG_DEBUG("Failed to get Present property"); 1046 return; 1047 } 1048 if (!isPresent) 1049 { 1050 asyncResp->res.jsonValue["Status"]["State"] = "Absent"; 1051 } 1052 }); 1053 1054 sdbusplus::asio::getAllProperties( 1055 *crow::connections::systemBus, connectionName, path, 1056 "xyz.openbmc_project.Inventory.Decorator.Asset", 1057 [asyncResp](const boost::system::error_code& ec, 1058 const std::vector< 1059 std::pair<std::string, dbus::utility::DbusVariantType>>& 1060 propertiesList) { 1061 getStorageControllerAsset(asyncResp, ec, propertiesList); 1062 }); 1063 } 1064 1065 inline void getStorageControllerHandler( 1066 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1067 const std::string& controllerId, const boost::system::error_code& ec, 1068 const dbus::utility::MapperGetSubTreeResponse& subtree) 1069 { 1070 if (ec || subtree.empty()) 1071 { 1072 // doesn't have to be there 1073 BMCWEB_LOG_DEBUG("Failed to handle StorageController"); 1074 return; 1075 } 1076 1077 for (const auto& [path, interfaceDict] : subtree) 1078 { 1079 sdbusplus::message::object_path object(path); 1080 std::string id = object.filename(); 1081 if (id.empty()) 1082 { 1083 BMCWEB_LOG_ERROR("Failed to find filename in {}", path); 1084 return; 1085 } 1086 if (id != controllerId) 1087 { 1088 continue; 1089 } 1090 1091 if (interfaceDict.size() != 1) 1092 { 1093 BMCWEB_LOG_ERROR("Connection size {}, greater than 1", 1094 interfaceDict.size()); 1095 messages::internalError(asyncResp->res); 1096 return; 1097 } 1098 1099 const std::string& connectionName = interfaceDict.front().first; 1100 populateStorageController(asyncResp, controllerId, connectionName, 1101 path); 1102 } 1103 } 1104 1105 inline void populateStorageControllerCollection( 1106 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1107 const boost::system::error_code& ec, 1108 const dbus::utility::MapperGetSubTreePathsResponse& controllerList) 1109 { 1110 nlohmann::json::array_t members; 1111 if (ec || controllerList.empty()) 1112 { 1113 asyncResp->res.jsonValue["Members"] = std::move(members); 1114 asyncResp->res.jsonValue["Members@odata.count"] = 0; 1115 BMCWEB_LOG_DEBUG("Failed to find any StorageController"); 1116 return; 1117 } 1118 1119 for (const std::string& path : controllerList) 1120 { 1121 std::string id = sdbusplus::message::object_path(path).filename(); 1122 if (id.empty()) 1123 { 1124 BMCWEB_LOG_ERROR("Failed to find filename in {}", path); 1125 return; 1126 } 1127 nlohmann::json::object_t member; 1128 member["@odata.id"] = boost::urls::format( 1129 "/redfish/v1/Systems/{}/Storage/1/Controllers/{}", 1130 BMCWEB_REDFISH_SYSTEM_URI_NAME, id); 1131 members.emplace_back(member); 1132 } 1133 asyncResp->res.jsonValue["Members@odata.count"] = members.size(); 1134 asyncResp->res.jsonValue["Members"] = std::move(members); 1135 } 1136 1137 inline void handleSystemsStorageControllerCollectionGet( 1138 App& app, const crow::Request& req, 1139 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1140 const std::string& systemName) 1141 { 1142 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1143 { 1144 BMCWEB_LOG_DEBUG( 1145 "Failed to setup Redfish Route for StorageController Collection"); 1146 return; 1147 } 1148 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 1149 { 1150 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1151 systemName); 1152 BMCWEB_LOG_DEBUG("Failed to find ComputerSystem of {}", systemName); 1153 return; 1154 } 1155 1156 asyncResp->res.jsonValue["@odata.type"] = 1157 "#StorageControllerCollection.StorageControllerCollection"; 1158 asyncResp->res.jsonValue["@odata.id"] = 1159 std::format("/redfish/v1/Systems/{}/Storage/1/Controllers", 1160 BMCWEB_REDFISH_SYSTEM_URI_NAME); 1161 asyncResp->res.jsonValue["Name"] = "Storage Controller Collection"; 1162 1163 constexpr std::array<std::string_view, 1> interfaces = { 1164 "xyz.openbmc_project.Inventory.Item.StorageController"}; 1165 dbus::utility::getSubTreePaths( 1166 "/xyz/openbmc_project/inventory", 0, interfaces, 1167 [asyncResp](const boost::system::error_code& ec, 1168 const dbus::utility::MapperGetSubTreePathsResponse& 1169 controllerList) { 1170 populateStorageControllerCollection(asyncResp, ec, controllerList); 1171 }); 1172 } 1173 1174 inline void handleSystemsStorageControllerGet( 1175 App& app, const crow::Request& req, 1176 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1177 const std::string& systemName, const std::string& controllerId) 1178 { 1179 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1180 { 1181 BMCWEB_LOG_DEBUG("Failed to setup Redfish Route for StorageController"); 1182 return; 1183 } 1184 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 1185 { 1186 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1187 systemName); 1188 BMCWEB_LOG_DEBUG("Failed to find ComputerSystem of {}", systemName); 1189 return; 1190 } 1191 constexpr std::array<std::string_view, 1> interfaces = { 1192 "xyz.openbmc_project.Inventory.Item.StorageController"}; 1193 dbus::utility::getSubTree( 1194 "/xyz/openbmc_project/inventory", 0, interfaces, 1195 [asyncResp, 1196 controllerId](const boost::system::error_code& ec, 1197 const dbus::utility::MapperGetSubTreeResponse& subtree) { 1198 getStorageControllerHandler(asyncResp, controllerId, ec, subtree); 1199 }); 1200 } 1201 1202 inline void requestRoutesStorageControllerCollection(App& app) 1203 { 1204 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Controllers/") 1205 .privileges(redfish::privileges::getStorageControllerCollection) 1206 .methods(boost::beast::http::verb::get)(std::bind_front( 1207 handleSystemsStorageControllerCollectionGet, std::ref(app))); 1208 } 1209 1210 inline void requestRoutesStorageController(App& app) 1211 { 1212 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Controllers/<str>") 1213 .privileges(redfish::privileges::getStorageController) 1214 .methods(boost::beast::http::verb::get)( 1215 std::bind_front(handleSystemsStorageControllerGet, std::ref(app))); 1216 } 1217 1218 } // namespace redfish 1219