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 // delete interface 670 crow::connections::systemBus->async_method_call( 671 [response, path](const boost::system::error_code ec) { 672 if (ec) 673 { 674 BMCWEB_LOG_ERROR << "Error patching " << path << ": " << ec; 675 messages::internalError(response->res); 676 return; 677 } 678 messages::success(response->res); 679 }, 680 "xyz.openbmc_project.EntityManager", path, iface, "Delete"); 681 return CreatePIDRet::del; 682 } 683 684 const dbus::utility::ManagedItem* managedItem = nullptr; 685 if (!createNewObject) 686 { 687 // if we aren't creating a new object, we should be able to find it on 688 // d-bus 689 managedItem = findChassis(managedObj, it.key(), chassis); 690 if (managedItem == nullptr) 691 { 692 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch"; 693 messages::invalidObject(response->res, it.key()); 694 return CreatePIDRet::fail; 695 } 696 } 697 698 if (profile.size() && 699 (type == "PidControllers" || type == "FanControllers" || 700 type == "StepwiseControllers")) 701 { 702 if (managedItem == nullptr) 703 { 704 output["Profiles"] = std::vector<std::string>{profile}; 705 } 706 else 707 { 708 std::string interface; 709 if (type == "StepwiseControllers") 710 { 711 interface = stepwiseConfigurationIface; 712 } 713 else 714 { 715 interface = pidConfigurationIface; 716 } 717 auto findConfig = managedItem->second.find(interface); 718 if (findConfig == managedItem->second.end()) 719 { 720 BMCWEB_LOG_ERROR 721 << "Failed to find interface in managed object"; 722 messages::internalError(response->res); 723 return CreatePIDRet::fail; 724 } 725 auto findProfiles = findConfig->second.find("Profiles"); 726 if (findProfiles != findConfig->second.end()) 727 { 728 const std::vector<std::string>* curProfiles = 729 std::get_if<std::vector<std::string>>( 730 &(findProfiles->second)); 731 if (curProfiles == nullptr) 732 { 733 BMCWEB_LOG_ERROR << "Illegal profiles in managed object"; 734 messages::internalError(response->res); 735 return CreatePIDRet::fail; 736 } 737 if (std::find(curProfiles->begin(), curProfiles->end(), 738 profile) == curProfiles->end()) 739 { 740 std::vector<std::string> newProfiles = *curProfiles; 741 newProfiles.push_back(profile); 742 output["Profiles"] = newProfiles; 743 } 744 } 745 } 746 } 747 748 if (type == "PidControllers" || type == "FanControllers") 749 { 750 if (createNewObject) 751 { 752 output["Class"] = type == "PidControllers" ? std::string("temp") 753 : std::string("fan"); 754 output["Type"] = std::string("Pid"); 755 } 756 757 std::optional<std::vector<nlohmann::json>> zones; 758 std::optional<std::vector<std::string>> inputs; 759 std::optional<std::vector<std::string>> outputs; 760 std::map<std::string, std::optional<double>> doubles; 761 std::optional<std::string> setpointOffset; 762 if (!redfish::json_util::readJson( 763 it.value(), response->res, "Inputs", inputs, "Outputs", outputs, 764 "Zones", zones, "FFGainCoefficient", 765 doubles["FFGainCoefficient"], "FFOffCoefficient", 766 doubles["FFOffCoefficient"], "ICoefficient", 767 doubles["ICoefficient"], "ILimitMax", doubles["ILimitMax"], 768 "ILimitMin", doubles["ILimitMin"], "OutLimitMax", 769 doubles["OutLimitMax"], "OutLimitMin", doubles["OutLimitMin"], 770 "PCoefficient", doubles["PCoefficient"], "SetPoint", 771 doubles["SetPoint"], "SetPointOffset", setpointOffset, 772 "SlewNeg", doubles["SlewNeg"], "SlewPos", doubles["SlewPos"], 773 "PositiveHysteresis", doubles["PositiveHysteresis"], 774 "NegativeHysteresis", doubles["NegativeHysteresis"])) 775 { 776 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 777 << it.value().dump(); 778 return CreatePIDRet::fail; 779 } 780 if (zones) 781 { 782 std::vector<std::string> zonesStr; 783 if (!getZonesFromJsonReq(response, *zones, zonesStr)) 784 { 785 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones"; 786 return CreatePIDRet::fail; 787 } 788 if (chassis.empty() && 789 !findChassis(managedObj, zonesStr[0], chassis)) 790 { 791 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch"; 792 messages::invalidObject(response->res, it.key()); 793 return CreatePIDRet::fail; 794 } 795 796 output["Zones"] = std::move(zonesStr); 797 } 798 if (inputs || outputs) 799 { 800 std::array<std::optional<std::vector<std::string>>*, 2> containers = 801 {&inputs, &outputs}; 802 size_t index = 0; 803 for (const auto& containerPtr : containers) 804 { 805 std::optional<std::vector<std::string>>& container = 806 *containerPtr; 807 if (!container) 808 { 809 index++; 810 continue; 811 } 812 813 for (std::string& value : *container) 814 { 815 boost::replace_all(value, "_", " "); 816 } 817 std::string key; 818 if (index == 0) 819 { 820 key = "Inputs"; 821 } 822 else 823 { 824 key = "Outputs"; 825 } 826 output[key] = *container; 827 index++; 828 } 829 } 830 831 if (setpointOffset) 832 { 833 // translate between redfish and dbus names 834 if (*setpointOffset == "UpperThresholdNonCritical") 835 { 836 output["SetPointOffset"] = std::string("WarningLow"); 837 } 838 else if (*setpointOffset == "LowerThresholdNonCritical") 839 { 840 output["SetPointOffset"] = std::string("WarningHigh"); 841 } 842 else if (*setpointOffset == "LowerThresholdCritical") 843 { 844 output["SetPointOffset"] = std::string("CriticalLow"); 845 } 846 else if (*setpointOffset == "UpperThresholdCritical") 847 { 848 output["SetPointOffset"] = std::string("CriticalHigh"); 849 } 850 else 851 { 852 BMCWEB_LOG_ERROR << "Invalid setpointoffset " 853 << *setpointOffset; 854 messages::invalidObject(response->res, it.key()); 855 return CreatePIDRet::fail; 856 } 857 } 858 859 // doubles 860 for (const auto& pairs : doubles) 861 { 862 if (!pairs.second) 863 { 864 continue; 865 } 866 BMCWEB_LOG_DEBUG << pairs.first << " = " << *pairs.second; 867 output[pairs.first] = *(pairs.second); 868 } 869 } 870 871 else if (type == "FanZones") 872 { 873 output["Type"] = std::string("Pid.Zone"); 874 875 std::optional<nlohmann::json> chassisContainer; 876 std::optional<double> failSafePercent; 877 std::optional<double> minThermalOutput; 878 if (!redfish::json_util::readJson(it.value(), response->res, "Chassis", 879 chassisContainer, "FailSafePercent", 880 failSafePercent, "MinThermalOutput", 881 minThermalOutput)) 882 { 883 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 884 << it.value().dump(); 885 return CreatePIDRet::fail; 886 } 887 888 if (chassisContainer) 889 { 890 891 std::string chassisId; 892 if (!redfish::json_util::readJson(*chassisContainer, response->res, 893 "@odata.id", chassisId)) 894 { 895 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 896 << chassisContainer->dump(); 897 return CreatePIDRet::fail; 898 } 899 900 // /refish/v1/chassis/chassis_name/ 901 if (!dbus::utility::getNthStringFromPath(chassisId, 3, chassis)) 902 { 903 BMCWEB_LOG_ERROR << "Got invalid path " << chassisId; 904 messages::invalidObject(response->res, chassisId); 905 return CreatePIDRet::fail; 906 } 907 } 908 if (minThermalOutput) 909 { 910 output["MinThermalOutput"] = *minThermalOutput; 911 } 912 if (failSafePercent) 913 { 914 output["FailSafePercent"] = *failSafePercent; 915 } 916 } 917 else if (type == "StepwiseControllers") 918 { 919 output["Type"] = std::string("Stepwise"); 920 921 std::optional<std::vector<nlohmann::json>> zones; 922 std::optional<std::vector<nlohmann::json>> steps; 923 std::optional<std::vector<std::string>> inputs; 924 std::optional<double> positiveHysteresis; 925 std::optional<double> negativeHysteresis; 926 std::optional<std::string> direction; // upper clipping curve vs lower 927 if (!redfish::json_util::readJson( 928 it.value(), response->res, "Zones", zones, "Steps", steps, 929 "Inputs", inputs, "PositiveHysteresis", positiveHysteresis, 930 "NegativeHysteresis", negativeHysteresis, "Direction", 931 direction)) 932 { 933 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 934 << it.value().dump(); 935 return CreatePIDRet::fail; 936 } 937 938 if (zones) 939 { 940 std::vector<std::string> zonesStrs; 941 if (!getZonesFromJsonReq(response, *zones, zonesStrs)) 942 { 943 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones"; 944 return CreatePIDRet::fail; 945 } 946 if (chassis.empty() && 947 !findChassis(managedObj, zonesStrs[0], chassis)) 948 { 949 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch"; 950 messages::invalidObject(response->res, it.key()); 951 return CreatePIDRet::fail; 952 } 953 output["Zones"] = std::move(zonesStrs); 954 } 955 if (steps) 956 { 957 std::vector<double> readings; 958 std::vector<double> outputs; 959 for (auto& step : *steps) 960 { 961 double target; 962 double output; 963 964 if (!redfish::json_util::readJson(step, response->res, "Target", 965 target, "Output", output)) 966 { 967 BMCWEB_LOG_ERROR << "Line:" << __LINE__ 968 << ", Illegal Property " 969 << it.value().dump(); 970 return CreatePIDRet::fail; 971 } 972 readings.emplace_back(target); 973 outputs.emplace_back(output); 974 } 975 output["Reading"] = std::move(readings); 976 output["Output"] = std::move(outputs); 977 } 978 if (inputs) 979 { 980 for (std::string& value : *inputs) 981 { 982 boost::replace_all(value, "_", " "); 983 } 984 output["Inputs"] = std::move(*inputs); 985 } 986 if (negativeHysteresis) 987 { 988 output["NegativeHysteresis"] = *negativeHysteresis; 989 } 990 if (positiveHysteresis) 991 { 992 output["PositiveHysteresis"] = *positiveHysteresis; 993 } 994 if (direction) 995 { 996 constexpr const std::array<const char*, 2> allowedDirections = { 997 "Ceiling", "Floor"}; 998 if (std::find(allowedDirections.begin(), allowedDirections.end(), 999 *direction) == allowedDirections.end()) 1000 { 1001 messages::propertyValueTypeError(response->res, "Direction", 1002 *direction); 1003 return CreatePIDRet::fail; 1004 } 1005 output["Class"] = *direction; 1006 } 1007 } 1008 else 1009 { 1010 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type " << type; 1011 messages::propertyUnknown(response->res, type); 1012 return CreatePIDRet::fail; 1013 } 1014 return CreatePIDRet::patch; 1015 } 1016 struct GetPIDValues : std::enable_shared_from_this<GetPIDValues> 1017 { 1018 1019 GetPIDValues(const std::shared_ptr<AsyncResp>& asyncResp) : 1020 asyncResp(asyncResp) 1021 1022 { 1023 } 1024 1025 void run() 1026 { 1027 std::shared_ptr<GetPIDValues> self = shared_from_this(); 1028 1029 // get all configurations 1030 crow::connections::systemBus->async_method_call( 1031 [self](const boost::system::error_code ec, 1032 const crow::openbmc_mapper::GetSubTreeType& subtree) { 1033 if (ec) 1034 { 1035 BMCWEB_LOG_ERROR << ec; 1036 messages::internalError(self->asyncResp->res); 1037 return; 1038 } 1039 self->subtree = subtree; 1040 }, 1041 "xyz.openbmc_project.ObjectMapper", 1042 "/xyz/openbmc_project/object_mapper", 1043 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0, 1044 std::array<const char*, 4>{ 1045 pidConfigurationIface, pidZoneConfigurationIface, 1046 objectManagerIface, stepwiseConfigurationIface}); 1047 1048 // at the same time get the selected profile 1049 crow::connections::systemBus->async_method_call( 1050 [self](const boost::system::error_code ec, 1051 const crow::openbmc_mapper::GetSubTreeType& subtree) { 1052 if (ec || subtree.empty()) 1053 { 1054 return; 1055 } 1056 if (subtree[0].second.size() != 1) 1057 { 1058 // invalid mapper response, should never happen 1059 BMCWEB_LOG_ERROR << "GetPIDValues: Mapper Error"; 1060 messages::internalError(self->asyncResp->res); 1061 return; 1062 } 1063 1064 const std::string& path = subtree[0].first; 1065 const std::string& owner = subtree[0].second[0].first; 1066 crow::connections::systemBus->async_method_call( 1067 [path, owner, self]( 1068 const boost::system::error_code ec, 1069 const boost::container::flat_map< 1070 std::string, std::variant<std::vector<std::string>, 1071 std::string>>& resp) { 1072 if (ec) 1073 { 1074 BMCWEB_LOG_ERROR << "GetPIDValues: Can't get " 1075 "thermalModeIface " 1076 << path; 1077 messages::internalError(self->asyncResp->res); 1078 return; 1079 } 1080 const std::string* current = nullptr; 1081 const std::vector<std::string>* supported = nullptr; 1082 for (auto& [key, value] : resp) 1083 { 1084 if (key == "Current") 1085 { 1086 current = std::get_if<std::string>(&value); 1087 if (current == nullptr) 1088 { 1089 BMCWEB_LOG_ERROR 1090 << "GetPIDValues: thermal mode " 1091 "iface invalid " 1092 << path; 1093 messages::internalError( 1094 self->asyncResp->res); 1095 return; 1096 } 1097 } 1098 if (key == "Supported") 1099 { 1100 supported = 1101 std::get_if<std::vector<std::string>>( 1102 &value); 1103 if (supported == nullptr) 1104 { 1105 BMCWEB_LOG_ERROR 1106 << "GetPIDValues: thermal mode " 1107 "iface invalid" 1108 << path; 1109 messages::internalError( 1110 self->asyncResp->res); 1111 return; 1112 } 1113 } 1114 } 1115 if (current == nullptr || supported == nullptr) 1116 { 1117 BMCWEB_LOG_ERROR << "GetPIDValues: thermal mode " 1118 "iface invalid " 1119 << path; 1120 messages::internalError(self->asyncResp->res); 1121 return; 1122 } 1123 self->currentProfile = *current; 1124 self->supportedProfiles = *supported; 1125 }, 1126 owner, path, "org.freedesktop.DBus.Properties", "GetAll", 1127 thermalModeIface); 1128 }, 1129 "xyz.openbmc_project.ObjectMapper", 1130 "/xyz/openbmc_project/object_mapper", 1131 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0, 1132 std::array<const char*, 1>{thermalModeIface}); 1133 } 1134 1135 ~GetPIDValues() 1136 { 1137 if (asyncResp->res.result() != boost::beast::http::status::ok) 1138 { 1139 return; 1140 } 1141 // create map of <connection, path to objMgr>> 1142 boost::container::flat_map<std::string, std::string> objectMgrPaths; 1143 boost::container::flat_set<std::string> calledConnections; 1144 for (const auto& pathGroup : subtree) 1145 { 1146 for (const auto& connectionGroup : pathGroup.second) 1147 { 1148 auto findConnection = 1149 calledConnections.find(connectionGroup.first); 1150 if (findConnection != calledConnections.end()) 1151 { 1152 break; 1153 } 1154 for (const std::string& interface : connectionGroup.second) 1155 { 1156 if (interface == objectManagerIface) 1157 { 1158 objectMgrPaths[connectionGroup.first] = pathGroup.first; 1159 } 1160 // this list is alphabetical, so we 1161 // should have found the objMgr by now 1162 if (interface == pidConfigurationIface || 1163 interface == pidZoneConfigurationIface || 1164 interface == stepwiseConfigurationIface) 1165 { 1166 auto findObjMgr = 1167 objectMgrPaths.find(connectionGroup.first); 1168 if (findObjMgr == objectMgrPaths.end()) 1169 { 1170 BMCWEB_LOG_DEBUG << connectionGroup.first 1171 << "Has no Object Manager"; 1172 continue; 1173 } 1174 1175 calledConnections.insert(connectionGroup.first); 1176 1177 asyncPopulatePid(findObjMgr->first, findObjMgr->second, 1178 currentProfile, supportedProfiles, 1179 asyncResp); 1180 break; 1181 } 1182 } 1183 } 1184 } 1185 } 1186 1187 std::vector<std::string> supportedProfiles; 1188 std::string currentProfile; 1189 crow::openbmc_mapper::GetSubTreeType subtree; 1190 std::shared_ptr<AsyncResp> asyncResp; 1191 }; 1192 1193 struct SetPIDValues : std::enable_shared_from_this<SetPIDValues> 1194 { 1195 1196 SetPIDValues(const std::shared_ptr<AsyncResp>& asyncRespIn, 1197 nlohmann::json& data) : 1198 asyncResp(asyncRespIn) 1199 { 1200 1201 std::optional<nlohmann::json> pidControllers; 1202 std::optional<nlohmann::json> fanControllers; 1203 std::optional<nlohmann::json> fanZones; 1204 std::optional<nlohmann::json> stepwiseControllers; 1205 1206 if (!redfish::json_util::readJson( 1207 data, asyncResp->res, "PidControllers", pidControllers, 1208 "FanControllers", fanControllers, "FanZones", fanZones, 1209 "StepwiseControllers", stepwiseControllers, "Profile", profile)) 1210 { 1211 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 1212 << data.dump(); 1213 return; 1214 } 1215 configuration.emplace_back("PidControllers", std::move(pidControllers)); 1216 configuration.emplace_back("FanControllers", std::move(fanControllers)); 1217 configuration.emplace_back("FanZones", std::move(fanZones)); 1218 configuration.emplace_back("StepwiseControllers", 1219 std::move(stepwiseControllers)); 1220 } 1221 void run() 1222 { 1223 if (asyncResp->res.result() != boost::beast::http::status::ok) 1224 { 1225 return; 1226 } 1227 1228 std::shared_ptr<SetPIDValues> self = shared_from_this(); 1229 1230 // todo(james): might make sense to do a mapper call here if this 1231 // interface gets more traction 1232 crow::connections::systemBus->async_method_call( 1233 [self](const boost::system::error_code ec, 1234 dbus::utility::ManagedObjectType& mObj) { 1235 if (ec) 1236 { 1237 BMCWEB_LOG_ERROR << "Error communicating to Entity Manager"; 1238 messages::internalError(self->asyncResp->res); 1239 return; 1240 } 1241 self->managedObj = std::move(mObj); 1242 }, 1243 "xyz.openbmc_project.EntityManager", "/", objectManagerIface, 1244 "GetManagedObjects"); 1245 1246 // at the same time get the profile information 1247 crow::connections::systemBus->async_method_call( 1248 [self](const boost::system::error_code ec, 1249 const crow::openbmc_mapper::GetSubTreeType& subtree) { 1250 if (ec || subtree.empty()) 1251 { 1252 return; 1253 } 1254 if (subtree[0].second.empty()) 1255 { 1256 // invalid mapper response, should never happen 1257 BMCWEB_LOG_ERROR << "SetPIDValues: Mapper Error"; 1258 messages::internalError(self->asyncResp->res); 1259 return; 1260 } 1261 1262 const std::string& path = subtree[0].first; 1263 const std::string& owner = subtree[0].second[0].first; 1264 crow::connections::systemBus->async_method_call( 1265 [self, path, owner]( 1266 const boost::system::error_code ec, 1267 const boost::container::flat_map< 1268 std::string, std::variant<std::vector<std::string>, 1269 std::string>>& r) { 1270 if (ec) 1271 { 1272 BMCWEB_LOG_ERROR << "SetPIDValues: Can't get " 1273 "thermalModeIface " 1274 << path; 1275 messages::internalError(self->asyncResp->res); 1276 return; 1277 } 1278 const std::string* current = nullptr; 1279 const std::vector<std::string>* supported = nullptr; 1280 for (auto& [key, value] : r) 1281 { 1282 if (key == "Current") 1283 { 1284 current = std::get_if<std::string>(&value); 1285 if (current == nullptr) 1286 { 1287 BMCWEB_LOG_ERROR 1288 << "SetPIDValues: thermal mode " 1289 "iface invalid " 1290 << path; 1291 messages::internalError( 1292 self->asyncResp->res); 1293 return; 1294 } 1295 } 1296 if (key == "Supported") 1297 { 1298 supported = 1299 std::get_if<std::vector<std::string>>( 1300 &value); 1301 if (supported == nullptr) 1302 { 1303 BMCWEB_LOG_ERROR 1304 << "SetPIDValues: thermal mode " 1305 "iface invalid" 1306 << path; 1307 messages::internalError( 1308 self->asyncResp->res); 1309 return; 1310 } 1311 } 1312 } 1313 if (current == nullptr || supported == nullptr) 1314 { 1315 BMCWEB_LOG_ERROR << "SetPIDValues: thermal mode " 1316 "iface invalid " 1317 << path; 1318 messages::internalError(self->asyncResp->res); 1319 return; 1320 } 1321 self->currentProfile = *current; 1322 self->supportedProfiles = *supported; 1323 self->profileConnection = owner; 1324 self->profilePath = path; 1325 }, 1326 owner, path, "org.freedesktop.DBus.Properties", "GetAll", 1327 thermalModeIface); 1328 }, 1329 "xyz.openbmc_project.ObjectMapper", 1330 "/xyz/openbmc_project/object_mapper", 1331 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0, 1332 std::array<const char*, 1>{thermalModeIface}); 1333 } 1334 ~SetPIDValues() 1335 { 1336 if (asyncResp->res.result() != boost::beast::http::status::ok) 1337 { 1338 return; 1339 } 1340 1341 std::shared_ptr<AsyncResp> response = asyncResp; 1342 1343 if (profile) 1344 { 1345 if (std::find(supportedProfiles.begin(), supportedProfiles.end(), 1346 *profile) == supportedProfiles.end()) 1347 { 1348 messages::actionParameterUnknown(response->res, "Profile", 1349 *profile); 1350 return; 1351 } 1352 currentProfile = *profile; 1353 crow::connections::systemBus->async_method_call( 1354 [response](const boost::system::error_code ec) { 1355 if (ec) 1356 { 1357 BMCWEB_LOG_ERROR << "Error patching profile" << ec; 1358 messages::internalError(response->res); 1359 } 1360 }, 1361 profileConnection, profilePath, 1362 "org.freedesktop.DBus.Properties", "Set", thermalModeIface, 1363 "Current", std::variant<std::string>(*profile)); 1364 } 1365 1366 for (auto& containerPair : configuration) 1367 { 1368 auto& container = containerPair.second; 1369 if (!container) 1370 { 1371 continue; 1372 } 1373 std::string& type = containerPair.first; 1374 1375 for (nlohmann::json::iterator it = container->begin(); 1376 it != container->end(); it++) 1377 { 1378 const auto& name = it.key(); 1379 auto pathItr = 1380 std::find_if(managedObj.begin(), managedObj.end(), 1381 [&name](const auto& obj) { 1382 return boost::algorithm::ends_with( 1383 obj.first.str, "/" + name); 1384 }); 1385 boost::container::flat_map<std::string, 1386 dbus::utility::DbusVariantType> 1387 output; 1388 1389 output.reserve(16); // The pid interface length 1390 1391 // determines if we're patching entity-manager or 1392 // creating a new object 1393 bool createNewObject = (pathItr == managedObj.end()); 1394 std::string iface; 1395 if (type == "PidControllers" || type == "FanControllers") 1396 { 1397 iface = pidConfigurationIface; 1398 if (!createNewObject && 1399 pathItr->second.find(pidConfigurationIface) == 1400 pathItr->second.end()) 1401 { 1402 createNewObject = true; 1403 } 1404 } 1405 else if (type == "FanZones") 1406 { 1407 iface = pidZoneConfigurationIface; 1408 if (!createNewObject && 1409 pathItr->second.find(pidZoneConfigurationIface) == 1410 pathItr->second.end()) 1411 { 1412 1413 createNewObject = true; 1414 } 1415 } 1416 else if (type == "StepwiseControllers") 1417 { 1418 iface = stepwiseConfigurationIface; 1419 if (!createNewObject && 1420 pathItr->second.find(stepwiseConfigurationIface) == 1421 pathItr->second.end()) 1422 { 1423 createNewObject = true; 1424 } 1425 } 1426 BMCWEB_LOG_DEBUG << "Create new = " << createNewObject << "\n"; 1427 output["Name"] = boost::replace_all_copy(name, "_", " "); 1428 1429 std::string chassis; 1430 CreatePIDRet ret = createPidInterface( 1431 response, type, it, pathItr->first.str, managedObj, 1432 createNewObject, output, chassis, currentProfile); 1433 if (ret == CreatePIDRet::fail) 1434 { 1435 return; 1436 } 1437 else if (ret == CreatePIDRet::del) 1438 { 1439 continue; 1440 } 1441 1442 if (!createNewObject) 1443 { 1444 for (const auto& property : output) 1445 { 1446 crow::connections::systemBus->async_method_call( 1447 [response, 1448 propertyName{std::string(property.first)}]( 1449 const boost::system::error_code ec) { 1450 if (ec) 1451 { 1452 BMCWEB_LOG_ERROR << "Error patching " 1453 << propertyName << ": " 1454 << ec; 1455 messages::internalError(response->res); 1456 return; 1457 } 1458 messages::success(response->res); 1459 }, 1460 "xyz.openbmc_project.EntityManager", 1461 pathItr->first.str, 1462 "org.freedesktop.DBus.Properties", "Set", iface, 1463 property.first, property.second); 1464 } 1465 } 1466 else 1467 { 1468 if (chassis.empty()) 1469 { 1470 BMCWEB_LOG_ERROR << "Failed to get chassis from config"; 1471 messages::invalidObject(response->res, name); 1472 return; 1473 } 1474 1475 bool foundChassis = false; 1476 for (const auto& obj : managedObj) 1477 { 1478 if (boost::algorithm::ends_with(obj.first.str, chassis)) 1479 { 1480 chassis = obj.first.str; 1481 foundChassis = true; 1482 break; 1483 } 1484 } 1485 if (!foundChassis) 1486 { 1487 BMCWEB_LOG_ERROR << "Failed to find chassis on dbus"; 1488 messages::resourceMissingAtURI( 1489 response->res, "/redfish/v1/Chassis/" + chassis); 1490 return; 1491 } 1492 1493 crow::connections::systemBus->async_method_call( 1494 [response](const boost::system::error_code ec) { 1495 if (ec) 1496 { 1497 BMCWEB_LOG_ERROR << "Error Adding Pid Object " 1498 << ec; 1499 messages::internalError(response->res); 1500 return; 1501 } 1502 messages::success(response->res); 1503 }, 1504 "xyz.openbmc_project.EntityManager", chassis, 1505 "xyz.openbmc_project.AddObject", "AddObject", output); 1506 } 1507 } 1508 } 1509 } 1510 std::shared_ptr<AsyncResp> asyncResp; 1511 std::vector<std::pair<std::string, std::optional<nlohmann::json>>> 1512 configuration; 1513 std::optional<std::string> profile; 1514 dbus::utility::ManagedObjectType managedObj; 1515 std::vector<std::string> supportedProfiles; 1516 std::string currentProfile; 1517 std::string profileConnection; 1518 std::string profilePath; 1519 }; 1520 1521 class Manager : public Node 1522 { 1523 public: 1524 Manager(CrowApp& app) : Node(app, "/redfish/v1/Managers/bmc/") 1525 { 1526 uuid = app.template getMiddleware<crow::persistent_data::Middleware>() 1527 .systemUuid; 1528 entityPrivileges = { 1529 {boost::beast::http::verb::get, {{"Login"}}}, 1530 {boost::beast::http::verb::head, {{"Login"}}}, 1531 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1532 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1533 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1534 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1535 } 1536 1537 private: 1538 void doGet(crow::Response& res, const crow::Request& req, 1539 const std::vector<std::string>& params) override 1540 { 1541 res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc"; 1542 res.jsonValue["@odata.type"] = "#Manager.v1_3_0.Manager"; 1543 res.jsonValue["@odata.context"] = 1544 "/redfish/v1/$metadata#Manager.Manager"; 1545 res.jsonValue["Id"] = "bmc"; 1546 res.jsonValue["Name"] = "OpenBmc Manager"; 1547 res.jsonValue["Description"] = "Baseboard Management Controller"; 1548 res.jsonValue["PowerState"] = "On"; 1549 res.jsonValue["Status"] = {{"State", "Enabled"}, {"Health", "OK"}}; 1550 res.jsonValue["ManagerType"] = "BMC"; 1551 res.jsonValue["UUID"] = systemd_utils::getUuid(); 1552 res.jsonValue["ServiceEntryPointUUID"] = uuid; 1553 res.jsonValue["Model"] = "OpenBmc"; // TODO(ed), get model 1554 1555 res.jsonValue["LogServices"] = { 1556 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices"}}; 1557 1558 res.jsonValue["NetworkProtocol"] = { 1559 {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol"}}; 1560 1561 res.jsonValue["EthernetInterfaces"] = { 1562 {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces"}}; 1563 // default oem data 1564 nlohmann::json& oem = res.jsonValue["Oem"]; 1565 nlohmann::json& oemOpenbmc = oem["OpenBmc"]; 1566 oem["@odata.type"] = "#OemManager.Oem"; 1567 oem["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem"; 1568 oem["@odata.context"] = "/redfish/v1/$metadata#OemManager.Oem"; 1569 oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc"; 1570 oemOpenbmc["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc"; 1571 oemOpenbmc["@odata.context"] = 1572 "/redfish/v1/$metadata#OemManager.OpenBmc"; 1573 oemOpenbmc["Certificates"] = { 1574 {"@odata.id", "/redfish/v1/Managers/bmc/Truststore/Certificates"}}; 1575 1576 // Update Actions object. 1577 nlohmann::json& manager_reset = 1578 res.jsonValue["Actions"]["#Manager.Reset"]; 1579 manager_reset["target"] = 1580 "/redfish/v1/Managers/bmc/Actions/Manager.Reset"; 1581 manager_reset["ResetType@Redfish.AllowableValues"] = { 1582 "GracefulRestart"}; 1583 1584 res.jsonValue["DateTime"] = crow::utility::dateTimeNow(); 1585 1586 // Fill in SerialConsole info 1587 res.jsonValue["SerialConsole"]["ServiceEnabled"] = true; 1588 res.jsonValue["SerialConsole"]["MaxConcurrentSessions"] = 15; 1589 res.jsonValue["SerialConsole"]["ConnectTypesSupported"] = {"IPMI", 1590 "SSH"}; 1591 #ifdef BMCWEB_ENABLE_KVM 1592 // Fill in GraphicalConsole info 1593 res.jsonValue["GraphicalConsole"]["ServiceEnabled"] = true; 1594 res.jsonValue["GraphicalConsole"]["MaxConcurrentSessions"] = 4; 1595 res.jsonValue["GraphicalConsole"]["ConnectTypesSupported"] = {"KVMIP"}; 1596 #endif // BMCWEB_ENABLE_KVM 1597 1598 res.jsonValue["Links"]["ManagerForServers@odata.count"] = 1; 1599 res.jsonValue["Links"]["ManagerForServers"] = { 1600 {{"@odata.id", "/redfish/v1/Systems/system"}}}; 1601 1602 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1603 1604 auto health = std::make_shared<HealthPopulate>(asyncResp); 1605 health->isManagersHealth = true; 1606 health->populate(); 1607 1608 fw_util::getActiveFwVersion(asyncResp, fw_util::bmcPurpose, 1609 "FirmwareVersion"); 1610 1611 auto pids = std::make_shared<GetPIDValues>(asyncResp); 1612 pids->run(); 1613 1614 getMainChassisId(asyncResp, [](const std::string& chassisId, 1615 const std::shared_ptr<AsyncResp> aRsp) { 1616 aRsp->res.jsonValue["Links"]["ManagerForChassis@odata.count"] = 1; 1617 aRsp->res.jsonValue["Links"]["ManagerForChassis"] = { 1618 {{"@odata.id", "/redfish/v1/Chassis/" + chassisId}}}; 1619 aRsp->res.jsonValue["Links"]["ManagerInChassis"] = { 1620 {"@odata.id", "/redfish/v1/Chassis/" + chassisId}}; 1621 }); 1622 1623 static bool started = false; 1624 1625 if (!started) 1626 { 1627 crow::connections::systemBus->async_method_call( 1628 [asyncResp](const boost::system::error_code ec, 1629 const std::variant<double>& resp) { 1630 if (ec) 1631 { 1632 BMCWEB_LOG_ERROR << "Error while getting progress"; 1633 messages::internalError(asyncResp->res); 1634 return; 1635 } 1636 const double* val = std::get_if<double>(&resp); 1637 if (val == nullptr) 1638 { 1639 BMCWEB_LOG_ERROR 1640 << "Invalid response while getting progress"; 1641 messages::internalError(asyncResp->res); 1642 return; 1643 } 1644 if (*val < 1.0) 1645 { 1646 asyncResp->res.jsonValue["Status"]["State"] = 1647 "Starting"; 1648 started = true; 1649 } 1650 }, 1651 "org.freedesktop.systemd1", "/org/freedesktop/systemd1", 1652 "org.freedesktop.DBus.Properties", "Get", 1653 "org.freedesktop.systemd1.Manager", "Progress"); 1654 } 1655 } 1656 1657 void doPatch(crow::Response& res, const crow::Request& req, 1658 const std::vector<std::string>& params) override 1659 { 1660 std::optional<nlohmann::json> oem; 1661 std::optional<std::string> datetime; 1662 std::shared_ptr<AsyncResp> response = std::make_shared<AsyncResp>(res); 1663 1664 if (!json_util::readJson(req, response->res, "Oem", oem, "DateTime", 1665 datetime)) 1666 { 1667 return; 1668 } 1669 1670 if (oem) 1671 { 1672 std::optional<nlohmann::json> openbmc; 1673 if (!redfish::json_util::readJson(*oem, res, "OpenBmc", openbmc)) 1674 { 1675 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 1676 << oem->dump(); 1677 return; 1678 } 1679 if (openbmc) 1680 { 1681 std::optional<nlohmann::json> fan; 1682 if (!redfish::json_util::readJson(*openbmc, res, "Fan", fan)) 1683 { 1684 BMCWEB_LOG_ERROR << "Line:" << __LINE__ 1685 << ", Illegal Property " 1686 << openbmc->dump(); 1687 return; 1688 } 1689 if (fan) 1690 { 1691 auto pid = std::make_shared<SetPIDValues>(response, *fan); 1692 pid->run(); 1693 } 1694 } 1695 } 1696 if (datetime) 1697 { 1698 setDateTime(response, std::move(*datetime)); 1699 } 1700 } 1701 1702 void setDateTime(std::shared_ptr<AsyncResp> aResp, 1703 std::string datetime) const 1704 { 1705 BMCWEB_LOG_DEBUG << "Set date time: " << datetime; 1706 1707 std::stringstream stream(datetime); 1708 // Convert from ISO 8601 to boost local_time 1709 // (BMC only has time in UTC) 1710 boost::posix_time::ptime posixTime; 1711 boost::posix_time::ptime epoch(boost::gregorian::date(1970, 1, 1)); 1712 // Facet gets deleted with the stringsteam 1713 auto ifc = std::make_unique<boost::local_time::local_time_input_facet>( 1714 "%Y-%m-%d %H:%M:%S%F %ZP"); 1715 stream.imbue(std::locale(stream.getloc(), ifc.release())); 1716 1717 boost::local_time::local_date_time ldt( 1718 boost::local_time::not_a_date_time); 1719 1720 if (stream >> ldt) 1721 { 1722 posixTime = ldt.utc_time(); 1723 boost::posix_time::time_duration dur = posixTime - epoch; 1724 uint64_t durMicroSecs = 1725 static_cast<uint64_t>(dur.total_microseconds()); 1726 crow::connections::systemBus->async_method_call( 1727 [aResp{std::move(aResp)}, datetime{std::move(datetime)}]( 1728 const boost::system::error_code ec) { 1729 if (ec) 1730 { 1731 BMCWEB_LOG_DEBUG << "Failed to set elapsed time. " 1732 "DBUS response error " 1733 << ec; 1734 messages::internalError(aResp->res); 1735 return; 1736 } 1737 aResp->res.jsonValue["DateTime"] = datetime; 1738 }, 1739 "xyz.openbmc_project.Time.Manager", 1740 "/xyz/openbmc_project/time/bmc", 1741 "org.freedesktop.DBus.Properties", "Set", 1742 "xyz.openbmc_project.Time.EpochTime", "Elapsed", 1743 std::variant<uint64_t>(durMicroSecs)); 1744 } 1745 else 1746 { 1747 messages::propertyValueFormatError(aResp->res, datetime, 1748 "DateTime"); 1749 return; 1750 } 1751 } 1752 1753 std::string uuid; 1754 }; 1755 1756 class ManagerCollection : public Node 1757 { 1758 public: 1759 ManagerCollection(CrowApp& app) : Node(app, "/redfish/v1/Managers/") 1760 { 1761 entityPrivileges = { 1762 {boost::beast::http::verb::get, {{"Login"}}}, 1763 {boost::beast::http::verb::head, {{"Login"}}}, 1764 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1765 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1766 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1767 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1768 } 1769 1770 private: 1771 void doGet(crow::Response& res, const crow::Request& req, 1772 const std::vector<std::string>& params) override 1773 { 1774 // Collections don't include the static data added by SubRoute 1775 // because it has a duplicate entry for members 1776 res.jsonValue["@odata.id"] = "/redfish/v1/Managers"; 1777 res.jsonValue["@odata.type"] = "#ManagerCollection.ManagerCollection"; 1778 res.jsonValue["@odata.context"] = 1779 "/redfish/v1/$metadata#ManagerCollection.ManagerCollection"; 1780 res.jsonValue["Name"] = "Manager Collection"; 1781 res.jsonValue["Members@odata.count"] = 1; 1782 res.jsonValue["Members"] = { 1783 {{"@odata.id", "/redfish/v1/Managers/bmc"}}}; 1784 res.end(); 1785 } 1786 }; 1787 } // namespace redfish 1788