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 "query.hpp" 26 #include "redfish_util.hpp" 27 #include "registries/privilege_registry.hpp" 28 #include "utils/collection.hpp" 29 #include "utils/dbus_utils.hpp" 30 31 #include <boost/system/error_code.hpp> 32 #include <boost/url/format.hpp> 33 #include <sdbusplus/asio/property.hpp> 34 #include <sdbusplus/unpack_properties.hpp> 35 36 #include <array> 37 #include <ranges> 38 #include <string_view> 39 40 namespace redfish 41 { 42 43 inline void handleSystemsStorageCollectionGet( 44 App& app, const crow::Request& req, 45 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 46 const std::string& systemName) 47 { 48 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 49 { 50 return; 51 } 52 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 53 { 54 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 55 systemName); 56 return; 57 } 58 59 asyncResp->res.jsonValue["@odata.type"] = 60 "#StorageCollection.StorageCollection"; 61 asyncResp->res.jsonValue["@odata.id"] = std::format( 62 "/redfish/v1/Systems/{}/Storage", BMCWEB_REDFISH_SYSTEM_URI_NAME); 63 asyncResp->res.jsonValue["Name"] = "Storage Collection"; 64 65 constexpr std::array<std::string_view, 1> interface{ 66 "xyz.openbmc_project.Inventory.Item.Storage"}; 67 collection_util::getCollectionMembers( 68 asyncResp, 69 boost::urls::format("/redfish/v1/Systems/{}/Storage", 70 BMCWEB_REDFISH_SYSTEM_URI_NAME), 71 interface, "/xyz/openbmc_project/inventory"); 72 } 73 74 inline void handleStorageCollectionGet( 75 App& app, const crow::Request& req, 76 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 77 { 78 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 79 { 80 return; 81 } 82 asyncResp->res.jsonValue["@odata.type"] = 83 "#StorageCollection.StorageCollection"; 84 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Storage"; 85 asyncResp->res.jsonValue["Name"] = "Storage Collection"; 86 constexpr std::array<std::string_view, 1> interface{ 87 "xyz.openbmc_project.Inventory.Item.Storage"}; 88 collection_util::getCollectionMembers( 89 asyncResp, boost::urls::format("/redfish/v1/Storage"), interface, 90 "/xyz/openbmc_project/inventory"); 91 } 92 93 inline void requestRoutesStorageCollection(App& app) 94 { 95 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/") 96 .privileges(redfish::privileges::getStorageCollection) 97 .methods(boost::beast::http::verb::get)( 98 std::bind_front(handleSystemsStorageCollectionGet, std::ref(app))); 99 BMCWEB_ROUTE(app, "/redfish/v1/Storage/") 100 .privileges(redfish::privileges::getStorageCollection) 101 .methods(boost::beast::http::verb::get)( 102 std::bind_front(handleStorageCollectionGet, std::ref(app))); 103 } 104 105 inline void afterChassisDriveCollectionSubtree( 106 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 107 const boost::system::error_code& ec, 108 const dbus::utility::MapperGetSubTreePathsResponse& driveList) 109 { 110 if (ec) 111 { 112 BMCWEB_LOG_ERROR("Drive mapper call error"); 113 messages::internalError(asyncResp->res); 114 return; 115 } 116 117 nlohmann::json& driveArray = asyncResp->res.jsonValue["Drives"]; 118 driveArray = nlohmann::json::array(); 119 auto& count = asyncResp->res.jsonValue["Drives@odata.count"]; 120 count = 0; 121 122 for (const std::string& drive : driveList) 123 { 124 sdbusplus::message::object_path object(drive); 125 if (object.filename().empty()) 126 { 127 BMCWEB_LOG_ERROR("Failed to find filename in {}", drive); 128 return; 129 } 130 131 nlohmann::json::object_t driveJson; 132 driveJson["@odata.id"] = boost::urls::format( 133 "/redfish/v1/Systems/{}/Storage/1/Drives/{}", 134 BMCWEB_REDFISH_SYSTEM_URI_NAME, object.filename()); 135 driveArray.emplace_back(std::move(driveJson)); 136 } 137 138 count = driveArray.size(); 139 } 140 inline void getDrives(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 141 { 142 const std::array<std::string_view, 1> interfaces = { 143 "xyz.openbmc_project.Inventory.Item.Drive"}; 144 dbus::utility::getSubTreePaths( 145 "/xyz/openbmc_project/inventory", 0, interfaces, 146 std::bind_front(afterChassisDriveCollectionSubtree, asyncResp)); 147 } 148 149 inline void afterSystemsStorageGetSubtree( 150 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 151 const std::string& storageId, const boost::system::error_code& ec, 152 const dbus::utility::MapperGetSubTreeResponse& subtree) 153 { 154 if (ec) 155 { 156 BMCWEB_LOG_DEBUG("requestRoutesStorage DBUS response error"); 157 messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage", 158 storageId); 159 return; 160 } 161 auto storage = std::ranges::find_if( 162 subtree, 163 [&storageId](const std::pair<std::string, 164 dbus::utility::MapperServiceMap>& object) { 165 return sdbusplus::message::object_path(object.first).filename() == 166 storageId; 167 }); 168 if (storage == subtree.end()) 169 { 170 messages::resourceNotFound(asyncResp->res, "#Storage.v1_13_0.Storage", 171 storageId); 172 return; 173 } 174 175 asyncResp->res.jsonValue["@odata.type"] = "#Storage.v1_13_0.Storage"; 176 asyncResp->res.jsonValue["@odata.id"] = 177 boost::urls::format("/redfish/v1/Systems/{}/Storage/{}", 178 BMCWEB_REDFISH_SYSTEM_URI_NAME, storageId); 179 asyncResp->res.jsonValue["Name"] = "Storage"; 180 asyncResp->res.jsonValue["Id"] = storageId; 181 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 182 183 getDrives(asyncResp); 184 asyncResp->res.jsonValue["Controllers"]["@odata.id"] = 185 boost::urls::format("/redfish/v1/Systems/{}/Storage/{}/Controllers", 186 BMCWEB_REDFISH_SYSTEM_URI_NAME, storageId); 187 } 188 189 inline void 190 handleSystemsStorageGet(App& app, const crow::Request& req, 191 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 192 const std::string& systemName, 193 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"] = "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, path](const boost::system::error_code& ec, 353 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"] = "Absent"; 364 } 365 }); 366 } 367 368 inline void getDriveState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 369 const std::string& connectionName, 370 const std::string& path) 371 { 372 sdbusplus::asio::getProperty<bool>( 373 *crow::connections::systemBus, connectionName, path, 374 "xyz.openbmc_project.State.Drive", "Rebuilding", 375 [asyncResp](const boost::system::error_code& ec, const bool updating) { 376 // this interface isn't necessary, only check it 377 // if we get a good return 378 if (ec) 379 { 380 return; 381 } 382 383 // updating and disabled in the backend shouldn't be 384 // able to be set at the same time, so we don't need 385 // to check for the race condition of these two 386 // calls 387 if (updating) 388 { 389 asyncResp->res.jsonValue["Status"]["State"] = "Updating"; 390 } 391 }); 392 } 393 394 inline std::optional<drive::MediaType> convertDriveType(std::string_view type) 395 { 396 if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.HDD") 397 { 398 return drive::MediaType::HDD; 399 } 400 if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.SSD") 401 { 402 return drive::MediaType::SSD; 403 } 404 if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.Unknown") 405 { 406 return std::nullopt; 407 } 408 409 return drive::MediaType::Invalid; 410 } 411 412 inline std::optional<protocol::Protocol> 413 convertDriveProtocol(std::string_view proto) 414 { 415 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SAS") 416 { 417 return protocol::Protocol::SAS; 418 } 419 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SATA") 420 { 421 return protocol::Protocol::SATA; 422 } 423 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.NVMe") 424 { 425 return protocol::Protocol::NVMe; 426 } 427 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.FC") 428 { 429 return protocol::Protocol::FC; 430 } 431 if (proto == 432 "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.Unknown") 433 { 434 return std::nullopt; 435 } 436 437 return protocol::Protocol::Invalid; 438 } 439 440 inline void 441 getDriveItemProperties(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 442 const std::string& connectionName, 443 const std::string& path) 444 { 445 sdbusplus::asio::getAllProperties( 446 *crow::connections::systemBus, connectionName, path, 447 "xyz.openbmc_project.Inventory.Item.Drive", 448 [asyncResp](const boost::system::error_code& ec, 449 const std::vector< 450 std::pair<std::string, dbus::utility::DbusVariantType>>& 451 propertiesList) { 452 if (ec) 453 { 454 // this interface isn't required 455 return; 456 } 457 const std::string* encryptionStatus = nullptr; 458 const bool* isLocked = nullptr; 459 for (const std::pair<std::string, dbus::utility::DbusVariantType>& 460 property : propertiesList) 461 { 462 const std::string& propertyName = property.first; 463 if (propertyName == "Type") 464 { 465 const std::string* value = 466 std::get_if<std::string>(&property.second); 467 if (value == nullptr) 468 { 469 // illegal property 470 BMCWEB_LOG_ERROR("Illegal property: Type"); 471 messages::internalError(asyncResp->res); 472 return; 473 } 474 475 std::optional<drive::MediaType> mediaType = 476 convertDriveType(*value); 477 if (!mediaType) 478 { 479 BMCWEB_LOG_WARNING("UnknownDriveType Interface: {}", 480 *value); 481 continue; 482 } 483 if (*mediaType == drive::MediaType::Invalid) 484 { 485 messages::internalError(asyncResp->res); 486 return; 487 } 488 489 asyncResp->res.jsonValue["MediaType"] = *mediaType; 490 } 491 else if (propertyName == "Capacity") 492 { 493 const uint64_t* capacity = 494 std::get_if<uint64_t>(&property.second); 495 if (capacity == nullptr) 496 { 497 BMCWEB_LOG_ERROR("Illegal property: Capacity"); 498 messages::internalError(asyncResp->res); 499 return; 500 } 501 if (*capacity == 0) 502 { 503 // drive capacity not known 504 continue; 505 } 506 507 asyncResp->res.jsonValue["CapacityBytes"] = *capacity; 508 } 509 else if (propertyName == "Protocol") 510 { 511 const std::string* value = 512 std::get_if<std::string>(&property.second); 513 if (value == nullptr) 514 { 515 BMCWEB_LOG_ERROR("Illegal property: Protocol"); 516 messages::internalError(asyncResp->res); 517 return; 518 } 519 520 std::optional<protocol::Protocol> proto = 521 convertDriveProtocol(*value); 522 if (!proto) 523 { 524 BMCWEB_LOG_WARNING("Unknown DrivePrototype Interface: {}", 525 *value); 526 continue; 527 } 528 if (*proto == protocol::Protocol::Invalid) 529 { 530 messages::internalError(asyncResp->res); 531 return; 532 } 533 asyncResp->res.jsonValue["Protocol"] = *proto; 534 } 535 else if (propertyName == "PredictedMediaLifeLeftPercent") 536 { 537 const uint8_t* lifeLeft = 538 std::get_if<uint8_t>(&property.second); 539 if (lifeLeft == nullptr) 540 { 541 BMCWEB_LOG_ERROR( 542 "Illegal property: PredictedMediaLifeLeftPercent"); 543 messages::internalError(asyncResp->res); 544 return; 545 } 546 // 255 means reading the value is not supported 547 if (*lifeLeft != 255) 548 { 549 asyncResp->res.jsonValue["PredictedMediaLifeLeftPercent"] = 550 *lifeLeft; 551 } 552 } 553 else if (propertyName == "EncryptionStatus") 554 { 555 encryptionStatus = std::get_if<std::string>(&property.second); 556 if (encryptionStatus == nullptr) 557 { 558 BMCWEB_LOG_ERROR("Illegal property: EncryptionStatus"); 559 messages::internalError(asyncResp->res); 560 return; 561 } 562 } 563 else if (propertyName == "Locked") 564 { 565 isLocked = std::get_if<bool>(&property.second); 566 if (isLocked == nullptr) 567 { 568 BMCWEB_LOG_ERROR("Illegal property: Locked"); 569 messages::internalError(asyncResp->res); 570 return; 571 } 572 } 573 } 574 575 if (encryptionStatus == nullptr || isLocked == nullptr || 576 *encryptionStatus == 577 "xyz.openbmc_project.Inventory.Item.Drive.DriveEncryptionState.Unknown") 578 { 579 return; 580 } 581 if (*encryptionStatus != 582 "xyz.openbmc_project.Inventory.Item.Drive.DriveEncryptionState.Encrypted") 583 { 584 //"The drive is not currently encrypted." 585 asyncResp->res.jsonValue["EncryptionStatus"] = 586 drive::EncryptionStatus::Unencrypted; 587 return; 588 } 589 if (*isLocked) 590 { 591 //"The drive is currently encrypted and the data is not 592 // accessible to the user." 593 asyncResp->res.jsonValue["EncryptionStatus"] = 594 drive::EncryptionStatus::Locked; 595 return; 596 } 597 // if not locked 598 // "The drive is currently encrypted but the data is accessible 599 // to the user in unencrypted form." 600 asyncResp->res.jsonValue["EncryptionStatus"] = 601 drive::EncryptionStatus::Unlocked; 602 }); 603 } 604 605 static void addAllDriveInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 606 const std::string& connectionName, 607 const std::string& path, 608 const std::vector<std::string>& interfaces) 609 { 610 for (const std::string& interface : interfaces) 611 { 612 if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset") 613 { 614 getDriveAsset(asyncResp, connectionName, path); 615 } 616 else if (interface == "xyz.openbmc_project.Inventory.Item") 617 { 618 getDrivePresent(asyncResp, connectionName, path); 619 } 620 else if (interface == "xyz.openbmc_project.State.Drive") 621 { 622 getDriveState(asyncResp, connectionName, path); 623 } 624 else if (interface == "xyz.openbmc_project.Inventory.Item.Drive") 625 { 626 getDriveItemProperties(asyncResp, connectionName, path); 627 } 628 } 629 } 630 631 inline void afterGetSubtreeSystemsStorageDrive( 632 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 633 const std::string& driveId, const boost::system::error_code& ec, 634 const dbus::utility::MapperGetSubTreeResponse& subtree) 635 { 636 if (ec) 637 { 638 BMCWEB_LOG_ERROR("Drive mapper call error"); 639 messages::internalError(asyncResp->res); 640 return; 641 } 642 643 auto drive = std::ranges::find_if( 644 subtree, 645 [&driveId](const std::pair<std::string, 646 dbus::utility::MapperServiceMap>& object) { 647 return sdbusplus::message::object_path(object.first).filename() == 648 driveId; 649 }); 650 651 if (drive == subtree.end()) 652 { 653 messages::resourceNotFound(asyncResp->res, "Drive", driveId); 654 return; 655 } 656 657 const std::string& path = drive->first; 658 const dbus::utility::MapperServiceMap& connectionNames = drive->second; 659 660 asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive"; 661 asyncResp->res.jsonValue["@odata.id"] = 662 boost::urls::format("/redfish/v1/Systems/{}/Storage/1/Drives/{}", 663 BMCWEB_REDFISH_SYSTEM_URI_NAME, driveId); 664 asyncResp->res.jsonValue["Name"] = driveId; 665 asyncResp->res.jsonValue["Id"] = driveId; 666 667 if (connectionNames.size() != 1) 668 { 669 BMCWEB_LOG_ERROR("Connection size {}, not equal to 1", 670 connectionNames.size()); 671 messages::internalError(asyncResp->res); 672 return; 673 } 674 675 getMainChassisId(asyncResp, 676 [](const std::string& chassisId, 677 const std::shared_ptr<bmcweb::AsyncResp>& aRsp) { 678 aRsp->res.jsonValue["Links"]["Chassis"]["@odata.id"] = 679 boost::urls::format("/redfish/v1/Chassis/{}", chassisId); 680 }); 681 682 // default it to Enabled 683 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 684 685 addAllDriveInfo(asyncResp, connectionNames[0].first, path, 686 connectionNames[0].second); 687 } 688 689 inline void handleSystemsStorageDriveGet( 690 App& app, const crow::Request& req, 691 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 692 const std::string& systemName, const std::string& driveId) 693 { 694 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 695 { 696 return; 697 } 698 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 699 { 700 // Option currently returns no systems. TBD 701 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 702 systemName); 703 return; 704 } 705 706 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 707 { 708 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 709 systemName); 710 return; 711 } 712 713 constexpr std::array<std::string_view, 1> interfaces = { 714 "xyz.openbmc_project.Inventory.Item.Drive"}; 715 dbus::utility::getSubTree( 716 "/xyz/openbmc_project/inventory", 0, interfaces, 717 std::bind_front(afterGetSubtreeSystemsStorageDrive, asyncResp, 718 driveId)); 719 } 720 721 inline void requestRoutesDrive(App& app) 722 { 723 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Drives/<str>/") 724 .privileges(redfish::privileges::getDrive) 725 .methods(boost::beast::http::verb::get)( 726 std::bind_front(handleSystemsStorageDriveGet, std::ref(app))); 727 } 728 729 inline void afterChassisDriveCollectionSubtreeGet( 730 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 731 const std::string& chassisId, const boost::system::error_code& ec, 732 const dbus::utility::MapperGetSubTreeResponse& subtree) 733 { 734 if (ec) 735 { 736 if (ec == boost::system::errc::host_unreachable) 737 { 738 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 739 return; 740 } 741 messages::internalError(asyncResp->res); 742 return; 743 } 744 745 // Iterate over all retrieved ObjectPaths. 746 for (const auto& [path, connectionNames] : subtree) 747 { 748 sdbusplus::message::object_path objPath(path); 749 if (objPath.filename() != chassisId) 750 { 751 continue; 752 } 753 754 if (connectionNames.empty()) 755 { 756 BMCWEB_LOG_ERROR("Got 0 Connection names"); 757 continue; 758 } 759 760 asyncResp->res.jsonValue["@odata.type"] = 761 "#DriveCollection.DriveCollection"; 762 asyncResp->res.jsonValue["@odata.id"] = 763 boost::urls::format("/redfish/v1/Chassis/{}/Drives", chassisId); 764 asyncResp->res.jsonValue["Name"] = "Drive Collection"; 765 766 // Association lambda 767 dbus::utility::getAssociationEndPoints( 768 path + "/drive", 769 [asyncResp, chassisId](const boost::system::error_code& ec3, 770 const dbus::utility::MapperEndPoints& resp) { 771 if (ec3) 772 { 773 BMCWEB_LOG_ERROR("Error in chassis Drive association "); 774 } 775 nlohmann::json& members = asyncResp->res.jsonValue["Members"]; 776 // important if array is empty 777 members = nlohmann::json::array(); 778 779 std::vector<std::string> leafNames; 780 for (const auto& drive : resp) 781 { 782 sdbusplus::message::object_path drivePath(drive); 783 leafNames.push_back(drivePath.filename()); 784 } 785 786 std::ranges::sort(leafNames, AlphanumLess<std::string>()); 787 788 for (const auto& leafName : leafNames) 789 { 790 nlohmann::json::object_t member; 791 member["@odata.id"] = boost::urls::format( 792 "/redfish/v1/Chassis/{}/Drives/{}", 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"] = "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 881 matchAndFillDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 882 const std::string& chassisId, 883 const std::string& driveName, 884 const std::vector<std::string>& resp) 885 { 886 for (const std::string& drivePath : resp) 887 { 888 sdbusplus::message::object_path path(drivePath); 889 std::string leaf = path.filename(); 890 if (leaf != driveName) 891 { 892 continue; 893 } 894 // mapper call drive 895 constexpr std::array<std::string_view, 1> driveInterface = { 896 "xyz.openbmc_project.Inventory.Item.Drive"}; 897 dbus::utility::getSubTree( 898 "/xyz/openbmc_project/inventory", 0, driveInterface, 899 [asyncResp, chassisId, driveName]( 900 const boost::system::error_code& ec, 901 const dbus::utility::MapperGetSubTreeResponse& subtree) { 902 buildDrive(asyncResp, chassisId, driveName, ec, subtree); 903 }); 904 } 905 } 906 907 inline void 908 handleChassisDriveGet(crow::App& app, const crow::Request& req, 909 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 910 const std::string& chassisId, 911 const std::string& driveName) 912 { 913 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 914 { 915 return; 916 } 917 constexpr std::array<std::string_view, 2> interfaces = { 918 "xyz.openbmc_project.Inventory.Item.Board", 919 "xyz.openbmc_project.Inventory.Item.Chassis"}; 920 921 // mapper call chassis 922 dbus::utility::getSubTree( 923 "/xyz/openbmc_project/inventory", 0, interfaces, 924 [asyncResp, chassisId, 925 driveName](const boost::system::error_code& ec, 926 const dbus::utility::MapperGetSubTreeResponse& subtree) { 927 if (ec) 928 { 929 messages::internalError(asyncResp->res); 930 return; 931 } 932 933 // Iterate over all retrieved ObjectPaths. 934 for (const auto& [path, connectionNames] : subtree) 935 { 936 sdbusplus::message::object_path objPath(path); 937 if (objPath.filename() != chassisId) 938 { 939 continue; 940 } 941 942 if (connectionNames.empty()) 943 { 944 BMCWEB_LOG_ERROR("Got 0 Connection names"); 945 continue; 946 } 947 948 dbus::utility::getAssociationEndPoints( 949 path + "/drive", 950 [asyncResp, chassisId, 951 driveName](const boost::system::error_code& ec3, 952 const dbus::utility::MapperEndPoints& resp) { 953 if (ec3) 954 { 955 return; // no drives = no failures 956 } 957 matchAndFillDrive(asyncResp, chassisId, driveName, resp); 958 }); 959 break; 960 } 961 }); 962 } 963 964 /** 965 * This URL will show the drive interface for the specific drive in the chassis 966 */ 967 inline void requestRoutesChassisDriveName(App& app) 968 { 969 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/<str>/") 970 .privileges(redfish::privileges::getChassis) 971 .methods(boost::beast::http::verb::get)( 972 std::bind_front(handleChassisDriveGet, std::ref(app))); 973 } 974 975 inline void getStorageControllerAsset( 976 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 977 const boost::system::error_code& ec, 978 const std::vector<std::pair<std::string, dbus::utility::DbusVariantType>>& 979 propertiesList) 980 { 981 if (ec) 982 { 983 // this interface isn't necessary 984 BMCWEB_LOG_DEBUG("Failed to get StorageControllerAsset"); 985 return; 986 } 987 988 const std::string* partNumber = nullptr; 989 const std::string* serialNumber = nullptr; 990 const std::string* manufacturer = nullptr; 991 const std::string* model = nullptr; 992 if (!sdbusplus::unpackPropertiesNoThrow( 993 dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber", 994 partNumber, "SerialNumber", serialNumber, "Manufacturer", 995 manufacturer, "Model", model)) 996 { 997 messages::internalError(asyncResp->res); 998 return; 999 } 1000 1001 if (partNumber != nullptr) 1002 { 1003 asyncResp->res.jsonValue["PartNumber"] = *partNumber; 1004 } 1005 1006 if (serialNumber != nullptr) 1007 { 1008 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber; 1009 } 1010 1011 if (manufacturer != nullptr) 1012 { 1013 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer; 1014 } 1015 1016 if (model != nullptr) 1017 { 1018 asyncResp->res.jsonValue["Model"] = *model; 1019 } 1020 } 1021 1022 inline void populateStorageController( 1023 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1024 const std::string& controllerId, const std::string& connectionName, 1025 const std::string& path) 1026 { 1027 asyncResp->res.jsonValue["@odata.type"] = 1028 "#StorageController.v1_6_0.StorageController"; 1029 asyncResp->res.jsonValue["@odata.id"] = 1030 boost::urls::format("/redfish/v1/Systems/{}/Storage/1/Controllers/{}", 1031 BMCWEB_REDFISH_SYSTEM_URI_NAME, controllerId); 1032 asyncResp->res.jsonValue["Name"] = controllerId; 1033 asyncResp->res.jsonValue["Id"] = controllerId; 1034 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 1035 1036 sdbusplus::asio::getProperty<bool>( 1037 *crow::connections::systemBus, connectionName, path, 1038 "xyz.openbmc_project.Inventory.Item", "Present", 1039 [asyncResp](const boost::system::error_code& ec, bool isPresent) { 1040 // this interface isn't necessary, only check it 1041 // if we get a good return 1042 if (ec) 1043 { 1044 BMCWEB_LOG_DEBUG("Failed to get Present property"); 1045 return; 1046 } 1047 if (!isPresent) 1048 { 1049 asyncResp->res.jsonValue["Status"]["State"] = "Absent"; 1050 } 1051 }); 1052 1053 sdbusplus::asio::getAllProperties( 1054 *crow::connections::systemBus, connectionName, path, 1055 "xyz.openbmc_project.Inventory.Decorator.Asset", 1056 [asyncResp](const boost::system::error_code& ec, 1057 const std::vector< 1058 std::pair<std::string, dbus::utility::DbusVariantType>>& 1059 propertiesList) { 1060 getStorageControllerAsset(asyncResp, ec, propertiesList); 1061 }); 1062 } 1063 1064 inline void getStorageControllerHandler( 1065 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1066 const std::string& controllerId, const boost::system::error_code& ec, 1067 const dbus::utility::MapperGetSubTreeResponse& subtree) 1068 { 1069 if (ec || subtree.empty()) 1070 { 1071 // doesn't have to be there 1072 BMCWEB_LOG_DEBUG("Failed to handle StorageController"); 1073 return; 1074 } 1075 1076 for (const auto& [path, interfaceDict] : subtree) 1077 { 1078 sdbusplus::message::object_path object(path); 1079 std::string id = object.filename(); 1080 if (id.empty()) 1081 { 1082 BMCWEB_LOG_ERROR("Failed to find filename in {}", path); 1083 return; 1084 } 1085 if (id != controllerId) 1086 { 1087 continue; 1088 } 1089 1090 if (interfaceDict.size() != 1) 1091 { 1092 BMCWEB_LOG_ERROR("Connection size {}, greater than 1", 1093 interfaceDict.size()); 1094 messages::internalError(asyncResp->res); 1095 return; 1096 } 1097 1098 const std::string& connectionName = interfaceDict.front().first; 1099 populateStorageController(asyncResp, controllerId, connectionName, 1100 path); 1101 } 1102 } 1103 1104 inline void populateStorageControllerCollection( 1105 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1106 const boost::system::error_code& ec, 1107 const dbus::utility::MapperGetSubTreePathsResponse& controllerList) 1108 { 1109 nlohmann::json::array_t members; 1110 if (ec || controllerList.empty()) 1111 { 1112 asyncResp->res.jsonValue["Members"] = std::move(members); 1113 asyncResp->res.jsonValue["Members@odata.count"] = 0; 1114 BMCWEB_LOG_DEBUG("Failed to find any StorageController"); 1115 return; 1116 } 1117 1118 for (const std::string& path : controllerList) 1119 { 1120 std::string id = sdbusplus::message::object_path(path).filename(); 1121 if (id.empty()) 1122 { 1123 BMCWEB_LOG_ERROR("Failed to find filename in {}", path); 1124 return; 1125 } 1126 nlohmann::json::object_t member; 1127 member["@odata.id"] = boost::urls::format( 1128 "/redfish/v1/Systems/{}/Storage/1/Controllers/{}", 1129 BMCWEB_REDFISH_SYSTEM_URI_NAME, id); 1130 members.emplace_back(member); 1131 } 1132 asyncResp->res.jsonValue["Members@odata.count"] = members.size(); 1133 asyncResp->res.jsonValue["Members"] = std::move(members); 1134 } 1135 1136 inline void handleSystemsStorageControllerCollectionGet( 1137 App& app, const crow::Request& req, 1138 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1139 const std::string& systemName) 1140 { 1141 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1142 { 1143 BMCWEB_LOG_DEBUG( 1144 "Failed to setup Redfish Route for StorageController Collection"); 1145 return; 1146 } 1147 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 1148 { 1149 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1150 systemName); 1151 BMCWEB_LOG_DEBUG("Failed to find ComputerSystem of {}", systemName); 1152 return; 1153 } 1154 1155 asyncResp->res.jsonValue["@odata.type"] = 1156 "#StorageControllerCollection.StorageControllerCollection"; 1157 asyncResp->res.jsonValue["@odata.id"] = 1158 std::format("/redfish/v1/Systems/{}/Storage/1/Controllers", 1159 BMCWEB_REDFISH_SYSTEM_URI_NAME); 1160 asyncResp->res.jsonValue["Name"] = "Storage Controller Collection"; 1161 1162 constexpr std::array<std::string_view, 1> interfaces = { 1163 "xyz.openbmc_project.Inventory.Item.StorageController"}; 1164 dbus::utility::getSubTreePaths( 1165 "/xyz/openbmc_project/inventory", 0, interfaces, 1166 [asyncResp](const boost::system::error_code& ec, 1167 const dbus::utility::MapperGetSubTreePathsResponse& 1168 controllerList) { 1169 populateStorageControllerCollection(asyncResp, ec, controllerList); 1170 }); 1171 } 1172 1173 inline void handleSystemsStorageControllerGet( 1174 App& app, const crow::Request& req, 1175 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1176 const std::string& systemName, const std::string& controllerId) 1177 { 1178 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1179 { 1180 BMCWEB_LOG_DEBUG("Failed to setup Redfish Route for StorageController"); 1181 return; 1182 } 1183 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 1184 { 1185 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1186 systemName); 1187 BMCWEB_LOG_DEBUG("Failed to find ComputerSystem of {}", systemName); 1188 return; 1189 } 1190 constexpr std::array<std::string_view, 1> interfaces = { 1191 "xyz.openbmc_project.Inventory.Item.StorageController"}; 1192 dbus::utility::getSubTree( 1193 "/xyz/openbmc_project/inventory", 0, interfaces, 1194 [asyncResp, 1195 controllerId](const boost::system::error_code& ec, 1196 const dbus::utility::MapperGetSubTreeResponse& subtree) { 1197 getStorageControllerHandler(asyncResp, controllerId, ec, subtree); 1198 }); 1199 } 1200 1201 inline void requestRoutesStorageControllerCollection(App& app) 1202 { 1203 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Controllers/") 1204 .privileges(redfish::privileges::getStorageControllerCollection) 1205 .methods(boost::beast::http::verb::get)(std::bind_front( 1206 handleSystemsStorageControllerCollectionGet, std::ref(app))); 1207 } 1208 1209 inline void requestRoutesStorageController(App& app) 1210 { 1211 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Controllers/<str>") 1212 .privileges(redfish::privileges::getStorageController) 1213 .methods(boost::beast::http::verb::get)( 1214 std::bind_front(handleSystemsStorageControllerGet, std::ref(app))); 1215 } 1216 1217 } // namespace redfish 1218