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