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