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