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