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