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