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