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