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