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