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