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 "node.hpp" 20 #include "redfish_util.hpp" 21 22 #include <boost/algorithm/string/replace.hpp> 23 #include <boost/date_time.hpp> 24 #include <dbus_utility.hpp> 25 #include <utils/fw_utils.hpp> 26 #include <utils/systemd_utils.hpp> 27 28 #include <cstdint> 29 #include <memory> 30 #include <sstream> 31 #include <variant> 32 33 namespace redfish 34 { 35 36 /** 37 * Function reboots the BMC. 38 * 39 * @param[in] asyncResp - Shared pointer for completing asynchronous calls 40 */ 41 inline void doBMCGracefulRestart(std::shared_ptr<AsyncResp> asyncResp) 42 { 43 const char* processName = "xyz.openbmc_project.State.BMC"; 44 const char* objectPath = "/xyz/openbmc_project/state/bmc0"; 45 const char* interfaceName = "xyz.openbmc_project.State.BMC"; 46 const std::string& propertyValue = 47 "xyz.openbmc_project.State.BMC.Transition.Reboot"; 48 const char* destProperty = "RequestedBMCTransition"; 49 50 // Create the D-Bus variant for D-Bus call. 51 VariantType dbusPropertyValue(propertyValue); 52 53 crow::connections::systemBus->async_method_call( 54 [asyncResp](const boost::system::error_code ec) { 55 // Use "Set" method to set the property value. 56 if (ec) 57 { 58 BMCWEB_LOG_DEBUG << "[Set] Bad D-Bus request error: " << ec; 59 messages::internalError(asyncResp->res); 60 return; 61 } 62 63 messages::success(asyncResp->res); 64 }, 65 processName, objectPath, "org.freedesktop.DBus.Properties", "Set", 66 interfaceName, destProperty, dbusPropertyValue); 67 } 68 69 inline void doBMCForceRestart(std::shared_ptr<AsyncResp> asyncResp) 70 { 71 const char* processName = "xyz.openbmc_project.State.BMC"; 72 const char* objectPath = "/xyz/openbmc_project/state/bmc0"; 73 const char* interfaceName = "xyz.openbmc_project.State.BMC"; 74 const std::string& propertyValue = 75 "xyz.openbmc_project.State.BMC.Transition.HardReboot"; 76 const char* destProperty = "RequestedBMCTransition"; 77 78 // Create the D-Bus variant for D-Bus call. 79 VariantType dbusPropertyValue(propertyValue); 80 81 crow::connections::systemBus->async_method_call( 82 [asyncResp](const boost::system::error_code ec) { 83 // Use "Set" method to set the property value. 84 if (ec) 85 { 86 BMCWEB_LOG_DEBUG << "[Set] Bad D-Bus request error: " << ec; 87 messages::internalError(asyncResp->res); 88 return; 89 } 90 91 messages::success(asyncResp->res); 92 }, 93 processName, objectPath, "org.freedesktop.DBus.Properties", "Set", 94 interfaceName, destProperty, dbusPropertyValue); 95 } 96 97 /** 98 * ManagerResetAction class supports the POST method for the Reset (reboot) 99 * action. 100 */ 101 class ManagerResetAction : public Node 102 { 103 public: 104 ManagerResetAction(App& app) : 105 Node(app, "/redfish/v1/Managers/bmc/Actions/Manager.Reset/") 106 { 107 entityPrivileges = { 108 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 109 } 110 111 private: 112 /** 113 * Function handles POST method request. 114 * Analyzes POST body before sending Reset (Reboot) request data to D-Bus. 115 * OpenBMC supports ResetType "GracefulRestart" and "ForceRestart". 116 */ 117 void doPost(crow::Response& res, const crow::Request& req, 118 const std::vector<std::string>&) override 119 { 120 BMCWEB_LOG_DEBUG << "Post Manager Reset."; 121 122 std::string resetType; 123 auto asyncResp = std::make_shared<AsyncResp>(res); 124 125 if (!json_util::readJson(req, asyncResp->res, "ResetType", resetType)) 126 { 127 return; 128 } 129 130 if (resetType == "GracefulRestart") 131 { 132 BMCWEB_LOG_DEBUG << "Proceeding with " << resetType; 133 doBMCGracefulRestart(asyncResp); 134 return; 135 } 136 if (resetType == "ForceRestart") 137 { 138 BMCWEB_LOG_DEBUG << "Proceeding with " << resetType; 139 doBMCForceRestart(asyncResp); 140 return; 141 } 142 BMCWEB_LOG_DEBUG << "Invalid property value for ResetType: " 143 << resetType; 144 messages::actionParameterNotSupported(asyncResp->res, resetType, 145 "ResetType"); 146 147 return; 148 } 149 }; 150 151 /** 152 * ManagerResetToDefaultsAction class supports POST method for factory reset 153 * action. 154 */ 155 class ManagerResetToDefaultsAction : public Node 156 { 157 public: 158 ManagerResetToDefaultsAction(App& app) : 159 Node(app, "/redfish/v1/Managers/bmc/Actions/Manager.ResetToDefaults/") 160 { 161 entityPrivileges = { 162 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 163 } 164 165 private: 166 /** 167 * Function handles ResetToDefaults POST method request. 168 * 169 * Analyzes POST body message and factory resets BMC by calling 170 * BMC code updater factory reset followed by a BMC reboot. 171 * 172 * BMC code updater factory reset wipes the whole BMC read-write 173 * filesystem which includes things like the network settings. 174 * 175 * OpenBMC only supports ResetToDefaultsType "ResetAll". 176 */ 177 void doPost(crow::Response& res, const crow::Request& req, 178 const std::vector<std::string>&) override 179 { 180 BMCWEB_LOG_DEBUG << "Post ResetToDefaults."; 181 182 std::string resetType; 183 auto asyncResp = std::make_shared<AsyncResp>(res); 184 185 if (!json_util::readJson(req, asyncResp->res, "ResetToDefaultsType", 186 resetType)) 187 { 188 BMCWEB_LOG_DEBUG << "Missing property ResetToDefaultsType."; 189 190 messages::actionParameterMissing(asyncResp->res, "ResetToDefaults", 191 "ResetToDefaultsType"); 192 return; 193 } 194 195 if (resetType != "ResetAll") 196 { 197 BMCWEB_LOG_DEBUG << "Invalid property value for " 198 "ResetToDefaultsType: " 199 << resetType; 200 messages::actionParameterNotSupported(asyncResp->res, resetType, 201 "ResetToDefaultsType"); 202 return; 203 } 204 205 crow::connections::systemBus->async_method_call( 206 [asyncResp](const boost::system::error_code ec) { 207 if (ec) 208 { 209 BMCWEB_LOG_DEBUG << "Failed to ResetToDefaults: " << ec; 210 messages::internalError(asyncResp->res); 211 return; 212 } 213 // Factory Reset doesn't actually happen until a reboot 214 // Can't erase what the BMC is running on 215 doBMCGracefulRestart(asyncResp); 216 }, 217 "xyz.openbmc_project.Software.BMC.Updater", 218 "/xyz/openbmc_project/software", 219 "xyz.openbmc_project.Common.FactoryReset", "Reset"); 220 } 221 }; 222 223 /** 224 * ManagerResetActionInfo derived class for delivering Manager 225 * ResetType AllowableValues using ResetInfo schema. 226 */ 227 class ManagerResetActionInfo : public Node 228 { 229 public: 230 /* 231 * Default Constructor 232 */ 233 ManagerResetActionInfo(App& app) : 234 Node(app, "/redfish/v1/Managers/bmc/ResetActionInfo/") 235 { 236 entityPrivileges = { 237 {boost::beast::http::verb::get, {{"Login"}}}, 238 {boost::beast::http::verb::head, {{"Login"}}}, 239 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 240 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 241 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 242 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 243 } 244 245 private: 246 /** 247 * Functions triggers appropriate requests on DBus 248 */ 249 void doGet(crow::Response& res, const crow::Request&, 250 const std::vector<std::string>&) override 251 { 252 res.jsonValue = { 253 {"@odata.type", "#ActionInfo.v1_1_2.ActionInfo"}, 254 {"@odata.id", "/redfish/v1/Managers/bmc/ResetActionInfo"}, 255 {"Name", "Reset Action Info"}, 256 {"Id", "ResetActionInfo"}, 257 {"Parameters", 258 {{{"Name", "ResetType"}, 259 {"Required", true}, 260 {"DataType", "String"}, 261 {"AllowableValues", {"GracefulRestart", "ForceRestart"}}}}}}; 262 res.end(); 263 } 264 }; 265 266 static constexpr const char* objectManagerIface = 267 "org.freedesktop.DBus.ObjectManager"; 268 static constexpr const char* pidConfigurationIface = 269 "xyz.openbmc_project.Configuration.Pid"; 270 static constexpr const char* pidZoneConfigurationIface = 271 "xyz.openbmc_project.Configuration.Pid.Zone"; 272 static constexpr const char* stepwiseConfigurationIface = 273 "xyz.openbmc_project.Configuration.Stepwise"; 274 static constexpr const char* thermalModeIface = 275 "xyz.openbmc_project.Control.ThermalMode"; 276 277 inline void asyncPopulatePid(const std::string& connection, 278 const std::string& path, 279 const std::string& currentProfile, 280 const std::vector<std::string>& supportedProfiles, 281 std::shared_ptr<AsyncResp> asyncResp) 282 { 283 284 crow::connections::systemBus->async_method_call( 285 [asyncResp, currentProfile, supportedProfiles]( 286 const boost::system::error_code ec, 287 const dbus::utility::ManagedObjectType& managedObj) { 288 if (ec) 289 { 290 BMCWEB_LOG_ERROR << ec; 291 asyncResp->res.jsonValue.clear(); 292 messages::internalError(asyncResp->res); 293 return; 294 } 295 nlohmann::json& configRoot = 296 asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"]; 297 nlohmann::json& fans = configRoot["FanControllers"]; 298 fans["@odata.type"] = "#OemManager.FanControllers"; 299 fans["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc/" 300 "Fan/FanControllers"; 301 302 nlohmann::json& pids = configRoot["PidControllers"]; 303 pids["@odata.type"] = "#OemManager.PidControllers"; 304 pids["@odata.id"] = 305 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/PidControllers"; 306 307 nlohmann::json& stepwise = configRoot["StepwiseControllers"]; 308 stepwise["@odata.type"] = "#OemManager.StepwiseControllers"; 309 stepwise["@odata.id"] = 310 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/StepwiseControllers"; 311 312 nlohmann::json& zones = configRoot["FanZones"]; 313 zones["@odata.id"] = 314 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones"; 315 zones["@odata.type"] = "#OemManager.FanZones"; 316 configRoot["@odata.id"] = 317 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan"; 318 configRoot["@odata.type"] = "#OemManager.Fan"; 319 configRoot["Profile@Redfish.AllowableValues"] = supportedProfiles; 320 321 if (!currentProfile.empty()) 322 { 323 configRoot["Profile"] = currentProfile; 324 } 325 BMCWEB_LOG_ERROR << "profile = " << currentProfile << " !"; 326 327 for (const auto& pathPair : managedObj) 328 { 329 for (const auto& intfPair : pathPair.second) 330 { 331 if (intfPair.first != pidConfigurationIface && 332 intfPair.first != pidZoneConfigurationIface && 333 intfPair.first != stepwiseConfigurationIface) 334 { 335 continue; 336 } 337 auto findName = intfPair.second.find("Name"); 338 if (findName == intfPair.second.end()) 339 { 340 BMCWEB_LOG_ERROR << "Pid Field missing Name"; 341 messages::internalError(asyncResp->res); 342 return; 343 } 344 345 const std::string* namePtr = 346 std::get_if<std::string>(&findName->second); 347 if (namePtr == nullptr) 348 { 349 BMCWEB_LOG_ERROR << "Pid Name Field illegal"; 350 messages::internalError(asyncResp->res); 351 return; 352 } 353 std::string name = *namePtr; 354 dbus::utility::escapePathForDbus(name); 355 356 auto findProfiles = intfPair.second.find("Profiles"); 357 if (findProfiles != intfPair.second.end()) 358 { 359 const std::vector<std::string>* profiles = 360 std::get_if<std::vector<std::string>>( 361 &findProfiles->second); 362 if (profiles == nullptr) 363 { 364 BMCWEB_LOG_ERROR << "Pid Profiles Field illegal"; 365 messages::internalError(asyncResp->res); 366 return; 367 } 368 if (std::find(profiles->begin(), profiles->end(), 369 currentProfile) == profiles->end()) 370 { 371 BMCWEB_LOG_INFO 372 << name << " not supported in current profile"; 373 continue; 374 } 375 } 376 nlohmann::json* config = nullptr; 377 378 const std::string* classPtr = nullptr; 379 auto findClass = intfPair.second.find("Class"); 380 if (findClass != intfPair.second.end()) 381 { 382 classPtr = std::get_if<std::string>(&findClass->second); 383 } 384 385 if (intfPair.first == pidZoneConfigurationIface) 386 { 387 std::string chassis; 388 if (!dbus::utility::getNthStringFromPath( 389 pathPair.first.str, 5, chassis)) 390 { 391 chassis = "#IllegalValue"; 392 } 393 nlohmann::json& zone = zones[name]; 394 zone["Chassis"] = { 395 {"@odata.id", "/redfish/v1/Chassis/" + chassis}}; 396 zone["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/" 397 "OpenBmc/Fan/FanZones/" + 398 name; 399 zone["@odata.type"] = "#OemManager.FanZone"; 400 config = &zone; 401 } 402 403 else if (intfPair.first == stepwiseConfigurationIface) 404 { 405 if (classPtr == nullptr) 406 { 407 BMCWEB_LOG_ERROR << "Pid Class Field illegal"; 408 messages::internalError(asyncResp->res); 409 return; 410 } 411 412 nlohmann::json& controller = stepwise[name]; 413 config = &controller; 414 415 controller["@odata.id"] = 416 "/redfish/v1/Managers/bmc#/Oem/" 417 "OpenBmc/Fan/StepwiseControllers/" + 418 name; 419 controller["@odata.type"] = 420 "#OemManager.StepwiseController"; 421 422 controller["Direction"] = *classPtr; 423 } 424 425 // pid and fans are off the same configuration 426 else if (intfPair.first == pidConfigurationIface) 427 { 428 429 if (classPtr == nullptr) 430 { 431 BMCWEB_LOG_ERROR << "Pid Class Field illegal"; 432 messages::internalError(asyncResp->res); 433 return; 434 } 435 bool isFan = *classPtr == "fan"; 436 nlohmann::json& element = 437 isFan ? fans[name] : pids[name]; 438 config = &element; 439 if (isFan) 440 { 441 element["@odata.id"] = 442 "/redfish/v1/Managers/bmc#/Oem/" 443 "OpenBmc/Fan/FanControllers/" + 444 name; 445 element["@odata.type"] = 446 "#OemManager.FanController"; 447 } 448 else 449 { 450 element["@odata.id"] = 451 "/redfish/v1/Managers/bmc#/Oem/" 452 "OpenBmc/Fan/PidControllers/" + 453 name; 454 element["@odata.type"] = 455 "#OemManager.PidController"; 456 } 457 } 458 else 459 { 460 BMCWEB_LOG_ERROR << "Unexpected configuration"; 461 messages::internalError(asyncResp->res); 462 return; 463 } 464 465 // used for making maps out of 2 vectors 466 const std::vector<double>* keys = nullptr; 467 const std::vector<double>* values = nullptr; 468 469 for (const auto& propertyPair : intfPair.second) 470 { 471 if (propertyPair.first == "Type" || 472 propertyPair.first == "Class" || 473 propertyPair.first == "Name") 474 { 475 continue; 476 } 477 478 // zones 479 if (intfPair.first == pidZoneConfigurationIface) 480 { 481 const double* ptr = 482 std::get_if<double>(&propertyPair.second); 483 if (ptr == nullptr) 484 { 485 BMCWEB_LOG_ERROR << "Field Illegal " 486 << propertyPair.first; 487 messages::internalError(asyncResp->res); 488 return; 489 } 490 (*config)[propertyPair.first] = *ptr; 491 } 492 493 if (intfPair.first == stepwiseConfigurationIface) 494 { 495 if (propertyPair.first == "Reading" || 496 propertyPair.first == "Output") 497 { 498 const std::vector<double>* ptr = 499 std::get_if<std::vector<double>>( 500 &propertyPair.second); 501 502 if (ptr == nullptr) 503 { 504 BMCWEB_LOG_ERROR << "Field Illegal " 505 << propertyPair.first; 506 messages::internalError(asyncResp->res); 507 return; 508 } 509 510 if (propertyPair.first == "Reading") 511 { 512 keys = ptr; 513 } 514 else 515 { 516 values = ptr; 517 } 518 if (keys && values) 519 { 520 if (keys->size() != values->size()) 521 { 522 BMCWEB_LOG_ERROR 523 << "Reading and Output size don't " 524 "match "; 525 messages::internalError(asyncResp->res); 526 return; 527 } 528 nlohmann::json& steps = (*config)["Steps"]; 529 steps = nlohmann::json::array(); 530 for (size_t ii = 0; ii < keys->size(); ii++) 531 { 532 steps.push_back( 533 {{"Target", (*keys)[ii]}, 534 {"Output", (*values)[ii]}}); 535 } 536 } 537 } 538 if (propertyPair.first == "NegativeHysteresis" || 539 propertyPair.first == "PositiveHysteresis") 540 { 541 const double* ptr = 542 std::get_if<double>(&propertyPair.second); 543 if (ptr == nullptr) 544 { 545 BMCWEB_LOG_ERROR << "Field Illegal " 546 << propertyPair.first; 547 messages::internalError(asyncResp->res); 548 return; 549 } 550 (*config)[propertyPair.first] = *ptr; 551 } 552 } 553 554 // pid and fans are off the same configuration 555 if (intfPair.first == pidConfigurationIface || 556 intfPair.first == stepwiseConfigurationIface) 557 { 558 559 if (propertyPair.first == "Zones") 560 { 561 const std::vector<std::string>* inputs = 562 std::get_if<std::vector<std::string>>( 563 &propertyPair.second); 564 565 if (inputs == nullptr) 566 { 567 BMCWEB_LOG_ERROR 568 << "Zones Pid Field Illegal"; 569 messages::internalError(asyncResp->res); 570 return; 571 } 572 auto& data = (*config)[propertyPair.first]; 573 data = nlohmann::json::array(); 574 for (std::string itemCopy : *inputs) 575 { 576 dbus::utility::escapePathForDbus(itemCopy); 577 data.push_back( 578 {{"@odata.id", 579 "/redfish/v1/Managers/bmc#/Oem/" 580 "OpenBmc/Fan/FanZones/" + 581 itemCopy}}); 582 } 583 } 584 // todo(james): may never happen, but this 585 // assumes configuration data referenced in the 586 // PID config is provided by the same daemon, we 587 // could add another loop to cover all cases, 588 // but I'm okay kicking this can down the road a 589 // bit 590 591 else if (propertyPair.first == "Inputs" || 592 propertyPair.first == "Outputs") 593 { 594 auto& data = (*config)[propertyPair.first]; 595 const std::vector<std::string>* inputs = 596 std::get_if<std::vector<std::string>>( 597 &propertyPair.second); 598 599 if (inputs == nullptr) 600 { 601 BMCWEB_LOG_ERROR << "Field Illegal " 602 << propertyPair.first; 603 messages::internalError(asyncResp->res); 604 return; 605 } 606 data = *inputs; 607 } 608 else if (propertyPair.first == "SetPointOffset") 609 { 610 const std::string* ptr = 611 std::get_if<std::string>( 612 &propertyPair.second); 613 614 if (ptr == nullptr) 615 { 616 BMCWEB_LOG_ERROR << "Field Illegal " 617 << propertyPair.first; 618 messages::internalError(asyncResp->res); 619 return; 620 } 621 // translate from dbus to redfish 622 if (*ptr == "WarningHigh") 623 { 624 (*config)["SetPointOffset"] = 625 "UpperThresholdNonCritical"; 626 } 627 else if (*ptr == "WarningLow") 628 { 629 (*config)["SetPointOffset"] = 630 "LowerThresholdNonCritical"; 631 } 632 else if (*ptr == "CriticalHigh") 633 { 634 (*config)["SetPointOffset"] = 635 "UpperThresholdCritical"; 636 } 637 else if (*ptr == "CriticalLow") 638 { 639 (*config)["SetPointOffset"] = 640 "LowerThresholdCritical"; 641 } 642 else 643 { 644 BMCWEB_LOG_ERROR << "Value Illegal " 645 << *ptr; 646 messages::internalError(asyncResp->res); 647 return; 648 } 649 } 650 // doubles 651 else if (propertyPair.first == 652 "FFGainCoefficient" || 653 propertyPair.first == "FFOffCoefficient" || 654 propertyPair.first == "ICoefficient" || 655 propertyPair.first == "ILimitMax" || 656 propertyPair.first == "ILimitMin" || 657 propertyPair.first == 658 "PositiveHysteresis" || 659 propertyPair.first == 660 "NegativeHysteresis" || 661 propertyPair.first == "OutLimitMax" || 662 propertyPair.first == "OutLimitMin" || 663 propertyPair.first == "PCoefficient" || 664 propertyPair.first == "SetPoint" || 665 propertyPair.first == "SlewNeg" || 666 propertyPair.first == "SlewPos") 667 { 668 const double* ptr = 669 std::get_if<double>(&propertyPair.second); 670 if (ptr == nullptr) 671 { 672 BMCWEB_LOG_ERROR << "Field Illegal " 673 << propertyPair.first; 674 messages::internalError(asyncResp->res); 675 return; 676 } 677 (*config)[propertyPair.first] = *ptr; 678 } 679 } 680 } 681 } 682 } 683 }, 684 connection, path, objectManagerIface, "GetManagedObjects"); 685 } 686 687 enum class CreatePIDRet 688 { 689 fail, 690 del, 691 patch 692 }; 693 694 inline bool getZonesFromJsonReq(const std::shared_ptr<AsyncResp>& response, 695 std::vector<nlohmann::json>& config, 696 std::vector<std::string>& zones) 697 { 698 if (config.empty()) 699 { 700 BMCWEB_LOG_ERROR << "Empty Zones"; 701 messages::propertyValueFormatError(response->res, 702 nlohmann::json::array(), "Zones"); 703 return false; 704 } 705 for (auto& odata : config) 706 { 707 std::string path; 708 if (!redfish::json_util::readJson(odata, response->res, "@odata.id", 709 path)) 710 { 711 return false; 712 } 713 std::string input; 714 715 // 8 below comes from 716 // /redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones/Left 717 // 0 1 2 3 4 5 6 7 8 718 if (!dbus::utility::getNthStringFromPath(path, 8, input)) 719 { 720 BMCWEB_LOG_ERROR << "Got invalid path " << path; 721 BMCWEB_LOG_ERROR << "Illegal Type Zones"; 722 messages::propertyValueFormatError(response->res, odata.dump(), 723 "Zones"); 724 return false; 725 } 726 boost::replace_all(input, "_", " "); 727 zones.emplace_back(std::move(input)); 728 } 729 return true; 730 } 731 732 inline const dbus::utility::ManagedItem* 733 findChassis(const dbus::utility::ManagedObjectType& managedObj, 734 const std::string& value, std::string& chassis) 735 { 736 BMCWEB_LOG_DEBUG << "Find Chassis: " << value << "\n"; 737 738 std::string escaped = boost::replace_all_copy(value, " ", "_"); 739 escaped = "/" + escaped; 740 auto it = std::find_if( 741 managedObj.begin(), managedObj.end(), [&escaped](const auto& obj) { 742 if (boost::algorithm::ends_with(obj.first.str, escaped)) 743 { 744 BMCWEB_LOG_DEBUG << "Matched " << obj.first.str << "\n"; 745 return true; 746 } 747 return false; 748 }); 749 750 if (it == managedObj.end()) 751 { 752 return nullptr; 753 } 754 // 5 comes from <chassis-name> being the 5th element 755 // /xyz/openbmc_project/inventory/system/chassis/<chassis-name> 756 if (dbus::utility::getNthStringFromPath(it->first.str, 5, chassis)) 757 { 758 return &(*it); 759 } 760 761 return nullptr; 762 } 763 764 inline CreatePIDRet createPidInterface( 765 const std::shared_ptr<AsyncResp>& response, const std::string& type, 766 nlohmann::json::iterator it, const std::string& path, 767 const dbus::utility::ManagedObjectType& managedObj, bool createNewObject, 768 boost::container::flat_map<std::string, dbus::utility::DbusVariantType>& 769 output, 770 std::string& chassis, const std::string& profile) 771 { 772 773 // common deleter 774 if (it.value() == nullptr) 775 { 776 std::string iface; 777 if (type == "PidControllers" || type == "FanControllers") 778 { 779 iface = pidConfigurationIface; 780 } 781 else if (type == "FanZones") 782 { 783 iface = pidZoneConfigurationIface; 784 } 785 else if (type == "StepwiseControllers") 786 { 787 iface = stepwiseConfigurationIface; 788 } 789 else 790 { 791 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type " 792 << type; 793 messages::propertyUnknown(response->res, type); 794 return CreatePIDRet::fail; 795 } 796 797 BMCWEB_LOG_DEBUG << "del " << path << " " << iface << "\n"; 798 // delete interface 799 crow::connections::systemBus->async_method_call( 800 [response, path](const boost::system::error_code ec) { 801 if (ec) 802 { 803 BMCWEB_LOG_ERROR << "Error patching " << path << ": " << ec; 804 messages::internalError(response->res); 805 return; 806 } 807 messages::success(response->res); 808 }, 809 "xyz.openbmc_project.EntityManager", path, iface, "Delete"); 810 return CreatePIDRet::del; 811 } 812 813 const dbus::utility::ManagedItem* managedItem = nullptr; 814 if (!createNewObject) 815 { 816 // if we aren't creating a new object, we should be able to find it on 817 // d-bus 818 managedItem = findChassis(managedObj, it.key(), chassis); 819 if (managedItem == nullptr) 820 { 821 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch"; 822 messages::invalidObject(response->res, it.key()); 823 return CreatePIDRet::fail; 824 } 825 } 826 827 if (profile.size() && 828 (type == "PidControllers" || type == "FanControllers" || 829 type == "StepwiseControllers")) 830 { 831 if (managedItem == nullptr) 832 { 833 output["Profiles"] = std::vector<std::string>{profile}; 834 } 835 else 836 { 837 std::string interface; 838 if (type == "StepwiseControllers") 839 { 840 interface = stepwiseConfigurationIface; 841 } 842 else 843 { 844 interface = pidConfigurationIface; 845 } 846 auto findConfig = managedItem->second.find(interface); 847 if (findConfig == managedItem->second.end()) 848 { 849 BMCWEB_LOG_ERROR 850 << "Failed to find interface in managed object"; 851 messages::internalError(response->res); 852 return CreatePIDRet::fail; 853 } 854 auto findProfiles = findConfig->second.find("Profiles"); 855 if (findProfiles != findConfig->second.end()) 856 { 857 const std::vector<std::string>* curProfiles = 858 std::get_if<std::vector<std::string>>( 859 &(findProfiles->second)); 860 if (curProfiles == nullptr) 861 { 862 BMCWEB_LOG_ERROR << "Illegal profiles in managed object"; 863 messages::internalError(response->res); 864 return CreatePIDRet::fail; 865 } 866 if (std::find(curProfiles->begin(), curProfiles->end(), 867 profile) == curProfiles->end()) 868 { 869 std::vector<std::string> newProfiles = *curProfiles; 870 newProfiles.push_back(profile); 871 output["Profiles"] = newProfiles; 872 } 873 } 874 } 875 } 876 877 if (type == "PidControllers" || type == "FanControllers") 878 { 879 if (createNewObject) 880 { 881 output["Class"] = type == "PidControllers" ? std::string("temp") 882 : std::string("fan"); 883 output["Type"] = std::string("Pid"); 884 } 885 886 std::optional<std::vector<nlohmann::json>> zones; 887 std::optional<std::vector<std::string>> inputs; 888 std::optional<std::vector<std::string>> outputs; 889 std::map<std::string, std::optional<double>> doubles; 890 std::optional<std::string> setpointOffset; 891 if (!redfish::json_util::readJson( 892 it.value(), response->res, "Inputs", inputs, "Outputs", outputs, 893 "Zones", zones, "FFGainCoefficient", 894 doubles["FFGainCoefficient"], "FFOffCoefficient", 895 doubles["FFOffCoefficient"], "ICoefficient", 896 doubles["ICoefficient"], "ILimitMax", doubles["ILimitMax"], 897 "ILimitMin", doubles["ILimitMin"], "OutLimitMax", 898 doubles["OutLimitMax"], "OutLimitMin", doubles["OutLimitMin"], 899 "PCoefficient", doubles["PCoefficient"], "SetPoint", 900 doubles["SetPoint"], "SetPointOffset", setpointOffset, 901 "SlewNeg", doubles["SlewNeg"], "SlewPos", doubles["SlewPos"], 902 "PositiveHysteresis", doubles["PositiveHysteresis"], 903 "NegativeHysteresis", doubles["NegativeHysteresis"])) 904 { 905 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 906 << it.value().dump(); 907 return CreatePIDRet::fail; 908 } 909 if (zones) 910 { 911 std::vector<std::string> zonesStr; 912 if (!getZonesFromJsonReq(response, *zones, zonesStr)) 913 { 914 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones"; 915 return CreatePIDRet::fail; 916 } 917 if (chassis.empty() && 918 !findChassis(managedObj, zonesStr[0], chassis)) 919 { 920 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch"; 921 messages::invalidObject(response->res, it.key()); 922 return CreatePIDRet::fail; 923 } 924 925 output["Zones"] = std::move(zonesStr); 926 } 927 if (inputs || outputs) 928 { 929 std::array<std::optional<std::vector<std::string>>*, 2> containers = 930 {&inputs, &outputs}; 931 size_t index = 0; 932 for (const auto& containerPtr : containers) 933 { 934 std::optional<std::vector<std::string>>& container = 935 *containerPtr; 936 if (!container) 937 { 938 index++; 939 continue; 940 } 941 942 for (std::string& value : *container) 943 { 944 boost::replace_all(value, "_", " "); 945 } 946 std::string key; 947 if (index == 0) 948 { 949 key = "Inputs"; 950 } 951 else 952 { 953 key = "Outputs"; 954 } 955 output[key] = *container; 956 index++; 957 } 958 } 959 960 if (setpointOffset) 961 { 962 // translate between redfish and dbus names 963 if (*setpointOffset == "UpperThresholdNonCritical") 964 { 965 output["SetPointOffset"] = std::string("WarningLow"); 966 } 967 else if (*setpointOffset == "LowerThresholdNonCritical") 968 { 969 output["SetPointOffset"] = std::string("WarningHigh"); 970 } 971 else if (*setpointOffset == "LowerThresholdCritical") 972 { 973 output["SetPointOffset"] = std::string("CriticalLow"); 974 } 975 else if (*setpointOffset == "UpperThresholdCritical") 976 { 977 output["SetPointOffset"] = std::string("CriticalHigh"); 978 } 979 else 980 { 981 BMCWEB_LOG_ERROR << "Invalid setpointoffset " 982 << *setpointOffset; 983 messages::invalidObject(response->res, it.key()); 984 return CreatePIDRet::fail; 985 } 986 } 987 988 // doubles 989 for (const auto& pairs : doubles) 990 { 991 if (!pairs.second) 992 { 993 continue; 994 } 995 BMCWEB_LOG_DEBUG << pairs.first << " = " << *pairs.second; 996 output[pairs.first] = *(pairs.second); 997 } 998 } 999 1000 else if (type == "FanZones") 1001 { 1002 output["Type"] = std::string("Pid.Zone"); 1003 1004 std::optional<nlohmann::json> chassisContainer; 1005 std::optional<double> failSafePercent; 1006 std::optional<double> minThermalOutput; 1007 if (!redfish::json_util::readJson(it.value(), response->res, "Chassis", 1008 chassisContainer, "FailSafePercent", 1009 failSafePercent, "MinThermalOutput", 1010 minThermalOutput)) 1011 { 1012 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 1013 << it.value().dump(); 1014 return CreatePIDRet::fail; 1015 } 1016 1017 if (chassisContainer) 1018 { 1019 1020 std::string chassisId; 1021 if (!redfish::json_util::readJson(*chassisContainer, response->res, 1022 "@odata.id", chassisId)) 1023 { 1024 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 1025 << chassisContainer->dump(); 1026 return CreatePIDRet::fail; 1027 } 1028 1029 // /redfish/v1/chassis/chassis_name/ 1030 if (!dbus::utility::getNthStringFromPath(chassisId, 3, chassis)) 1031 { 1032 BMCWEB_LOG_ERROR << "Got invalid path " << chassisId; 1033 messages::invalidObject(response->res, chassisId); 1034 return CreatePIDRet::fail; 1035 } 1036 } 1037 if (minThermalOutput) 1038 { 1039 output["MinThermalOutput"] = *minThermalOutput; 1040 } 1041 if (failSafePercent) 1042 { 1043 output["FailSafePercent"] = *failSafePercent; 1044 } 1045 } 1046 else if (type == "StepwiseControllers") 1047 { 1048 output["Type"] = std::string("Stepwise"); 1049 1050 std::optional<std::vector<nlohmann::json>> zones; 1051 std::optional<std::vector<nlohmann::json>> steps; 1052 std::optional<std::vector<std::string>> inputs; 1053 std::optional<double> positiveHysteresis; 1054 std::optional<double> negativeHysteresis; 1055 std::optional<std::string> direction; // upper clipping curve vs lower 1056 if (!redfish::json_util::readJson( 1057 it.value(), response->res, "Zones", zones, "Steps", steps, 1058 "Inputs", inputs, "PositiveHysteresis", positiveHysteresis, 1059 "NegativeHysteresis", negativeHysteresis, "Direction", 1060 direction)) 1061 { 1062 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 1063 << it.value().dump(); 1064 return CreatePIDRet::fail; 1065 } 1066 1067 if (zones) 1068 { 1069 std::vector<std::string> zonesStrs; 1070 if (!getZonesFromJsonReq(response, *zones, zonesStrs)) 1071 { 1072 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones"; 1073 return CreatePIDRet::fail; 1074 } 1075 if (chassis.empty() && 1076 !findChassis(managedObj, zonesStrs[0], chassis)) 1077 { 1078 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch"; 1079 messages::invalidObject(response->res, it.key()); 1080 return CreatePIDRet::fail; 1081 } 1082 output["Zones"] = std::move(zonesStrs); 1083 } 1084 if (steps) 1085 { 1086 std::vector<double> readings; 1087 std::vector<double> outputs; 1088 for (auto& step : *steps) 1089 { 1090 double target; 1091 double out; 1092 1093 if (!redfish::json_util::readJson(step, response->res, "Target", 1094 target, "Output", out)) 1095 { 1096 BMCWEB_LOG_ERROR << "Line:" << __LINE__ 1097 << ", Illegal Property " 1098 << it.value().dump(); 1099 return CreatePIDRet::fail; 1100 } 1101 readings.emplace_back(target); 1102 outputs.emplace_back(out); 1103 } 1104 output["Reading"] = std::move(readings); 1105 output["Output"] = std::move(outputs); 1106 } 1107 if (inputs) 1108 { 1109 for (std::string& value : *inputs) 1110 { 1111 boost::replace_all(value, "_", " "); 1112 } 1113 output["Inputs"] = std::move(*inputs); 1114 } 1115 if (negativeHysteresis) 1116 { 1117 output["NegativeHysteresis"] = *negativeHysteresis; 1118 } 1119 if (positiveHysteresis) 1120 { 1121 output["PositiveHysteresis"] = *positiveHysteresis; 1122 } 1123 if (direction) 1124 { 1125 constexpr const std::array<const char*, 2> allowedDirections = { 1126 "Ceiling", "Floor"}; 1127 if (std::find(allowedDirections.begin(), allowedDirections.end(), 1128 *direction) == allowedDirections.end()) 1129 { 1130 messages::propertyValueTypeError(response->res, "Direction", 1131 *direction); 1132 return CreatePIDRet::fail; 1133 } 1134 output["Class"] = *direction; 1135 } 1136 } 1137 else 1138 { 1139 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type " << type; 1140 messages::propertyUnknown(response->res, type); 1141 return CreatePIDRet::fail; 1142 } 1143 return CreatePIDRet::patch; 1144 } 1145 struct GetPIDValues : std::enable_shared_from_this<GetPIDValues> 1146 { 1147 1148 GetPIDValues(const std::shared_ptr<AsyncResp>& asyncRespIn) : 1149 asyncResp(asyncRespIn) 1150 1151 {} 1152 1153 void run() 1154 { 1155 std::shared_ptr<GetPIDValues> self = shared_from_this(); 1156 1157 // get all configurations 1158 crow::connections::systemBus->async_method_call( 1159 [self](const boost::system::error_code ec, 1160 const crow::openbmc_mapper::GetSubTreeType& subtreeLocal) { 1161 if (ec) 1162 { 1163 BMCWEB_LOG_ERROR << ec; 1164 messages::internalError(self->asyncResp->res); 1165 return; 1166 } 1167 self->subtree = subtreeLocal; 1168 }, 1169 "xyz.openbmc_project.ObjectMapper", 1170 "/xyz/openbmc_project/object_mapper", 1171 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0, 1172 std::array<const char*, 4>{ 1173 pidConfigurationIface, pidZoneConfigurationIface, 1174 objectManagerIface, stepwiseConfigurationIface}); 1175 1176 // at the same time get the selected profile 1177 crow::connections::systemBus->async_method_call( 1178 [self](const boost::system::error_code ec, 1179 const crow::openbmc_mapper::GetSubTreeType& subtreeLocal) { 1180 if (ec || subtreeLocal.empty()) 1181 { 1182 return; 1183 } 1184 if (subtreeLocal[0].second.size() != 1) 1185 { 1186 // invalid mapper response, should never happen 1187 BMCWEB_LOG_ERROR << "GetPIDValues: Mapper Error"; 1188 messages::internalError(self->asyncResp->res); 1189 return; 1190 } 1191 1192 const std::string& path = subtreeLocal[0].first; 1193 const std::string& owner = subtreeLocal[0].second[0].first; 1194 crow::connections::systemBus->async_method_call( 1195 [path, owner, self]( 1196 const boost::system::error_code ec2, 1197 const boost::container::flat_map< 1198 std::string, std::variant<std::vector<std::string>, 1199 std::string>>& resp) { 1200 if (ec2) 1201 { 1202 BMCWEB_LOG_ERROR << "GetPIDValues: Can't get " 1203 "thermalModeIface " 1204 << path; 1205 messages::internalError(self->asyncResp->res); 1206 return; 1207 } 1208 const std::string* current = nullptr; 1209 const std::vector<std::string>* supported = nullptr; 1210 for (auto& [key, value] : resp) 1211 { 1212 if (key == "Current") 1213 { 1214 current = std::get_if<std::string>(&value); 1215 if (current == nullptr) 1216 { 1217 BMCWEB_LOG_ERROR 1218 << "GetPIDValues: thermal mode " 1219 "iface invalid " 1220 << path; 1221 messages::internalError( 1222 self->asyncResp->res); 1223 return; 1224 } 1225 } 1226 if (key == "Supported") 1227 { 1228 supported = 1229 std::get_if<std::vector<std::string>>( 1230 &value); 1231 if (supported == nullptr) 1232 { 1233 BMCWEB_LOG_ERROR 1234 << "GetPIDValues: thermal mode " 1235 "iface invalid" 1236 << path; 1237 messages::internalError( 1238 self->asyncResp->res); 1239 return; 1240 } 1241 } 1242 } 1243 if (current == nullptr || supported == nullptr) 1244 { 1245 BMCWEB_LOG_ERROR << "GetPIDValues: thermal mode " 1246 "iface invalid " 1247 << path; 1248 messages::internalError(self->asyncResp->res); 1249 return; 1250 } 1251 self->currentProfile = *current; 1252 self->supportedProfiles = *supported; 1253 }, 1254 owner, path, "org.freedesktop.DBus.Properties", "GetAll", 1255 thermalModeIface); 1256 }, 1257 "xyz.openbmc_project.ObjectMapper", 1258 "/xyz/openbmc_project/object_mapper", 1259 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0, 1260 std::array<const char*, 1>{thermalModeIface}); 1261 } 1262 1263 ~GetPIDValues() 1264 { 1265 if (asyncResp->res.result() != boost::beast::http::status::ok) 1266 { 1267 return; 1268 } 1269 // create map of <connection, path to objMgr>> 1270 boost::container::flat_map<std::string, std::string> objectMgrPaths; 1271 boost::container::flat_set<std::string> calledConnections; 1272 for (const auto& pathGroup : subtree) 1273 { 1274 for (const auto& connectionGroup : pathGroup.second) 1275 { 1276 auto findConnection = 1277 calledConnections.find(connectionGroup.first); 1278 if (findConnection != calledConnections.end()) 1279 { 1280 break; 1281 } 1282 for (const std::string& interface : connectionGroup.second) 1283 { 1284 if (interface == objectManagerIface) 1285 { 1286 objectMgrPaths[connectionGroup.first] = pathGroup.first; 1287 } 1288 // this list is alphabetical, so we 1289 // should have found the objMgr by now 1290 if (interface == pidConfigurationIface || 1291 interface == pidZoneConfigurationIface || 1292 interface == stepwiseConfigurationIface) 1293 { 1294 auto findObjMgr = 1295 objectMgrPaths.find(connectionGroup.first); 1296 if (findObjMgr == objectMgrPaths.end()) 1297 { 1298 BMCWEB_LOG_DEBUG << connectionGroup.first 1299 << "Has no Object Manager"; 1300 continue; 1301 } 1302 1303 calledConnections.insert(connectionGroup.first); 1304 1305 asyncPopulatePid(findObjMgr->first, findObjMgr->second, 1306 currentProfile, supportedProfiles, 1307 asyncResp); 1308 break; 1309 } 1310 } 1311 } 1312 } 1313 } 1314 1315 std::vector<std::string> supportedProfiles; 1316 std::string currentProfile; 1317 crow::openbmc_mapper::GetSubTreeType subtree; 1318 std::shared_ptr<AsyncResp> asyncResp; 1319 }; 1320 1321 struct SetPIDValues : std::enable_shared_from_this<SetPIDValues> 1322 { 1323 1324 SetPIDValues(const std::shared_ptr<AsyncResp>& asyncRespIn, 1325 nlohmann::json& data) : 1326 asyncResp(asyncRespIn) 1327 { 1328 1329 std::optional<nlohmann::json> pidControllers; 1330 std::optional<nlohmann::json> fanControllers; 1331 std::optional<nlohmann::json> fanZones; 1332 std::optional<nlohmann::json> stepwiseControllers; 1333 1334 if (!redfish::json_util::readJson( 1335 data, asyncResp->res, "PidControllers", pidControllers, 1336 "FanControllers", fanControllers, "FanZones", fanZones, 1337 "StepwiseControllers", stepwiseControllers, "Profile", profile)) 1338 { 1339 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 1340 << data.dump(); 1341 return; 1342 } 1343 configuration.emplace_back("PidControllers", std::move(pidControllers)); 1344 configuration.emplace_back("FanControllers", std::move(fanControllers)); 1345 configuration.emplace_back("FanZones", std::move(fanZones)); 1346 configuration.emplace_back("StepwiseControllers", 1347 std::move(stepwiseControllers)); 1348 } 1349 void run() 1350 { 1351 if (asyncResp->res.result() != boost::beast::http::status::ok) 1352 { 1353 return; 1354 } 1355 1356 std::shared_ptr<SetPIDValues> self = shared_from_this(); 1357 1358 // todo(james): might make sense to do a mapper call here if this 1359 // interface gets more traction 1360 crow::connections::systemBus->async_method_call( 1361 [self](const boost::system::error_code ec, 1362 dbus::utility::ManagedObjectType& mObj) { 1363 if (ec) 1364 { 1365 BMCWEB_LOG_ERROR << "Error communicating to Entity Manager"; 1366 messages::internalError(self->asyncResp->res); 1367 return; 1368 } 1369 const std::array<const char*, 3> configurations = { 1370 pidConfigurationIface, pidZoneConfigurationIface, 1371 stepwiseConfigurationIface}; 1372 1373 for (const auto& [path, object] : mObj) 1374 { 1375 for (const auto& [interface, _] : object) 1376 { 1377 if (std::find(configurations.begin(), 1378 configurations.end(), 1379 interface) != configurations.end()) 1380 { 1381 self->objectCount++; 1382 break; 1383 } 1384 } 1385 } 1386 self->managedObj = std::move(mObj); 1387 }, 1388 "xyz.openbmc_project.EntityManager", "/", objectManagerIface, 1389 "GetManagedObjects"); 1390 1391 // at the same time get the profile information 1392 crow::connections::systemBus->async_method_call( 1393 [self](const boost::system::error_code ec, 1394 const crow::openbmc_mapper::GetSubTreeType& subtree) { 1395 if (ec || subtree.empty()) 1396 { 1397 return; 1398 } 1399 if (subtree[0].second.empty()) 1400 { 1401 // invalid mapper response, should never happen 1402 BMCWEB_LOG_ERROR << "SetPIDValues: Mapper Error"; 1403 messages::internalError(self->asyncResp->res); 1404 return; 1405 } 1406 1407 const std::string& path = subtree[0].first; 1408 const std::string& owner = subtree[0].second[0].first; 1409 crow::connections::systemBus->async_method_call( 1410 [self, path, owner]( 1411 const boost::system::error_code ec2, 1412 const boost::container::flat_map< 1413 std::string, std::variant<std::vector<std::string>, 1414 std::string>>& r) { 1415 if (ec2) 1416 { 1417 BMCWEB_LOG_ERROR << "SetPIDValues: Can't get " 1418 "thermalModeIface " 1419 << path; 1420 messages::internalError(self->asyncResp->res); 1421 return; 1422 } 1423 const std::string* current = nullptr; 1424 const std::vector<std::string>* supported = nullptr; 1425 for (auto& [key, value] : r) 1426 { 1427 if (key == "Current") 1428 { 1429 current = std::get_if<std::string>(&value); 1430 if (current == nullptr) 1431 { 1432 BMCWEB_LOG_ERROR 1433 << "SetPIDValues: thermal mode " 1434 "iface invalid " 1435 << path; 1436 messages::internalError( 1437 self->asyncResp->res); 1438 return; 1439 } 1440 } 1441 if (key == "Supported") 1442 { 1443 supported = 1444 std::get_if<std::vector<std::string>>( 1445 &value); 1446 if (supported == nullptr) 1447 { 1448 BMCWEB_LOG_ERROR 1449 << "SetPIDValues: thermal mode " 1450 "iface invalid" 1451 << path; 1452 messages::internalError( 1453 self->asyncResp->res); 1454 return; 1455 } 1456 } 1457 } 1458 if (current == nullptr || supported == nullptr) 1459 { 1460 BMCWEB_LOG_ERROR << "SetPIDValues: thermal mode " 1461 "iface invalid " 1462 << path; 1463 messages::internalError(self->asyncResp->res); 1464 return; 1465 } 1466 self->currentProfile = *current; 1467 self->supportedProfiles = *supported; 1468 self->profileConnection = owner; 1469 self->profilePath = path; 1470 }, 1471 owner, path, "org.freedesktop.DBus.Properties", "GetAll", 1472 thermalModeIface); 1473 }, 1474 "xyz.openbmc_project.ObjectMapper", 1475 "/xyz/openbmc_project/object_mapper", 1476 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0, 1477 std::array<const char*, 1>{thermalModeIface}); 1478 } 1479 ~SetPIDValues() 1480 { 1481 if (asyncResp->res.result() != boost::beast::http::status::ok) 1482 { 1483 return; 1484 } 1485 1486 std::shared_ptr<AsyncResp> response = asyncResp; 1487 1488 if (profile) 1489 { 1490 if (std::find(supportedProfiles.begin(), supportedProfiles.end(), 1491 *profile) == supportedProfiles.end()) 1492 { 1493 messages::actionParameterUnknown(response->res, "Profile", 1494 *profile); 1495 return; 1496 } 1497 currentProfile = *profile; 1498 crow::connections::systemBus->async_method_call( 1499 [response](const boost::system::error_code ec) { 1500 if (ec) 1501 { 1502 BMCWEB_LOG_ERROR << "Error patching profile" << ec; 1503 messages::internalError(response->res); 1504 } 1505 }, 1506 profileConnection, profilePath, 1507 "org.freedesktop.DBus.Properties", "Set", thermalModeIface, 1508 "Current", std::variant<std::string>(*profile)); 1509 } 1510 1511 for (auto& containerPair : configuration) 1512 { 1513 auto& container = containerPair.second; 1514 if (!container) 1515 { 1516 continue; 1517 } 1518 BMCWEB_LOG_DEBUG << *container; 1519 1520 std::string& type = containerPair.first; 1521 1522 for (nlohmann::json::iterator it = container->begin(); 1523 it != container->end(); it++) 1524 { 1525 const auto& name = it.key(); 1526 BMCWEB_LOG_DEBUG << "looking for " << name; 1527 1528 auto pathItr = 1529 std::find_if(managedObj.begin(), managedObj.end(), 1530 [&name](const auto& obj) { 1531 return boost::algorithm::ends_with( 1532 obj.first.str, "/" + name); 1533 }); 1534 boost::container::flat_map<std::string, 1535 dbus::utility::DbusVariantType> 1536 output; 1537 1538 output.reserve(16); // The pid interface length 1539 1540 // determines if we're patching entity-manager or 1541 // creating a new object 1542 bool createNewObject = (pathItr == managedObj.end()); 1543 BMCWEB_LOG_DEBUG << "Found = " << !createNewObject; 1544 1545 std::string iface; 1546 if (type == "PidControllers" || type == "FanControllers") 1547 { 1548 iface = pidConfigurationIface; 1549 if (!createNewObject && 1550 pathItr->second.find(pidConfigurationIface) == 1551 pathItr->second.end()) 1552 { 1553 createNewObject = true; 1554 } 1555 } 1556 else if (type == "FanZones") 1557 { 1558 iface = pidZoneConfigurationIface; 1559 if (!createNewObject && 1560 pathItr->second.find(pidZoneConfigurationIface) == 1561 pathItr->second.end()) 1562 { 1563 1564 createNewObject = true; 1565 } 1566 } 1567 else if (type == "StepwiseControllers") 1568 { 1569 iface = stepwiseConfigurationIface; 1570 if (!createNewObject && 1571 pathItr->second.find(stepwiseConfigurationIface) == 1572 pathItr->second.end()) 1573 { 1574 createNewObject = true; 1575 } 1576 } 1577 1578 if (createNewObject && it.value() == nullptr) 1579 { 1580 // can't delete a non-existent object 1581 messages::invalidObject(response->res, name); 1582 continue; 1583 } 1584 1585 std::string path; 1586 if (pathItr != managedObj.end()) 1587 { 1588 path = pathItr->first.str; 1589 } 1590 1591 BMCWEB_LOG_DEBUG << "Create new = " << createNewObject << "\n"; 1592 1593 // arbitrary limit to avoid attacks 1594 constexpr const size_t controllerLimit = 500; 1595 if (createNewObject && objectCount >= controllerLimit) 1596 { 1597 messages::resourceExhaustion(response->res, type); 1598 continue; 1599 } 1600 1601 output["Name"] = boost::replace_all_copy(name, "_", " "); 1602 1603 std::string chassis; 1604 CreatePIDRet ret = createPidInterface( 1605 response, type, it, path, managedObj, createNewObject, 1606 output, chassis, currentProfile); 1607 if (ret == CreatePIDRet::fail) 1608 { 1609 return; 1610 } 1611 if (ret == CreatePIDRet::del) 1612 { 1613 continue; 1614 } 1615 1616 if (!createNewObject) 1617 { 1618 for (const auto& property : output) 1619 { 1620 crow::connections::systemBus->async_method_call( 1621 [response, 1622 propertyName{std::string(property.first)}]( 1623 const boost::system::error_code ec) { 1624 if (ec) 1625 { 1626 BMCWEB_LOG_ERROR << "Error patching " 1627 << propertyName << ": " 1628 << ec; 1629 messages::internalError(response->res); 1630 return; 1631 } 1632 messages::success(response->res); 1633 }, 1634 "xyz.openbmc_project.EntityManager", path, 1635 "org.freedesktop.DBus.Properties", "Set", iface, 1636 property.first, property.second); 1637 } 1638 } 1639 else 1640 { 1641 if (chassis.empty()) 1642 { 1643 BMCWEB_LOG_ERROR << "Failed to get chassis from config"; 1644 messages::invalidObject(response->res, name); 1645 return; 1646 } 1647 1648 bool foundChassis = false; 1649 for (const auto& obj : managedObj) 1650 { 1651 if (boost::algorithm::ends_with(obj.first.str, chassis)) 1652 { 1653 chassis = obj.first.str; 1654 foundChassis = true; 1655 break; 1656 } 1657 } 1658 if (!foundChassis) 1659 { 1660 BMCWEB_LOG_ERROR << "Failed to find chassis on dbus"; 1661 messages::resourceMissingAtURI( 1662 response->res, "/redfish/v1/Chassis/" + chassis); 1663 return; 1664 } 1665 1666 crow::connections::systemBus->async_method_call( 1667 [response](const boost::system::error_code ec) { 1668 if (ec) 1669 { 1670 BMCWEB_LOG_ERROR << "Error Adding Pid Object " 1671 << ec; 1672 messages::internalError(response->res); 1673 return; 1674 } 1675 messages::success(response->res); 1676 }, 1677 "xyz.openbmc_project.EntityManager", chassis, 1678 "xyz.openbmc_project.AddObject", "AddObject", output); 1679 } 1680 } 1681 } 1682 } 1683 std::shared_ptr<AsyncResp> asyncResp; 1684 std::vector<std::pair<std::string, std::optional<nlohmann::json>>> 1685 configuration; 1686 std::optional<std::string> profile; 1687 dbus::utility::ManagedObjectType managedObj; 1688 std::vector<std::string> supportedProfiles; 1689 std::string currentProfile; 1690 std::string profileConnection; 1691 std::string profilePath; 1692 size_t objectCount = 0; 1693 }; 1694 1695 class Manager : public Node 1696 { 1697 public: 1698 Manager(App& app) : Node(app, "/redfish/v1/Managers/bmc/") 1699 { 1700 1701 uuid = persistent_data::getConfig().systemUuid; 1702 entityPrivileges = { 1703 {boost::beast::http::verb::get, {{"Login"}}}, 1704 {boost::beast::http::verb::head, {{"Login"}}}, 1705 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1706 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1707 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1708 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1709 } 1710 1711 private: 1712 void doGet(crow::Response& res, const crow::Request&, 1713 const std::vector<std::string>&) override 1714 { 1715 res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc"; 1716 res.jsonValue["@odata.type"] = "#Manager.v1_9_0.Manager"; 1717 res.jsonValue["Id"] = "bmc"; 1718 res.jsonValue["Name"] = "OpenBmc Manager"; 1719 res.jsonValue["Description"] = "Baseboard Management Controller"; 1720 res.jsonValue["PowerState"] = "On"; 1721 res.jsonValue["Status"] = {{"State", "Enabled"}, {"Health", "OK"}}; 1722 res.jsonValue["ManagerType"] = "BMC"; 1723 res.jsonValue["UUID"] = systemd_utils::getUuid(); 1724 res.jsonValue["ServiceEntryPointUUID"] = uuid; 1725 res.jsonValue["Model"] = "OpenBmc"; // TODO(ed), get model 1726 1727 res.jsonValue["LogServices"] = { 1728 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices"}}; 1729 1730 res.jsonValue["NetworkProtocol"] = { 1731 {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol"}}; 1732 1733 res.jsonValue["EthernetInterfaces"] = { 1734 {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces"}}; 1735 1736 #ifdef BMCWEB_ENABLE_VM_NBDPROXY 1737 res.jsonValue["VirtualMedia"] = { 1738 {"@odata.id", "/redfish/v1/Managers/bmc/VirtualMedia"}}; 1739 #endif // BMCWEB_ENABLE_VM_NBDPROXY 1740 1741 // default oem data 1742 nlohmann::json& oem = res.jsonValue["Oem"]; 1743 nlohmann::json& oemOpenbmc = oem["OpenBmc"]; 1744 oem["@odata.type"] = "#OemManager.Oem"; 1745 oem["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem"; 1746 oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc"; 1747 oemOpenbmc["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc"; 1748 oemOpenbmc["Certificates"] = { 1749 {"@odata.id", "/redfish/v1/Managers/bmc/Truststore/Certificates"}}; 1750 1751 // Manager.Reset (an action) can be many values, OpenBMC only supports 1752 // BMC reboot. 1753 nlohmann::json& managerReset = 1754 res.jsonValue["Actions"]["#Manager.Reset"]; 1755 managerReset["target"] = 1756 "/redfish/v1/Managers/bmc/Actions/Manager.Reset"; 1757 managerReset["@Redfish.ActionInfo"] = 1758 "/redfish/v1/Managers/bmc/ResetActionInfo"; 1759 1760 // ResetToDefaults (Factory Reset) has values like 1761 // PreserveNetworkAndUsers and PreserveNetwork that aren't supported 1762 // on OpenBMC 1763 nlohmann::json& resetToDefaults = 1764 res.jsonValue["Actions"]["#Manager.ResetToDefaults"]; 1765 resetToDefaults["target"] = 1766 "/redfish/v1/Managers/bmc/Actions/Manager.ResetToDefaults"; 1767 resetToDefaults["ResetType@Redfish.AllowableValues"] = {"ResetAll"}; 1768 1769 res.jsonValue["DateTime"] = crow::utility::dateTimeNow(); 1770 1771 // Fill in SerialConsole info 1772 res.jsonValue["SerialConsole"]["ServiceEnabled"] = true; 1773 res.jsonValue["SerialConsole"]["MaxConcurrentSessions"] = 15; 1774 res.jsonValue["SerialConsole"]["ConnectTypesSupported"] = {"IPMI", 1775 "SSH"}; 1776 #ifdef BMCWEB_ENABLE_KVM 1777 // Fill in GraphicalConsole info 1778 res.jsonValue["GraphicalConsole"]["ServiceEnabled"] = true; 1779 res.jsonValue["GraphicalConsole"]["MaxConcurrentSessions"] = 4; 1780 res.jsonValue["GraphicalConsole"]["ConnectTypesSupported"] = {"KVMIP"}; 1781 #endif // BMCWEB_ENABLE_KVM 1782 1783 res.jsonValue["Links"]["ManagerForServers@odata.count"] = 1; 1784 res.jsonValue["Links"]["ManagerForServers"] = { 1785 {{"@odata.id", "/redfish/v1/Systems/system"}}}; 1786 1787 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1788 1789 auto health = std::make_shared<HealthPopulate>(asyncResp); 1790 health->isManagersHealth = true; 1791 health->populate(); 1792 1793 fw_util::populateFirmwareInformation(asyncResp, fw_util::bmcPurpose, 1794 "FirmwareVersion", true); 1795 1796 getLastResetTime(asyncResp); 1797 1798 auto pids = std::make_shared<GetPIDValues>(asyncResp); 1799 pids->run(); 1800 1801 getMainChassisId(asyncResp, [](const std::string& chassisId, 1802 const std::shared_ptr<AsyncResp> aRsp) { 1803 aRsp->res.jsonValue["Links"]["ManagerForChassis@odata.count"] = 1; 1804 aRsp->res.jsonValue["Links"]["ManagerForChassis"] = { 1805 {{"@odata.id", "/redfish/v1/Chassis/" + chassisId}}}; 1806 aRsp->res.jsonValue["Links"]["ManagerInChassis"] = { 1807 {"@odata.id", "/redfish/v1/Chassis/" + chassisId}}; 1808 }); 1809 1810 static bool started = false; 1811 1812 if (!started) 1813 { 1814 crow::connections::systemBus->async_method_call( 1815 [asyncResp](const boost::system::error_code ec, 1816 const std::variant<double>& resp) { 1817 if (ec) 1818 { 1819 BMCWEB_LOG_ERROR << "Error while getting progress"; 1820 messages::internalError(asyncResp->res); 1821 return; 1822 } 1823 const double* val = std::get_if<double>(&resp); 1824 if (val == nullptr) 1825 { 1826 BMCWEB_LOG_ERROR 1827 << "Invalid response while getting progress"; 1828 messages::internalError(asyncResp->res); 1829 return; 1830 } 1831 if (*val < 1.0) 1832 { 1833 asyncResp->res.jsonValue["Status"]["State"] = 1834 "Starting"; 1835 started = true; 1836 } 1837 }, 1838 "org.freedesktop.systemd1", "/org/freedesktop/systemd1", 1839 "org.freedesktop.DBus.Properties", "Get", 1840 "org.freedesktop.systemd1.Manager", "Progress"); 1841 } 1842 } 1843 1844 void doPatch(crow::Response& res, const crow::Request& req, 1845 const std::vector<std::string>&) override 1846 { 1847 std::optional<nlohmann::json> oem; 1848 std::optional<nlohmann::json> links; 1849 std::optional<std::string> datetime; 1850 std::shared_ptr<AsyncResp> response = std::make_shared<AsyncResp>(res); 1851 1852 if (!json_util::readJson(req, response->res, "Oem", oem, "DateTime", 1853 datetime, "Links", links)) 1854 { 1855 return; 1856 } 1857 1858 if (oem) 1859 { 1860 std::optional<nlohmann::json> openbmc; 1861 if (!redfish::json_util::readJson(*oem, res, "OpenBmc", openbmc)) 1862 { 1863 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 1864 << oem->dump(); 1865 return; 1866 } 1867 if (openbmc) 1868 { 1869 std::optional<nlohmann::json> fan; 1870 if (!redfish::json_util::readJson(*openbmc, res, "Fan", fan)) 1871 { 1872 BMCWEB_LOG_ERROR << "Line:" << __LINE__ 1873 << ", Illegal Property " 1874 << openbmc->dump(); 1875 return; 1876 } 1877 if (fan) 1878 { 1879 auto pid = std::make_shared<SetPIDValues>(response, *fan); 1880 pid->run(); 1881 } 1882 } 1883 } 1884 if (links) 1885 { 1886 std::optional<nlohmann::json> activeSoftwareImage; 1887 if (!redfish::json_util::readJson( 1888 *links, res, "ActiveSoftwareImage", activeSoftwareImage)) 1889 { 1890 return; 1891 } 1892 if (activeSoftwareImage) 1893 { 1894 std::optional<std::string> odataId; 1895 if (!json_util::readJson(*activeSoftwareImage, res, "@odata.id", 1896 odataId)) 1897 { 1898 return; 1899 } 1900 1901 if (odataId) 1902 { 1903 setActiveFirmwareImage(response, std::move(*odataId)); 1904 } 1905 } 1906 } 1907 if (datetime) 1908 { 1909 setDateTime(response, std::move(*datetime)); 1910 } 1911 } 1912 1913 void getLastResetTime(std::shared_ptr<AsyncResp> aResp) 1914 { 1915 BMCWEB_LOG_DEBUG << "Getting Manager Last Reset Time"; 1916 1917 crow::connections::systemBus->async_method_call( 1918 [aResp](const boost::system::error_code ec, 1919 std::variant<uint64_t>& lastResetTime) { 1920 if (ec) 1921 { 1922 BMCWEB_LOG_DEBUG << "D-BUS response error " << ec; 1923 return; 1924 } 1925 1926 const uint64_t* lastResetTimePtr = 1927 std::get_if<uint64_t>(&lastResetTime); 1928 1929 if (!lastResetTimePtr) 1930 { 1931 messages::internalError(aResp->res); 1932 return; 1933 } 1934 // LastRebootTime is epoch time, in milliseconds 1935 // https://github.com/openbmc/phosphor-dbus-interfaces/blob/7f9a128eb9296e926422ddc312c148b625890bb6/xyz/openbmc_project/State/BMC.interface.yaml#L19 1936 time_t lastResetTimeStamp = 1937 static_cast<time_t>(*lastResetTimePtr / 1000); 1938 1939 // Convert to ISO 8601 standard 1940 aResp->res.jsonValue["LastResetTime"] = 1941 crow::utility::getDateTime(lastResetTimeStamp); 1942 }, 1943 "xyz.openbmc_project.State.BMC", "/xyz/openbmc_project/state/bmc0", 1944 "org.freedesktop.DBus.Properties", "Get", 1945 "xyz.openbmc_project.State.BMC", "LastRebootTime"); 1946 } 1947 1948 /** 1949 * @brief Set the running firmware image 1950 * 1951 * @param[i,o] aResp - Async response object 1952 * @param[i] runningFirmwareTarget - Image to make the running image 1953 * 1954 * @return void 1955 */ 1956 void setActiveFirmwareImage(std::shared_ptr<AsyncResp> aResp, 1957 const std::string&& runningFirmwareTarget) 1958 { 1959 // Get the Id from /redfish/v1/UpdateService/FirmwareInventory/<Id> 1960 std::string::size_type idPos = runningFirmwareTarget.rfind("/"); 1961 if (idPos == std::string::npos) 1962 { 1963 messages::propertyValueNotInList(aResp->res, runningFirmwareTarget, 1964 "@odata.id"); 1965 BMCWEB_LOG_DEBUG << "Can't parse firmware ID!"; 1966 return; 1967 } 1968 idPos++; 1969 if (idPos >= runningFirmwareTarget.size()) 1970 { 1971 messages::propertyValueNotInList(aResp->res, runningFirmwareTarget, 1972 "@odata.id"); 1973 BMCWEB_LOG_DEBUG << "Invalid firmware ID."; 1974 return; 1975 } 1976 std::string firmwareId = runningFirmwareTarget.substr(idPos); 1977 1978 // Make sure the image is valid before setting priority 1979 crow::connections::systemBus->async_method_call( 1980 [aResp, firmwareId, 1981 runningFirmwareTarget](const boost::system::error_code ec, 1982 ManagedObjectType& subtree) { 1983 if (ec) 1984 { 1985 BMCWEB_LOG_DEBUG << "D-Bus response error getting objects."; 1986 messages::internalError(aResp->res); 1987 return; 1988 } 1989 1990 if (subtree.size() == 0) 1991 { 1992 BMCWEB_LOG_DEBUG << "Can't find image!"; 1993 messages::internalError(aResp->res); 1994 return; 1995 } 1996 1997 bool foundImage = false; 1998 for (auto& object : subtree) 1999 { 2000 const std::string& path = 2001 static_cast<const std::string&>(object.first); 2002 std::size_t idPos2 = path.rfind("/"); 2003 2004 if (idPos2 == std::string::npos) 2005 { 2006 continue; 2007 } 2008 2009 idPos2++; 2010 if (idPos2 >= path.size()) 2011 { 2012 continue; 2013 } 2014 2015 if (path.substr(idPos2) == firmwareId) 2016 { 2017 foundImage = true; 2018 break; 2019 } 2020 } 2021 2022 if (!foundImage) 2023 { 2024 messages::propertyValueNotInList( 2025 aResp->res, runningFirmwareTarget, "@odata.id"); 2026 BMCWEB_LOG_DEBUG << "Invalid firmware ID."; 2027 return; 2028 } 2029 2030 BMCWEB_LOG_DEBUG << "Setting firmware version " + firmwareId + 2031 " to priority 0."; 2032 2033 // Only support Immediate 2034 // An addition could be a Redfish Setting like 2035 // ActiveSoftwareImageApplyTime and support OnReset 2036 crow::connections::systemBus->async_method_call( 2037 [aResp](const boost::system::error_code ec) { 2038 if (ec) 2039 { 2040 BMCWEB_LOG_DEBUG << "D-Bus response error setting."; 2041 messages::internalError(aResp->res); 2042 return; 2043 } 2044 doBMCGracefulRestart(aResp); 2045 }, 2046 2047 "xyz.openbmc_project.Software.BMC.Updater", 2048 "/xyz/openbmc_project/software/" + firmwareId, 2049 "org.freedesktop.DBus.Properties", "Set", 2050 "xyz.openbmc_project.Software.RedundancyPriority", 2051 "Priority", std::variant<uint8_t>(static_cast<uint8_t>(0))); 2052 }, 2053 "xyz.openbmc_project.Software.BMC.Updater", 2054 "/xyz/openbmc_project/software", 2055 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 2056 } 2057 2058 void setDateTime(std::shared_ptr<AsyncResp> aResp, 2059 std::string datetime) const 2060 { 2061 BMCWEB_LOG_DEBUG << "Set date time: " << datetime; 2062 2063 std::stringstream stream(datetime); 2064 // Convert from ISO 8601 to boost local_time 2065 // (BMC only has time in UTC) 2066 boost::posix_time::ptime posixTime; 2067 boost::posix_time::ptime epoch(boost::gregorian::date(1970, 1, 1)); 2068 // Facet gets deleted with the stringsteam 2069 auto ifc = std::make_unique<boost::local_time::local_time_input_facet>( 2070 "%Y-%m-%d %H:%M:%S%F %ZP"); 2071 stream.imbue(std::locale(stream.getloc(), ifc.release())); 2072 2073 boost::local_time::local_date_time ldt( 2074 boost::local_time::not_a_date_time); 2075 2076 if (stream >> ldt) 2077 { 2078 posixTime = ldt.utc_time(); 2079 boost::posix_time::time_duration dur = posixTime - epoch; 2080 uint64_t durMicroSecs = 2081 static_cast<uint64_t>(dur.total_microseconds()); 2082 crow::connections::systemBus->async_method_call( 2083 [aResp{std::move(aResp)}, datetime{std::move(datetime)}]( 2084 const boost::system::error_code ec) { 2085 if (ec) 2086 { 2087 BMCWEB_LOG_DEBUG << "Failed to set elapsed time. " 2088 "DBUS response error " 2089 << ec; 2090 messages::internalError(aResp->res); 2091 return; 2092 } 2093 aResp->res.jsonValue["DateTime"] = datetime; 2094 }, 2095 "xyz.openbmc_project.Time.Manager", 2096 "/xyz/openbmc_project/time/bmc", 2097 "org.freedesktop.DBus.Properties", "Set", 2098 "xyz.openbmc_project.Time.EpochTime", "Elapsed", 2099 std::variant<uint64_t>(durMicroSecs)); 2100 } 2101 else 2102 { 2103 messages::propertyValueFormatError(aResp->res, datetime, 2104 "DateTime"); 2105 return; 2106 } 2107 } 2108 2109 std::string uuid; 2110 }; 2111 2112 class ManagerCollection : public Node 2113 { 2114 public: 2115 ManagerCollection(App& app) : Node(app, "/redfish/v1/Managers/") 2116 { 2117 entityPrivileges = { 2118 {boost::beast::http::verb::get, {{"Login"}}}, 2119 {boost::beast::http::verb::head, {{"Login"}}}, 2120 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2121 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2122 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2123 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2124 } 2125 2126 private: 2127 void doGet(crow::Response& res, const crow::Request&, 2128 const std::vector<std::string>&) override 2129 { 2130 // Collections don't include the static data added by SubRoute 2131 // because it has a duplicate entry for members 2132 res.jsonValue["@odata.id"] = "/redfish/v1/Managers"; 2133 res.jsonValue["@odata.type"] = "#ManagerCollection.ManagerCollection"; 2134 res.jsonValue["Name"] = "Manager Collection"; 2135 res.jsonValue["Members@odata.count"] = 1; 2136 res.jsonValue["Members"] = { 2137 {{"@odata.id", "/redfish/v1/Managers/bmc"}}}; 2138 res.end(); 2139 } 2140 }; 2141 } // namespace redfish 2142