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