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 "health.hpp" 19 #include "led.hpp" 20 21 #include <app.hpp> 22 #include <boost/container/flat_map.hpp> 23 #include <registries/privilege_registry.hpp> 24 #include <utils/collection.hpp> 25 26 #include <variant> 27 28 namespace redfish 29 { 30 31 /** 32 * @brief Retrieves chassis state properties over dbus 33 * 34 * @param[in] aResp - Shared pointer for completing asynchronous calls. 35 * 36 * @return None. 37 */ 38 inline void getChassisState(std::shared_ptr<bmcweb::AsyncResp> aResp) 39 { 40 crow::connections::systemBus->async_method_call( 41 [aResp{std::move(aResp)}]( 42 const boost::system::error_code ec, 43 const std::variant<std::string>& chassisState) { 44 if (ec) 45 { 46 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 47 messages::internalError(aResp->res); 48 return; 49 } 50 51 const std::string* s = std::get_if<std::string>(&chassisState); 52 BMCWEB_LOG_DEBUG << "Chassis state: " << *s; 53 if (s != nullptr) 54 { 55 // Verify Chassis State 56 if (*s == "xyz.openbmc_project.State.Chassis.PowerState.On") 57 { 58 aResp->res.jsonValue["PowerState"] = "On"; 59 aResp->res.jsonValue["Status"]["State"] = "Enabled"; 60 } 61 else if (*s == 62 "xyz.openbmc_project.State.Chassis.PowerState.Off") 63 { 64 aResp->res.jsonValue["PowerState"] = "Off"; 65 aResp->res.jsonValue["Status"]["State"] = "StandbyOffline"; 66 } 67 } 68 }, 69 "xyz.openbmc_project.State.Chassis", 70 "/xyz/openbmc_project/state/chassis0", 71 "org.freedesktop.DBus.Properties", "Get", 72 "xyz.openbmc_project.State.Chassis", "CurrentPowerState"); 73 } 74 75 /** 76 * DBus types primitives for several generic DBus interfaces 77 * TODO(Pawel) consider move this to separate file into boost::dbus 78 */ 79 // Note, this is not a very useful Variant, but because it isn't used to get 80 // values, it should be as simple as possible 81 // TODO(ed) invent a nullvariant type 82 using VariantType = std::variant<bool, std::string, uint64_t, uint32_t>; 83 using ManagedObjectsType = std::vector<std::pair< 84 sdbusplus::message::object_path, 85 std::vector<std::pair<std::string, 86 std::vector<std::pair<std::string, VariantType>>>>>>; 87 88 using PropertiesType = boost::container::flat_map<std::string, VariantType>; 89 90 inline void getIntrusionByService(std::shared_ptr<bmcweb::AsyncResp> aResp, 91 const std::string& service, 92 const std::string& objPath) 93 { 94 BMCWEB_LOG_DEBUG << "Get intrusion status by service \n"; 95 96 crow::connections::systemBus->async_method_call( 97 [aResp{std::move(aResp)}](const boost::system::error_code ec, 98 const std::variant<std::string>& value) { 99 if (ec) 100 { 101 // do not add err msg in redfish response, because this is not 102 // mandatory property 103 BMCWEB_LOG_ERROR << "DBUS response error " << ec << "\n"; 104 return; 105 } 106 107 const std::string* status = std::get_if<std::string>(&value); 108 109 if (status == nullptr) 110 { 111 BMCWEB_LOG_ERROR << "intrusion status read error \n"; 112 return; 113 } 114 115 aResp->res.jsonValue["PhysicalSecurity"] = { 116 {"IntrusionSensorNumber", 1}, {"IntrusionSensor", *status}}; 117 }, 118 service, objPath, "org.freedesktop.DBus.Properties", "Get", 119 "xyz.openbmc_project.Chassis.Intrusion", "Status"); 120 } 121 122 /** 123 * Retrieves physical security properties over dbus 124 */ 125 inline void getPhysicalSecurityData(std::shared_ptr<bmcweb::AsyncResp> aResp) 126 { 127 crow::connections::systemBus->async_method_call( 128 [aResp{std::move(aResp)}]( 129 const boost::system::error_code ec, 130 const std::vector<std::pair< 131 std::string, 132 std::vector<std::pair<std::string, std::vector<std::string>>>>>& 133 subtree) { 134 if (ec) 135 { 136 // do not add err msg in redfish response, because this is not 137 // mandatory property 138 BMCWEB_LOG_ERROR << "DBUS error: no matched iface " << ec 139 << "\n"; 140 return; 141 } 142 // Iterate over all retrieved ObjectPaths. 143 for (const auto& object : subtree) 144 { 145 for (const auto& service : object.second) 146 { 147 getIntrusionByService(aResp, service.first, object.first); 148 return; 149 } 150 } 151 }, 152 "xyz.openbmc_project.ObjectMapper", 153 "/xyz/openbmc_project/object_mapper", 154 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 155 "/xyz/openbmc_project/Intrusion", 1, 156 std::array<const char*, 1>{"xyz.openbmc_project.Chassis.Intrusion"}); 157 } 158 159 /** 160 * ChassisCollection derived class for delivering Chassis Collection Schema 161 * Functions triggers appropriate requests on DBus 162 */ 163 inline void requestRoutesChassisCollection(App& app) 164 { 165 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/") 166 .privileges(redfish::privileges::getChassisCollection) 167 .methods(boost::beast::http::verb::get)( 168 [](const crow::Request&, 169 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 170 asyncResp->res.jsonValue["@odata.type"] = 171 "#ChassisCollection.ChassisCollection"; 172 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Chassis"; 173 asyncResp->res.jsonValue["Name"] = "Chassis Collection"; 174 175 collection_util::getCollectionMembers( 176 asyncResp, "/redfish/v1/Chassis", 177 {"xyz.openbmc_project.Inventory.Item.Board", 178 "xyz.openbmc_project.Inventory.Item.Chassis"}); 179 }); 180 } 181 182 /** 183 * Chassis override class for delivering Chassis Schema 184 * Functions triggers appropriate requests on DBus 185 */ 186 inline void requestRoutesChassis(App& app) 187 { 188 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/") 189 .privileges(redfish::privileges::getChassis) 190 .methods( 191 boost::beast::http::verb::get)([](const crow::Request&, 192 const std::shared_ptr< 193 bmcweb::AsyncResp>& asyncResp, 194 const std::string& chassisId) { 195 const std::array<const char*, 2> interfaces = { 196 "xyz.openbmc_project.Inventory.Item.Board", 197 "xyz.openbmc_project.Inventory.Item.Chassis"}; 198 199 crow::connections::systemBus->async_method_call( 200 [asyncResp, chassisId(std::string(chassisId))]( 201 const boost::system::error_code ec, 202 const crow::openbmc_mapper::GetSubTreeType& subtree) { 203 if (ec) 204 { 205 messages::internalError(asyncResp->res); 206 return; 207 } 208 // Iterate over all retrieved ObjectPaths. 209 for (const std::pair< 210 std::string, 211 std::vector<std::pair<std::string, 212 std::vector<std::string>>>>& 213 object : subtree) 214 { 215 const std::string& path = object.first; 216 const std::vector< 217 std::pair<std::string, std::vector<std::string>>>& 218 connectionNames = object.second; 219 220 if (!boost::ends_with(path, chassisId)) 221 { 222 continue; 223 } 224 225 auto health = 226 std::make_shared<HealthPopulate>(asyncResp); 227 228 crow::connections::systemBus->async_method_call( 229 [health]( 230 const boost::system::error_code ec2, 231 std::variant<std::vector<std::string>>& resp) { 232 if (ec2) 233 { 234 return; // no sensors = no failures 235 } 236 std::vector<std::string>* data = 237 std::get_if<std::vector<std::string>>( 238 &resp); 239 if (data == nullptr) 240 { 241 return; 242 } 243 health->inventory = std::move(*data); 244 }, 245 "xyz.openbmc_project.ObjectMapper", 246 path + "/all_sensors", 247 "org.freedesktop.DBus.Properties", "Get", 248 "xyz.openbmc_project.Association", "endpoints"); 249 250 health->populate(); 251 252 if (connectionNames.size() < 1) 253 { 254 BMCWEB_LOG_ERROR << "Got 0 Connection names"; 255 continue; 256 } 257 258 asyncResp->res.jsonValue["@odata.type"] = 259 "#Chassis.v1_14_0.Chassis"; 260 asyncResp->res.jsonValue["@odata.id"] = 261 "/redfish/v1/Chassis/" + chassisId; 262 asyncResp->res.jsonValue["Name"] = "Chassis Collection"; 263 asyncResp->res.jsonValue["ChassisType"] = "RackMount"; 264 asyncResp->res.jsonValue["Actions"]["#Chassis.Reset"] = 265 {{"target", "/redfish/v1/Chassis/" + chassisId + 266 "/Actions/Chassis.Reset"}, 267 {"@Redfish.ActionInfo", "/redfish/v1/Chassis/" + 268 chassisId + 269 "/ResetActionInfo"}}; 270 asyncResp->res.jsonValue["PCIeDevices"] = { 271 {"@odata.id", 272 "/redfish/v1/Systems/system/PCIeDevices"}}; 273 274 const std::string& connectionName = 275 connectionNames[0].first; 276 277 const std::vector<std::string>& interfaces2 = 278 connectionNames[0].second; 279 const std::array<const char*, 2> hasIndicatorLed = { 280 "xyz.openbmc_project.Inventory.Item.Panel", 281 "xyz.openbmc_project.Inventory.Item.Board." 282 "Motherboard"}; 283 284 for (const char* interface : hasIndicatorLed) 285 { 286 if (std::find(interfaces2.begin(), 287 interfaces2.end(), 288 interface) != interfaces2.end()) 289 { 290 getIndicatorLedState(asyncResp); 291 getLocationIndicatorActive(asyncResp); 292 break; 293 } 294 } 295 296 const std::string locationInterface = 297 "xyz.openbmc_project.Inventory.Decorator." 298 "LocationCode"; 299 if (std::find(interfaces2.begin(), interfaces2.end(), 300 locationInterface) != interfaces2.end()) 301 { 302 crow::connections::systemBus->async_method_call( 303 [asyncResp, chassisId(std::string(chassisId))]( 304 const boost::system::error_code ec, 305 const std::variant<std::string>& property) { 306 if (ec) 307 { 308 BMCWEB_LOG_DEBUG 309 << "DBUS response error for " 310 "Location"; 311 messages::internalError(asyncResp->res); 312 return; 313 } 314 315 const std::string* value = 316 std::get_if<std::string>(&property); 317 if (value == nullptr) 318 { 319 BMCWEB_LOG_DEBUG 320 << "Null value returned " 321 "for locaton code"; 322 messages::internalError(asyncResp->res); 323 return; 324 } 325 asyncResp->res 326 .jsonValue["Location"]["PartLocation"] 327 ["ServiceLabel"] = *value; 328 }, 329 connectionName, path, 330 "org.freedesktop.DBus.Properties", "Get", 331 locationInterface, "LocationCode"); 332 } 333 334 crow::connections::systemBus->async_method_call( 335 [asyncResp, chassisId(std::string(chassisId))]( 336 const boost::system::error_code /*ec2*/, 337 const std::vector< 338 std::pair<std::string, VariantType>>& 339 propertiesList) { 340 for (const std::pair<std::string, VariantType>& 341 property : propertiesList) 342 { 343 // Store DBus properties that are also 344 // Redfish properties with same name and a 345 // string value 346 const std::string& propertyName = 347 property.first; 348 if ((propertyName == "PartNumber") || 349 (propertyName == "SerialNumber") || 350 (propertyName == "Manufacturer") || 351 (propertyName == "Model")) 352 { 353 const std::string* value = 354 std::get_if<std::string>( 355 &property.second); 356 if (value != nullptr) 357 { 358 asyncResp->res 359 .jsonValue[propertyName] = 360 *value; 361 } 362 } 363 } 364 asyncResp->res.jsonValue["Name"] = chassisId; 365 asyncResp->res.jsonValue["Id"] = chassisId; 366 #ifdef BMCWEB_ALLOW_DEPRECATED_POWER_THERMAL 367 asyncResp->res.jsonValue["Thermal"] = { 368 {"@odata.id", "/redfish/v1/Chassis/" + 369 chassisId + "/Thermal"}}; 370 // Power object 371 asyncResp->res.jsonValue["Power"] = { 372 {"@odata.id", "/redfish/v1/Chassis/" + 373 chassisId + "/Power"}}; 374 #endif 375 // SensorCollection 376 asyncResp->res.jsonValue["Sensors"] = { 377 {"@odata.id", "/redfish/v1/Chassis/" + 378 chassisId + "/Sensors"}}; 379 asyncResp->res.jsonValue["Status"] = { 380 {"State", "Enabled"}, 381 }; 382 383 asyncResp->res 384 .jsonValue["Links"]["ComputerSystems"] = { 385 {{"@odata.id", 386 "/redfish/v1/Systems/system"}}}; 387 asyncResp->res.jsonValue["Links"]["ManagedBy"] = 388 {{{"@odata.id", 389 "/redfish/v1/Managers/bmc"}}}; 390 getChassisState(asyncResp); 391 }, 392 connectionName, path, 393 "org.freedesktop.DBus.Properties", "GetAll", 394 "xyz.openbmc_project.Inventory.Decorator.Asset"); 395 396 // Chassis UUID 397 const std::string uuidInterface = 398 "xyz.openbmc_project.Common.UUID"; 399 if (std::find(interfaces2.begin(), interfaces2.end(), 400 uuidInterface) != interfaces2.end()) 401 { 402 crow::connections::systemBus->async_method_call( 403 [asyncResp](const boost::system::error_code ec, 404 const std::variant<std::string>& 405 chassisUUID) { 406 if (ec) 407 { 408 BMCWEB_LOG_DEBUG 409 << "DBUS response error for " 410 "UUID"; 411 messages::internalError(asyncResp->res); 412 return; 413 } 414 const std::string* value = 415 std::get_if<std::string>(&chassisUUID); 416 if (value == nullptr) 417 { 418 BMCWEB_LOG_DEBUG 419 << "Null value returned " 420 "for UUID"; 421 messages::internalError(asyncResp->res); 422 return; 423 } 424 asyncResp->res.jsonValue["UUID"] = *value; 425 }, 426 connectionName, path, 427 "org.freedesktop.DBus.Properties", "Get", 428 uuidInterface, "UUID"); 429 } 430 431 return; 432 } 433 434 // Couldn't find an object with that name. return an error 435 messages::resourceNotFound( 436 asyncResp->res, "#Chassis.v1_14_0.Chassis", chassisId); 437 }, 438 "xyz.openbmc_project.ObjectMapper", 439 "/xyz/openbmc_project/object_mapper", 440 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 441 "/xyz/openbmc_project/inventory", 0, interfaces); 442 443 getPhysicalSecurityData(asyncResp); 444 }); 445 446 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/") 447 .privileges(redfish::privileges::patchChassis) 448 .methods( 449 boost::beast::http::verb:: 450 patch)([](const crow::Request& req, 451 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 452 const std::string& param) { 453 std::optional<bool> locationIndicatorActive; 454 std::optional<std::string> indicatorLed; 455 456 if (param.empty()) 457 { 458 return; 459 } 460 461 if (!json_util::readJson( 462 req, asyncResp->res, "LocationIndicatorActive", 463 locationIndicatorActive, "IndicatorLED", indicatorLed)) 464 { 465 return; 466 } 467 468 // TODO (Gunnar): Remove IndicatorLED after enough time has passed 469 if (!locationIndicatorActive && !indicatorLed) 470 { 471 return; // delete this when we support more patch properties 472 } 473 if (indicatorLed) 474 { 475 asyncResp->res.addHeader( 476 boost::beast::http::field::warning, 477 "299 - \"IndicatorLED is deprecated. Use " 478 "LocationIndicatorActive instead.\""); 479 } 480 481 const std::array<const char*, 2> interfaces = { 482 "xyz.openbmc_project.Inventory.Item.Board", 483 "xyz.openbmc_project.Inventory.Item.Chassis"}; 484 485 const std::string& chassisId = param; 486 487 crow::connections::systemBus->async_method_call( 488 [asyncResp, chassisId, locationIndicatorActive, indicatorLed]( 489 const boost::system::error_code ec, 490 const crow::openbmc_mapper::GetSubTreeType& subtree) { 491 if (ec) 492 { 493 messages::internalError(asyncResp->res); 494 return; 495 } 496 497 // Iterate over all retrieved ObjectPaths. 498 for (const std::pair< 499 std::string, 500 std::vector<std::pair<std::string, 501 std::vector<std::string>>>>& 502 object : subtree) 503 { 504 const std::string& path = object.first; 505 const std::vector< 506 std::pair<std::string, std::vector<std::string>>>& 507 connectionNames = object.second; 508 509 if (!boost::ends_with(path, chassisId)) 510 { 511 continue; 512 } 513 514 if (connectionNames.size() < 1) 515 { 516 BMCWEB_LOG_ERROR << "Got 0 Connection names"; 517 continue; 518 } 519 520 const std::vector<std::string>& interfaces3 = 521 connectionNames[0].second; 522 523 const std::array<const char*, 2> hasIndicatorLed = { 524 "xyz.openbmc_project.Inventory.Item.Panel", 525 "xyz.openbmc_project.Inventory.Item.Board." 526 "Motherboard"}; 527 bool indicatorChassis = false; 528 for (const char* interface : hasIndicatorLed) 529 { 530 if (std::find(interfaces3.begin(), 531 interfaces3.end(), 532 interface) != interfaces3.end()) 533 { 534 indicatorChassis = true; 535 break; 536 } 537 } 538 if (locationIndicatorActive) 539 { 540 if (indicatorChassis) 541 { 542 setLocationIndicatorActive( 543 asyncResp, *locationIndicatorActive); 544 } 545 else 546 { 547 messages::propertyUnknown( 548 asyncResp->res, "LocationIndicatorActive"); 549 } 550 } 551 if (indicatorLed) 552 { 553 if (indicatorChassis) 554 { 555 setIndicatorLedState(asyncResp, *indicatorLed); 556 } 557 else 558 { 559 messages::propertyUnknown(asyncResp->res, 560 "IndicatorLED"); 561 } 562 } 563 return; 564 } 565 566 messages::resourceNotFound( 567 asyncResp->res, "#Chassis.v1_14_0.Chassis", chassisId); 568 }, 569 "xyz.openbmc_project.ObjectMapper", 570 "/xyz/openbmc_project/object_mapper", 571 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 572 "/xyz/openbmc_project/inventory", 0, interfaces); 573 }); 574 } 575 576 inline void 577 doChassisPowerCycle(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 578 { 579 const char* busName = "xyz.openbmc_project.ObjectMapper"; 580 const char* path = "/xyz/openbmc_project/object_mapper"; 581 const char* interface = "xyz.openbmc_project.ObjectMapper"; 582 const char* method = "GetSubTreePaths"; 583 584 const std::array<const char*, 1> interfaces = { 585 "xyz.openbmc_project.State.Chassis"}; 586 587 // Use mapper to get subtree paths. 588 crow::connections::systemBus->async_method_call( 589 [asyncResp](const boost::system::error_code ec, 590 const std::vector<std::string>& chassisList) { 591 if (ec) 592 { 593 BMCWEB_LOG_DEBUG << "[mapper] Bad D-Bus request error: " << ec; 594 messages::internalError(asyncResp->res); 595 return; 596 } 597 598 const char* processName = "xyz.openbmc_project.State.Chassis"; 599 const char* interfaceName = "xyz.openbmc_project.State.Chassis"; 600 const char* destProperty = "RequestedPowerTransition"; 601 const std::string propertyValue = 602 "xyz.openbmc_project.State.Chassis.Transition.PowerCycle"; 603 std::string objectPath = 604 "/xyz/openbmc_project/state/chassis_system0"; 605 606 /* Look for system reset chassis path */ 607 if ((std::find(chassisList.begin(), chassisList.end(), 608 objectPath)) == chassisList.end()) 609 { 610 /* We prefer to reset the full chassis_system, but if it doesn't 611 * exist on some platforms, fall back to a host-only power reset 612 */ 613 objectPath = "/xyz/openbmc_project/state/chassis0"; 614 } 615 616 crow::connections::systemBus->async_method_call( 617 [asyncResp](const boost::system::error_code ec) { 618 // Use "Set" method to set the property value. 619 if (ec) 620 { 621 BMCWEB_LOG_DEBUG << "[Set] Bad D-Bus request error: " 622 << ec; 623 messages::internalError(asyncResp->res); 624 return; 625 } 626 627 messages::success(asyncResp->res); 628 }, 629 processName, objectPath, "org.freedesktop.DBus.Properties", 630 "Set", interfaceName, destProperty, 631 std::variant<std::string>{propertyValue}); 632 }, 633 busName, path, interface, method, "/", 0, interfaces); 634 } 635 636 /** 637 * ChassisResetAction class supports the POST method for the Reset 638 * action. 639 * Function handles POST method request. 640 * Analyzes POST body before sending Reset request data to D-Bus. 641 */ 642 643 inline void requestRoutesChassisResetAction(App& app) 644 { 645 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Actions/Chassis.Reset/") 646 .privileges(redfish::privileges::postChassis) 647 .methods(boost::beast::http::verb::post)( 648 [](const crow::Request& req, 649 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 650 const std::string&) { 651 BMCWEB_LOG_DEBUG << "Post Chassis Reset."; 652 653 std::string resetType; 654 655 if (!json_util::readJson(req, asyncResp->res, "ResetType", 656 resetType)) 657 { 658 return; 659 } 660 661 if (resetType != "PowerCycle") 662 { 663 BMCWEB_LOG_DEBUG << "Invalid property value for ResetType: " 664 << resetType; 665 messages::actionParameterNotSupported( 666 asyncResp->res, resetType, "ResetType"); 667 668 return; 669 } 670 doChassisPowerCycle(asyncResp); 671 }); 672 } 673 674 /** 675 * ChassisResetActionInfo derived class for delivering Chassis 676 * ResetType AllowableValues using ResetInfo schema. 677 */ 678 inline void requestRoutesChassisResetActionInfo(App& app) 679 { 680 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ResetActionInfo/") 681 .privileges(redfish::privileges::getActionInfo) 682 .methods(boost::beast::http::verb::get)( 683 [](const crow::Request&, 684 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 685 const std::string& chassisId) 686 687 { 688 asyncResp->res.jsonValue = { 689 {"@odata.type", "#ActionInfo.v1_1_2.ActionInfo"}, 690 {"@odata.id", 691 "/redfish/v1/Chassis/" + chassisId + "/ResetActionInfo"}, 692 {"Name", "Reset Action Info"}, 693 {"Id", "ResetActionInfo"}, 694 {"Parameters", 695 {{{"Name", "ResetType"}, 696 {"Required", true}, 697 {"DataType", "String"}, 698 {"AllowableValues", {"PowerCycle"}}}}}}; 699 }); 700 } 701 702 } // namespace redfish 703