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