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 inline 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 inline 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 inline 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(App& 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&, 179 const std::vector<std::string>&) 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 messages::internalError(asyncResp->res); 208 return; 209 } 210 if ((lastPos + 1) >= objpath.size()) 211 { 212 BMCWEB_LOG_ERROR << "Failed to parse path " << objpath; 213 messages::internalError(asyncResp->res); 214 return; 215 } 216 chassisArray.push_back( 217 {{"@odata.id", "/redfish/v1/Chassis/" + 218 objpath.substr(lastPos + 1)}}); 219 } 220 221 asyncResp->res.jsonValue["Members@odata.count"] = 222 chassisArray.size(); 223 }, 224 "xyz.openbmc_project.ObjectMapper", 225 "/xyz/openbmc_project/object_mapper", 226 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", 227 "/xyz/openbmc_project/inventory", 0, interfaces); 228 } 229 }; 230 231 /** 232 * Chassis override class for delivering Chassis Schema 233 */ 234 class Chassis : public Node 235 { 236 public: 237 Chassis(App& app) : Node(app, "/redfish/v1/Chassis/<str>/", std::string()) 238 { 239 entityPrivileges = { 240 {boost::beast::http::verb::get, {{"Login"}}}, 241 {boost::beast::http::verb::head, {{"Login"}}}, 242 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 243 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 244 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 245 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 246 } 247 248 private: 249 /** 250 * Functions triggers appropriate requests on DBus 251 */ 252 void doGet(crow::Response& res, const crow::Request&, 253 const std::vector<std::string>& params) override 254 { 255 const std::array<const char*, 2> interfaces = { 256 "xyz.openbmc_project.Inventory.Item.Board", 257 "xyz.openbmc_project.Inventory.Item.Chassis"}; 258 259 // Check if there is required param, truly entering this shall be 260 // impossible. 261 if (params.size() != 1) 262 { 263 messages::internalError(res); 264 res.end(); 265 return; 266 } 267 const std::string& chassisId = params[0]; 268 269 auto asyncResp = std::make_shared<AsyncResp>(res); 270 crow::connections::systemBus->async_method_call( 271 [asyncResp, chassisId(std::string(chassisId))]( 272 const boost::system::error_code ec, 273 const crow::openbmc_mapper::GetSubTreeType& subtree) { 274 if (ec) 275 { 276 messages::internalError(asyncResp->res); 277 return; 278 } 279 // Iterate over all retrieved ObjectPaths. 280 for (const std::pair< 281 std::string, 282 std::vector< 283 std::pair<std::string, std::vector<std::string>>>>& 284 object : subtree) 285 { 286 const std::string& path = object.first; 287 const std::vector< 288 std::pair<std::string, std::vector<std::string>>>& 289 connectionNames = object.second; 290 291 if (!boost::ends_with(path, chassisId)) 292 { 293 continue; 294 } 295 296 auto health = std::make_shared<HealthPopulate>(asyncResp); 297 298 crow::connections::systemBus->async_method_call( 299 [health](const boost::system::error_code ec2, 300 std::variant<std::vector<std::string>>& resp) { 301 if (ec2) 302 { 303 return; // no sensors = no failures 304 } 305 std::vector<std::string>* data = 306 std::get_if<std::vector<std::string>>(&resp); 307 if (data == nullptr) 308 { 309 return; 310 } 311 health->inventory = std::move(*data); 312 }, 313 "xyz.openbmc_project.ObjectMapper", 314 path + "/all_sensors", 315 "org.freedesktop.DBus.Properties", "Get", 316 "xyz.openbmc_project.Association", "endpoints"); 317 318 health->populate(); 319 320 if (connectionNames.size() < 1) 321 { 322 BMCWEB_LOG_ERROR << "Got 0 Connection names"; 323 continue; 324 } 325 326 asyncResp->res.jsonValue["@odata.type"] = 327 "#Chassis.v1_10_0.Chassis"; 328 asyncResp->res.jsonValue["@odata.id"] = 329 "/redfish/v1/Chassis/" + chassisId; 330 asyncResp->res.jsonValue["Name"] = "Chassis Collection"; 331 asyncResp->res.jsonValue["ChassisType"] = "RackMount"; 332 asyncResp->res.jsonValue["Actions"]["#Chassis.Reset"] = { 333 {"target", "/redfish/v1/Chassis/" + chassisId + 334 "/Actions/Chassis.Reset"}, 335 {"@Redfish.ActionInfo", "/redfish/v1/Chassis/" + 336 chassisId + 337 "/ResetActionInfo"}}; 338 asyncResp->res.jsonValue["PCIeDevices"] = { 339 {"@odata.id", 340 "/redfish/v1/Systems/system/PCIeDevices"}}; 341 342 const std::string& connectionName = 343 connectionNames[0].first; 344 345 const std::vector<std::string>& interfaces2 = 346 connectionNames[0].second; 347 const std::array<const char*, 2> hasIndicatorLed = { 348 "xyz.openbmc_project.Inventory.Item.Panel", 349 "xyz.openbmc_project.Inventory.Item.Board.Motherboard"}; 350 351 for (const char* interface : hasIndicatorLed) 352 { 353 if (std::find(interfaces2.begin(), interfaces2.end(), 354 interface) != interfaces2.end()) 355 { 356 getIndicatorLedState(asyncResp); 357 break; 358 } 359 } 360 361 crow::connections::systemBus->async_method_call( 362 [asyncResp, chassisId(std::string(chassisId))]( 363 const boost::system::error_code /*ec2*/, 364 const std::vector<std::pair< 365 std::string, VariantType>>& propertiesList) { 366 for (const std::pair<std::string, VariantType>& 367 property : propertiesList) 368 { 369 // Store DBus properties that are also Redfish 370 // properties with same name and a string value 371 const std::string& propertyName = 372 property.first; 373 if ((propertyName == "PartNumber") || 374 (propertyName == "SerialNumber") || 375 (propertyName == "Manufacturer") || 376 (propertyName == "Model")) 377 { 378 const std::string* value = 379 std::get_if<std::string>( 380 &property.second); 381 if (value != nullptr) 382 { 383 asyncResp->res.jsonValue[propertyName] = 384 *value; 385 } 386 } 387 } 388 asyncResp->res.jsonValue["Name"] = chassisId; 389 asyncResp->res.jsonValue["Id"] = chassisId; 390 asyncResp->res.jsonValue["Thermal"] = { 391 {"@odata.id", "/redfish/v1/Chassis/" + 392 chassisId + "/Thermal"}}; 393 // Power object 394 asyncResp->res.jsonValue["Power"] = { 395 {"@odata.id", "/redfish/v1/Chassis/" + 396 chassisId + "/Power"}}; 397 // SensorCollection 398 asyncResp->res.jsonValue["Sensors"] = { 399 {"@odata.id", "/redfish/v1/Chassis/" + 400 chassisId + "/Sensors"}}; 401 asyncResp->res.jsonValue["Status"] = { 402 {"State", "Enabled"}, 403 }; 404 405 asyncResp->res 406 .jsonValue["Links"]["ComputerSystems"] = { 407 {{"@odata.id", "/redfish/v1/Systems/system"}}}; 408 asyncResp->res.jsonValue["Links"]["ManagedBy"] = { 409 {{"@odata.id", "/redfish/v1/Managers/bmc"}}}; 410 getChassisState(asyncResp); 411 }, 412 connectionName, path, "org.freedesktop.DBus.Properties", 413 "GetAll", 414 "xyz.openbmc_project.Inventory.Decorator.Asset"); 415 return; 416 } 417 418 // Couldn't find an object with that name. return an error 419 messages::resourceNotFound( 420 asyncResp->res, "#Chassis.v1_10_0.Chassis", chassisId); 421 }, 422 "xyz.openbmc_project.ObjectMapper", 423 "/xyz/openbmc_project/object_mapper", 424 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 425 "/xyz/openbmc_project/inventory", 0, interfaces); 426 427 getPhysicalSecurityData(asyncResp); 428 } 429 430 void doPatch(crow::Response& res, const crow::Request& req, 431 const std::vector<std::string>& params) override 432 { 433 std::optional<std::string> indicatorLed; 434 auto asyncResp = std::make_shared<AsyncResp>(res); 435 436 if (params.size() != 1) 437 { 438 return; 439 } 440 441 if (!json_util::readJson(req, res, "IndicatorLED", indicatorLed)) 442 { 443 return; 444 } 445 446 if (!indicatorLed) 447 { 448 return; // delete this when we support more patch properties 449 } 450 451 const std::array<const char*, 2> interfaces = { 452 "xyz.openbmc_project.Inventory.Item.Board", 453 "xyz.openbmc_project.Inventory.Item.Chassis"}; 454 455 const std::string& chassisId = params[0]; 456 457 crow::connections::systemBus->async_method_call( 458 [asyncResp, chassisId, indicatorLed]( 459 const boost::system::error_code ec, 460 const crow::openbmc_mapper::GetSubTreeType& subtree) { 461 if (ec) 462 { 463 messages::internalError(asyncResp->res); 464 return; 465 } 466 467 // Iterate over all retrieved ObjectPaths. 468 for (const std::pair< 469 std::string, 470 std::vector< 471 std::pair<std::string, std::vector<std::string>>>>& 472 object : subtree) 473 { 474 const std::string& path = object.first; 475 const std::vector< 476 std::pair<std::string, std::vector<std::string>>>& 477 connectionNames = object.second; 478 479 if (!boost::ends_with(path, chassisId)) 480 { 481 continue; 482 } 483 484 if (connectionNames.size() < 1) 485 { 486 BMCWEB_LOG_ERROR << "Got 0 Connection names"; 487 continue; 488 } 489 490 const std::vector<std::string>& interfaces3 = 491 connectionNames[0].second; 492 493 if (indicatorLed) 494 { 495 const std::array<const char*, 2> hasIndicatorLed = { 496 "xyz.openbmc_project.Inventory.Item.Panel", 497 "xyz.openbmc_project.Inventory.Item.Board." 498 "Motherboard"}; 499 bool indicatorChassis = false; 500 for (const char* interface : hasIndicatorLed) 501 { 502 if (std::find(interfaces3.begin(), 503 interfaces3.end(), 504 interface) != interfaces3.end()) 505 { 506 indicatorChassis = true; 507 break; 508 } 509 } 510 if (indicatorChassis) 511 { 512 setIndicatorLedState(asyncResp, 513 std::move(*indicatorLed)); 514 } 515 else 516 { 517 messages::propertyUnknown(asyncResp->res, 518 "IndicatorLED"); 519 } 520 } 521 return; 522 } 523 524 messages::resourceNotFound( 525 asyncResp->res, "#Chassis.v1_10_0.Chassis", chassisId); 526 }, 527 "xyz.openbmc_project.ObjectMapper", 528 "/xyz/openbmc_project/object_mapper", 529 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 530 "/xyz/openbmc_project/inventory", 0, interfaces); 531 } 532 }; 533 534 inline void doChassisPowerCycle(const std::shared_ptr<AsyncResp>& asyncResp) 535 { 536 const char* processName = "xyz.openbmc_project.State.Chassis"; 537 const char* objectPath = "/xyz/openbmc_project/state/chassis0"; 538 const char* interfaceName = "xyz.openbmc_project.State.Chassis"; 539 const char* destProperty = "RequestedPowerTransition"; 540 const std::string propertyValue = 541 "xyz.openbmc_project.State.Chassis.Transition.PowerCycle"; 542 543 crow::connections::systemBus->async_method_call( 544 [asyncResp](const boost::system::error_code ec) { 545 // Use "Set" method to set the property value. 546 if (ec) 547 { 548 BMCWEB_LOG_DEBUG << "[Set] Bad D-Bus request error: " << ec; 549 messages::internalError(asyncResp->res); 550 return; 551 } 552 553 messages::success(asyncResp->res); 554 }, 555 processName, objectPath, "org.freedesktop.DBus.Properties", "Set", 556 interfaceName, destProperty, std::variant<std::string>{propertyValue}); 557 } 558 559 /** 560 * ChassisResetAction class supports the POST method for the Reset 561 * action. 562 */ 563 class ChassisResetAction : public Node 564 { 565 public: 566 ChassisResetAction(App& app) : 567 Node(app, "/redfish/v1/Chassis/<str>/Actions/Chassis.Reset/", 568 std::string()) 569 { 570 entityPrivileges = { 571 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 572 } 573 574 private: 575 /** 576 * Function handles POST method request. 577 * Analyzes POST body before sending Reset request data to D-Bus. 578 */ 579 void doPost(crow::Response& res, const crow::Request& req, 580 const std::vector<std::string>&) override 581 { 582 BMCWEB_LOG_DEBUG << "Post Chassis Reset."; 583 584 std::string resetType; 585 auto asyncResp = std::make_shared<AsyncResp>(res); 586 587 if (!json_util::readJson(req, asyncResp->res, "ResetType", resetType)) 588 { 589 return; 590 } 591 592 if (resetType != "PowerCycle") 593 { 594 BMCWEB_LOG_DEBUG << "Invalid property value for ResetType: " 595 << resetType; 596 messages::actionParameterNotSupported(asyncResp->res, resetType, 597 "ResetType"); 598 599 return; 600 } 601 doChassisPowerCycle(asyncResp); 602 } 603 }; 604 605 /** 606 * ChassisResetActionInfo derived class for delivering Chassis 607 * ResetType AllowableValues using ResetInfo schema. 608 */ 609 class ChassisResetActionInfo : public Node 610 { 611 public: 612 /* 613 * Default Constructor 614 */ 615 ChassisResetActionInfo(App& app) : 616 Node(app, "/redfish/v1/Chassis/<str>/ResetActionInfo/", std::string()) 617 { 618 entityPrivileges = { 619 {boost::beast::http::verb::get, {{"Login"}}}, 620 {boost::beast::http::verb::head, {{"Login"}}}, 621 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 622 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 623 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 624 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 625 } 626 627 private: 628 /** 629 * Functions triggers appropriate requests on DBus 630 */ 631 void doGet(crow::Response& res, const crow::Request&, 632 const std::vector<std::string>& params) override 633 { 634 if (params.size() != 1) 635 { 636 messages::internalError(res); 637 res.end(); 638 return; 639 } 640 const std::string& chassisId = params[0]; 641 642 res.jsonValue = {{"@odata.type", "#ActionInfo.v1_1_2.ActionInfo"}, 643 {"@odata.id", "/redfish/v1/Chassis/" + chassisId + 644 "/ResetActionInfo"}, 645 {"Name", "Reset Action Info"}, 646 {"Id", "ResetActionInfo"}, 647 {"Parameters", 648 {{{"Name", "ResetType"}, 649 {"Required", true}, 650 {"DataType", "String"}, 651 {"AllowableValues", {"PowerCycle"}}}}}}; 652 res.end(); 653 } 654 }; 655 656 } // namespace redfish 657