1 /* 2 // Copyright (c) 2019 Intel Corporation 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 */ 16 #pragma once 17 18 #include "health.hpp" 19 #include "openbmc_dbus_rest.hpp" 20 21 #include <app.hpp> 22 #include <dbus_utility.hpp> 23 #include <query.hpp> 24 #include <registries/privilege_registry.hpp> 25 #include <sdbusplus/asio/property.hpp> 26 27 namespace redfish 28 { 29 inline void requestRoutesStorageCollection(App& app) 30 { 31 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/Storage/") 32 .privileges(redfish::privileges::getStorageCollection) 33 .methods(boost::beast::http::verb::get)( 34 [&app](const crow::Request& req, 35 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 36 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 37 { 38 return; 39 } 40 asyncResp->res.jsonValue["@odata.type"] = 41 "#StorageCollection.StorageCollection"; 42 asyncResp->res.jsonValue["@odata.id"] = 43 "/redfish/v1/Systems/system/Storage"; 44 asyncResp->res.jsonValue["Name"] = "Storage Collection"; 45 nlohmann::json::array_t members; 46 nlohmann::json::object_t member; 47 member["@odata.id"] = "/redfish/v1/Systems/system/Storage/1"; 48 members.emplace_back(member); 49 asyncResp->res.jsonValue["Members"] = std::move(members); 50 asyncResp->res.jsonValue["Members@odata.count"] = 1; 51 }); 52 } 53 54 inline void getDrives(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 55 const std::shared_ptr<HealthPopulate>& health) 56 { 57 crow::connections::systemBus->async_method_call( 58 [asyncResp, health]( 59 const boost::system::error_code ec, 60 const dbus::utility::MapperGetSubTreePathsResponse& driveList) { 61 if (ec) 62 { 63 BMCWEB_LOG_ERROR << "Drive mapper call error"; 64 messages::internalError(asyncResp->res); 65 return; 66 } 67 68 nlohmann::json& driveArray = asyncResp->res.jsonValue["Drives"]; 69 driveArray = nlohmann::json::array(); 70 auto& count = asyncResp->res.jsonValue["Drives@odata.count"]; 71 count = 0; 72 73 health->inventory.insert(health->inventory.end(), driveList.begin(), 74 driveList.end()); 75 76 for (const std::string& drive : driveList) 77 { 78 sdbusplus::message::object_path object(drive); 79 if (object.filename().empty()) 80 { 81 BMCWEB_LOG_ERROR << "Failed to find filename in " << drive; 82 return; 83 } 84 85 nlohmann::json::object_t driveJson; 86 driveJson["@odata.id"] = 87 "/redfish/v1/Systems/system/Storage/1/Drives/" + 88 object.filename(); 89 driveArray.push_back(std::move(driveJson)); 90 } 91 92 count = driveArray.size(); 93 }, 94 "xyz.openbmc_project.ObjectMapper", 95 "/xyz/openbmc_project/object_mapper", 96 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", 97 "/xyz/openbmc_project/inventory", int32_t(0), 98 std::array<const char*, 1>{"xyz.openbmc_project.Inventory.Item.Drive"}); 99 } 100 101 inline void 102 getStorageControllers(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 103 const std::shared_ptr<HealthPopulate>& health) 104 { 105 crow::connections::systemBus->async_method_call( 106 [asyncResp, 107 health](const boost::system::error_code ec, 108 const dbus::utility::MapperGetSubTreeResponse& subtree) { 109 if (ec || subtree.empty()) 110 { 111 // doesn't have to be there 112 return; 113 } 114 115 nlohmann::json& root = asyncResp->res.jsonValue["StorageControllers"]; 116 root = nlohmann::json::array(); 117 for (const auto& [path, interfaceDict] : subtree) 118 { 119 sdbusplus::message::object_path object(path); 120 std::string id = object.filename(); 121 if (id.empty()) 122 { 123 BMCWEB_LOG_ERROR << "Failed to find filename in " << path; 124 return; 125 } 126 127 if (interfaceDict.size() != 1) 128 { 129 BMCWEB_LOG_ERROR << "Connection size " << interfaceDict.size() 130 << ", greater than 1"; 131 messages::internalError(asyncResp->res); 132 return; 133 } 134 135 const std::string& connectionName = interfaceDict.front().first; 136 137 size_t index = root.size(); 138 nlohmann::json& storageController = 139 root.emplace_back(nlohmann::json::object()); 140 141 storageController["@odata.type"] = 142 "#Storage.v1_7_0.StorageController"; 143 storageController["@odata.id"] = 144 "/redfish/v1/Systems/system/Storage/1#/StorageControllers/" + 145 std::to_string(index); 146 storageController["Name"] = id; 147 storageController["MemberId"] = id; 148 storageController["Status"]["State"] = "Enabled"; 149 150 sdbusplus::asio::getProperty<bool>( 151 *crow::connections::systemBus, connectionName, path, 152 "xyz.openbmc_project.Inventory.Item", "Present", 153 [asyncResp, index](const boost::system::error_code ec2, 154 bool enabled) { 155 // this interface isn't necessary, only check it 156 // if we get a good return 157 if (ec2) 158 { 159 return; 160 } 161 if (!enabled) 162 { 163 asyncResp->res.jsonValue["StorageControllers"][index] 164 ["Status"]["State"] = "Disabled"; 165 } 166 }); 167 168 crow::connections::systemBus->async_method_call( 169 [asyncResp, index]( 170 const boost::system::error_code ec2, 171 const std::vector< 172 std::pair<std::string, dbus::utility::DbusVariantType>>& 173 propertiesList) { 174 if (ec2) 175 { 176 // this interface isn't necessary 177 return; 178 } 179 for (const std::pair<std::string, 180 dbus::utility::DbusVariantType>& property : 181 propertiesList) 182 { 183 // Store DBus properties that are also 184 // Redfish properties with same name and a 185 // string value 186 const std::string& propertyName = property.first; 187 nlohmann::json& object = 188 asyncResp->res.jsonValue["StorageControllers"][index]; 189 if ((propertyName == "PartNumber") || 190 (propertyName == "SerialNumber") || 191 (propertyName == "Manufacturer") || 192 (propertyName == "Model")) 193 { 194 const std::string* value = 195 std::get_if<std::string>(&property.second); 196 if (value == nullptr) 197 { 198 // illegal property 199 messages::internalError(asyncResp->res); 200 return; 201 } 202 object[propertyName] = *value; 203 } 204 } 205 }, 206 connectionName, path, "org.freedesktop.DBus.Properties", 207 "GetAll", "xyz.openbmc_project.Inventory.Decorator.Asset"); 208 } 209 210 // this is done after we know the json array will no longer 211 // be resized, as json::array uses vector underneath and we 212 // need references to its members that won't change 213 size_t count = 0; 214 // Pointer based on |asyncResp->res.jsonValue| 215 nlohmann::json::json_pointer rootPtr = 216 "/StorageControllers"_json_pointer; 217 for (const auto& [path, interfaceDict] : subtree) 218 { 219 auto subHealth = std::make_shared<HealthPopulate>( 220 asyncResp, rootPtr / count / "Status"); 221 subHealth->inventory.emplace_back(path); 222 health->inventory.emplace_back(path); 223 health->children.emplace_back(subHealth); 224 count++; 225 } 226 }, 227 "xyz.openbmc_project.ObjectMapper", 228 "/xyz/openbmc_project/object_mapper", 229 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 230 "/xyz/openbmc_project/inventory", int32_t(0), 231 std::array<const char*, 1>{ 232 "xyz.openbmc_project.Inventory.Item.StorageController"}); 233 } 234 235 inline void requestRoutesStorage(App& app) 236 { 237 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/Storage/1/") 238 .privileges(redfish::privileges::getStorage) 239 .methods(boost::beast::http::verb::get)( 240 [&app](const crow::Request& req, 241 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 242 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 243 { 244 return; 245 } 246 asyncResp->res.jsonValue["@odata.type"] = "#Storage.v1_7_1.Storage"; 247 asyncResp->res.jsonValue["@odata.id"] = 248 "/redfish/v1/Systems/system/Storage/1"; 249 asyncResp->res.jsonValue["Name"] = "Storage"; 250 asyncResp->res.jsonValue["Id"] = "1"; 251 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 252 253 auto health = std::make_shared<HealthPopulate>(asyncResp); 254 health->populate(); 255 256 getDrives(asyncResp, health); 257 getStorageControllers(asyncResp, health); 258 }); 259 } 260 261 inline void getDriveAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 262 const std::string& connectionName, 263 const std::string& path) 264 { 265 crow::connections::systemBus->async_method_call( 266 [asyncResp](const boost::system::error_code ec, 267 const std::vector< 268 std::pair<std::string, dbus::utility::DbusVariantType>>& 269 propertiesList) { 270 if (ec) 271 { 272 // this interface isn't necessary 273 return; 274 } 275 for (const std::pair<std::string, dbus::utility::DbusVariantType>& 276 property : propertiesList) 277 { 278 // Store DBus properties that are also 279 // Redfish properties with same name and a 280 // string value 281 const std::string& propertyName = property.first; 282 if ((propertyName == "PartNumber") || 283 (propertyName == "SerialNumber") || 284 (propertyName == "Manufacturer") || (propertyName == "Model")) 285 { 286 const std::string* value = 287 std::get_if<std::string>(&property.second); 288 if (value == nullptr) 289 { 290 // illegal property 291 messages::internalError(asyncResp->res); 292 return; 293 } 294 asyncResp->res.jsonValue[propertyName] = *value; 295 } 296 } 297 }, 298 connectionName, path, "org.freedesktop.DBus.Properties", "GetAll", 299 "xyz.openbmc_project.Inventory.Decorator.Asset"); 300 } 301 302 inline void getDrivePresent(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 303 const std::string& connectionName, 304 const std::string& path) 305 { 306 sdbusplus::asio::getProperty<bool>( 307 *crow::connections::systemBus, connectionName, path, 308 "xyz.openbmc_project.Inventory.Item", "Present", 309 [asyncResp, path](const boost::system::error_code ec, 310 const bool enabled) { 311 // this interface isn't necessary, only check it if 312 // we get a good return 313 if (ec) 314 { 315 return; 316 } 317 318 if (!enabled) 319 { 320 asyncResp->res.jsonValue["Status"]["State"] = "Disabled"; 321 } 322 }); 323 } 324 325 inline void getDriveState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 326 const std::string& connectionName, 327 const std::string& path) 328 { 329 sdbusplus::asio::getProperty<bool>( 330 *crow::connections::systemBus, connectionName, path, 331 "xyz.openbmc_project.State.Drive", "Rebuilding", 332 [asyncResp](const boost::system::error_code ec, const bool updating) { 333 // this interface isn't necessary, only check it 334 // if we get a good return 335 if (ec) 336 { 337 return; 338 } 339 340 // updating and disabled in the backend shouldn't be 341 // able to be set at the same time, so we don't need 342 // to check for the race condition of these two 343 // calls 344 if (updating) 345 { 346 asyncResp->res.jsonValue["Status"]["State"] = "Updating"; 347 } 348 }); 349 } 350 351 inline std::optional<std::string> convertDriveType(const std::string& type) 352 { 353 if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.HDD") 354 { 355 return "HDD"; 356 } 357 if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.SSD") 358 { 359 return "SSD"; 360 } 361 362 return std::nullopt; 363 } 364 365 inline std::optional<std::string> convertDriveProtocol(const std::string& proto) 366 { 367 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SAS") 368 { 369 return "SAS"; 370 } 371 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SATA") 372 { 373 return "SATA"; 374 } 375 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.NVMe") 376 { 377 return "NVMe"; 378 } 379 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.FC") 380 { 381 return "FC"; 382 } 383 384 return std::nullopt; 385 } 386 387 inline void 388 getDriveItemProperties(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 389 const std::string& connectionName, 390 const std::string& path) 391 { 392 sdbusplus::asio::getAllProperties( 393 *crow::connections::systemBus, connectionName, path, 394 "xyz.openbmc_project.Inventory.Item.Drive", 395 [asyncResp](const boost::system::error_code ec, 396 const std::vector< 397 std::pair<std::string, dbus::utility::DbusVariantType>>& 398 propertiesList) { 399 if (ec) 400 { 401 // this interface isn't required 402 return; 403 } 404 for (const std::pair<std::string, dbus::utility::DbusVariantType>& 405 property : propertiesList) 406 { 407 const std::string& propertyName = property.first; 408 if (propertyName == "Type") 409 { 410 const std::string* value = 411 std::get_if<std::string>(&property.second); 412 if (value == nullptr) 413 { 414 // illegal property 415 BMCWEB_LOG_ERROR << "Illegal property: Type"; 416 messages::internalError(asyncResp->res); 417 return; 418 } 419 420 std::optional<std::string> mediaType = convertDriveType(*value); 421 if (!mediaType) 422 { 423 BMCWEB_LOG_ERROR << "Unsupported DriveType Interface: " 424 << *value; 425 messages::internalError(asyncResp->res); 426 return; 427 } 428 429 asyncResp->res.jsonValue["MediaType"] = *mediaType; 430 } 431 else if (propertyName == "Capacity") 432 { 433 const uint64_t* capacity = 434 std::get_if<uint64_t>(&property.second); 435 if (capacity == nullptr) 436 { 437 BMCWEB_LOG_ERROR << "Illegal property: Capacity"; 438 messages::internalError(asyncResp->res); 439 return; 440 } 441 if (*capacity == 0) 442 { 443 // drive capacity not known 444 continue; 445 } 446 447 asyncResp->res.jsonValue["CapacityBytes"] = *capacity; 448 } 449 else if (propertyName == "Protocol") 450 { 451 const std::string* value = 452 std::get_if<std::string>(&property.second); 453 if (value == nullptr) 454 { 455 BMCWEB_LOG_ERROR << "Illegal property: Protocol"; 456 messages::internalError(asyncResp->res); 457 return; 458 } 459 460 std::optional<std::string> proto = convertDriveProtocol(*value); 461 if (!proto) 462 { 463 BMCWEB_LOG_ERROR << "Unsupported DrivePrototype Interface: " 464 << *value; 465 messages::internalError(asyncResp->res); 466 return; 467 } 468 asyncResp->res.jsonValue["Protocol"] = *proto; 469 } 470 } 471 }); 472 } 473 474 inline void requestRoutesDrive(App& app) 475 { 476 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/Storage/1/Drives/<str>/") 477 .privileges(redfish::privileges::getDrive) 478 .methods(boost::beast::http::verb::get)( 479 [&app](const crow::Request& req, 480 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 481 const std::string& driveId) { 482 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 483 { 484 return; 485 } 486 crow::connections::systemBus->async_method_call( 487 [asyncResp, 488 driveId](const boost::system::error_code ec, 489 const dbus::utility::MapperGetSubTreeResponse& subtree) { 490 if (ec) 491 { 492 BMCWEB_LOG_ERROR << "Drive mapper call error"; 493 messages::internalError(asyncResp->res); 494 return; 495 } 496 497 auto drive = std::find_if( 498 subtree.begin(), subtree.end(), 499 [&driveId]( 500 const std::pair< 501 std::string, 502 std::vector<std::pair< 503 std::string, std::vector<std::string>>>>& object) { 504 return sdbusplus::message::object_path(object.first) 505 .filename() == driveId; 506 }); 507 508 if (drive == subtree.end()) 509 { 510 messages::resourceNotFound(asyncResp->res, "Drive", driveId); 511 return; 512 } 513 514 const std::string& path = drive->first; 515 const std::vector<std::pair<std::string, std::vector<std::string>>>& 516 connectionNames = drive->second; 517 518 asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive"; 519 asyncResp->res.jsonValue["@odata.id"] = 520 "/redfish/v1/Systems/system/Storage/1/Drives/" + driveId; 521 asyncResp->res.jsonValue["Name"] = driveId; 522 asyncResp->res.jsonValue["Id"] = driveId; 523 524 if (connectionNames.size() != 1) 525 { 526 BMCWEB_LOG_ERROR << "Connection size " << connectionNames.size() 527 << ", not equal to 1"; 528 messages::internalError(asyncResp->res); 529 return; 530 } 531 532 getMainChassisId( 533 asyncResp, [](const std::string& chassisId, 534 const std::shared_ptr<bmcweb::AsyncResp>& aRsp) { 535 aRsp->res.jsonValue["Links"]["Chassis"]["@odata.id"] = 536 "/redfish/v1/Chassis/" + chassisId; 537 }); 538 539 // default it to Enabled 540 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 541 542 auto health = std::make_shared<HealthPopulate>(asyncResp); 543 health->inventory.emplace_back(path); 544 health->populate(); 545 546 const std::string& connectionName = connectionNames[0].first; 547 548 for (const std::string& interface : connectionNames[0].second) 549 { 550 if (interface == 551 "xyz.openbmc_project.Inventory.Decorator.Asset") 552 { 553 getDriveAsset(asyncResp, connectionName, path); 554 } 555 else if (interface == "xyz.openbmc_project.Inventory.Item") 556 { 557 getDrivePresent(asyncResp, connectionName, path); 558 } 559 else if (interface == "xyz.openbmc_project.State.Drive") 560 { 561 getDriveState(asyncResp, connectionName, path); 562 } 563 else if (interface == 564 "xyz.openbmc_project.Inventory.Item.Drive") 565 { 566 getDriveItemProperties(asyncResp, connectionName, path); 567 } 568 } 569 }, 570 "xyz.openbmc_project.ObjectMapper", 571 "/xyz/openbmc_project/object_mapper", 572 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 573 "/xyz/openbmc_project/inventory", int32_t(0), 574 std::array<const char*, 1>{ 575 "xyz.openbmc_project.Inventory.Item.Drive"}); 576 }); 577 } 578 579 /** 580 * Chassis drives, this URL will show all the DriveCollection 581 * information 582 */ 583 void chassisDriveCollectionGet( 584 crow::App& app, const crow::Request& req, 585 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 586 const std::string& chassisId) 587 { 588 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 589 { 590 return; 591 } 592 593 // mapper call lambda 594 crow::connections::systemBus->async_method_call( 595 [asyncResp, 596 chassisId](const boost::system::error_code ec, 597 const dbus::utility::MapperGetSubTreeResponse& subtree) { 598 if (ec) 599 { 600 if (ec == boost::system::errc::host_unreachable) 601 { 602 messages::resourceNotFound(asyncResp->res, "Chassis", 603 chassisId); 604 return; 605 } 606 messages::internalError(asyncResp->res); 607 return; 608 } 609 610 // Iterate over all retrieved ObjectPaths. 611 for (const std::pair< 612 std::string, 613 std::vector<std::pair<std::string, std::vector<std::string>>>>& 614 object : subtree) 615 { 616 const std::string& path = object.first; 617 const dbus::utility::MapperGetObject& connectionNames = 618 object.second; 619 620 sdbusplus::message::object_path objPath(path); 621 if (objPath.filename() != chassisId) 622 { 623 continue; 624 } 625 626 if (connectionNames.empty()) 627 { 628 BMCWEB_LOG_ERROR << "Got 0 Connection names"; 629 continue; 630 } 631 632 asyncResp->res.jsonValue["@odata.type"] = 633 "#DriveCollection.DriveCollection"; 634 asyncResp->res.jsonValue["@odata.id"] = 635 crow::utility::urlFromPieces("redfish", "v1", 636 "Chassis" + chassisId + "Drives"); 637 asyncResp->res.jsonValue["Name"] = "Drive Collection"; 638 639 // Association lambda 640 sdbusplus::asio::getProperty<std::vector<std::string>>( 641 *crow::connections::systemBus, 642 "xyz.openbmc_project.ObjectMapper", path + "/drive", 643 "xyz.openbmc_project.Association", "endpoints", 644 [asyncResp, chassisId](const boost::system::error_code ec3, 645 const std::vector<std::string>& 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 path(drive); 658 leafNames.push_back(path.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"] = crow::utility::urlFromPieces( 668 "redfish", "v1", "Chassis", chassisId, "Drives", 669 leafName); 670 members.push_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 "xyz.openbmc_project.ObjectMapper", 679 "/xyz/openbmc_project/object_mapper", 680 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 681 "/xyz/openbmc_project/inventory", 0, 682 std::array<const char*, 2>{ 683 "xyz.openbmc_project.Inventory.Item.Board", 684 "xyz.openbmc_project.Inventory.Item.Chassis"}); 685 } 686 687 inline void requestRoutesChassisDrive(App& app) 688 { 689 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/") 690 .privileges(redfish::privileges::getDriveCollection) 691 .methods(boost::beast::http::verb::get)( 692 std::bind_front(chassisDriveCollectionGet, std::ref(app))); 693 } 694 695 } // namespace redfish 696