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