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 "bmcweb_config.h" 19 20 #include "app.hpp" 21 #include "dbus_utility.hpp" 22 #include "generated/enums/drive.hpp" 23 #include "generated/enums/protocol.hpp" 24 #include "health.hpp" 25 #include "human_sort.hpp" 26 #include "openbmc_dbus_rest.hpp" 27 #include "query.hpp" 28 #include "redfish_util.hpp" 29 #include "registries/privilege_registry.hpp" 30 #include "utils/dbus_utils.hpp" 31 32 #include <boost/system/error_code.hpp> 33 #include <boost/url/format.hpp> 34 #include <sdbusplus/asio/property.hpp> 35 #include <sdbusplus/unpack_properties.hpp> 36 37 #include <array> 38 #include <string_view> 39 40 namespace redfish 41 { 42 43 inline void handleSystemsStorageCollectionGet( 44 App& app, const crow::Request& req, 45 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 46 const std::string& systemName) 47 { 48 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 49 { 50 return; 51 } 52 if (systemName != "system") 53 { 54 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 55 systemName); 56 return; 57 } 58 59 asyncResp->res.jsonValue["@odata.type"] = 60 "#StorageCollection.StorageCollection"; 61 asyncResp->res.jsonValue["@odata.id"] = 62 "/redfish/v1/Systems/system/Storage"; 63 asyncResp->res.jsonValue["Name"] = "Storage Collection"; 64 nlohmann::json::array_t members; 65 nlohmann::json::object_t member; 66 member["@odata.id"] = "/redfish/v1/Systems/system/Storage/1"; 67 members.emplace_back(member); 68 asyncResp->res.jsonValue["Members"] = std::move(members); 69 asyncResp->res.jsonValue["Members@odata.count"] = 1; 70 } 71 72 inline void requestRoutesStorageCollection(App& app) 73 { 74 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/") 75 .privileges(redfish::privileges::getStorageCollection) 76 .methods(boost::beast::http::verb::get)( 77 std::bind_front(handleSystemsStorageCollectionGet, std::ref(app))); 78 } 79 80 inline void afterChassisDriveCollectionSubtree( 81 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 82 const std::shared_ptr<HealthPopulate>& health, 83 const boost::system::error_code& ec, 84 const dbus::utility::MapperGetSubTreePathsResponse& driveList) 85 { 86 if (ec) 87 { 88 BMCWEB_LOG_ERROR << "Drive mapper call error"; 89 messages::internalError(asyncResp->res); 90 return; 91 } 92 93 nlohmann::json& driveArray = asyncResp->res.jsonValue["Drives"]; 94 driveArray = nlohmann::json::array(); 95 auto& count = asyncResp->res.jsonValue["Drives@odata.count"]; 96 count = 0; 97 98 if constexpr (bmcwebEnableHealthPopulate) 99 { 100 health->inventory.insert(health->inventory.end(), driveList.begin(), 101 driveList.end()); 102 } 103 104 for (const std::string& drive : driveList) 105 { 106 sdbusplus::message::object_path object(drive); 107 if (object.filename().empty()) 108 { 109 BMCWEB_LOG_ERROR << "Failed to find filename in " << drive; 110 return; 111 } 112 113 nlohmann::json::object_t driveJson; 114 driveJson["@odata.id"] = boost::urls::format( 115 "/redfish/v1/Systems/system/Storage/1/Drives/{}", 116 object.filename()); 117 driveArray.emplace_back(std::move(driveJson)); 118 } 119 120 count = driveArray.size(); 121 } 122 inline void getDrives(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 123 const std::shared_ptr<HealthPopulate>& health) 124 { 125 const std::array<std::string_view, 1> interfaces = { 126 "xyz.openbmc_project.Inventory.Item.Drive"}; 127 dbus::utility::getSubTreePaths( 128 "/xyz/openbmc_project/inventory", 0, interfaces, 129 std::bind_front(afterChassisDriveCollectionSubtree, asyncResp, health)); 130 } 131 132 inline void 133 handleSystemsStorageGet(App& app, const crow::Request& req, 134 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 135 { 136 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 137 { 138 return; 139 } 140 asyncResp->res.jsonValue["@odata.type"] = "#Storage.v1_13_0.Storage"; 141 asyncResp->res.jsonValue["@odata.id"] = 142 "/redfish/v1/Systems/system/Storage/1"; 143 asyncResp->res.jsonValue["Name"] = "Storage"; 144 asyncResp->res.jsonValue["Id"] = "1"; 145 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 146 147 auto health = std::make_shared<HealthPopulate>(asyncResp); 148 if constexpr (bmcwebEnableHealthPopulate) 149 { 150 health->populate(); 151 } 152 153 getDrives(asyncResp, health); 154 asyncResp->res.jsonValue["Controllers"]["@odata.id"] = 155 "/redfish/v1/Systems/system/Storage/1/Controllers"; 156 } 157 158 inline void requestRoutesStorage(App& app) 159 { 160 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/Storage/1/") 161 .privileges(redfish::privileges::getStorage) 162 .methods(boost::beast::http::verb::get)( 163 std::bind_front(handleSystemsStorageGet, std::ref(app))); 164 } 165 166 inline void getDriveAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 167 const std::string& connectionName, 168 const std::string& path) 169 { 170 sdbusplus::asio::getAllProperties( 171 *crow::connections::systemBus, connectionName, path, 172 "xyz.openbmc_project.Inventory.Decorator.Asset", 173 [asyncResp](const boost::system::error_code& ec, 174 const std::vector< 175 std::pair<std::string, dbus::utility::DbusVariantType>>& 176 propertiesList) { 177 if (ec) 178 { 179 // this interface isn't necessary 180 return; 181 } 182 183 const std::string* partNumber = nullptr; 184 const std::string* serialNumber = nullptr; 185 const std::string* manufacturer = nullptr; 186 const std::string* model = nullptr; 187 188 const bool success = sdbusplus::unpackPropertiesNoThrow( 189 dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber", 190 partNumber, "SerialNumber", serialNumber, "Manufacturer", 191 manufacturer, "Model", model); 192 193 if (!success) 194 { 195 messages::internalError(asyncResp->res); 196 return; 197 } 198 199 if (partNumber != nullptr) 200 { 201 asyncResp->res.jsonValue["PartNumber"] = *partNumber; 202 } 203 204 if (serialNumber != nullptr) 205 { 206 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber; 207 } 208 209 if (manufacturer != nullptr) 210 { 211 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer; 212 } 213 214 if (model != nullptr) 215 { 216 asyncResp->res.jsonValue["Model"] = *model; 217 } 218 }); 219 } 220 221 inline void getDrivePresent(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 222 const std::string& connectionName, 223 const std::string& path) 224 { 225 sdbusplus::asio::getProperty<bool>( 226 *crow::connections::systemBus, connectionName, path, 227 "xyz.openbmc_project.Inventory.Item", "Present", 228 [asyncResp, path](const boost::system::error_code& ec, 229 const bool isPresent) { 230 // this interface isn't necessary, only check it if 231 // we get a good return 232 if (ec) 233 { 234 return; 235 } 236 237 if (!isPresent) 238 { 239 asyncResp->res.jsonValue["Status"]["State"] = "Absent"; 240 } 241 }); 242 } 243 244 inline void getDriveState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 245 const std::string& connectionName, 246 const std::string& path) 247 { 248 sdbusplus::asio::getProperty<bool>( 249 *crow::connections::systemBus, connectionName, path, 250 "xyz.openbmc_project.State.Drive", "Rebuilding", 251 [asyncResp](const boost::system::error_code& ec, const bool updating) { 252 // this interface isn't necessary, only check it 253 // if we get a good return 254 if (ec) 255 { 256 return; 257 } 258 259 // updating and disabled in the backend shouldn't be 260 // able to be set at the same time, so we don't need 261 // to check for the race condition of these two 262 // calls 263 if (updating) 264 { 265 asyncResp->res.jsonValue["Status"]["State"] = "Updating"; 266 } 267 }); 268 } 269 270 inline std::optional<drive::MediaType> convertDriveType(std::string_view type) 271 { 272 if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.HDD") 273 { 274 return drive::MediaType::HDD; 275 } 276 if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.SSD") 277 { 278 return drive::MediaType::SSD; 279 } 280 if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.Unknown") 281 { 282 return std::nullopt; 283 } 284 285 return drive::MediaType::Invalid; 286 } 287 288 inline std::optional<protocol::Protocol> 289 convertDriveProtocol(std::string_view proto) 290 { 291 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SAS") 292 { 293 return protocol::Protocol::SAS; 294 } 295 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SATA") 296 { 297 return protocol::Protocol::SATA; 298 } 299 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.NVMe") 300 { 301 return protocol::Protocol::NVMe; 302 } 303 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.FC") 304 { 305 return protocol::Protocol::FC; 306 } 307 if (proto == 308 "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.Unknown") 309 { 310 return std::nullopt; 311 } 312 313 return protocol::Protocol::Invalid; 314 } 315 316 inline void 317 getDriveItemProperties(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 318 const std::string& connectionName, 319 const std::string& path) 320 { 321 sdbusplus::asio::getAllProperties( 322 *crow::connections::systemBus, connectionName, path, 323 "xyz.openbmc_project.Inventory.Item.Drive", 324 [asyncResp](const boost::system::error_code& ec, 325 const std::vector< 326 std::pair<std::string, dbus::utility::DbusVariantType>>& 327 propertiesList) { 328 if (ec) 329 { 330 // this interface isn't required 331 return; 332 } 333 const std::string* encryptionStatus = nullptr; 334 const bool* isLocked = nullptr; 335 for (const std::pair<std::string, dbus::utility::DbusVariantType>& 336 property : propertiesList) 337 { 338 const std::string& propertyName = property.first; 339 if (propertyName == "Type") 340 { 341 const std::string* value = 342 std::get_if<std::string>(&property.second); 343 if (value == nullptr) 344 { 345 // illegal property 346 BMCWEB_LOG_ERROR << "Illegal property: Type"; 347 messages::internalError(asyncResp->res); 348 return; 349 } 350 351 std::optional<drive::MediaType> mediaType = 352 convertDriveType(*value); 353 if (!mediaType) 354 { 355 BMCWEB_LOG_WARNING << "UnknownDriveType Interface: " 356 << *value; 357 continue; 358 } 359 if (*mediaType == drive::MediaType::Invalid) 360 { 361 messages::internalError(asyncResp->res); 362 return; 363 } 364 365 asyncResp->res.jsonValue["MediaType"] = *mediaType; 366 } 367 else if (propertyName == "Capacity") 368 { 369 const uint64_t* capacity = 370 std::get_if<uint64_t>(&property.second); 371 if (capacity == nullptr) 372 { 373 BMCWEB_LOG_ERROR << "Illegal property: Capacity"; 374 messages::internalError(asyncResp->res); 375 return; 376 } 377 if (*capacity == 0) 378 { 379 // drive capacity not known 380 continue; 381 } 382 383 asyncResp->res.jsonValue["CapacityBytes"] = *capacity; 384 } 385 else if (propertyName == "Protocol") 386 { 387 const std::string* value = 388 std::get_if<std::string>(&property.second); 389 if (value == nullptr) 390 { 391 BMCWEB_LOG_ERROR << "Illegal property: Protocol"; 392 messages::internalError(asyncResp->res); 393 return; 394 } 395 396 std::optional<protocol::Protocol> proto = 397 convertDriveProtocol(*value); 398 if (!proto) 399 { 400 BMCWEB_LOG_WARNING << "Unknown DrivePrototype Interface: " 401 << *value; 402 continue; 403 } 404 if (*proto == protocol::Protocol::Invalid) 405 { 406 messages::internalError(asyncResp->res); 407 return; 408 } 409 asyncResp->res.jsonValue["Protocol"] = *proto; 410 } 411 else if (propertyName == "PredictedMediaLifeLeftPercent") 412 { 413 const uint8_t* lifeLeft = 414 std::get_if<uint8_t>(&property.second); 415 if (lifeLeft == nullptr) 416 { 417 BMCWEB_LOG_ERROR 418 << "Illegal property: PredictedMediaLifeLeftPercent"; 419 messages::internalError(asyncResp->res); 420 return; 421 } 422 // 255 means reading the value is not supported 423 if (*lifeLeft != 255) 424 { 425 asyncResp->res.jsonValue["PredictedMediaLifeLeftPercent"] = 426 *lifeLeft; 427 } 428 } 429 else if (propertyName == "EncryptionStatus") 430 { 431 encryptionStatus = std::get_if<std::string>(&property.second); 432 if (encryptionStatus == nullptr) 433 { 434 BMCWEB_LOG_ERROR << "Illegal property: EncryptionStatus"; 435 messages::internalError(asyncResp->res); 436 return; 437 } 438 } 439 else if (propertyName == "Locked") 440 { 441 isLocked = std::get_if<bool>(&property.second); 442 if (isLocked == nullptr) 443 { 444 BMCWEB_LOG_ERROR << "Illegal property: Locked"; 445 messages::internalError(asyncResp->res); 446 return; 447 } 448 } 449 } 450 451 if (encryptionStatus == nullptr || isLocked == nullptr || 452 *encryptionStatus == 453 "xyz.openbmc_project.Drive.DriveEncryptionState.Unknown") 454 { 455 return; 456 } 457 if (*encryptionStatus != 458 "xyz.openbmc_project.Drive.DriveEncryptionState.Encrypted") 459 { 460 //"The drive is not currently encrypted." 461 asyncResp->res.jsonValue["EncryptionStatus"] = 462 drive::EncryptionStatus::Unencrypted; 463 return; 464 } 465 if (*isLocked) 466 { 467 //"The drive is currently encrypted and the data is not 468 // accessible to the user." 469 asyncResp->res.jsonValue["EncryptionStatus"] = 470 drive::EncryptionStatus::Locked; 471 return; 472 } 473 // if not locked 474 // "The drive is currently encrypted but the data is accessible 475 // to the user in unencrypted form." 476 asyncResp->res.jsonValue["EncryptionStatus"] = 477 drive::EncryptionStatus::Unlocked; 478 }); 479 } 480 481 static void addAllDriveInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 482 const std::string& connectionName, 483 const std::string& path, 484 const std::vector<std::string>& interfaces) 485 { 486 for (const std::string& interface : interfaces) 487 { 488 if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset") 489 { 490 getDriveAsset(asyncResp, connectionName, path); 491 } 492 else if (interface == "xyz.openbmc_project.Inventory.Item") 493 { 494 getDrivePresent(asyncResp, connectionName, path); 495 } 496 else if (interface == "xyz.openbmc_project.State.Drive") 497 { 498 getDriveState(asyncResp, connectionName, path); 499 } 500 else if (interface == "xyz.openbmc_project.Inventory.Item.Drive") 501 { 502 getDriveItemProperties(asyncResp, connectionName, path); 503 } 504 } 505 } 506 507 inline void afterGetSubtreeSystemsStorageDrive( 508 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 509 const std::string& driveId, const boost::system::error_code& ec, 510 const dbus::utility::MapperGetSubTreeResponse& subtree) 511 { 512 if (ec) 513 { 514 BMCWEB_LOG_ERROR << "Drive mapper call error"; 515 messages::internalError(asyncResp->res); 516 return; 517 } 518 519 auto drive = std::find_if( 520 subtree.begin(), subtree.end(), 521 [&driveId](const std::pair<std::string, 522 dbus::utility::MapperServiceMap>& object) { 523 return sdbusplus::message::object_path(object.first).filename() == 524 driveId; 525 }); 526 527 if (drive == subtree.end()) 528 { 529 messages::resourceNotFound(asyncResp->res, "Drive", driveId); 530 return; 531 } 532 533 const std::string& path = drive->first; 534 const dbus::utility::MapperServiceMap& connectionNames = drive->second; 535 536 asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive"; 537 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 538 "/redfish/v1/Systems/system/Storage/1/Drives/{}", driveId); 539 asyncResp->res.jsonValue["Name"] = driveId; 540 asyncResp->res.jsonValue["Id"] = driveId; 541 542 if (connectionNames.size() != 1) 543 { 544 BMCWEB_LOG_ERROR << "Connection size " << connectionNames.size() 545 << ", not equal to 1"; 546 messages::internalError(asyncResp->res); 547 return; 548 } 549 550 getMainChassisId(asyncResp, 551 [](const std::string& chassisId, 552 const std::shared_ptr<bmcweb::AsyncResp>& aRsp) { 553 aRsp->res.jsonValue["Links"]["Chassis"]["@odata.id"] = 554 boost::urls::format("/redfish/v1/Chassis/{}", chassisId); 555 }); 556 557 // default it to Enabled 558 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 559 560 if constexpr (bmcwebEnableHealthPopulate) 561 { 562 auto health = std::make_shared<HealthPopulate>(asyncResp); 563 health->inventory.emplace_back(path); 564 health->populate(); 565 } 566 567 addAllDriveInfo(asyncResp, connectionNames[0].first, path, 568 connectionNames[0].second); 569 } 570 571 inline void handleSystemsStorageDriveGet( 572 App& app, const crow::Request& req, 573 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 574 const std::string& systemName, const std::string& driveId) 575 { 576 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 577 { 578 return; 579 } 580 if (systemName != "system") 581 { 582 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 583 systemName); 584 return; 585 } 586 587 constexpr std::array<std::string_view, 1> interfaces = { 588 "xyz.openbmc_project.Inventory.Item.Drive"}; 589 dbus::utility::getSubTree( 590 "/xyz/openbmc_project/inventory", 0, interfaces, 591 std::bind_front(afterGetSubtreeSystemsStorageDrive, asyncResp, 592 driveId)); 593 } 594 595 inline void requestRoutesDrive(App& app) 596 { 597 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Drives/<str>/") 598 .privileges(redfish::privileges::getDrive) 599 .methods(boost::beast::http::verb::get)( 600 std::bind_front(handleSystemsStorageDriveGet, std::ref(app))); 601 } 602 603 inline void afterChassisDriveCollectionSubtreeGet( 604 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 605 const std::string& chassisId, const boost::system::error_code& ec, 606 const dbus::utility::MapperGetSubTreeResponse& subtree) 607 { 608 if (ec) 609 { 610 if (ec == boost::system::errc::host_unreachable) 611 { 612 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 613 return; 614 } 615 messages::internalError(asyncResp->res); 616 return; 617 } 618 619 // Iterate over all retrieved ObjectPaths. 620 for (const auto& [path, connectionNames] : subtree) 621 { 622 sdbusplus::message::object_path objPath(path); 623 if (objPath.filename() != chassisId) 624 { 625 continue; 626 } 627 628 if (connectionNames.empty()) 629 { 630 BMCWEB_LOG_ERROR << "Got 0 Connection names"; 631 continue; 632 } 633 634 asyncResp->res.jsonValue["@odata.type"] = 635 "#DriveCollection.DriveCollection"; 636 asyncResp->res.jsonValue["@odata.id"] = 637 boost::urls::format("/redfish/v1/Chassis/{}/Drives", chassisId); 638 asyncResp->res.jsonValue["Name"] = "Drive Collection"; 639 640 // Association lambda 641 dbus::utility::getAssociationEndPoints( 642 path + "/drive", 643 [asyncResp, chassisId](const boost::system::error_code& ec3, 644 const dbus::utility::MapperEndPoints& resp) { 645 if (ec3) 646 { 647 BMCWEB_LOG_ERROR << "Error in chassis Drive association "; 648 } 649 nlohmann::json& members = asyncResp->res.jsonValue["Members"]; 650 // important if array is empty 651 members = nlohmann::json::array(); 652 653 std::vector<std::string> leafNames; 654 for (const auto& drive : resp) 655 { 656 sdbusplus::message::object_path drivePath(drive); 657 leafNames.push_back(drivePath.filename()); 658 } 659 660 std::sort(leafNames.begin(), leafNames.end(), 661 AlphanumLess<std::string>()); 662 663 for (const auto& leafName : leafNames) 664 { 665 nlohmann::json::object_t member; 666 member["@odata.id"] = boost::urls::format( 667 "/redfish/v1/Chassis/{}/Drives/{}", chassisId, leafName); 668 members.emplace_back(std::move(member)); 669 // navigation links will be registered in next patch set 670 } 671 asyncResp->res.jsonValue["Members@odata.count"] = resp.size(); 672 }); // end association lambda 673 674 } // end Iterate over all retrieved ObjectPaths 675 } 676 /** 677 * Chassis drives, this URL will show all the DriveCollection 678 * information 679 */ 680 inline void chassisDriveCollectionGet( 681 crow::App& app, const crow::Request& req, 682 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 683 const std::string& chassisId) 684 { 685 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 686 { 687 return; 688 } 689 690 // mapper call lambda 691 constexpr std::array<std::string_view, 2> interfaces = { 692 "xyz.openbmc_project.Inventory.Item.Board", 693 "xyz.openbmc_project.Inventory.Item.Chassis"}; 694 dbus::utility::getSubTree( 695 "/xyz/openbmc_project/inventory", 0, interfaces, 696 std::bind_front(afterChassisDriveCollectionSubtreeGet, asyncResp, 697 chassisId)); 698 } 699 700 inline void requestRoutesChassisDrive(App& app) 701 { 702 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/") 703 .privileges(redfish::privileges::getDriveCollection) 704 .methods(boost::beast::http::verb::get)( 705 std::bind_front(chassisDriveCollectionGet, std::ref(app))); 706 } 707 708 inline void buildDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 709 const std::string& chassisId, 710 const std::string& driveName, 711 const boost::system::error_code& ec, 712 const dbus::utility::MapperGetSubTreeResponse& subtree) 713 { 714 if (ec) 715 { 716 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 717 messages::internalError(asyncResp->res); 718 return; 719 } 720 721 // Iterate over all retrieved ObjectPaths. 722 for (const auto& [path, connectionNames] : subtree) 723 { 724 sdbusplus::message::object_path objPath(path); 725 if (objPath.filename() != driveName) 726 { 727 continue; 728 } 729 730 if (connectionNames.empty()) 731 { 732 BMCWEB_LOG_ERROR << "Got 0 Connection names"; 733 continue; 734 } 735 736 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 737 "/redfish/v1/Chassis/{}/Drives/{}", chassisId, driveName); 738 739 asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive"; 740 asyncResp->res.jsonValue["Name"] = driveName; 741 asyncResp->res.jsonValue["Id"] = driveName; 742 // default it to Enabled 743 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 744 745 nlohmann::json::object_t linkChassisNav; 746 linkChassisNav["@odata.id"] = 747 boost::urls::format("/redfish/v1/Chassis/{}", chassisId); 748 asyncResp->res.jsonValue["Links"]["Chassis"] = linkChassisNav; 749 750 addAllDriveInfo(asyncResp, connectionNames[0].first, path, 751 connectionNames[0].second); 752 } 753 } 754 755 inline void 756 matchAndFillDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 757 const std::string& chassisId, 758 const std::string& driveName, 759 const std::vector<std::string>& resp) 760 { 761 for (const std::string& drivePath : resp) 762 { 763 sdbusplus::message::object_path path(drivePath); 764 std::string leaf = path.filename(); 765 if (leaf != driveName) 766 { 767 continue; 768 } 769 // mapper call drive 770 constexpr std::array<std::string_view, 1> driveInterface = { 771 "xyz.openbmc_project.Inventory.Item.Drive"}; 772 dbus::utility::getSubTree( 773 "/xyz/openbmc_project/inventory", 0, driveInterface, 774 [asyncResp, chassisId, driveName]( 775 const boost::system::error_code& ec, 776 const dbus::utility::MapperGetSubTreeResponse& subtree) { 777 buildDrive(asyncResp, chassisId, driveName, ec, subtree); 778 }); 779 } 780 } 781 782 inline void 783 handleChassisDriveGet(crow::App& app, const crow::Request& req, 784 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 785 const std::string& chassisId, 786 const std::string& driveName) 787 { 788 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 789 { 790 return; 791 } 792 constexpr std::array<std::string_view, 2> interfaces = { 793 "xyz.openbmc_project.Inventory.Item.Board", 794 "xyz.openbmc_project.Inventory.Item.Chassis"}; 795 796 // mapper call chassis 797 dbus::utility::getSubTree( 798 "/xyz/openbmc_project/inventory", 0, interfaces, 799 [asyncResp, chassisId, 800 driveName](const boost::system::error_code& ec, 801 const dbus::utility::MapperGetSubTreeResponse& subtree) { 802 if (ec) 803 { 804 messages::internalError(asyncResp->res); 805 return; 806 } 807 808 // Iterate over all retrieved ObjectPaths. 809 for (const auto& [path, connectionNames] : subtree) 810 { 811 sdbusplus::message::object_path objPath(path); 812 if (objPath.filename() != chassisId) 813 { 814 continue; 815 } 816 817 if (connectionNames.empty()) 818 { 819 BMCWEB_LOG_ERROR << "Got 0 Connection names"; 820 continue; 821 } 822 823 dbus::utility::getAssociationEndPoints( 824 path + "/drive", 825 [asyncResp, chassisId, 826 driveName](const boost::system::error_code& ec3, 827 const dbus::utility::MapperEndPoints& resp) { 828 if (ec3) 829 { 830 return; // no drives = no failures 831 } 832 matchAndFillDrive(asyncResp, chassisId, driveName, resp); 833 }); 834 break; 835 } 836 }); 837 } 838 839 /** 840 * This URL will show the drive interface for the specific drive in the chassis 841 */ 842 inline void requestRoutesChassisDriveName(App& app) 843 { 844 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/<str>/") 845 .privileges(redfish::privileges::getChassis) 846 .methods(boost::beast::http::verb::get)( 847 std::bind_front(handleChassisDriveGet, std::ref(app))); 848 } 849 850 inline void getStorageControllerAsset( 851 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 852 const boost::system::error_code& ec, 853 const std::vector<std::pair<std::string, dbus::utility::DbusVariantType>>& 854 propertiesList) 855 { 856 if (ec) 857 { 858 // this interface isn't necessary 859 BMCWEB_LOG_DEBUG << "Failed to get StorageControllerAsset"; 860 return; 861 } 862 863 const std::string* partNumber = nullptr; 864 const std::string* serialNumber = nullptr; 865 const std::string* manufacturer = nullptr; 866 const std::string* model = nullptr; 867 if (!sdbusplus::unpackPropertiesNoThrow( 868 dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber", 869 partNumber, "SerialNumber", serialNumber, "Manufacturer", 870 manufacturer, "Model", model)) 871 { 872 messages::internalError(asyncResp->res); 873 return; 874 } 875 876 if (partNumber != nullptr) 877 { 878 asyncResp->res.jsonValue["PartNumber"] = *partNumber; 879 } 880 881 if (serialNumber != nullptr) 882 { 883 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber; 884 } 885 886 if (manufacturer != nullptr) 887 { 888 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer; 889 } 890 891 if (model != nullptr) 892 { 893 asyncResp->res.jsonValue["Model"] = *model; 894 } 895 } 896 897 inline void populateStorageController( 898 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 899 const std::string& controllerId, const std::string& connectionName, 900 const std::string& path) 901 { 902 asyncResp->res.jsonValue["@odata.type"] = 903 "#StorageController.v1_6_0.StorageController"; 904 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 905 "/redfish/v1/Systems/system/Storage/1/Controllers/{}", controllerId); 906 asyncResp->res.jsonValue["Name"] = controllerId; 907 asyncResp->res.jsonValue["Id"] = controllerId; 908 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 909 910 sdbusplus::asio::getProperty<bool>( 911 *crow::connections::systemBus, connectionName, path, 912 "xyz.openbmc_project.Inventory.Item", "Present", 913 [asyncResp](const boost::system::error_code& ec, bool isPresent) { 914 // this interface isn't necessary, only check it 915 // if we get a good return 916 if (ec) 917 { 918 BMCWEB_LOG_DEBUG << "Failed to get Present property"; 919 return; 920 } 921 if (!isPresent) 922 { 923 asyncResp->res.jsonValue["Status"]["State"] = "Absent"; 924 } 925 }); 926 927 sdbusplus::asio::getAllProperties( 928 *crow::connections::systemBus, connectionName, path, 929 "xyz.openbmc_project.Inventory.Decorator.Asset", 930 [asyncResp](const boost::system::error_code& ec, 931 const std::vector< 932 std::pair<std::string, dbus::utility::DbusVariantType>>& 933 propertiesList) { 934 getStorageControllerAsset(asyncResp, ec, propertiesList); 935 }); 936 } 937 938 inline void getStorageControllerHandler( 939 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 940 const std::string& controllerId, const boost::system::error_code& ec, 941 const dbus::utility::MapperGetSubTreeResponse& subtree) 942 { 943 if (ec || subtree.empty()) 944 { 945 // doesn't have to be there 946 BMCWEB_LOG_DEBUG << "Failed to handle StorageController"; 947 return; 948 } 949 950 for (const auto& [path, interfaceDict] : subtree) 951 { 952 sdbusplus::message::object_path object(path); 953 std::string id = object.filename(); 954 if (id.empty()) 955 { 956 BMCWEB_LOG_ERROR << "Failed to find filename in " << path; 957 return; 958 } 959 if (id != controllerId) 960 { 961 continue; 962 } 963 964 if (interfaceDict.size() != 1) 965 { 966 BMCWEB_LOG_ERROR << "Connection size " << interfaceDict.size() 967 << ", greater than 1"; 968 messages::internalError(asyncResp->res); 969 return; 970 } 971 972 const std::string& connectionName = interfaceDict.front().first; 973 populateStorageController(asyncResp, controllerId, connectionName, 974 path); 975 } 976 } 977 978 inline void populateStorageControllerCollection( 979 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 980 const boost::system::error_code& ec, 981 const dbus::utility::MapperGetSubTreePathsResponse& controllerList) 982 { 983 nlohmann::json::array_t members; 984 if (ec || controllerList.empty()) 985 { 986 asyncResp->res.jsonValue["Members"] = std::move(members); 987 asyncResp->res.jsonValue["Members@odata.count"] = 0; 988 BMCWEB_LOG_DEBUG << "Failed to find any StorageController"; 989 return; 990 } 991 992 for (const std::string& path : controllerList) 993 { 994 std::string id = sdbusplus::message::object_path(path).filename(); 995 if (id.empty()) 996 { 997 BMCWEB_LOG_ERROR << "Failed to find filename in " << path; 998 return; 999 } 1000 nlohmann::json::object_t member; 1001 member["@odata.id"] = boost::urls::format( 1002 "/redfish/v1/Systems/system/Storage/1/Controllers/{}", id); 1003 members.emplace_back(member); 1004 } 1005 asyncResp->res.jsonValue["Members@odata.count"] = members.size(); 1006 asyncResp->res.jsonValue["Members"] = std::move(members); 1007 } 1008 1009 inline void handleSystemsStorageControllerCollectionGet( 1010 App& app, const crow::Request& req, 1011 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1012 const std::string& systemName) 1013 { 1014 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1015 { 1016 BMCWEB_LOG_DEBUG 1017 << "Failed to setup Redfish Route for StorageController Collection"; 1018 return; 1019 } 1020 if (systemName != "system") 1021 { 1022 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1023 systemName); 1024 BMCWEB_LOG_DEBUG << "Failed to find ComputerSystem of " << systemName; 1025 return; 1026 } 1027 1028 asyncResp->res.jsonValue["@odata.type"] = 1029 "#StorageControllerCollection.StorageControllerCollection"; 1030 asyncResp->res.jsonValue["@odata.id"] = 1031 "/redfish/v1/Systems/system/Storage/1/Controllers"; 1032 asyncResp->res.jsonValue["Name"] = "Storage Controller Collection"; 1033 1034 constexpr std::array<std::string_view, 1> interfaces = { 1035 "xyz.openbmc_project.Inventory.Item.StorageController"}; 1036 dbus::utility::getSubTreePaths( 1037 "/xyz/openbmc_project/inventory", 0, interfaces, 1038 [asyncResp](const boost::system::error_code& ec, 1039 const dbus::utility::MapperGetSubTreePathsResponse& 1040 controllerList) { 1041 populateStorageControllerCollection(asyncResp, ec, controllerList); 1042 }); 1043 } 1044 1045 inline void handleSystemsStorageControllerGet( 1046 App& app, const crow::Request& req, 1047 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1048 const std::string& systemName, const std::string& controllerId) 1049 { 1050 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1051 { 1052 BMCWEB_LOG_DEBUG 1053 << "Failed to setup Redfish Route for StorageController"; 1054 return; 1055 } 1056 if (systemName != "system") 1057 { 1058 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1059 systemName); 1060 BMCWEB_LOG_DEBUG << "Failed to find ComputerSystem of " << systemName; 1061 return; 1062 } 1063 constexpr std::array<std::string_view, 1> interfaces = { 1064 "xyz.openbmc_project.Inventory.Item.StorageController"}; 1065 dbus::utility::getSubTree( 1066 "/xyz/openbmc_project/inventory", 0, interfaces, 1067 [asyncResp, 1068 controllerId](const boost::system::error_code& ec, 1069 const dbus::utility::MapperGetSubTreeResponse& subtree) { 1070 getStorageControllerHandler(asyncResp, controllerId, ec, subtree); 1071 }); 1072 } 1073 1074 inline void requestRoutesStorageControllerCollection(App& app) 1075 { 1076 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Controllers/") 1077 .privileges(redfish::privileges::getStorageControllerCollection) 1078 .methods(boost::beast::http::verb::get)(std::bind_front( 1079 handleSystemsStorageControllerCollectionGet, std::ref(app))); 1080 } 1081 1082 inline void requestRoutesStorageController(App& app) 1083 { 1084 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Storage/1/Controllers/<str>") 1085 .privileges(redfish::privileges::getStorageController) 1086 .methods(boost::beast::http::verb::get)( 1087 std::bind_front(handleSystemsStorageControllerGet, std::ref(app))); 1088 } 1089 1090 } // namespace redfish 1091