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