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