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/dbus_utils.hpp" 31 32 #include <boost/system/error_code.hpp> 33 #include <boost/url/format.hpp> 34 #include <sdbusplus/asio/property.hpp> 35 #include <sdbusplus/unpack_properties.hpp> 36 37 #include <array> 38 #include <string_view> 39 40 namespace redfish 41 { 42 inline void requestRoutesStorageCollection(App& app) 43 { 44 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/") 45 .privileges(redfish::privileges::getStorageCollection) 46 .methods(boost::beast::http::verb::get)( 47 [&app](const crow::Request& req, 48 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 49 const std::string& systemName) { 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 nlohmann::json::array_t members; 67 nlohmann::json::object_t member; 68 member["@odata.id"] = "/redfish/v1/Systems/system/Storage/1"; 69 members.emplace_back(member); 70 asyncResp->res.jsonValue["Members"] = std::move(members); 71 asyncResp->res.jsonValue["Members@odata.count"] = 1; 72 }); 73 } 74 75 inline void getDrives(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 76 const std::shared_ptr<HealthPopulate>& health) 77 { 78 const std::array<std::string_view, 1> interfaces = { 79 "xyz.openbmc_project.Inventory.Item.Drive"}; 80 dbus::utility::getSubTreePaths( 81 "/xyz/openbmc_project/inventory", 0, interfaces, 82 [asyncResp, health]( 83 const boost::system::error_code& ec, 84 const dbus::utility::MapperGetSubTreePathsResponse& driveList) { 85 if (ec) 86 { 87 BMCWEB_LOG_ERROR << "Drive mapper call error"; 88 messages::internalError(asyncResp->res); 89 return; 90 } 91 92 nlohmann::json& driveArray = asyncResp->res.jsonValue["Drives"]; 93 driveArray = nlohmann::json::array(); 94 auto& count = asyncResp->res.jsonValue["Drives@odata.count"]; 95 count = 0; 96 97 if constexpr (bmcwebEnableHealthPopulate) 98 { 99 health->inventory.insert(health->inventory.end(), driveList.begin(), 100 driveList.end()); 101 } 102 103 for (const std::string& drive : driveList) 104 { 105 sdbusplus::message::object_path object(drive); 106 if (object.filename().empty()) 107 { 108 BMCWEB_LOG_ERROR << "Failed to find filename in " << drive; 109 return; 110 } 111 112 nlohmann::json::object_t driveJson; 113 driveJson["@odata.id"] = boost::urls::format( 114 "/redfish/v1/Systems/system/Storage/1/Drives/{}", 115 object.filename()); 116 driveArray.emplace_back(std::move(driveJson)); 117 } 118 119 count = driveArray.size(); 120 }); 121 } 122 123 inline void 124 getStorageControllers(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 125 const std::shared_ptr<HealthPopulate>& health) 126 { 127 constexpr std::array<std::string_view, 1> interfaces = { 128 "xyz.openbmc_project.Inventory.Item.StorageController"}; 129 dbus::utility::getSubTree( 130 "/xyz/openbmc_project/inventory", 0, interfaces, 131 [asyncResp, 132 health](const boost::system::error_code& ec, 133 const dbus::utility::MapperGetSubTreeResponse& subtree) { 134 if (ec || subtree.empty()) 135 { 136 // doesn't have to be there 137 return; 138 } 139 140 nlohmann::json& root = asyncResp->res.jsonValue["StorageControllers"]; 141 root = nlohmann::json::array(); 142 for (const auto& [path, interfaceDict] : subtree) 143 { 144 sdbusplus::message::object_path object(path); 145 std::string id = object.filename(); 146 if (id.empty()) 147 { 148 BMCWEB_LOG_ERROR << "Failed to find filename in " << path; 149 return; 150 } 151 152 if (interfaceDict.size() != 1) 153 { 154 BMCWEB_LOG_ERROR << "Connection size " << interfaceDict.size() 155 << ", greater than 1"; 156 messages::internalError(asyncResp->res); 157 return; 158 } 159 160 const std::string& connectionName = interfaceDict.front().first; 161 162 size_t index = root.size(); 163 nlohmann::json& storageController = 164 root.emplace_back(nlohmann::json::object()); 165 166 storageController["@odata.type"] = 167 "#Storage.v1_7_0.StorageController"; 168 storageController["@odata.id"] = boost::urls::format( 169 "/redfish/v1/Systems/system/Storage/1#{}", 170 ("/StorageControllers"_json_pointer / index).to_string()); 171 storageController["Name"] = id; 172 storageController["MemberId"] = id; 173 storageController["Status"]["State"] = "Enabled"; 174 175 sdbusplus::asio::getProperty<bool>( 176 *crow::connections::systemBus, connectionName, path, 177 "xyz.openbmc_project.Inventory.Item", "Present", 178 [asyncResp, index](const boost::system::error_code& ec2, 179 bool isPresent) { 180 // this interface isn't necessary, only check it 181 // if we get a good return 182 if (ec2) 183 { 184 return; 185 } 186 if (!isPresent) 187 { 188 asyncResp->res.jsonValue["StorageControllers"][index] 189 ["Status"]["State"] = "Absent"; 190 } 191 }); 192 193 sdbusplus::asio::getAllProperties( 194 *crow::connections::systemBus, connectionName, path, 195 "xyz.openbmc_project.Inventory.Decorator.Asset", 196 [asyncResp, index]( 197 const boost::system::error_code& ec2, 198 const std::vector< 199 std::pair<std::string, dbus::utility::DbusVariantType>>& 200 propertiesList) { 201 if (ec2) 202 { 203 // this interface isn't necessary 204 return; 205 } 206 207 const std::string* partNumber = nullptr; 208 const std::string* serialNumber = nullptr; 209 const std::string* manufacturer = nullptr; 210 const std::string* model = nullptr; 211 212 const bool success = sdbusplus::unpackPropertiesNoThrow( 213 dbus_utils::UnpackErrorPrinter(), propertiesList, 214 "PartNumber", partNumber, "SerialNumber", serialNumber, 215 "Manufacturer", manufacturer, "Model", model); 216 217 if (!success) 218 { 219 messages::internalError(asyncResp->res); 220 return; 221 } 222 223 nlohmann::json& controller = 224 asyncResp->res.jsonValue["StorageControllers"][index]; 225 226 if (partNumber != nullptr) 227 { 228 controller["PartNumber"] = *partNumber; 229 } 230 231 if (serialNumber != nullptr) 232 { 233 controller["SerialNumber"] = *serialNumber; 234 } 235 236 if (manufacturer != nullptr) 237 { 238 controller["Manufacturer"] = *manufacturer; 239 } 240 241 if (model != nullptr) 242 { 243 controller["Model"] = *model; 244 } 245 }); 246 } 247 248 if constexpr (bmcwebEnableHealthPopulate) 249 { 250 // this is done after we know the json array will no longer 251 // be resized, as json::array uses vector underneath and we 252 // need references to its members that won't change 253 size_t count = 0; 254 // Pointer based on |asyncResp->res.jsonValue| 255 nlohmann::json::json_pointer rootPtr = 256 "/StorageControllers"_json_pointer; 257 for (const auto& [path, interfaceDict] : subtree) 258 { 259 auto subHealth = std::make_shared<HealthPopulate>( 260 asyncResp, rootPtr / count / "Status"); 261 subHealth->inventory.emplace_back(path); 262 health->inventory.emplace_back(path); 263 health->children.emplace_back(subHealth); 264 count++; 265 } 266 } 267 }); 268 } 269 270 inline void requestRoutesStorage(App& app) 271 { 272 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/Storage/1/") 273 .privileges(redfish::privileges::getStorage) 274 .methods(boost::beast::http::verb::get)( 275 [&app](const crow::Request& req, 276 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 277 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 278 { 279 return; 280 } 281 asyncResp->res.jsonValue["@odata.type"] = "#Storage.v1_7_1.Storage"; 282 asyncResp->res.jsonValue["@odata.id"] = 283 "/redfish/v1/Systems/system/Storage/1"; 284 asyncResp->res.jsonValue["Name"] = "Storage"; 285 asyncResp->res.jsonValue["Id"] = "1"; 286 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 287 288 auto health = std::make_shared<HealthPopulate>(asyncResp); 289 if constexpr (bmcwebEnableHealthPopulate) 290 { 291 health->populate(); 292 } 293 294 getDrives(asyncResp, health); 295 getStorageControllers(asyncResp, health); 296 }); 297 } 298 299 inline void getDriveAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 300 const std::string& connectionName, 301 const std::string& path) 302 { 303 sdbusplus::asio::getAllProperties( 304 *crow::connections::systemBus, connectionName, path, 305 "xyz.openbmc_project.Inventory.Decorator.Asset", 306 [asyncResp](const boost::system::error_code& ec, 307 const std::vector< 308 std::pair<std::string, dbus::utility::DbusVariantType>>& 309 propertiesList) { 310 if (ec) 311 { 312 // this interface isn't necessary 313 return; 314 } 315 316 const std::string* partNumber = nullptr; 317 const std::string* serialNumber = nullptr; 318 const std::string* manufacturer = nullptr; 319 const std::string* model = nullptr; 320 321 const bool success = sdbusplus::unpackPropertiesNoThrow( 322 dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber", 323 partNumber, "SerialNumber", serialNumber, "Manufacturer", 324 manufacturer, "Model", model); 325 326 if (!success) 327 { 328 messages::internalError(asyncResp->res); 329 return; 330 } 331 332 if (partNumber != nullptr) 333 { 334 asyncResp->res.jsonValue["PartNumber"] = *partNumber; 335 } 336 337 if (serialNumber != nullptr) 338 { 339 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber; 340 } 341 342 if (manufacturer != nullptr) 343 { 344 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer; 345 } 346 347 if (model != nullptr) 348 { 349 asyncResp->res.jsonValue["Model"] = *model; 350 } 351 }); 352 } 353 354 inline void getDrivePresent(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 355 const std::string& connectionName, 356 const std::string& path) 357 { 358 sdbusplus::asio::getProperty<bool>( 359 *crow::connections::systemBus, connectionName, path, 360 "xyz.openbmc_project.Inventory.Item", "Present", 361 [asyncResp, path](const boost::system::error_code& ec, 362 const bool isPresent) { 363 // this interface isn't necessary, only check it if 364 // we get a good return 365 if (ec) 366 { 367 return; 368 } 369 370 if (!isPresent) 371 { 372 asyncResp->res.jsonValue["Status"]["State"] = "Absent"; 373 } 374 }); 375 } 376 377 inline void getDriveState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 378 const std::string& connectionName, 379 const std::string& path) 380 { 381 sdbusplus::asio::getProperty<bool>( 382 *crow::connections::systemBus, connectionName, path, 383 "xyz.openbmc_project.State.Drive", "Rebuilding", 384 [asyncResp](const boost::system::error_code& ec, const bool updating) { 385 // this interface isn't necessary, only check it 386 // if we get a good return 387 if (ec) 388 { 389 return; 390 } 391 392 // updating and disabled in the backend shouldn't be 393 // able to be set at the same time, so we don't need 394 // to check for the race condition of these two 395 // calls 396 if (updating) 397 { 398 asyncResp->res.jsonValue["Status"]["State"] = "Updating"; 399 } 400 }); 401 } 402 403 inline std::optional<drive::MediaType> convertDriveType(std::string_view type) 404 { 405 if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.HDD") 406 { 407 return drive::MediaType::HDD; 408 } 409 if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.SSD") 410 { 411 return drive::MediaType::SSD; 412 } 413 if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.Unknown") 414 { 415 return std::nullopt; 416 } 417 418 return drive::MediaType::Invalid; 419 } 420 421 inline std::optional<protocol::Protocol> 422 convertDriveProtocol(std::string_view proto) 423 { 424 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SAS") 425 { 426 return protocol::Protocol::SAS; 427 } 428 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SATA") 429 { 430 return protocol::Protocol::SATA; 431 } 432 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.NVMe") 433 { 434 return protocol::Protocol::NVMe; 435 } 436 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.FC") 437 { 438 return protocol::Protocol::FC; 439 } 440 if (proto == 441 "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.Unknown") 442 { 443 return std::nullopt; 444 } 445 446 return protocol::Protocol::Invalid; 447 } 448 449 inline void 450 getDriveItemProperties(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 451 const std::string& connectionName, 452 const std::string& path) 453 { 454 sdbusplus::asio::getAllProperties( 455 *crow::connections::systemBus, connectionName, path, 456 "xyz.openbmc_project.Inventory.Item.Drive", 457 [asyncResp](const boost::system::error_code& ec, 458 const std::vector< 459 std::pair<std::string, dbus::utility::DbusVariantType>>& 460 propertiesList) { 461 if (ec) 462 { 463 // this interface isn't required 464 return; 465 } 466 const std::string* encryptionStatus = nullptr; 467 const bool* isLocked = nullptr; 468 for (const std::pair<std::string, dbus::utility::DbusVariantType>& 469 property : propertiesList) 470 { 471 const std::string& propertyName = property.first; 472 if (propertyName == "Type") 473 { 474 const std::string* value = 475 std::get_if<std::string>(&property.second); 476 if (value == nullptr) 477 { 478 // illegal property 479 BMCWEB_LOG_ERROR << "Illegal property: Type"; 480 messages::internalError(asyncResp->res); 481 return; 482 } 483 484 std::optional<drive::MediaType> mediaType = 485 convertDriveType(*value); 486 if (!mediaType) 487 { 488 BMCWEB_LOG_WARNING << "UnknownDriveType Interface: " 489 << *value; 490 continue; 491 } 492 if (*mediaType == drive::MediaType::Invalid) 493 { 494 messages::internalError(asyncResp->res); 495 return; 496 } 497 498 asyncResp->res.jsonValue["MediaType"] = *mediaType; 499 } 500 else if (propertyName == "Capacity") 501 { 502 const uint64_t* capacity = 503 std::get_if<uint64_t>(&property.second); 504 if (capacity == nullptr) 505 { 506 BMCWEB_LOG_ERROR << "Illegal property: Capacity"; 507 messages::internalError(asyncResp->res); 508 return; 509 } 510 if (*capacity == 0) 511 { 512 // drive capacity not known 513 continue; 514 } 515 516 asyncResp->res.jsonValue["CapacityBytes"] = *capacity; 517 } 518 else if (propertyName == "Protocol") 519 { 520 const std::string* value = 521 std::get_if<std::string>(&property.second); 522 if (value == nullptr) 523 { 524 BMCWEB_LOG_ERROR << "Illegal property: Protocol"; 525 messages::internalError(asyncResp->res); 526 return; 527 } 528 529 std::optional<protocol::Protocol> proto = 530 convertDriveProtocol(*value); 531 if (!proto) 532 { 533 BMCWEB_LOG_WARNING << "Unknown DrivePrototype Interface: " 534 << *value; 535 continue; 536 } 537 if (*proto == protocol::Protocol::Invalid) 538 { 539 messages::internalError(asyncResp->res); 540 return; 541 } 542 asyncResp->res.jsonValue["Protocol"] = *proto; 543 } 544 else if (propertyName == "PredictedMediaLifeLeftPercent") 545 { 546 const uint8_t* lifeLeft = 547 std::get_if<uint8_t>(&property.second); 548 if (lifeLeft == nullptr) 549 { 550 BMCWEB_LOG_ERROR 551 << "Illegal property: PredictedMediaLifeLeftPercent"; 552 messages::internalError(asyncResp->res); 553 return; 554 } 555 // 255 means reading the value is not supported 556 if (*lifeLeft != 255) 557 { 558 asyncResp->res.jsonValue["PredictedMediaLifeLeftPercent"] = 559 *lifeLeft; 560 } 561 } 562 else if (propertyName == "EncryptionStatus") 563 { 564 encryptionStatus = std::get_if<std::string>(&property.second); 565 if (encryptionStatus == nullptr) 566 { 567 BMCWEB_LOG_ERROR << "Illegal property: EncryptionStatus"; 568 messages::internalError(asyncResp->res); 569 return; 570 } 571 } 572 else if (propertyName == "Locked") 573 { 574 isLocked = std::get_if<bool>(&property.second); 575 if (isLocked == nullptr) 576 { 577 BMCWEB_LOG_ERROR << "Illegal property: Locked"; 578 messages::internalError(asyncResp->res); 579 return; 580 } 581 } 582 } 583 584 if (encryptionStatus == nullptr || isLocked == nullptr || 585 *encryptionStatus == 586 "xyz.openbmc_project.Drive.DriveEncryptionState.Unknown") 587 { 588 return; 589 } 590 if (*encryptionStatus != 591 "xyz.openbmc_project.Drive.DriveEncryptionState.Encrypted") 592 { 593 //"The drive is not currently encrypted." 594 asyncResp->res.jsonValue["EncryptionStatus"] = 595 drive::EncryptionStatus::Unencrypted; 596 return; 597 } 598 if (*isLocked) 599 { 600 //"The drive is currently encrypted and the data is not 601 // accessible to the user." 602 asyncResp->res.jsonValue["EncryptionStatus"] = 603 drive::EncryptionStatus::Locked; 604 return; 605 } 606 // if not locked 607 // "The drive is currently encrypted but the data is accessible 608 // to the user in unencrypted form." 609 asyncResp->res.jsonValue["EncryptionStatus"] = 610 drive::EncryptionStatus::Unlocked; 611 }); 612 } 613 614 static void addAllDriveInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 615 const std::string& connectionName, 616 const std::string& path, 617 const std::vector<std::string>& interfaces) 618 { 619 for (const std::string& interface : interfaces) 620 { 621 if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset") 622 { 623 getDriveAsset(asyncResp, connectionName, path); 624 } 625 else if (interface == "xyz.openbmc_project.Inventory.Item") 626 { 627 getDrivePresent(asyncResp, connectionName, path); 628 } 629 else if (interface == "xyz.openbmc_project.State.Drive") 630 { 631 getDriveState(asyncResp, connectionName, path); 632 } 633 else if (interface == "xyz.openbmc_project.Inventory.Item.Drive") 634 { 635 getDriveItemProperties(asyncResp, connectionName, path); 636 } 637 } 638 } 639 640 inline void requestRoutesDrive(App& app) 641 { 642 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Drives/<str>/") 643 .privileges(redfish::privileges::getDrive) 644 .methods(boost::beast::http::verb::get)( 645 [&app](const crow::Request& req, 646 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 647 const std::string& systemName, const std::string& driveId) { 648 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 649 { 650 return; 651 } 652 if (systemName != "system") 653 { 654 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 655 systemName); 656 return; 657 } 658 659 constexpr std::array<std::string_view, 1> interfaces = { 660 "xyz.openbmc_project.Inventory.Item.Drive"}; 661 dbus::utility::getSubTree( 662 "/xyz/openbmc_project/inventory", 0, interfaces, 663 [asyncResp, 664 driveId](const boost::system::error_code& ec, 665 const dbus::utility::MapperGetSubTreeResponse& subtree) { 666 if (ec) 667 { 668 BMCWEB_LOG_ERROR << "Drive mapper call error"; 669 messages::internalError(asyncResp->res); 670 return; 671 } 672 673 auto drive = std::find_if( 674 subtree.begin(), subtree.end(), 675 [&driveId]( 676 const std::pair<std::string, 677 dbus::utility::MapperServiceMap>& object) { 678 return sdbusplus::message::object_path(object.first) 679 .filename() == driveId; 680 }); 681 682 if (drive == subtree.end()) 683 { 684 messages::resourceNotFound(asyncResp->res, "Drive", driveId); 685 return; 686 } 687 688 const std::string& path = drive->first; 689 const dbus::utility::MapperServiceMap& connectionNames = 690 drive->second; 691 692 asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive"; 693 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 694 "/redfish/v1/Systems/system/Storage/1/Drives/{}", driveId); 695 asyncResp->res.jsonValue["Name"] = driveId; 696 asyncResp->res.jsonValue["Id"] = driveId; 697 698 if (connectionNames.size() != 1) 699 { 700 BMCWEB_LOG_ERROR << "Connection size " << connectionNames.size() 701 << ", not equal to 1"; 702 messages::internalError(asyncResp->res); 703 return; 704 } 705 706 getMainChassisId( 707 asyncResp, 708 [](const std::string& chassisId, 709 const std::shared_ptr<bmcweb::AsyncResp>& aRsp) { 710 aRsp->res.jsonValue["Links"]["Chassis"]["@odata.id"] = 711 boost::urls::format("/redfish/v1/Chassis/{}", chassisId); 712 }); 713 714 // default it to Enabled 715 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 716 717 if constexpr (bmcwebEnableHealthPopulate) 718 { 719 auto health = std::make_shared<HealthPopulate>(asyncResp); 720 health->inventory.emplace_back(path); 721 health->populate(); 722 } 723 724 addAllDriveInfo(asyncResp, connectionNames[0].first, path, 725 connectionNames[0].second); 726 }); 727 }); 728 } 729 730 /** 731 * Chassis drives, this URL will show all the DriveCollection 732 * information 733 */ 734 inline void chassisDriveCollectionGet( 735 crow::App& app, const crow::Request& req, 736 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 737 const std::string& chassisId) 738 { 739 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 740 { 741 return; 742 } 743 744 // mapper call lambda 745 constexpr std::array<std::string_view, 2> interfaces = { 746 "xyz.openbmc_project.Inventory.Item.Board", 747 "xyz.openbmc_project.Inventory.Item.Chassis"}; 748 dbus::utility::getSubTree( 749 "/xyz/openbmc_project/inventory", 0, interfaces, 750 [asyncResp, 751 chassisId](const boost::system::error_code& ec, 752 const dbus::utility::MapperGetSubTreeResponse& subtree) { 753 if (ec) 754 { 755 if (ec == boost::system::errc::host_unreachable) 756 { 757 messages::resourceNotFound(asyncResp->res, "Chassis", 758 chassisId); 759 return; 760 } 761 messages::internalError(asyncResp->res); 762 return; 763 } 764 765 // Iterate over all retrieved ObjectPaths. 766 for (const auto& [path, connectionNames] : subtree) 767 { 768 sdbusplus::message::object_path objPath(path); 769 if (objPath.filename() != chassisId) 770 { 771 continue; 772 } 773 774 if (connectionNames.empty()) 775 { 776 BMCWEB_LOG_ERROR << "Got 0 Connection names"; 777 continue; 778 } 779 780 asyncResp->res.jsonValue["@odata.type"] = 781 "#DriveCollection.DriveCollection"; 782 asyncResp->res.jsonValue["@odata.id"] = 783 boost::urls::format("/redfish/v1/Chassis/{}/Drives", chassisId); 784 asyncResp->res.jsonValue["Name"] = "Drive Collection"; 785 786 // Association lambda 787 dbus::utility::getAssociationEndPoints( 788 path + "/drive", 789 [asyncResp, 790 chassisId](const boost::system::error_code& ec3, 791 const dbus::utility::MapperEndPoints& resp) { 792 if (ec3) 793 { 794 BMCWEB_LOG_ERROR << "Error in chassis Drive association "; 795 } 796 nlohmann::json& members = asyncResp->res.jsonValue["Members"]; 797 // important if array is empty 798 members = nlohmann::json::array(); 799 800 std::vector<std::string> leafNames; 801 for (const auto& drive : resp) 802 { 803 sdbusplus::message::object_path drivePath(drive); 804 leafNames.push_back(drivePath.filename()); 805 } 806 807 std::sort(leafNames.begin(), leafNames.end(), 808 AlphanumLess<std::string>()); 809 810 for (const auto& leafName : leafNames) 811 { 812 nlohmann::json::object_t member; 813 member["@odata.id"] = 814 boost::urls::format("/redfish/v1/Chassis/{}/Drives/{}", 815 chassisId, leafName); 816 members.emplace_back(std::move(member)); 817 // navigation links will be registered in next patch set 818 } 819 asyncResp->res.jsonValue["Members@odata.count"] = resp.size(); 820 }); // end association lambda 821 822 } // end Iterate over all retrieved ObjectPaths 823 }); 824 } 825 826 inline void requestRoutesChassisDrive(App& app) 827 { 828 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/") 829 .privileges(redfish::privileges::getDriveCollection) 830 .methods(boost::beast::http::verb::get)( 831 std::bind_front(chassisDriveCollectionGet, std::ref(app))); 832 } 833 834 inline void buildDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 835 const std::string& chassisId, 836 const std::string& driveName, 837 const boost::system::error_code& ec, 838 const dbus::utility::MapperGetSubTreeResponse& subtree) 839 { 840 if (ec) 841 { 842 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 843 messages::internalError(asyncResp->res); 844 return; 845 } 846 847 // Iterate over all retrieved ObjectPaths. 848 for (const auto& [path, connectionNames] : subtree) 849 { 850 sdbusplus::message::object_path objPath(path); 851 if (objPath.filename() != driveName) 852 { 853 continue; 854 } 855 856 if (connectionNames.empty()) 857 { 858 BMCWEB_LOG_ERROR << "Got 0 Connection names"; 859 continue; 860 } 861 862 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 863 "/redfish/v1/Chassis/{}/Drives/{}", chassisId, driveName); 864 865 asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive"; 866 asyncResp->res.jsonValue["Name"] = driveName; 867 asyncResp->res.jsonValue["Id"] = driveName; 868 // default it to Enabled 869 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 870 871 nlohmann::json::object_t linkChassisNav; 872 linkChassisNav["@odata.id"] = 873 boost::urls::format("/redfish/v1/Chassis/{}", chassisId); 874 asyncResp->res.jsonValue["Links"]["Chassis"] = linkChassisNav; 875 876 addAllDriveInfo(asyncResp, connectionNames[0].first, path, 877 connectionNames[0].second); 878 } 879 } 880 881 inline void 882 matchAndFillDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 883 const std::string& chassisId, 884 const std::string& driveName, 885 const std::vector<std::string>& resp) 886 { 887 for (const std::string& drivePath : resp) 888 { 889 sdbusplus::message::object_path path(drivePath); 890 std::string leaf = path.filename(); 891 if (leaf != driveName) 892 { 893 continue; 894 } 895 // mapper call drive 896 constexpr std::array<std::string_view, 1> driveInterface = { 897 "xyz.openbmc_project.Inventory.Item.Drive"}; 898 dbus::utility::getSubTree( 899 "/xyz/openbmc_project/inventory", 0, driveInterface, 900 [asyncResp, chassisId, driveName]( 901 const boost::system::error_code& ec, 902 const dbus::utility::MapperGetSubTreeResponse& subtree) { 903 buildDrive(asyncResp, chassisId, driveName, ec, subtree); 904 }); 905 } 906 } 907 908 inline void 909 handleChassisDriveGet(crow::App& app, const crow::Request& req, 910 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 911 const std::string& chassisId, 912 const std::string& driveName) 913 { 914 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 915 { 916 return; 917 } 918 constexpr std::array<std::string_view, 2> interfaces = { 919 "xyz.openbmc_project.Inventory.Item.Board", 920 "xyz.openbmc_project.Inventory.Item.Chassis"}; 921 922 // mapper call chassis 923 dbus::utility::getSubTree( 924 "/xyz/openbmc_project/inventory", 0, interfaces, 925 [asyncResp, chassisId, 926 driveName](const boost::system::error_code& ec, 927 const dbus::utility::MapperGetSubTreeResponse& subtree) { 928 if (ec) 929 { 930 messages::internalError(asyncResp->res); 931 return; 932 } 933 934 // Iterate over all retrieved ObjectPaths. 935 for (const auto& [path, connectionNames] : subtree) 936 { 937 sdbusplus::message::object_path objPath(path); 938 if (objPath.filename() != chassisId) 939 { 940 continue; 941 } 942 943 if (connectionNames.empty()) 944 { 945 BMCWEB_LOG_ERROR << "Got 0 Connection names"; 946 continue; 947 } 948 949 dbus::utility::getAssociationEndPoints( 950 path + "/drive", 951 [asyncResp, chassisId, 952 driveName](const boost::system::error_code& ec3, 953 const dbus::utility::MapperEndPoints& resp) { 954 if (ec3) 955 { 956 return; // no drives = no failures 957 } 958 matchAndFillDrive(asyncResp, chassisId, driveName, resp); 959 }); 960 break; 961 } 962 }); 963 } 964 965 /** 966 * This URL will show the drive interface for the specific drive in the chassis 967 */ 968 inline void requestRoutesChassisDriveName(App& app) 969 { 970 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/<str>/") 971 .privileges(redfish::privileges::getChassis) 972 .methods(boost::beast::http::verb::get)( 973 std::bind_front(handleChassisDriveGet, std::ref(app))); 974 } 975 976 } // namespace redfish 977