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"] = crow::utility::urlFromPieces( 106 "redfish", "v1", "Systems", "system", "Storage", "1", "Drives", 107 object.filename()); 108 driveArray.emplace_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 crow::utility::urlFromPieces("redfish", "v1", "Systems", 162 "system", "Storage", "1") 163 .set_fragment(("/StorageControllers"_json_pointer / index) 164 .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 for (const std::pair<std::string, dbus::utility::DbusVariantType>& 445 property : propertiesList) 446 { 447 const std::string& propertyName = property.first; 448 if (propertyName == "Type") 449 { 450 const std::string* value = 451 std::get_if<std::string>(&property.second); 452 if (value == nullptr) 453 { 454 // illegal property 455 BMCWEB_LOG_ERROR << "Illegal property: Type"; 456 messages::internalError(asyncResp->res); 457 return; 458 } 459 460 std::optional<std::string> mediaType = convertDriveType(*value); 461 if (!mediaType) 462 { 463 BMCWEB_LOG_ERROR << "Unsupported DriveType Interface: " 464 << *value; 465 messages::internalError(asyncResp->res); 466 return; 467 } 468 469 asyncResp->res.jsonValue["MediaType"] = *mediaType; 470 } 471 else if (propertyName == "Capacity") 472 { 473 const uint64_t* capacity = 474 std::get_if<uint64_t>(&property.second); 475 if (capacity == nullptr) 476 { 477 BMCWEB_LOG_ERROR << "Illegal property: Capacity"; 478 messages::internalError(asyncResp->res); 479 return; 480 } 481 if (*capacity == 0) 482 { 483 // drive capacity not known 484 continue; 485 } 486 487 asyncResp->res.jsonValue["CapacityBytes"] = *capacity; 488 } 489 else if (propertyName == "Protocol") 490 { 491 const std::string* value = 492 std::get_if<std::string>(&property.second); 493 if (value == nullptr) 494 { 495 BMCWEB_LOG_ERROR << "Illegal property: Protocol"; 496 messages::internalError(asyncResp->res); 497 return; 498 } 499 500 std::optional<std::string> proto = convertDriveProtocol(*value); 501 if (!proto) 502 { 503 BMCWEB_LOG_ERROR << "Unsupported DrivePrototype Interface: " 504 << *value; 505 messages::internalError(asyncResp->res); 506 return; 507 } 508 asyncResp->res.jsonValue["Protocol"] = *proto; 509 } 510 else if (propertyName == "PredictedMediaLifeLeftPercent") 511 { 512 const uint8_t* lifeLeft = 513 std::get_if<uint8_t>(&property.second); 514 if (lifeLeft == nullptr) 515 { 516 BMCWEB_LOG_ERROR 517 << "Illegal property: PredictedMediaLifeLeftPercent"; 518 messages::internalError(asyncResp->res); 519 return; 520 } 521 // 255 means reading the value is not supported 522 if (*lifeLeft != 255) 523 { 524 asyncResp->res.jsonValue["PredictedMediaLifeLeftPercent"] = 525 *lifeLeft; 526 } 527 } 528 } 529 }); 530 } 531 532 static void addAllDriveInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 533 const std::string& connectionName, 534 const std::string& path, 535 const std::vector<std::string>& interfaces) 536 { 537 for (const std::string& interface : interfaces) 538 { 539 if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset") 540 { 541 getDriveAsset(asyncResp, connectionName, path); 542 } 543 else if (interface == "xyz.openbmc_project.Inventory.Item") 544 { 545 getDrivePresent(asyncResp, connectionName, path); 546 } 547 else if (interface == "xyz.openbmc_project.State.Drive") 548 { 549 getDriveState(asyncResp, connectionName, path); 550 } 551 else if (interface == "xyz.openbmc_project.Inventory.Item.Drive") 552 { 553 getDriveItemProperties(asyncResp, connectionName, path); 554 } 555 } 556 } 557 558 inline void requestRoutesDrive(App& app) 559 { 560 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Drives/<str>/") 561 .privileges(redfish::privileges::getDrive) 562 .methods(boost::beast::http::verb::get)( 563 [&app](const crow::Request& req, 564 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 565 const std::string& systemName, const std::string& driveId) { 566 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 567 { 568 return; 569 } 570 if (systemName != "system") 571 { 572 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 573 systemName); 574 return; 575 } 576 577 constexpr std::array<std::string_view, 1> interfaces = { 578 "xyz.openbmc_project.Inventory.Item.Drive"}; 579 dbus::utility::getSubTree( 580 "/xyz/openbmc_project/inventory", 0, interfaces, 581 [asyncResp, 582 driveId](const boost::system::error_code& ec, 583 const dbus::utility::MapperGetSubTreeResponse& subtree) { 584 if (ec) 585 { 586 BMCWEB_LOG_ERROR << "Drive mapper call error"; 587 messages::internalError(asyncResp->res); 588 return; 589 } 590 591 auto drive = std::find_if( 592 subtree.begin(), subtree.end(), 593 [&driveId]( 594 const std::pair<std::string, 595 dbus::utility::MapperServiceMap>& object) { 596 return sdbusplus::message::object_path(object.first) 597 .filename() == driveId; 598 }); 599 600 if (drive == subtree.end()) 601 { 602 messages::resourceNotFound(asyncResp->res, "Drive", driveId); 603 return; 604 } 605 606 const std::string& path = drive->first; 607 const dbus::utility::MapperServiceMap& connectionNames = 608 drive->second; 609 610 asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive"; 611 asyncResp->res.jsonValue["@odata.id"] = 612 crow::utility::urlFromPieces("redfish", "v1", "Systems", 613 "system", "Storage", "1", "Drives", 614 driveId); 615 asyncResp->res.jsonValue["Name"] = driveId; 616 asyncResp->res.jsonValue["Id"] = driveId; 617 618 if (connectionNames.size() != 1) 619 { 620 BMCWEB_LOG_ERROR << "Connection size " << connectionNames.size() 621 << ", not equal to 1"; 622 messages::internalError(asyncResp->res); 623 return; 624 } 625 626 getMainChassisId( 627 asyncResp, [](const std::string& chassisId, 628 const std::shared_ptr<bmcweb::AsyncResp>& aRsp) { 629 aRsp->res.jsonValue["Links"]["Chassis"]["@odata.id"] = 630 crow::utility::urlFromPieces("redfish", "v1", "Chassis", 631 chassisId); 632 }); 633 634 // default it to Enabled 635 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 636 637 auto health = std::make_shared<HealthPopulate>(asyncResp); 638 health->inventory.emplace_back(path); 639 health->populate(); 640 641 addAllDriveInfo(asyncResp, connectionNames[0].first, path, 642 connectionNames[0].second); 643 }); 644 }); 645 } 646 647 /** 648 * Chassis drives, this URL will show all the DriveCollection 649 * information 650 */ 651 inline void chassisDriveCollectionGet( 652 crow::App& app, const crow::Request& req, 653 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 654 const std::string& chassisId) 655 { 656 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 657 { 658 return; 659 } 660 661 // mapper call lambda 662 constexpr std::array<std::string_view, 2> interfaces = { 663 "xyz.openbmc_project.Inventory.Item.Board", 664 "xyz.openbmc_project.Inventory.Item.Chassis"}; 665 dbus::utility::getSubTree( 666 "/xyz/openbmc_project/inventory", 0, interfaces, 667 [asyncResp, 668 chassisId](const boost::system::error_code& ec, 669 const dbus::utility::MapperGetSubTreeResponse& subtree) { 670 if (ec) 671 { 672 if (ec == boost::system::errc::host_unreachable) 673 { 674 messages::resourceNotFound(asyncResp->res, "Chassis", 675 chassisId); 676 return; 677 } 678 messages::internalError(asyncResp->res); 679 return; 680 } 681 682 // Iterate over all retrieved ObjectPaths. 683 for (const auto& [path, connectionNames] : subtree) 684 { 685 sdbusplus::message::object_path objPath(path); 686 if (objPath.filename() != chassisId) 687 { 688 continue; 689 } 690 691 if (connectionNames.empty()) 692 { 693 BMCWEB_LOG_ERROR << "Got 0 Connection names"; 694 continue; 695 } 696 697 asyncResp->res.jsonValue["@odata.type"] = 698 "#DriveCollection.DriveCollection"; 699 asyncResp->res.jsonValue["@odata.id"] = 700 crow::utility::urlFromPieces("redfish", "v1", "Chassis", 701 chassisId, "Drives"); 702 asyncResp->res.jsonValue["Name"] = "Drive Collection"; 703 704 // Association lambda 705 dbus::utility::getAssociationEndPoints( 706 path + "/drive", 707 [asyncResp, 708 chassisId](const boost::system::error_code& ec3, 709 const dbus::utility::MapperEndPoints& resp) { 710 if (ec3) 711 { 712 BMCWEB_LOG_ERROR << "Error in chassis Drive association "; 713 } 714 nlohmann::json& members = asyncResp->res.jsonValue["Members"]; 715 // important if array is empty 716 members = nlohmann::json::array(); 717 718 std::vector<std::string> leafNames; 719 for (const auto& drive : resp) 720 { 721 sdbusplus::message::object_path drivePath(drive); 722 leafNames.push_back(drivePath.filename()); 723 } 724 725 std::sort(leafNames.begin(), leafNames.end(), 726 AlphanumLess<std::string>()); 727 728 for (const auto& leafName : leafNames) 729 { 730 nlohmann::json::object_t member; 731 member["@odata.id"] = crow::utility::urlFromPieces( 732 "redfish", "v1", "Chassis", chassisId, "Drives", 733 leafName); 734 members.emplace_back(std::move(member)); 735 // navigation links will be registered in next patch set 736 } 737 asyncResp->res.jsonValue["Members@odata.count"] = resp.size(); 738 }); // end association lambda 739 740 } // end Iterate over all retrieved ObjectPaths 741 }); 742 } 743 744 inline void requestRoutesChassisDrive(App& app) 745 { 746 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/") 747 .privileges(redfish::privileges::getDriveCollection) 748 .methods(boost::beast::http::verb::get)( 749 std::bind_front(chassisDriveCollectionGet, std::ref(app))); 750 } 751 752 inline void buildDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 753 const std::string& chassisId, 754 const std::string& driveName, 755 const boost::system::error_code& ec, 756 const dbus::utility::MapperGetSubTreeResponse& subtree) 757 { 758 if (ec) 759 { 760 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 761 messages::internalError(asyncResp->res); 762 return; 763 } 764 765 // Iterate over all retrieved ObjectPaths. 766 for (const auto& [path, connectionNames] : subtree) 767 { 768 sdbusplus::message::object_path objPath(path); 769 if (objPath.filename() != driveName) 770 { 771 continue; 772 } 773 774 if (connectionNames.empty()) 775 { 776 BMCWEB_LOG_ERROR << "Got 0 Connection names"; 777 continue; 778 } 779 780 asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( 781 "redfish", "v1", "Chassis", chassisId, "Drives", driveName); 782 783 asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive"; 784 asyncResp->res.jsonValue["Name"] = driveName; 785 asyncResp->res.jsonValue["Id"] = driveName; 786 // default it to Enabled 787 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 788 789 nlohmann::json::object_t linkChassisNav; 790 linkChassisNav["@odata.id"] = 791 crow::utility::urlFromPieces("redfish", "v1", "Chassis", chassisId); 792 asyncResp->res.jsonValue["Links"]["Chassis"] = linkChassisNav; 793 794 addAllDriveInfo(asyncResp, connectionNames[0].first, path, 795 connectionNames[0].second); 796 } 797 } 798 799 inline void 800 matchAndFillDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 801 const std::string& chassisId, 802 const std::string& driveName, 803 const std::vector<std::string>& resp) 804 { 805 for (const std::string& drivePath : resp) 806 { 807 sdbusplus::message::object_path path(drivePath); 808 std::string leaf = path.filename(); 809 if (leaf != driveName) 810 { 811 continue; 812 } 813 // mapper call drive 814 constexpr std::array<std::string_view, 1> driveInterface = { 815 "xyz.openbmc_project.Inventory.Item.Drive"}; 816 dbus::utility::getSubTree( 817 "/xyz/openbmc_project/inventory", 0, driveInterface, 818 [asyncResp, chassisId, driveName]( 819 const boost::system::error_code& ec, 820 const dbus::utility::MapperGetSubTreeResponse& subtree) { 821 buildDrive(asyncResp, chassisId, driveName, ec, subtree); 822 }); 823 } 824 } 825 826 inline void 827 handleChassisDriveGet(crow::App& app, const crow::Request& req, 828 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 829 const std::string& chassisId, 830 const std::string& driveName) 831 { 832 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 833 { 834 return; 835 } 836 constexpr std::array<std::string_view, 2> interfaces = { 837 "xyz.openbmc_project.Inventory.Item.Board", 838 "xyz.openbmc_project.Inventory.Item.Chassis"}; 839 840 // mapper call chassis 841 dbus::utility::getSubTree( 842 "/xyz/openbmc_project/inventory", 0, interfaces, 843 [asyncResp, chassisId, 844 driveName](const boost::system::error_code& ec, 845 const dbus::utility::MapperGetSubTreeResponse& subtree) { 846 if (ec) 847 { 848 messages::internalError(asyncResp->res); 849 return; 850 } 851 852 // Iterate over all retrieved ObjectPaths. 853 for (const auto& [path, connectionNames] : subtree) 854 { 855 sdbusplus::message::object_path objPath(path); 856 if (objPath.filename() != chassisId) 857 { 858 continue; 859 } 860 861 if (connectionNames.empty()) 862 { 863 BMCWEB_LOG_ERROR << "Got 0 Connection names"; 864 continue; 865 } 866 867 dbus::utility::getAssociationEndPoints( 868 path + "/drive", 869 [asyncResp, chassisId, 870 driveName](const boost::system::error_code& ec3, 871 const dbus::utility::MapperEndPoints& 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