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 for (const auto& [path, object] : mObj) 1248 { 1249 for (const auto& [interface, _] : object) 1250 { 1251 if (std::find(configurations.begin(), 1252 configurations.end(), 1253 interface) != configurations.end()) 1254 { 1255 self->objectCount++; 1256 break; 1257 } 1258 } 1259 } 1260 self->managedObj = std::move(mObj); 1261 }, 1262 "xyz.openbmc_project.EntityManager", "/", objectManagerIface, 1263 "GetManagedObjects"); 1264 1265 // at the same time get the profile information 1266 crow::connections::systemBus->async_method_call( 1267 [self](const boost::system::error_code ec, 1268 const crow::openbmc_mapper::GetSubTreeType& subtree) { 1269 if (ec || subtree.empty()) 1270 { 1271 return; 1272 } 1273 if (subtree[0].second.empty()) 1274 { 1275 // invalid mapper response, should never happen 1276 BMCWEB_LOG_ERROR << "SetPIDValues: Mapper Error"; 1277 messages::internalError(self->asyncResp->res); 1278 return; 1279 } 1280 1281 const std::string& path = subtree[0].first; 1282 const std::string& owner = subtree[0].second[0].first; 1283 crow::connections::systemBus->async_method_call( 1284 [self, path, owner]( 1285 const boost::system::error_code ec, 1286 const boost::container::flat_map< 1287 std::string, std::variant<std::vector<std::string>, 1288 std::string>>& r) { 1289 if (ec) 1290 { 1291 BMCWEB_LOG_ERROR << "SetPIDValues: Can't get " 1292 "thermalModeIface " 1293 << path; 1294 messages::internalError(self->asyncResp->res); 1295 return; 1296 } 1297 const std::string* current = nullptr; 1298 const std::vector<std::string>* supported = nullptr; 1299 for (auto& [key, value] : r) 1300 { 1301 if (key == "Current") 1302 { 1303 current = std::get_if<std::string>(&value); 1304 if (current == nullptr) 1305 { 1306 BMCWEB_LOG_ERROR 1307 << "SetPIDValues: thermal mode " 1308 "iface invalid " 1309 << path; 1310 messages::internalError( 1311 self->asyncResp->res); 1312 return; 1313 } 1314 } 1315 if (key == "Supported") 1316 { 1317 supported = 1318 std::get_if<std::vector<std::string>>( 1319 &value); 1320 if (supported == nullptr) 1321 { 1322 BMCWEB_LOG_ERROR 1323 << "SetPIDValues: thermal mode " 1324 "iface invalid" 1325 << path; 1326 messages::internalError( 1327 self->asyncResp->res); 1328 return; 1329 } 1330 } 1331 } 1332 if (current == nullptr || supported == nullptr) 1333 { 1334 BMCWEB_LOG_ERROR << "SetPIDValues: thermal mode " 1335 "iface invalid " 1336 << path; 1337 messages::internalError(self->asyncResp->res); 1338 return; 1339 } 1340 self->currentProfile = *current; 1341 self->supportedProfiles = *supported; 1342 self->profileConnection = owner; 1343 self->profilePath = path; 1344 }, 1345 owner, path, "org.freedesktop.DBus.Properties", "GetAll", 1346 thermalModeIface); 1347 }, 1348 "xyz.openbmc_project.ObjectMapper", 1349 "/xyz/openbmc_project/object_mapper", 1350 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0, 1351 std::array<const char*, 1>{thermalModeIface}); 1352 } 1353 ~SetPIDValues() 1354 { 1355 if (asyncResp->res.result() != boost::beast::http::status::ok) 1356 { 1357 return; 1358 } 1359 1360 std::shared_ptr<AsyncResp> response = asyncResp; 1361 1362 if (profile) 1363 { 1364 if (std::find(supportedProfiles.begin(), supportedProfiles.end(), 1365 *profile) == supportedProfiles.end()) 1366 { 1367 messages::actionParameterUnknown(response->res, "Profile", 1368 *profile); 1369 return; 1370 } 1371 currentProfile = *profile; 1372 crow::connections::systemBus->async_method_call( 1373 [response](const boost::system::error_code ec) { 1374 if (ec) 1375 { 1376 BMCWEB_LOG_ERROR << "Error patching profile" << ec; 1377 messages::internalError(response->res); 1378 } 1379 }, 1380 profileConnection, profilePath, 1381 "org.freedesktop.DBus.Properties", "Set", thermalModeIface, 1382 "Current", std::variant<std::string>(*profile)); 1383 } 1384 1385 for (auto& containerPair : configuration) 1386 { 1387 auto& container = containerPair.second; 1388 if (!container) 1389 { 1390 continue; 1391 } 1392 BMCWEB_LOG_DEBUG << *container; 1393 1394 std::string& type = containerPair.first; 1395 1396 for (nlohmann::json::iterator it = container->begin(); 1397 it != container->end(); it++) 1398 { 1399 const auto& name = it.key(); 1400 BMCWEB_LOG_DEBUG << "looking for " << name; 1401 1402 auto pathItr = 1403 std::find_if(managedObj.begin(), managedObj.end(), 1404 [&name](const auto& obj) { 1405 return boost::algorithm::ends_with( 1406 obj.first.str, "/" + name); 1407 }); 1408 boost::container::flat_map<std::string, 1409 dbus::utility::DbusVariantType> 1410 output; 1411 1412 output.reserve(16); // The pid interface length 1413 1414 // determines if we're patching entity-manager or 1415 // creating a new object 1416 bool createNewObject = (pathItr == managedObj.end()); 1417 BMCWEB_LOG_DEBUG << "Found = " << !createNewObject; 1418 1419 std::string iface; 1420 if (type == "PidControllers" || type == "FanControllers") 1421 { 1422 iface = pidConfigurationIface; 1423 if (!createNewObject && 1424 pathItr->second.find(pidConfigurationIface) == 1425 pathItr->second.end()) 1426 { 1427 createNewObject = true; 1428 } 1429 } 1430 else if (type == "FanZones") 1431 { 1432 iface = pidZoneConfigurationIface; 1433 if (!createNewObject && 1434 pathItr->second.find(pidZoneConfigurationIface) == 1435 pathItr->second.end()) 1436 { 1437 1438 createNewObject = true; 1439 } 1440 } 1441 else if (type == "StepwiseControllers") 1442 { 1443 iface = stepwiseConfigurationIface; 1444 if (!createNewObject && 1445 pathItr->second.find(stepwiseConfigurationIface) == 1446 pathItr->second.end()) 1447 { 1448 createNewObject = true; 1449 } 1450 } 1451 1452 if (createNewObject && it.value() == nullptr) 1453 { 1454 // can't delete a non-existant object 1455 messages::invalidObject(response->res, name); 1456 continue; 1457 } 1458 1459 std::string path; 1460 if (pathItr != managedObj.end()) 1461 { 1462 path = pathItr->first.str; 1463 } 1464 1465 BMCWEB_LOG_DEBUG << "Create new = " << createNewObject << "\n"; 1466 1467 // arbitrary limit to avoid attacks 1468 constexpr const size_t controllerLimit = 500; 1469 if (createNewObject && objectCount >= controllerLimit) 1470 { 1471 messages::resourceExhaustion(response->res, type); 1472 continue; 1473 } 1474 1475 output["Name"] = boost::replace_all_copy(name, "_", " "); 1476 1477 std::string chassis; 1478 CreatePIDRet ret = createPidInterface( 1479 response, type, it, path, managedObj, createNewObject, 1480 output, chassis, currentProfile); 1481 if (ret == CreatePIDRet::fail) 1482 { 1483 return; 1484 } 1485 else if (ret == CreatePIDRet::del) 1486 { 1487 continue; 1488 } 1489 1490 if (!createNewObject) 1491 { 1492 for (const auto& property : output) 1493 { 1494 crow::connections::systemBus->async_method_call( 1495 [response, 1496 propertyName{std::string(property.first)}]( 1497 const boost::system::error_code ec) { 1498 if (ec) 1499 { 1500 BMCWEB_LOG_ERROR << "Error patching " 1501 << propertyName << ": " 1502 << ec; 1503 messages::internalError(response->res); 1504 return; 1505 } 1506 messages::success(response->res); 1507 }, 1508 "xyz.openbmc_project.EntityManager", path, 1509 "org.freedesktop.DBus.Properties", "Set", iface, 1510 property.first, property.second); 1511 } 1512 } 1513 else 1514 { 1515 if (chassis.empty()) 1516 { 1517 BMCWEB_LOG_ERROR << "Failed to get chassis from config"; 1518 messages::invalidObject(response->res, name); 1519 return; 1520 } 1521 1522 bool foundChassis = false; 1523 for (const auto& obj : managedObj) 1524 { 1525 if (boost::algorithm::ends_with(obj.first.str, chassis)) 1526 { 1527 chassis = obj.first.str; 1528 foundChassis = true; 1529 break; 1530 } 1531 } 1532 if (!foundChassis) 1533 { 1534 BMCWEB_LOG_ERROR << "Failed to find chassis on dbus"; 1535 messages::resourceMissingAtURI( 1536 response->res, "/redfish/v1/Chassis/" + chassis); 1537 return; 1538 } 1539 1540 crow::connections::systemBus->async_method_call( 1541 [response](const boost::system::error_code ec) { 1542 if (ec) 1543 { 1544 BMCWEB_LOG_ERROR << "Error Adding Pid Object " 1545 << ec; 1546 messages::internalError(response->res); 1547 return; 1548 } 1549 messages::success(response->res); 1550 }, 1551 "xyz.openbmc_project.EntityManager", chassis, 1552 "xyz.openbmc_project.AddObject", "AddObject", output); 1553 } 1554 } 1555 } 1556 } 1557 std::shared_ptr<AsyncResp> asyncResp; 1558 std::vector<std::pair<std::string, std::optional<nlohmann::json>>> 1559 configuration; 1560 std::optional<std::string> profile; 1561 dbus::utility::ManagedObjectType managedObj; 1562 std::vector<std::string> supportedProfiles; 1563 std::string currentProfile; 1564 std::string profileConnection; 1565 std::string profilePath; 1566 size_t objectCount = 0; 1567 }; 1568 1569 class Manager : public Node 1570 { 1571 public: 1572 Manager(CrowApp& app) : Node(app, "/redfish/v1/Managers/bmc/") 1573 { 1574 uuid = app.template getMiddleware<crow::persistent_data::Middleware>() 1575 .systemUuid; 1576 entityPrivileges = { 1577 {boost::beast::http::verb::get, {{"Login"}}}, 1578 {boost::beast::http::verb::head, {{"Login"}}}, 1579 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1580 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1581 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1582 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1583 } 1584 1585 private: 1586 void doGet(crow::Response& res, const crow::Request& req, 1587 const std::vector<std::string>& params) override 1588 { 1589 res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc"; 1590 res.jsonValue["@odata.type"] = "#Manager.v1_3_0.Manager"; 1591 res.jsonValue["@odata.context"] = 1592 "/redfish/v1/$metadata#Manager.Manager"; 1593 res.jsonValue["Id"] = "bmc"; 1594 res.jsonValue["Name"] = "OpenBmc Manager"; 1595 res.jsonValue["Description"] = "Baseboard Management Controller"; 1596 res.jsonValue["PowerState"] = "On"; 1597 res.jsonValue["Status"] = {{"State", "Enabled"}, {"Health", "OK"}}; 1598 res.jsonValue["ManagerType"] = "BMC"; 1599 res.jsonValue["UUID"] = systemd_utils::getUuid(); 1600 res.jsonValue["ServiceEntryPointUUID"] = uuid; 1601 res.jsonValue["Model"] = "OpenBmc"; // TODO(ed), get model 1602 1603 res.jsonValue["LogServices"] = { 1604 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices"}}; 1605 1606 res.jsonValue["NetworkProtocol"] = { 1607 {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol"}}; 1608 1609 res.jsonValue["EthernetInterfaces"] = { 1610 {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces"}}; 1611 1612 #ifdef BMCWEB_ENABLE_VM_NBDPROXY 1613 res.jsonValue["VirtualMedia"] = { 1614 {"@odata.id", "/redfish/v1/Managers/bmc/VirtualMedia"}}; 1615 #endif // BMCWEB_ENABLE_VM_NBDPROXY 1616 1617 // default oem data 1618 nlohmann::json& oem = res.jsonValue["Oem"]; 1619 nlohmann::json& oemOpenbmc = oem["OpenBmc"]; 1620 oem["@odata.type"] = "#OemManager.Oem"; 1621 oem["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem"; 1622 oem["@odata.context"] = "/redfish/v1/$metadata#OemManager.Oem"; 1623 oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc"; 1624 oemOpenbmc["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc"; 1625 oemOpenbmc["@odata.context"] = 1626 "/redfish/v1/$metadata#OemManager.OpenBmc"; 1627 oemOpenbmc["Certificates"] = { 1628 {"@odata.id", "/redfish/v1/Managers/bmc/Truststore/Certificates"}}; 1629 1630 // Update Actions object. 1631 nlohmann::json& manager_reset = 1632 res.jsonValue["Actions"]["#Manager.Reset"]; 1633 manager_reset["target"] = 1634 "/redfish/v1/Managers/bmc/Actions/Manager.Reset"; 1635 manager_reset["ResetType@Redfish.AllowableValues"] = { 1636 "GracefulRestart"}; 1637 1638 res.jsonValue["DateTime"] = crow::utility::dateTimeNow(); 1639 1640 // Fill in SerialConsole info 1641 res.jsonValue["SerialConsole"]["ServiceEnabled"] = true; 1642 res.jsonValue["SerialConsole"]["MaxConcurrentSessions"] = 15; 1643 res.jsonValue["SerialConsole"]["ConnectTypesSupported"] = {"IPMI", 1644 "SSH"}; 1645 #ifdef BMCWEB_ENABLE_KVM 1646 // Fill in GraphicalConsole info 1647 res.jsonValue["GraphicalConsole"]["ServiceEnabled"] = true; 1648 res.jsonValue["GraphicalConsole"]["MaxConcurrentSessions"] = 4; 1649 res.jsonValue["GraphicalConsole"]["ConnectTypesSupported"] = {"KVMIP"}; 1650 #endif // BMCWEB_ENABLE_KVM 1651 1652 res.jsonValue["Links"]["ManagerForServers@odata.count"] = 1; 1653 res.jsonValue["Links"]["ManagerForServers"] = { 1654 {{"@odata.id", "/redfish/v1/Systems/system"}}}; 1655 1656 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1657 1658 auto health = std::make_shared<HealthPopulate>(asyncResp); 1659 health->isManagersHealth = true; 1660 health->populate(); 1661 1662 fw_util::getActiveFwVersion(asyncResp, fw_util::bmcPurpose, 1663 "FirmwareVersion"); 1664 1665 auto pids = std::make_shared<GetPIDValues>(asyncResp); 1666 pids->run(); 1667 1668 getMainChassisId(asyncResp, [](const std::string& chassisId, 1669 const std::shared_ptr<AsyncResp> aRsp) { 1670 aRsp->res.jsonValue["Links"]["ManagerForChassis@odata.count"] = 1; 1671 aRsp->res.jsonValue["Links"]["ManagerForChassis"] = { 1672 {{"@odata.id", "/redfish/v1/Chassis/" + chassisId}}}; 1673 aRsp->res.jsonValue["Links"]["ManagerInChassis"] = { 1674 {"@odata.id", "/redfish/v1/Chassis/" + chassisId}}; 1675 }); 1676 1677 static bool started = false; 1678 1679 if (!started) 1680 { 1681 crow::connections::systemBus->async_method_call( 1682 [asyncResp](const boost::system::error_code ec, 1683 const std::variant<double>& resp) { 1684 if (ec) 1685 { 1686 BMCWEB_LOG_ERROR << "Error while getting progress"; 1687 messages::internalError(asyncResp->res); 1688 return; 1689 } 1690 const double* val = std::get_if<double>(&resp); 1691 if (val == nullptr) 1692 { 1693 BMCWEB_LOG_ERROR 1694 << "Invalid response while getting progress"; 1695 messages::internalError(asyncResp->res); 1696 return; 1697 } 1698 if (*val < 1.0) 1699 { 1700 asyncResp->res.jsonValue["Status"]["State"] = 1701 "Starting"; 1702 started = true; 1703 } 1704 }, 1705 "org.freedesktop.systemd1", "/org/freedesktop/systemd1", 1706 "org.freedesktop.DBus.Properties", "Get", 1707 "org.freedesktop.systemd1.Manager", "Progress"); 1708 } 1709 } 1710 1711 void doPatch(crow::Response& res, const crow::Request& req, 1712 const std::vector<std::string>& params) override 1713 { 1714 std::optional<nlohmann::json> oem; 1715 std::optional<std::string> datetime; 1716 std::shared_ptr<AsyncResp> response = std::make_shared<AsyncResp>(res); 1717 1718 if (!json_util::readJson(req, response->res, "Oem", oem, "DateTime", 1719 datetime)) 1720 { 1721 return; 1722 } 1723 1724 if (oem) 1725 { 1726 std::optional<nlohmann::json> openbmc; 1727 if (!redfish::json_util::readJson(*oem, res, "OpenBmc", openbmc)) 1728 { 1729 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 1730 << oem->dump(); 1731 return; 1732 } 1733 if (openbmc) 1734 { 1735 std::optional<nlohmann::json> fan; 1736 if (!redfish::json_util::readJson(*openbmc, res, "Fan", fan)) 1737 { 1738 BMCWEB_LOG_ERROR << "Line:" << __LINE__ 1739 << ", Illegal Property " 1740 << openbmc->dump(); 1741 return; 1742 } 1743 if (fan) 1744 { 1745 auto pid = std::make_shared<SetPIDValues>(response, *fan); 1746 pid->run(); 1747 } 1748 } 1749 } 1750 if (datetime) 1751 { 1752 setDateTime(response, std::move(*datetime)); 1753 } 1754 } 1755 1756 void setDateTime(std::shared_ptr<AsyncResp> aResp, 1757 std::string datetime) const 1758 { 1759 BMCWEB_LOG_DEBUG << "Set date time: " << datetime; 1760 1761 std::stringstream stream(datetime); 1762 // Convert from ISO 8601 to boost local_time 1763 // (BMC only has time in UTC) 1764 boost::posix_time::ptime posixTime; 1765 boost::posix_time::ptime epoch(boost::gregorian::date(1970, 1, 1)); 1766 // Facet gets deleted with the stringsteam 1767 auto ifc = std::make_unique<boost::local_time::local_time_input_facet>( 1768 "%Y-%m-%d %H:%M:%S%F %ZP"); 1769 stream.imbue(std::locale(stream.getloc(), ifc.release())); 1770 1771 boost::local_time::local_date_time ldt( 1772 boost::local_time::not_a_date_time); 1773 1774 if (stream >> ldt) 1775 { 1776 posixTime = ldt.utc_time(); 1777 boost::posix_time::time_duration dur = posixTime - epoch; 1778 uint64_t durMicroSecs = 1779 static_cast<uint64_t>(dur.total_microseconds()); 1780 crow::connections::systemBus->async_method_call( 1781 [aResp{std::move(aResp)}, datetime{std::move(datetime)}]( 1782 const boost::system::error_code ec) { 1783 if (ec) 1784 { 1785 BMCWEB_LOG_DEBUG << "Failed to set elapsed time. " 1786 "DBUS response error " 1787 << ec; 1788 messages::internalError(aResp->res); 1789 return; 1790 } 1791 aResp->res.jsonValue["DateTime"] = datetime; 1792 }, 1793 "xyz.openbmc_project.Time.Manager", 1794 "/xyz/openbmc_project/time/bmc", 1795 "org.freedesktop.DBus.Properties", "Set", 1796 "xyz.openbmc_project.Time.EpochTime", "Elapsed", 1797 std::variant<uint64_t>(durMicroSecs)); 1798 } 1799 else 1800 { 1801 messages::propertyValueFormatError(aResp->res, datetime, 1802 "DateTime"); 1803 return; 1804 } 1805 } 1806 1807 std::string uuid; 1808 }; 1809 1810 class ManagerCollection : public Node 1811 { 1812 public: 1813 ManagerCollection(CrowApp& app) : Node(app, "/redfish/v1/Managers/") 1814 { 1815 entityPrivileges = { 1816 {boost::beast::http::verb::get, {{"Login"}}}, 1817 {boost::beast::http::verb::head, {{"Login"}}}, 1818 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1819 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1820 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1821 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1822 } 1823 1824 private: 1825 void doGet(crow::Response& res, const crow::Request& req, 1826 const std::vector<std::string>& params) override 1827 { 1828 // Collections don't include the static data added by SubRoute 1829 // because it has a duplicate entry for members 1830 res.jsonValue["@odata.id"] = "/redfish/v1/Managers"; 1831 res.jsonValue["@odata.type"] = "#ManagerCollection.ManagerCollection"; 1832 res.jsonValue["@odata.context"] = 1833 "/redfish/v1/$metadata#ManagerCollection.ManagerCollection"; 1834 res.jsonValue["Name"] = "Manager Collection"; 1835 res.jsonValue["Members@odata.count"] = 1; 1836 res.jsonValue["Members"] = { 1837 {{"@odata.id", "/redfish/v1/Managers/bmc"}}}; 1838 res.end(); 1839 } 1840 }; 1841 } // namespace redfish 1842