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