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