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