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