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