1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation 4 #pragma once 5 6 #include "bmcweb_config.h" 7 8 #include "app.hpp" 9 #include "async_resp.hpp" 10 #include "dbus_singleton.hpp" 11 #include "dbus_utility.hpp" 12 #include "error_messages.hpp" 13 #include "generated/enums/action_info.hpp" 14 #include "generated/enums/chassis.hpp" 15 #include "generated/enums/resource.hpp" 16 #include "http_request.hpp" 17 #include "led.hpp" 18 #include "logging.hpp" 19 #include "query.hpp" 20 #include "registries/privilege_registry.hpp" 21 #include "utils/collection.hpp" 22 #include "utils/dbus_utils.hpp" 23 #include "utils/json_utils.hpp" 24 25 #include <asm-generic/errno.h> 26 27 #include <boost/beast/http/field.hpp> 28 #include <boost/beast/http/verb.hpp> 29 #include <boost/system/error_code.hpp> 30 #include <boost/url/format.hpp> 31 #include <boost/url/url.hpp> 32 #include <nlohmann/json.hpp> 33 #include <sdbusplus/asio/property.hpp> 34 #include <sdbusplus/message/native_types.hpp> 35 #include <sdbusplus/unpack_properties.hpp> 36 37 #include <algorithm> 38 #include <array> 39 #include <format> 40 #include <functional> 41 #include <memory> 42 #include <optional> 43 #include <ranges> 44 #include <string> 45 #include <string_view> 46 #include <utility> 47 #include <vector> 48 49 namespace redfish 50 { 51 52 inline chassis::ChassisType 53 translateChassisTypeToRedfish(const std::string_view& chassisType) 54 { 55 if (chassisType == 56 "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.Blade") 57 { 58 return chassis::ChassisType::Blade; 59 } 60 if (chassisType == 61 "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.Component") 62 { 63 return chassis::ChassisType::Component; 64 } 65 if (chassisType == 66 "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.Enclosure") 67 { 68 return chassis::ChassisType::Enclosure; 69 } 70 if (chassisType == 71 "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.Module") 72 { 73 return chassis::ChassisType::Module; 74 } 75 if (chassisType == 76 "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.RackMount") 77 { 78 return chassis::ChassisType::RackMount; 79 } 80 if (chassisType == 81 "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.StandAlone") 82 { 83 return chassis::ChassisType::StandAlone; 84 } 85 if (chassisType == 86 "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.StorageEnclosure") 87 { 88 return chassis::ChassisType::StorageEnclosure; 89 } 90 if (chassisType == 91 "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.Zone") 92 { 93 return chassis::ChassisType::Zone; 94 } 95 return chassis::ChassisType::Invalid; 96 } 97 98 /** 99 * @brief Retrieves resources over dbus to link to the chassis 100 * 101 * @param[in] asyncResp - Shared pointer for completing asynchronous 102 * calls 103 * @param[in] path - Chassis dbus path to look for the storage. 104 * 105 * Calls the Association endpoints on the path + "/storage" and add the link of 106 * json["Links"]["Storage@odata.count"] = 107 * {"@odata.id", "/redfish/v1/Storage/" + resourceId} 108 * 109 * @return None. 110 */ 111 inline void getStorageLink(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 112 const sdbusplus::message::object_path& path) 113 { 114 dbus::utility::getProperty<std::vector<std::string>>( 115 "xyz.openbmc_project.ObjectMapper", (path / "storage").str, 116 "xyz.openbmc_project.Association", "endpoints", 117 [asyncResp](const boost::system::error_code& ec, 118 const std::vector<std::string>& storageList) { 119 if (ec) 120 { 121 BMCWEB_LOG_DEBUG("getStorageLink got DBUS response error"); 122 return; 123 } 124 125 nlohmann::json::array_t storages; 126 for (const std::string& storagePath : storageList) 127 { 128 std::string id = 129 sdbusplus::message::object_path(storagePath).filename(); 130 if (id.empty()) 131 { 132 continue; 133 } 134 135 nlohmann::json::object_t storage; 136 storage["@odata.id"] = 137 boost::urls::format("/redfish/v1/Systems/{}/Storage/{}", 138 BMCWEB_REDFISH_SYSTEM_URI_NAME, id); 139 storages.emplace_back(std::move(storage)); 140 } 141 asyncResp->res.jsonValue["Links"]["Storage@odata.count"] = 142 storages.size(); 143 asyncResp->res.jsonValue["Links"]["Storage"] = std::move(storages); 144 }); 145 } 146 147 /** 148 * @brief Retrieves chassis state properties over dbus 149 * 150 * @param[in] asyncResp - Shared pointer for completing asynchronous calls. 151 * 152 * @return None. 153 */ 154 inline void getChassisState(std::shared_ptr<bmcweb::AsyncResp> asyncResp) 155 { 156 // crow::connections::systemBus->async_method_call( 157 dbus::utility::getProperty<std::string>( 158 "xyz.openbmc_project.State.Chassis", 159 "/xyz/openbmc_project/state/chassis0", 160 "xyz.openbmc_project.State.Chassis", "CurrentPowerState", 161 [asyncResp{std::move(asyncResp)}](const boost::system::error_code& ec, 162 const std::string& chassisState) { 163 if (ec) 164 { 165 if (ec == boost::system::errc::host_unreachable) 166 { 167 // Service not available, no error, just don't return 168 // chassis state info 169 BMCWEB_LOG_DEBUG("Service not available {}", ec); 170 return; 171 } 172 BMCWEB_LOG_DEBUG("DBUS response error {}", ec); 173 messages::internalError(asyncResp->res); 174 return; 175 } 176 177 BMCWEB_LOG_DEBUG("Chassis state: {}", chassisState); 178 // Verify Chassis State 179 if (chassisState == 180 "xyz.openbmc_project.State.Chassis.PowerState.On") 181 { 182 asyncResp->res.jsonValue["PowerState"] = 183 resource::PowerState::On; 184 asyncResp->res.jsonValue["Status"]["State"] = 185 resource::State::Enabled; 186 } 187 else if (chassisState == 188 "xyz.openbmc_project.State.Chassis.PowerState.Off") 189 { 190 asyncResp->res.jsonValue["PowerState"] = 191 resource::PowerState::Off; 192 asyncResp->res.jsonValue["Status"]["State"] = 193 resource::State::StandbyOffline; 194 } 195 }); 196 } 197 198 /** 199 * Retrieves physical security properties over dbus 200 */ 201 inline void handlePhysicalSecurityGetSubTree( 202 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 203 const boost::system::error_code& ec, 204 const dbus::utility::MapperGetSubTreeResponse& subtree) 205 { 206 if (ec) 207 { 208 // do not add err msg in redfish response, because this is not 209 // mandatory property 210 BMCWEB_LOG_INFO("DBUS error: no matched iface {}", ec); 211 return; 212 } 213 // Iterate over all retrieved ObjectPaths. 214 for (const auto& object : subtree) 215 { 216 if (!object.second.empty()) 217 { 218 const auto& service = object.second.front(); 219 220 BMCWEB_LOG_DEBUG("Get intrusion status by service "); 221 222 dbus::utility::getProperty<std::string>( 223 service.first, object.first, 224 "xyz.openbmc_project.Chassis.Intrusion", "Status", 225 [asyncResp](const boost::system::error_code& ec1, 226 const std::string& value) { 227 if (ec1) 228 { 229 // do not add err msg in redfish response, because this 230 // is not 231 // mandatory property 232 BMCWEB_LOG_ERROR("DBUS response error {}", ec1); 233 return; 234 } 235 asyncResp->res.jsonValue["PhysicalSecurity"] 236 ["IntrusionSensorNumber"] = 1; 237 asyncResp->res 238 .jsonValue["PhysicalSecurity"]["IntrusionSensor"] = 239 value; 240 }); 241 242 return; 243 } 244 } 245 } 246 247 inline void handleChassisCollectionGet( 248 App& app, const crow::Request& req, 249 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 250 { 251 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 252 { 253 return; 254 } 255 asyncResp->res.jsonValue["@odata.type"] = 256 "#ChassisCollection.ChassisCollection"; 257 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Chassis"; 258 asyncResp->res.jsonValue["Name"] = "Chassis Collection"; 259 260 constexpr std::array<std::string_view, 2> interfaces{ 261 "xyz.openbmc_project.Inventory.Item.Board", 262 "xyz.openbmc_project.Inventory.Item.Chassis"}; 263 collection_util::getCollectionMembers( 264 asyncResp, boost::urls::url("/redfish/v1/Chassis"), interfaces, 265 "/xyz/openbmc_project/inventory"); 266 } 267 268 inline void getChassisContainedBy( 269 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 270 const std::string& chassisId, const boost::system::error_code& ec, 271 const dbus::utility::MapperGetSubTreePathsResponse& upstreamChassisPaths) 272 { 273 if (ec) 274 { 275 if (ec.value() != EBADR) 276 { 277 BMCWEB_LOG_ERROR("DBUS response error {}", ec); 278 messages::internalError(asyncResp->res); 279 } 280 return; 281 } 282 if (upstreamChassisPaths.empty()) 283 { 284 return; 285 } 286 if (upstreamChassisPaths.size() > 1) 287 { 288 BMCWEB_LOG_ERROR("{} is contained by multiple chassis", chassisId); 289 messages::internalError(asyncResp->res); 290 return; 291 } 292 293 sdbusplus::message::object_path upstreamChassisPath( 294 upstreamChassisPaths[0]); 295 std::string upstreamChassis = upstreamChassisPath.filename(); 296 if (upstreamChassis.empty()) 297 { 298 BMCWEB_LOG_WARNING("Malformed upstream Chassis path {} on {}", 299 upstreamChassisPath.str, chassisId); 300 return; 301 } 302 303 asyncResp->res.jsonValue["Links"]["ContainedBy"]["@odata.id"] = 304 boost::urls::format("/redfish/v1/Chassis/{}", upstreamChassis); 305 } 306 307 inline void getChassisContains( 308 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 309 const std::string& chassisId, const boost::system::error_code& ec, 310 const dbus::utility::MapperGetSubTreePathsResponse& downstreamChassisPaths) 311 { 312 if (ec) 313 { 314 if (ec.value() != EBADR) 315 { 316 BMCWEB_LOG_ERROR("DBUS response error {}", ec); 317 messages::internalError(asyncResp->res); 318 } 319 return; 320 } 321 if (downstreamChassisPaths.empty()) 322 { 323 return; 324 } 325 nlohmann::json& jValue = asyncResp->res.jsonValue["Links"]["Contains"]; 326 if (!jValue.is_array()) 327 { 328 // Create the array if it was empty 329 jValue = nlohmann::json::array(); 330 } 331 for (const auto& p : downstreamChassisPaths) 332 { 333 sdbusplus::message::object_path downstreamChassisPath(p); 334 std::string downstreamChassis = downstreamChassisPath.filename(); 335 if (downstreamChassis.empty()) 336 { 337 BMCWEB_LOG_WARNING("Malformed downstream Chassis path {} on {}", 338 downstreamChassisPath.str, chassisId); 339 continue; 340 } 341 nlohmann::json link; 342 link["@odata.id"] = 343 boost::urls::format("/redfish/v1/Chassis/{}", downstreamChassis); 344 jValue.push_back(std::move(link)); 345 } 346 asyncResp->res.jsonValue["Links"]["Contains@odata.count"] = jValue.size(); 347 } 348 349 inline void getChassisConnectivity( 350 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 351 const std::string& chassisId, const std::string& chassisPath) 352 { 353 BMCWEB_LOG_DEBUG("Get chassis connectivity"); 354 355 constexpr std::array<std::string_view, 2> interfaces{ 356 "xyz.openbmc_project.Inventory.Item.Board", 357 "xyz.openbmc_project.Inventory.Item.Chassis"}; 358 359 dbus::utility::getAssociatedSubTreePaths( 360 chassisPath + "/contained_by", 361 sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0, 362 interfaces, 363 std::bind_front(getChassisContainedBy, asyncResp, chassisId)); 364 365 dbus::utility::getAssociatedSubTreePaths( 366 chassisPath + "/containing", 367 sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0, 368 interfaces, std::bind_front(getChassisContains, asyncResp, chassisId)); 369 } 370 371 /** 372 * ChassisCollection derived class for delivering Chassis Collection Schema 373 * Functions triggers appropriate requests on DBus 374 */ 375 inline void requestRoutesChassisCollection(App& app) 376 { 377 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/") 378 .privileges(redfish::privileges::getChassisCollection) 379 .methods(boost::beast::http::verb::get)( 380 std::bind_front(handleChassisCollectionGet, std::ref(app))); 381 } 382 383 inline void getChassisLocationCode( 384 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 385 const std::string& connectionName, const std::string& path) 386 { 387 dbus::utility::getProperty<std::string>( 388 connectionName, path, 389 "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode", 390 [asyncResp](const boost::system::error_code& ec, 391 const std::string& property) { 392 if (ec) 393 { 394 BMCWEB_LOG_ERROR("DBUS response error for Location"); 395 messages::internalError(asyncResp->res); 396 return; 397 } 398 399 asyncResp->res 400 .jsonValue["Location"]["PartLocation"]["ServiceLabel"] = 401 property; 402 }); 403 } 404 405 inline void getChassisUUID(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 406 const std::string& connectionName, 407 const std::string& path) 408 { 409 dbus::utility::getProperty<std::string>( 410 connectionName, path, "xyz.openbmc_project.Common.UUID", "UUID", 411 [asyncResp](const boost::system::error_code& ec, 412 const std::string& chassisUUID) { 413 if (ec) 414 { 415 BMCWEB_LOG_ERROR("DBUS response error for UUID"); 416 messages::internalError(asyncResp->res); 417 return; 418 } 419 asyncResp->res.jsonValue["UUID"] = chassisUUID; 420 }); 421 } 422 423 inline void handleDecoratorAssetProperties( 424 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 425 const std::string& chassisId, const std::string& path, 426 const dbus::utility::DBusPropertiesMap& propertiesList) 427 { 428 const std::string* partNumber = nullptr; 429 const std::string* serialNumber = nullptr; 430 const std::string* manufacturer = nullptr; 431 const std::string* model = nullptr; 432 const std::string* sparePartNumber = nullptr; 433 434 const bool success = sdbusplus::unpackPropertiesNoThrow( 435 dbus_utils::UnpackErrorPrinter(), propertiesList, "PartNumber", 436 partNumber, "SerialNumber", serialNumber, "Manufacturer", manufacturer, 437 "Model", model, "SparePartNumber", sparePartNumber); 438 439 if (!success) 440 { 441 messages::internalError(asyncResp->res); 442 return; 443 } 444 445 if (partNumber != nullptr) 446 { 447 asyncResp->res.jsonValue["PartNumber"] = *partNumber; 448 } 449 450 if (serialNumber != nullptr) 451 { 452 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber; 453 } 454 455 if (manufacturer != nullptr) 456 { 457 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer; 458 } 459 460 if (model != nullptr) 461 { 462 asyncResp->res.jsonValue["Model"] = *model; 463 } 464 465 // SparePartNumber is optional on D-Bus 466 // so skip if it is empty 467 if (sparePartNumber != nullptr && !sparePartNumber->empty()) 468 { 469 asyncResp->res.jsonValue["SparePartNumber"] = *sparePartNumber; 470 } 471 472 asyncResp->res.jsonValue["Name"] = chassisId; 473 asyncResp->res.jsonValue["Id"] = chassisId; 474 475 if constexpr (BMCWEB_REDFISH_ALLOW_DEPRECATED_POWER_THERMAL) 476 { 477 asyncResp->res.jsonValue["Thermal"]["@odata.id"] = 478 boost::urls::format("/redfish/v1/Chassis/{}/Thermal", chassisId); 479 // Power object 480 asyncResp->res.jsonValue["Power"]["@odata.id"] = 481 boost::urls::format("/redfish/v1/Chassis/{}/Power", chassisId); 482 } 483 484 if constexpr (BMCWEB_REDFISH_NEW_POWERSUBSYSTEM_THERMALSUBSYSTEM) 485 { 486 asyncResp->res.jsonValue["ThermalSubsystem"]["@odata.id"] = 487 boost::urls::format("/redfish/v1/Chassis/{}/ThermalSubsystem", 488 chassisId); 489 asyncResp->res.jsonValue["PowerSubsystem"]["@odata.id"] = 490 boost::urls::format("/redfish/v1/Chassis/{}/PowerSubsystem", 491 chassisId); 492 asyncResp->res.jsonValue["EnvironmentMetrics"]["@odata.id"] = 493 boost::urls::format("/redfish/v1/Chassis/{}/EnvironmentMetrics", 494 chassisId); 495 } 496 // SensorCollection 497 asyncResp->res.jsonValue["Sensors"]["@odata.id"] = 498 boost::urls::format("/redfish/v1/Chassis/{}/Sensors", chassisId); 499 asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled; 500 501 nlohmann::json::array_t computerSystems; 502 nlohmann::json::object_t system; 503 system["@odata.id"] = 504 std::format("/redfish/v1/Systems/{}", BMCWEB_REDFISH_SYSTEM_URI_NAME); 505 computerSystems.emplace_back(std::move(system)); 506 asyncResp->res.jsonValue["Links"]["ComputerSystems"] = 507 std::move(computerSystems); 508 509 nlohmann::json::array_t managedBy; 510 nlohmann::json::object_t manager; 511 manager["@odata.id"] = boost::urls::format("/redfish/v1/Managers/{}", 512 BMCWEB_REDFISH_MANAGER_URI_NAME); 513 managedBy.emplace_back(std::move(manager)); 514 asyncResp->res.jsonValue["Links"]["ManagedBy"] = std::move(managedBy); 515 getChassisState(asyncResp); 516 getStorageLink(asyncResp, path); 517 } 518 519 inline void handleChassisProperties( 520 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 521 const dbus::utility::DBusPropertiesMap& propertiesList) 522 { 523 const std::string* type = nullptr; 524 525 const bool success = sdbusplus::unpackPropertiesNoThrow( 526 dbus_utils::UnpackErrorPrinter(), propertiesList, "Type", type); 527 528 if (!success) 529 { 530 messages::internalError(asyncResp->res); 531 return; 532 } 533 534 // Chassis Type is a required property in Redfish 535 // If there is an error or some enum we don't support just sit it to Rack 536 // Mount 537 asyncResp->res.jsonValue["ChassisType"] = chassis::ChassisType::RackMount; 538 539 if (type != nullptr) 540 { 541 auto chassisType = translateChassisTypeToRedfish(*type); 542 if (chassisType != chassis::ChassisType::Invalid) 543 { 544 asyncResp->res.jsonValue["ChassisType"] = chassisType; 545 } 546 } 547 } 548 549 inline void handleChassisGetSubTree( 550 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 551 const std::string& chassisId, const boost::system::error_code& ec, 552 const dbus::utility::MapperGetSubTreeResponse& subtree) 553 { 554 if (ec) 555 { 556 BMCWEB_LOG_ERROR("DBUS response error {}", ec); 557 messages::internalError(asyncResp->res); 558 return; 559 } 560 // Iterate over all retrieved ObjectPaths. 561 for (const std::pair< 562 std::string, 563 std::vector<std::pair<std::string, std::vector<std::string>>>>& 564 object : subtree) 565 { 566 const std::string& path = object.first; 567 const std::vector<std::pair<std::string, std::vector<std::string>>>& 568 connectionNames = object.second; 569 570 sdbusplus::message::object_path objPath(path); 571 if (objPath.filename() != chassisId) 572 { 573 continue; 574 } 575 576 getChassisConnectivity(asyncResp, chassisId, path); 577 578 if (connectionNames.empty()) 579 { 580 BMCWEB_LOG_ERROR("Got 0 Connection names"); 581 continue; 582 } 583 584 asyncResp->res.jsonValue["@odata.type"] = "#Chassis.v1_22_0.Chassis"; 585 asyncResp->res.jsonValue["@odata.id"] = 586 boost::urls::format("/redfish/v1/Chassis/{}", chassisId); 587 asyncResp->res.jsonValue["Name"] = "Chassis Collection"; 588 asyncResp->res.jsonValue["Actions"]["#Chassis.Reset"]["target"] = 589 boost::urls::format("/redfish/v1/Chassis/{}/Actions/Chassis.Reset", 590 chassisId); 591 asyncResp->res 592 .jsonValue["Actions"]["#Chassis.Reset"]["@Redfish.ActionInfo"] = 593 boost::urls::format("/redfish/v1/Chassis/{}/ResetActionInfo", 594 chassisId); 595 dbus::utility::getAssociationEndPoints( 596 path + "/drive", 597 [asyncResp, chassisId](const boost::system::error_code& ec3, 598 const dbus::utility::MapperEndPoints& resp) { 599 if (ec3 || resp.empty()) 600 { 601 return; // no drives = no failures 602 } 603 604 nlohmann::json reference; 605 reference["@odata.id"] = boost::urls::format( 606 "/redfish/v1/Chassis/{}/Drives", chassisId); 607 asyncResp->res.jsonValue["Drives"] = std::move(reference); 608 }); 609 610 const std::string& connectionName = connectionNames[0].first; 611 612 const std::vector<std::string>& interfaces2 = connectionNames[0].second; 613 const std::array<const char*, 3> hasIndicatorLed = { 614 "xyz.openbmc_project.Inventory.Item.Chassis", 615 "xyz.openbmc_project.Inventory.Item.Panel", 616 "xyz.openbmc_project.Inventory.Item.Board.Motherboard"}; 617 618 const std::string assetTagInterface = 619 "xyz.openbmc_project.Inventory.Decorator.AssetTag"; 620 const std::string replaceableInterface = 621 "xyz.openbmc_project.Inventory.Decorator.Replaceable"; 622 const std::string revisionInterface = 623 "xyz.openbmc_project.Inventory.Decorator.Revision"; 624 for (const auto& interface : interfaces2) 625 { 626 if (interface == assetTagInterface) 627 { 628 dbus::utility::getProperty<std::string>( 629 connectionName, path, assetTagInterface, "AssetTag", 630 [asyncResp, chassisId](const boost::system::error_code& ec2, 631 const std::string& property) { 632 if (ec2) 633 { 634 BMCWEB_LOG_ERROR( 635 "DBus response error for AssetTag: {}", ec2); 636 messages::internalError(asyncResp->res); 637 return; 638 } 639 asyncResp->res.jsonValue["AssetTag"] = property; 640 }); 641 } 642 else if (interface == replaceableInterface) 643 { 644 dbus::utility::getProperty<bool>( 645 connectionName, path, replaceableInterface, "HotPluggable", 646 [asyncResp, chassisId](const boost::system::error_code& ec2, 647 const bool property) { 648 if (ec2) 649 { 650 BMCWEB_LOG_ERROR( 651 "DBus response error for HotPluggable: {}", 652 ec2); 653 messages::internalError(asyncResp->res); 654 return; 655 } 656 asyncResp->res.jsonValue["HotPluggable"] = property; 657 }); 658 } 659 else if (interface == revisionInterface) 660 { 661 dbus::utility::getProperty<std::string>( 662 connectionName, path, revisionInterface, "Version", 663 [asyncResp, chassisId](const boost::system::error_code& ec2, 664 const std::string& property) { 665 if (ec2) 666 { 667 BMCWEB_LOG_ERROR( 668 "DBus response error for Version: {}", ec2); 669 messages::internalError(asyncResp->res); 670 return; 671 } 672 asyncResp->res.jsonValue["Version"] = property; 673 }); 674 } 675 } 676 677 for (const char* interface : hasIndicatorLed) 678 { 679 if (std::ranges::find(interfaces2, interface) != interfaces2.end()) 680 { 681 getIndicatorLedState(asyncResp); 682 getSystemLocationIndicatorActive(asyncResp); 683 break; 684 } 685 } 686 687 dbus::utility::getAllProperties( 688 *crow::connections::systemBus, connectionName, path, 689 "xyz.openbmc_project.Inventory.Decorator.Asset", 690 [asyncResp, chassisId, 691 path](const boost::system::error_code&, 692 const dbus::utility::DBusPropertiesMap& propertiesList) { 693 handleDecoratorAssetProperties(asyncResp, chassisId, path, 694 propertiesList); 695 }); 696 697 sdbusplus::asio::getAllProperties( 698 *crow::connections::systemBus, connectionName, path, 699 "xyz.openbmc_project.Inventory.Item.Chassis", 700 [asyncResp]( 701 const boost::system::error_code&, 702 const dbus::utility::DBusPropertiesMap& propertiesList) { 703 handleChassisProperties(asyncResp, propertiesList); 704 }); 705 706 for (const auto& interface : interfaces2) 707 { 708 if (interface == "xyz.openbmc_project.Common.UUID") 709 { 710 getChassisUUID(asyncResp, connectionName, path); 711 } 712 else if (interface == 713 "xyz.openbmc_project.Inventory.Decorator.LocationCode") 714 { 715 getChassisLocationCode(asyncResp, connectionName, path); 716 } 717 } 718 719 return; 720 } 721 722 // Couldn't find an object with that name. return an error 723 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 724 } 725 726 inline void 727 handleChassisGet(App& app, const crow::Request& req, 728 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 729 const std::string& chassisId) 730 { 731 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 732 { 733 return; 734 } 735 constexpr std::array<std::string_view, 2> interfaces = { 736 "xyz.openbmc_project.Inventory.Item.Board", 737 "xyz.openbmc_project.Inventory.Item.Chassis"}; 738 739 dbus::utility::getSubTree( 740 "/xyz/openbmc_project/inventory", 0, interfaces, 741 std::bind_front(handleChassisGetSubTree, asyncResp, chassisId)); 742 743 constexpr std::array<std::string_view, 1> interfaces2 = { 744 "xyz.openbmc_project.Chassis.Intrusion"}; 745 746 dbus::utility::getSubTree( 747 "/xyz/openbmc_project", 0, interfaces2, 748 std::bind_front(handlePhysicalSecurityGetSubTree, asyncResp)); 749 } 750 751 inline void 752 handleChassisPatch(App& app, const crow::Request& req, 753 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 754 const std::string& param) 755 { 756 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 757 { 758 return; 759 } 760 std::optional<bool> locationIndicatorActive; 761 std::optional<std::string> indicatorLed; 762 763 if (param.empty()) 764 { 765 return; 766 } 767 768 if (!json_util::readJsonPatch( // 769 req, asyncResp->res, // 770 "IndicatorLED", indicatorLed, // 771 "LocationIndicatorActive", locationIndicatorActive // 772 )) 773 { 774 return; 775 } 776 777 // TODO (Gunnar): Remove IndicatorLED after enough time has passed 778 if (!locationIndicatorActive && !indicatorLed) 779 { 780 return; // delete this when we support more patch properties 781 } 782 if (indicatorLed) 783 { 784 asyncResp->res.addHeader( 785 boost::beast::http::field::warning, 786 "299 - \"IndicatorLED is deprecated. Use LocationIndicatorActive instead.\""); 787 } 788 789 constexpr std::array<std::string_view, 2> interfaces = { 790 "xyz.openbmc_project.Inventory.Item.Board", 791 "xyz.openbmc_project.Inventory.Item.Chassis"}; 792 793 const std::string& chassisId = param; 794 795 dbus::utility::getSubTree( 796 "/xyz/openbmc_project/inventory", 0, interfaces, 797 [asyncResp, chassisId, locationIndicatorActive, 798 indicatorLed](const boost::system::error_code& ec, 799 const dbus::utility::MapperGetSubTreeResponse& subtree) { 800 if (ec) 801 { 802 BMCWEB_LOG_ERROR("DBUS response error {}", ec); 803 messages::internalError(asyncResp->res); 804 return; 805 } 806 807 // Iterate over all retrieved ObjectPaths. 808 for (const std::pair<std::string, 809 std::vector<std::pair< 810 std::string, std::vector<std::string>>>>& 811 object : subtree) 812 { 813 const std::string& path = object.first; 814 const std::vector< 815 std::pair<std::string, std::vector<std::string>>>& 816 connectionNames = object.second; 817 818 sdbusplus::message::object_path objPath(path); 819 if (objPath.filename() != chassisId) 820 { 821 continue; 822 } 823 824 if (connectionNames.empty()) 825 { 826 BMCWEB_LOG_ERROR("Got 0 Connection names"); 827 continue; 828 } 829 830 const std::vector<std::string>& interfaces3 = 831 connectionNames[0].second; 832 833 const std::array<const char*, 3> hasIndicatorLed = { 834 "xyz.openbmc_project.Inventory.Item.Chassis", 835 "xyz.openbmc_project.Inventory.Item.Panel", 836 "xyz.openbmc_project.Inventory.Item.Board.Motherboard"}; 837 bool indicatorChassis = false; 838 for (const char* interface : hasIndicatorLed) 839 { 840 if (std::ranges::find(interfaces3, interface) != 841 interfaces3.end()) 842 { 843 indicatorChassis = true; 844 break; 845 } 846 } 847 if (locationIndicatorActive) 848 { 849 if (indicatorChassis) 850 { 851 setSystemLocationIndicatorActive( 852 asyncResp, *locationIndicatorActive); 853 } 854 else 855 { 856 messages::propertyUnknown(asyncResp->res, 857 "LocationIndicatorActive"); 858 } 859 } 860 if (indicatorLed) 861 { 862 if (indicatorChassis) 863 { 864 setIndicatorLedState(asyncResp, *indicatorLed); 865 } 866 else 867 { 868 messages::propertyUnknown(asyncResp->res, 869 "IndicatorLED"); 870 } 871 } 872 return; 873 } 874 875 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 876 }); 877 } 878 879 /** 880 * Chassis override class for delivering Chassis Schema 881 * Functions triggers appropriate requests on DBus 882 */ 883 inline void requestRoutesChassis(App& app) 884 { 885 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/") 886 .privileges(redfish::privileges::getChassis) 887 .methods(boost::beast::http::verb::get)( 888 std::bind_front(handleChassisGet, std::ref(app))); 889 890 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/") 891 .privileges(redfish::privileges::patchChassis) 892 .methods(boost::beast::http::verb::patch)( 893 std::bind_front(handleChassisPatch, std::ref(app))); 894 } 895 896 inline void 897 doChassisPowerCycle(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 898 { 899 constexpr std::array<std::string_view, 1> interfaces = { 900 "xyz.openbmc_project.State.Chassis"}; 901 902 // Use mapper to get subtree paths. 903 dbus::utility::getSubTreePaths( 904 "/", 0, interfaces, 905 [asyncResp]( 906 const boost::system::error_code& ec, 907 const dbus::utility::MapperGetSubTreePathsResponse& chassisList) { 908 if (ec) 909 { 910 BMCWEB_LOG_ERROR("[mapper] Bad D-Bus request error: {}", ec); 911 messages::internalError(asyncResp->res); 912 return; 913 } 914 915 const char* processName = "xyz.openbmc_project.State.Chassis"; 916 const char* interfaceName = "xyz.openbmc_project.State.Chassis"; 917 const char* destProperty = "RequestedPowerTransition"; 918 const std::string propertyValue = 919 "xyz.openbmc_project.State.Chassis.Transition.PowerCycle"; 920 std::string objectPath = 921 "/xyz/openbmc_project/state/chassis_system0"; 922 923 /* Look for system reset chassis path */ 924 if ((std::ranges::find(chassisList, objectPath)) == 925 chassisList.end()) 926 { 927 /* We prefer to reset the full chassis_system, but if it doesn't 928 * exist on some platforms, fall back to a host-only power reset 929 */ 930 objectPath = "/xyz/openbmc_project/state/chassis0"; 931 } 932 933 setDbusProperty(asyncResp, "ResetType", processName, objectPath, 934 interfaceName, destProperty, propertyValue); 935 }); 936 } 937 938 inline void handleChassisResetActionInfoPost( 939 App& app, const crow::Request& req, 940 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 941 const std::string& /*chassisId*/) 942 { 943 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 944 { 945 return; 946 } 947 BMCWEB_LOG_DEBUG("Post Chassis Reset."); 948 949 std::string resetType; 950 951 if (!json_util::readJsonAction(req, asyncResp->res, "ResetType", resetType)) 952 { 953 return; 954 } 955 956 if (resetType != "PowerCycle") 957 { 958 BMCWEB_LOG_DEBUG("Invalid property value for ResetType: {}", resetType); 959 messages::actionParameterNotSupported(asyncResp->res, resetType, 960 "ResetType"); 961 962 return; 963 } 964 doChassisPowerCycle(asyncResp); 965 } 966 967 /** 968 * ChassisResetAction class supports the POST method for the Reset 969 * action. 970 * Function handles POST method request. 971 * Analyzes POST body before sending Reset request data to D-Bus. 972 */ 973 974 inline void requestRoutesChassisResetAction(App& app) 975 { 976 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Actions/Chassis.Reset/") 977 .privileges(redfish::privileges::postChassis) 978 .methods(boost::beast::http::verb::post)( 979 std::bind_front(handleChassisResetActionInfoPost, std::ref(app))); 980 } 981 982 inline void handleChassisResetActionInfoGet( 983 App& app, const crow::Request& req, 984 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 985 const std::string& chassisId) 986 { 987 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 988 { 989 return; 990 } 991 asyncResp->res.jsonValue["@odata.type"] = "#ActionInfo.v1_1_2.ActionInfo"; 992 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 993 "/redfish/v1/Chassis/{}/ResetActionInfo", chassisId); 994 asyncResp->res.jsonValue["Name"] = "Reset Action Info"; 995 996 asyncResp->res.jsonValue["Id"] = "ResetActionInfo"; 997 nlohmann::json::array_t parameters; 998 nlohmann::json::object_t parameter; 999 parameter["Name"] = "ResetType"; 1000 parameter["Required"] = true; 1001 parameter["DataType"] = action_info::ParameterTypes::String; 1002 nlohmann::json::array_t allowed; 1003 allowed.emplace_back("PowerCycle"); 1004 parameter["AllowableValues"] = std::move(allowed); 1005 parameters.emplace_back(std::move(parameter)); 1006 1007 asyncResp->res.jsonValue["Parameters"] = std::move(parameters); 1008 } 1009 1010 /** 1011 * ChassisResetActionInfo derived class for delivering Chassis 1012 * ResetType AllowableValues using ResetInfo schema. 1013 */ 1014 inline void requestRoutesChassisResetActionInfo(App& app) 1015 { 1016 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ResetActionInfo/") 1017 .privileges(redfish::privileges::getActionInfo) 1018 .methods(boost::beast::http::verb::get)( 1019 std::bind_front(handleChassisResetActionInfoGet, std::ref(app))); 1020 } 1021 1022 } // namespace redfish 1023