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