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