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