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