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 <memory> 26 #include <sstream> 27 #include <utils/fw_utils.hpp> 28 #include <utils/systemd_utils.hpp> 29 #include <variant> 30 31 namespace redfish 32 { 33 34 /** 35 * Function reboots the BMC. 36 * 37 * @param[in] asyncResp - Shared pointer for completing asynchronous calls 38 */ 39 void doBMCGracefulRestart(std::shared_ptr<AsyncResp> asyncResp) 40 { 41 const char* processName = "xyz.openbmc_project.State.BMC"; 42 const char* objectPath = "/xyz/openbmc_project/state/bmc0"; 43 const char* interfaceName = "xyz.openbmc_project.State.BMC"; 44 const std::string& propertyValue = 45 "xyz.openbmc_project.State.BMC.Transition.Reboot"; 46 const char* destProperty = "RequestedBMCTransition"; 47 48 // Create the D-Bus variant for D-Bus call. 49 VariantType dbusPropertyValue(propertyValue); 50 51 crow::connections::systemBus->async_method_call( 52 [asyncResp](const boost::system::error_code ec) { 53 // Use "Set" method to set the property value. 54 if (ec) 55 { 56 BMCWEB_LOG_DEBUG << "[Set] Bad D-Bus request error: " << ec; 57 messages::internalError(asyncResp->res); 58 return; 59 } 60 61 messages::success(asyncResp->res); 62 }, 63 processName, objectPath, "org.freedesktop.DBus.Properties", "Set", 64 interfaceName, destProperty, dbusPropertyValue); 65 } 66 67 /** 68 * ManagerResetAction class supports the POST method for the Reset (reboot) 69 * action. 70 */ 71 class ManagerResetAction : public Node 72 { 73 public: 74 ManagerResetAction(CrowApp& app) : 75 Node(app, "/redfish/v1/Managers/bmc/Actions/Manager.Reset/") 76 { 77 entityPrivileges = { 78 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 79 } 80 81 private: 82 /** 83 * Function handles POST method request. 84 * Analyzes POST body before sending Reset (Reboot) request data to D-Bus. 85 * OpenBMC only supports ResetType "GracefulRestart". 86 */ 87 void doPost(crow::Response& res, const crow::Request& req, 88 const std::vector<std::string>& params) override 89 { 90 BMCWEB_LOG_DEBUG << "Post Manager Reset."; 91 92 std::string resetType; 93 auto asyncResp = std::make_shared<AsyncResp>(res); 94 95 if (!json_util::readJson(req, asyncResp->res, "ResetType", resetType)) 96 { 97 return; 98 } 99 100 if (resetType != "GracefulRestart") 101 { 102 BMCWEB_LOG_DEBUG << "Invalid property value for ResetType: " 103 << resetType; 104 messages::actionParameterNotSupported(asyncResp->res, resetType, 105 "ResetType"); 106 107 return; 108 } 109 doBMCGracefulRestart(asyncResp); 110 } 111 }; 112 113 /** 114 * ManagerResetToDefaultsAction class supports POST method for factory reset 115 * action. 116 */ 117 class ManagerResetToDefaultsAction : public Node 118 { 119 public: 120 ManagerResetToDefaultsAction(CrowApp& app) : 121 Node(app, "/redfish/v1/Managers/bmc/Actions/Manager.ResetToDefaults/") 122 { 123 entityPrivileges = { 124 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 125 } 126 127 private: 128 /** 129 * Function handles ResetToDefaults POST method request. 130 * 131 * Analyzes POST body message and factory resets BMC by calling 132 * BMC code updater factory reset followed by a BMC reboot. 133 * 134 * BMC code updater factory reset wipes the whole BMC read-write 135 * filesystem which includes things like the network settings. 136 * 137 * OpenBMC only supports ResetToDefaultsType "ResetAll". 138 */ 139 void doPost(crow::Response& res, const crow::Request& req, 140 const std::vector<std::string>& params) override 141 { 142 BMCWEB_LOG_DEBUG << "Post ResetToDefaults."; 143 144 std::string resetType; 145 auto asyncResp = std::make_shared<AsyncResp>(res); 146 147 if (!json_util::readJson(req, asyncResp->res, "ResetToDefaultsType", 148 resetType)) 149 { 150 BMCWEB_LOG_DEBUG << "Missing property ResetToDefaultsType."; 151 152 messages::actionParameterMissing(asyncResp->res, "ResetToDefaults", 153 "ResetToDefaultsType"); 154 return; 155 } 156 157 if (resetType != "ResetAll") 158 { 159 BMCWEB_LOG_DEBUG << "Invalid property value for " 160 "ResetToDefaultsType: " 161 << resetType; 162 messages::actionParameterNotSupported(asyncResp->res, resetType, 163 "ResetToDefaultsType"); 164 return; 165 } 166 167 crow::connections::systemBus->async_method_call( 168 [asyncResp](const boost::system::error_code ec) { 169 if (ec) 170 { 171 BMCWEB_LOG_DEBUG << "Failed to ResetToDefaults: " << ec; 172 messages::internalError(asyncResp->res); 173 return; 174 } 175 // Factory Reset doesn't actually happen until a reboot 176 // Can't erase what the BMC is running on 177 doBMCGracefulRestart(asyncResp); 178 }, 179 "xyz.openbmc_project.Software.BMC.Updater", 180 "/xyz/openbmc_project/software", 181 "xyz.openbmc_project.Common.FactoryReset", "Reset"); 182 } 183 }; 184 185 static constexpr const char* objectManagerIface = 186 "org.freedesktop.DBus.ObjectManager"; 187 static constexpr const char* pidConfigurationIface = 188 "xyz.openbmc_project.Configuration.Pid"; 189 static constexpr const char* pidZoneConfigurationIface = 190 "xyz.openbmc_project.Configuration.Pid.Zone"; 191 static constexpr const char* stepwiseConfigurationIface = 192 "xyz.openbmc_project.Configuration.Stepwise"; 193 static constexpr const char* thermalModeIface = 194 "xyz.openbmc_project.Control.ThermalMode"; 195 196 static void asyncPopulatePid(const std::string& connection, 197 const std::string& path, 198 const std::string& currentProfile, 199 const std::vector<std::string>& supportedProfiles, 200 std::shared_ptr<AsyncResp> asyncResp) 201 { 202 203 crow::connections::systemBus->async_method_call( 204 [asyncResp, currentProfile, supportedProfiles]( 205 const boost::system::error_code ec, 206 const dbus::utility::ManagedObjectType& managedObj) { 207 if (ec) 208 { 209 BMCWEB_LOG_ERROR << ec; 210 asyncResp->res.jsonValue.clear(); 211 messages::internalError(asyncResp->res); 212 return; 213 } 214 nlohmann::json& configRoot = 215 asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"]; 216 nlohmann::json& fans = configRoot["FanControllers"]; 217 fans["@odata.type"] = "#OemManager.FanControllers"; 218 fans["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc/" 219 "Fan/FanControllers"; 220 221 nlohmann::json& pids = configRoot["PidControllers"]; 222 pids["@odata.type"] = "#OemManager.PidControllers"; 223 pids["@odata.id"] = 224 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/PidControllers"; 225 226 nlohmann::json& stepwise = configRoot["StepwiseControllers"]; 227 stepwise["@odata.type"] = "#OemManager.StepwiseControllers"; 228 stepwise["@odata.id"] = 229 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/StepwiseControllers"; 230 231 nlohmann::json& zones = configRoot["FanZones"]; 232 zones["@odata.id"] = 233 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones"; 234 zones["@odata.type"] = "#OemManager.FanZones"; 235 configRoot["@odata.id"] = 236 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan"; 237 configRoot["@odata.type"] = "#OemManager.Fan"; 238 configRoot["Profile@Redfish.AllowableValues"] = supportedProfiles; 239 240 if (!currentProfile.empty()) 241 { 242 configRoot["Profile"] = currentProfile; 243 } 244 BMCWEB_LOG_ERROR << "profile = " << currentProfile << " !"; 245 246 for (const auto& pathPair : managedObj) 247 { 248 for (const auto& intfPair : pathPair.second) 249 { 250 if (intfPair.first != pidConfigurationIface && 251 intfPair.first != pidZoneConfigurationIface && 252 intfPair.first != stepwiseConfigurationIface) 253 { 254 continue; 255 } 256 auto findName = intfPair.second.find("Name"); 257 if (findName == intfPair.second.end()) 258 { 259 BMCWEB_LOG_ERROR << "Pid Field missing Name"; 260 messages::internalError(asyncResp->res); 261 return; 262 } 263 264 const std::string* namePtr = 265 std::get_if<std::string>(&findName->second); 266 if (namePtr == nullptr) 267 { 268 BMCWEB_LOG_ERROR << "Pid Name Field illegal"; 269 messages::internalError(asyncResp->res); 270 return; 271 } 272 std::string name = *namePtr; 273 dbus::utility::escapePathForDbus(name); 274 275 auto findProfiles = intfPair.second.find("Profiles"); 276 if (findProfiles != intfPair.second.end()) 277 { 278 const std::vector<std::string>* profiles = 279 std::get_if<std::vector<std::string>>( 280 &findProfiles->second); 281 if (profiles == nullptr) 282 { 283 BMCWEB_LOG_ERROR << "Pid Profiles Field illegal"; 284 messages::internalError(asyncResp->res); 285 return; 286 } 287 if (std::find(profiles->begin(), profiles->end(), 288 currentProfile) == profiles->end()) 289 { 290 BMCWEB_LOG_INFO 291 << name << " not supported in current profile"; 292 continue; 293 } 294 } 295 nlohmann::json* config = nullptr; 296 297 const std::string* classPtr = nullptr; 298 auto findClass = intfPair.second.find("Class"); 299 if (findClass != intfPair.second.end()) 300 { 301 classPtr = std::get_if<std::string>(&findClass->second); 302 } 303 304 if (intfPair.first == pidZoneConfigurationIface) 305 { 306 std::string chassis; 307 if (!dbus::utility::getNthStringFromPath( 308 pathPair.first.str, 5, chassis)) 309 { 310 chassis = "#IllegalValue"; 311 } 312 nlohmann::json& zone = zones[name]; 313 zone["Chassis"] = { 314 {"@odata.id", "/redfish/v1/Chassis/" + chassis}}; 315 zone["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/" 316 "OpenBmc/Fan/FanZones/" + 317 name; 318 zone["@odata.type"] = "#OemManager.FanZone"; 319 config = &zone; 320 } 321 322 else if (intfPair.first == stepwiseConfigurationIface) 323 { 324 if (classPtr == nullptr) 325 { 326 BMCWEB_LOG_ERROR << "Pid Class Field illegal"; 327 messages::internalError(asyncResp->res); 328 return; 329 } 330 331 nlohmann::json& controller = stepwise[name]; 332 config = &controller; 333 334 controller["@odata.id"] = 335 "/redfish/v1/Managers/bmc#/Oem/" 336 "OpenBmc/Fan/StepwiseControllers/" + 337 name; 338 controller["@odata.type"] = 339 "#OemManager.StepwiseController"; 340 341 controller["Direction"] = *classPtr; 342 } 343 344 // pid and fans are off the same configuration 345 else if (intfPair.first == pidConfigurationIface) 346 { 347 348 if (classPtr == nullptr) 349 { 350 BMCWEB_LOG_ERROR << "Pid Class Field illegal"; 351 messages::internalError(asyncResp->res); 352 return; 353 } 354 bool isFan = *classPtr == "fan"; 355 nlohmann::json& element = 356 isFan ? fans[name] : pids[name]; 357 config = &element; 358 if (isFan) 359 { 360 element["@odata.id"] = 361 "/redfish/v1/Managers/bmc#/Oem/" 362 "OpenBmc/Fan/FanControllers/" + 363 name; 364 element["@odata.type"] = 365 "#OemManager.FanController"; 366 } 367 else 368 { 369 element["@odata.id"] = 370 "/redfish/v1/Managers/bmc#/Oem/" 371 "OpenBmc/Fan/PidControllers/" + 372 name; 373 element["@odata.type"] = 374 "#OemManager.PidController"; 375 } 376 } 377 else 378 { 379 BMCWEB_LOG_ERROR << "Unexpected configuration"; 380 messages::internalError(asyncResp->res); 381 return; 382 } 383 384 // used for making maps out of 2 vectors 385 const std::vector<double>* keys = nullptr; 386 const std::vector<double>* values = nullptr; 387 388 for (const auto& propertyPair : intfPair.second) 389 { 390 if (propertyPair.first == "Type" || 391 propertyPair.first == "Class" || 392 propertyPair.first == "Name") 393 { 394 continue; 395 } 396 397 // zones 398 if (intfPair.first == pidZoneConfigurationIface) 399 { 400 const double* ptr = 401 std::get_if<double>(&propertyPair.second); 402 if (ptr == nullptr) 403 { 404 BMCWEB_LOG_ERROR << "Field Illegal " 405 << propertyPair.first; 406 messages::internalError(asyncResp->res); 407 return; 408 } 409 (*config)[propertyPair.first] = *ptr; 410 } 411 412 if (intfPair.first == stepwiseConfigurationIface) 413 { 414 if (propertyPair.first == "Reading" || 415 propertyPair.first == "Output") 416 { 417 const std::vector<double>* ptr = 418 std::get_if<std::vector<double>>( 419 &propertyPair.second); 420 421 if (ptr == nullptr) 422 { 423 BMCWEB_LOG_ERROR << "Field Illegal " 424 << propertyPair.first; 425 messages::internalError(asyncResp->res); 426 return; 427 } 428 429 if (propertyPair.first == "Reading") 430 { 431 keys = ptr; 432 } 433 else 434 { 435 values = ptr; 436 } 437 if (keys && values) 438 { 439 if (keys->size() != values->size()) 440 { 441 BMCWEB_LOG_ERROR 442 << "Reading and Output size don't " 443 "match "; 444 messages::internalError(asyncResp->res); 445 return; 446 } 447 nlohmann::json& steps = (*config)["Steps"]; 448 steps = nlohmann::json::array(); 449 for (size_t ii = 0; ii < keys->size(); ii++) 450 { 451 steps.push_back( 452 {{"Target", (*keys)[ii]}, 453 {"Output", (*values)[ii]}}); 454 } 455 } 456 } 457 if (propertyPair.first == "NegativeHysteresis" || 458 propertyPair.first == "PositiveHysteresis") 459 { 460 const double* ptr = 461 std::get_if<double>(&propertyPair.second); 462 if (ptr == nullptr) 463 { 464 BMCWEB_LOG_ERROR << "Field Illegal " 465 << propertyPair.first; 466 messages::internalError(asyncResp->res); 467 return; 468 } 469 (*config)[propertyPair.first] = *ptr; 470 } 471 } 472 473 // pid and fans are off the same configuration 474 if (intfPair.first == pidConfigurationIface || 475 intfPair.first == stepwiseConfigurationIface) 476 { 477 478 if (propertyPair.first == "Zones") 479 { 480 const std::vector<std::string>* inputs = 481 std::get_if<std::vector<std::string>>( 482 &propertyPair.second); 483 484 if (inputs == nullptr) 485 { 486 BMCWEB_LOG_ERROR 487 << "Zones Pid Field Illegal"; 488 messages::internalError(asyncResp->res); 489 return; 490 } 491 auto& data = (*config)[propertyPair.first]; 492 data = nlohmann::json::array(); 493 for (std::string itemCopy : *inputs) 494 { 495 dbus::utility::escapePathForDbus(itemCopy); 496 data.push_back( 497 {{"@odata.id", 498 "/redfish/v1/Managers/bmc#/Oem/" 499 "OpenBmc/Fan/FanZones/" + 500 itemCopy}}); 501 } 502 } 503 // todo(james): may never happen, but this 504 // assumes configuration data referenced in the 505 // PID config is provided by the same daemon, we 506 // could add another loop to cover all cases, 507 // but I'm okay kicking this can down the road a 508 // bit 509 510 else if (propertyPair.first == "Inputs" || 511 propertyPair.first == "Outputs") 512 { 513 auto& data = (*config)[propertyPair.first]; 514 const std::vector<std::string>* inputs = 515 std::get_if<std::vector<std::string>>( 516 &propertyPair.second); 517 518 if (inputs == nullptr) 519 { 520 BMCWEB_LOG_ERROR << "Field Illegal " 521 << propertyPair.first; 522 messages::internalError(asyncResp->res); 523 return; 524 } 525 data = *inputs; 526 } 527 else if (propertyPair.first == "SetPointOffset") 528 { 529 const std::string* ptr = 530 std::get_if<std::string>( 531 &propertyPair.second); 532 533 if (ptr == nullptr) 534 { 535 BMCWEB_LOG_ERROR << "Field Illegal " 536 << propertyPair.first; 537 messages::internalError(asyncResp->res); 538 return; 539 } 540 // translate from dbus to redfish 541 if (*ptr == "WarningHigh") 542 { 543 (*config)["SetPointOffset"] = 544 "UpperThresholdNonCritical"; 545 } 546 else if (*ptr == "WarningLow") 547 { 548 (*config)["SetPointOffset"] = 549 "LowerThresholdNonCritical"; 550 } 551 else if (*ptr == "CriticalHigh") 552 { 553 (*config)["SetPointOffset"] = 554 "UpperThresholdCritical"; 555 } 556 else if (*ptr == "CriticalLow") 557 { 558 (*config)["SetPointOffset"] = 559 "LowerThresholdCritical"; 560 } 561 else 562 { 563 BMCWEB_LOG_ERROR << "Value Illegal " 564 << *ptr; 565 messages::internalError(asyncResp->res); 566 return; 567 } 568 } 569 // doubles 570 else if (propertyPair.first == 571 "FFGainCoefficient" || 572 propertyPair.first == "FFOffCoefficient" || 573 propertyPair.first == "ICoefficient" || 574 propertyPair.first == "ILimitMax" || 575 propertyPair.first == "ILimitMin" || 576 propertyPair.first == 577 "PositiveHysteresis" || 578 propertyPair.first == 579 "NegativeHysteresis" || 580 propertyPair.first == "OutLimitMax" || 581 propertyPair.first == "OutLimitMin" || 582 propertyPair.first == "PCoefficient" || 583 propertyPair.first == "SetPoint" || 584 propertyPair.first == "SlewNeg" || 585 propertyPair.first == "SlewPos") 586 { 587 const double* ptr = 588 std::get_if<double>(&propertyPair.second); 589 if (ptr == nullptr) 590 { 591 BMCWEB_LOG_ERROR << "Field Illegal " 592 << propertyPair.first; 593 messages::internalError(asyncResp->res); 594 return; 595 } 596 (*config)[propertyPair.first] = *ptr; 597 } 598 } 599 } 600 } 601 } 602 }, 603 connection, path, objectManagerIface, "GetManagedObjects"); 604 } 605 606 enum class CreatePIDRet 607 { 608 fail, 609 del, 610 patch 611 }; 612 613 static bool getZonesFromJsonReq(const std::shared_ptr<AsyncResp>& response, 614 std::vector<nlohmann::json>& config, 615 std::vector<std::string>& zones) 616 { 617 if (config.empty()) 618 { 619 BMCWEB_LOG_ERROR << "Empty Zones"; 620 messages::propertyValueFormatError(response->res, 621 nlohmann::json::array(), "Zones"); 622 return false; 623 } 624 for (auto& odata : config) 625 { 626 std::string path; 627 if (!redfish::json_util::readJson(odata, response->res, "@odata.id", 628 path)) 629 { 630 return false; 631 } 632 std::string input; 633 634 // 8 below comes from 635 // /redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones/Left 636 // 0 1 2 3 4 5 6 7 8 637 if (!dbus::utility::getNthStringFromPath(path, 8, input)) 638 { 639 BMCWEB_LOG_ERROR << "Got invalid path " << path; 640 BMCWEB_LOG_ERROR << "Illegal Type Zones"; 641 messages::propertyValueFormatError(response->res, odata.dump(), 642 "Zones"); 643 return false; 644 } 645 boost::replace_all(input, "_", " "); 646 zones.emplace_back(std::move(input)); 647 } 648 return true; 649 } 650 651 static const dbus::utility::ManagedItem* 652 findChassis(const dbus::utility::ManagedObjectType& managedObj, 653 const std::string& value, std::string& chassis) 654 { 655 BMCWEB_LOG_DEBUG << "Find Chassis: " << value << "\n"; 656 657 std::string escaped = boost::replace_all_copy(value, " ", "_"); 658 escaped = "/" + escaped; 659 auto it = std::find_if( 660 managedObj.begin(), managedObj.end(), [&escaped](const auto& obj) { 661 if (boost::algorithm::ends_with(obj.first.str, escaped)) 662 { 663 BMCWEB_LOG_DEBUG << "Matched " << obj.first.str << "\n"; 664 return true; 665 } 666 return false; 667 }); 668 669 if (it == managedObj.end()) 670 { 671 return nullptr; 672 } 673 // 5 comes from <chassis-name> being the 5th element 674 // /xyz/openbmc_project/inventory/system/chassis/<chassis-name> 675 if (dbus::utility::getNthStringFromPath(it->first.str, 5, chassis)) 676 { 677 return &(*it); 678 } 679 680 return nullptr; 681 } 682 683 static CreatePIDRet createPidInterface( 684 const std::shared_ptr<AsyncResp>& response, const std::string& type, 685 nlohmann::json::iterator it, const std::string& path, 686 const dbus::utility::ManagedObjectType& managedObj, bool createNewObject, 687 boost::container::flat_map<std::string, dbus::utility::DbusVariantType>& 688 output, 689 std::string& chassis, const std::string& profile) 690 { 691 692 // common deleter 693 if (it.value() == nullptr) 694 { 695 std::string iface; 696 if (type == "PidControllers" || type == "FanControllers") 697 { 698 iface = pidConfigurationIface; 699 } 700 else if (type == "FanZones") 701 { 702 iface = pidZoneConfigurationIface; 703 } 704 else if (type == "StepwiseControllers") 705 { 706 iface = stepwiseConfigurationIface; 707 } 708 else 709 { 710 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type " 711 << type; 712 messages::propertyUnknown(response->res, type); 713 return CreatePIDRet::fail; 714 } 715 716 BMCWEB_LOG_DEBUG << "del " << path << " " << iface << "\n"; 717 // delete interface 718 crow::connections::systemBus->async_method_call( 719 [response, path](const boost::system::error_code ec) { 720 if (ec) 721 { 722 BMCWEB_LOG_ERROR << "Error patching " << path << ": " << ec; 723 messages::internalError(response->res); 724 return; 725 } 726 messages::success(response->res); 727 }, 728 "xyz.openbmc_project.EntityManager", path, iface, "Delete"); 729 return CreatePIDRet::del; 730 } 731 732 const dbus::utility::ManagedItem* managedItem = nullptr; 733 if (!createNewObject) 734 { 735 // if we aren't creating a new object, we should be able to find it on 736 // d-bus 737 managedItem = findChassis(managedObj, it.key(), chassis); 738 if (managedItem == nullptr) 739 { 740 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch"; 741 messages::invalidObject(response->res, it.key()); 742 return CreatePIDRet::fail; 743 } 744 } 745 746 if (profile.size() && 747 (type == "PidControllers" || type == "FanControllers" || 748 type == "StepwiseControllers")) 749 { 750 if (managedItem == nullptr) 751 { 752 output["Profiles"] = std::vector<std::string>{profile}; 753 } 754 else 755 { 756 std::string interface; 757 if (type == "StepwiseControllers") 758 { 759 interface = stepwiseConfigurationIface; 760 } 761 else 762 { 763 interface = pidConfigurationIface; 764 } 765 auto findConfig = managedItem->second.find(interface); 766 if (findConfig == managedItem->second.end()) 767 { 768 BMCWEB_LOG_ERROR 769 << "Failed to find interface in managed object"; 770 messages::internalError(response->res); 771 return CreatePIDRet::fail; 772 } 773 auto findProfiles = findConfig->second.find("Profiles"); 774 if (findProfiles != findConfig->second.end()) 775 { 776 const std::vector<std::string>* curProfiles = 777 std::get_if<std::vector<std::string>>( 778 &(findProfiles->second)); 779 if (curProfiles == nullptr) 780 { 781 BMCWEB_LOG_ERROR << "Illegal profiles in managed object"; 782 messages::internalError(response->res); 783 return CreatePIDRet::fail; 784 } 785 if (std::find(curProfiles->begin(), curProfiles->end(), 786 profile) == curProfiles->end()) 787 { 788 std::vector<std::string> newProfiles = *curProfiles; 789 newProfiles.push_back(profile); 790 output["Profiles"] = newProfiles; 791 } 792 } 793 } 794 } 795 796 if (type == "PidControllers" || type == "FanControllers") 797 { 798 if (createNewObject) 799 { 800 output["Class"] = type == "PidControllers" ? std::string("temp") 801 : std::string("fan"); 802 output["Type"] = std::string("Pid"); 803 } 804 805 std::optional<std::vector<nlohmann::json>> zones; 806 std::optional<std::vector<std::string>> inputs; 807 std::optional<std::vector<std::string>> outputs; 808 std::map<std::string, std::optional<double>> doubles; 809 std::optional<std::string> setpointOffset; 810 if (!redfish::json_util::readJson( 811 it.value(), response->res, "Inputs", inputs, "Outputs", outputs, 812 "Zones", zones, "FFGainCoefficient", 813 doubles["FFGainCoefficient"], "FFOffCoefficient", 814 doubles["FFOffCoefficient"], "ICoefficient", 815 doubles["ICoefficient"], "ILimitMax", doubles["ILimitMax"], 816 "ILimitMin", doubles["ILimitMin"], "OutLimitMax", 817 doubles["OutLimitMax"], "OutLimitMin", doubles["OutLimitMin"], 818 "PCoefficient", doubles["PCoefficient"], "SetPoint", 819 doubles["SetPoint"], "SetPointOffset", setpointOffset, 820 "SlewNeg", doubles["SlewNeg"], "SlewPos", doubles["SlewPos"], 821 "PositiveHysteresis", doubles["PositiveHysteresis"], 822 "NegativeHysteresis", doubles["NegativeHysteresis"])) 823 { 824 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 825 << it.value().dump(); 826 return CreatePIDRet::fail; 827 } 828 if (zones) 829 { 830 std::vector<std::string> zonesStr; 831 if (!getZonesFromJsonReq(response, *zones, zonesStr)) 832 { 833 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones"; 834 return CreatePIDRet::fail; 835 } 836 if (chassis.empty() && 837 !findChassis(managedObj, zonesStr[0], chassis)) 838 { 839 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch"; 840 messages::invalidObject(response->res, it.key()); 841 return CreatePIDRet::fail; 842 } 843 844 output["Zones"] = std::move(zonesStr); 845 } 846 if (inputs || outputs) 847 { 848 std::array<std::optional<std::vector<std::string>>*, 2> containers = 849 {&inputs, &outputs}; 850 size_t index = 0; 851 for (const auto& containerPtr : containers) 852 { 853 std::optional<std::vector<std::string>>& container = 854 *containerPtr; 855 if (!container) 856 { 857 index++; 858 continue; 859 } 860 861 for (std::string& value : *container) 862 { 863 boost::replace_all(value, "_", " "); 864 } 865 std::string key; 866 if (index == 0) 867 { 868 key = "Inputs"; 869 } 870 else 871 { 872 key = "Outputs"; 873 } 874 output[key] = *container; 875 index++; 876 } 877 } 878 879 if (setpointOffset) 880 { 881 // translate between redfish and dbus names 882 if (*setpointOffset == "UpperThresholdNonCritical") 883 { 884 output["SetPointOffset"] = std::string("WarningLow"); 885 } 886 else if (*setpointOffset == "LowerThresholdNonCritical") 887 { 888 output["SetPointOffset"] = std::string("WarningHigh"); 889 } 890 else if (*setpointOffset == "LowerThresholdCritical") 891 { 892 output["SetPointOffset"] = std::string("CriticalLow"); 893 } 894 else if (*setpointOffset == "UpperThresholdCritical") 895 { 896 output["SetPointOffset"] = std::string("CriticalHigh"); 897 } 898 else 899 { 900 BMCWEB_LOG_ERROR << "Invalid setpointoffset " 901 << *setpointOffset; 902 messages::invalidObject(response->res, it.key()); 903 return CreatePIDRet::fail; 904 } 905 } 906 907 // doubles 908 for (const auto& pairs : doubles) 909 { 910 if (!pairs.second) 911 { 912 continue; 913 } 914 BMCWEB_LOG_DEBUG << pairs.first << " = " << *pairs.second; 915 output[pairs.first] = *(pairs.second); 916 } 917 } 918 919 else if (type == "FanZones") 920 { 921 output["Type"] = std::string("Pid.Zone"); 922 923 std::optional<nlohmann::json> chassisContainer; 924 std::optional<double> failSafePercent; 925 std::optional<double> minThermalOutput; 926 if (!redfish::json_util::readJson(it.value(), response->res, "Chassis", 927 chassisContainer, "FailSafePercent", 928 failSafePercent, "MinThermalOutput", 929 minThermalOutput)) 930 { 931 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 932 << it.value().dump(); 933 return CreatePIDRet::fail; 934 } 935 936 if (chassisContainer) 937 { 938 939 std::string chassisId; 940 if (!redfish::json_util::readJson(*chassisContainer, response->res, 941 "@odata.id", chassisId)) 942 { 943 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 944 << chassisContainer->dump(); 945 return CreatePIDRet::fail; 946 } 947 948 // /redfish/v1/chassis/chassis_name/ 949 if (!dbus::utility::getNthStringFromPath(chassisId, 3, chassis)) 950 { 951 BMCWEB_LOG_ERROR << "Got invalid path " << chassisId; 952 messages::invalidObject(response->res, chassisId); 953 return CreatePIDRet::fail; 954 } 955 } 956 if (minThermalOutput) 957 { 958 output["MinThermalOutput"] = *minThermalOutput; 959 } 960 if (failSafePercent) 961 { 962 output["FailSafePercent"] = *failSafePercent; 963 } 964 } 965 else if (type == "StepwiseControllers") 966 { 967 output["Type"] = std::string("Stepwise"); 968 969 std::optional<std::vector<nlohmann::json>> zones; 970 std::optional<std::vector<nlohmann::json>> steps; 971 std::optional<std::vector<std::string>> inputs; 972 std::optional<double> positiveHysteresis; 973 std::optional<double> negativeHysteresis; 974 std::optional<std::string> direction; // upper clipping curve vs lower 975 if (!redfish::json_util::readJson( 976 it.value(), response->res, "Zones", zones, "Steps", steps, 977 "Inputs", inputs, "PositiveHysteresis", positiveHysteresis, 978 "NegativeHysteresis", negativeHysteresis, "Direction", 979 direction)) 980 { 981 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 982 << it.value().dump(); 983 return CreatePIDRet::fail; 984 } 985 986 if (zones) 987 { 988 std::vector<std::string> zonesStrs; 989 if (!getZonesFromJsonReq(response, *zones, zonesStrs)) 990 { 991 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones"; 992 return CreatePIDRet::fail; 993 } 994 if (chassis.empty() && 995 !findChassis(managedObj, zonesStrs[0], chassis)) 996 { 997 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch"; 998 messages::invalidObject(response->res, it.key()); 999 return CreatePIDRet::fail; 1000 } 1001 output["Zones"] = std::move(zonesStrs); 1002 } 1003 if (steps) 1004 { 1005 std::vector<double> readings; 1006 std::vector<double> outputs; 1007 for (auto& step : *steps) 1008 { 1009 double target; 1010 double output; 1011 1012 if (!redfish::json_util::readJson(step, response->res, "Target", 1013 target, "Output", output)) 1014 { 1015 BMCWEB_LOG_ERROR << "Line:" << __LINE__ 1016 << ", Illegal Property " 1017 << it.value().dump(); 1018 return CreatePIDRet::fail; 1019 } 1020 readings.emplace_back(target); 1021 outputs.emplace_back(output); 1022 } 1023 output["Reading"] = std::move(readings); 1024 output["Output"] = std::move(outputs); 1025 } 1026 if (inputs) 1027 { 1028 for (std::string& value : *inputs) 1029 { 1030 boost::replace_all(value, "_", " "); 1031 } 1032 output["Inputs"] = std::move(*inputs); 1033 } 1034 if (negativeHysteresis) 1035 { 1036 output["NegativeHysteresis"] = *negativeHysteresis; 1037 } 1038 if (positiveHysteresis) 1039 { 1040 output["PositiveHysteresis"] = *positiveHysteresis; 1041 } 1042 if (direction) 1043 { 1044 constexpr const std::array<const char*, 2> allowedDirections = { 1045 "Ceiling", "Floor"}; 1046 if (std::find(allowedDirections.begin(), allowedDirections.end(), 1047 *direction) == allowedDirections.end()) 1048 { 1049 messages::propertyValueTypeError(response->res, "Direction", 1050 *direction); 1051 return CreatePIDRet::fail; 1052 } 1053 output["Class"] = *direction; 1054 } 1055 } 1056 else 1057 { 1058 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type " << type; 1059 messages::propertyUnknown(response->res, type); 1060 return CreatePIDRet::fail; 1061 } 1062 return CreatePIDRet::patch; 1063 } 1064 struct GetPIDValues : std::enable_shared_from_this<GetPIDValues> 1065 { 1066 1067 GetPIDValues(const std::shared_ptr<AsyncResp>& asyncResp) : 1068 asyncResp(asyncResp) 1069 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