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