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