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 self->managedObj = std::move(mObj); 1244 }, 1245 "xyz.openbmc_project.EntityManager", "/", objectManagerIface, 1246 "GetManagedObjects"); 1247 1248 // at the same time get the profile information 1249 crow::connections::systemBus->async_method_call( 1250 [self](const boost::system::error_code ec, 1251 const crow::openbmc_mapper::GetSubTreeType& subtree) { 1252 if (ec || subtree.empty()) 1253 { 1254 return; 1255 } 1256 if (subtree[0].second.empty()) 1257 { 1258 // invalid mapper response, should never happen 1259 BMCWEB_LOG_ERROR << "SetPIDValues: Mapper Error"; 1260 messages::internalError(self->asyncResp->res); 1261 return; 1262 } 1263 1264 const std::string& path = subtree[0].first; 1265 const std::string& owner = subtree[0].second[0].first; 1266 crow::connections::systemBus->async_method_call( 1267 [self, path, owner]( 1268 const boost::system::error_code ec, 1269 const boost::container::flat_map< 1270 std::string, std::variant<std::vector<std::string>, 1271 std::string>>& r) { 1272 if (ec) 1273 { 1274 BMCWEB_LOG_ERROR << "SetPIDValues: Can't get " 1275 "thermalModeIface " 1276 << path; 1277 messages::internalError(self->asyncResp->res); 1278 return; 1279 } 1280 const std::string* current = nullptr; 1281 const std::vector<std::string>* supported = nullptr; 1282 for (auto& [key, value] : r) 1283 { 1284 if (key == "Current") 1285 { 1286 current = std::get_if<std::string>(&value); 1287 if (current == nullptr) 1288 { 1289 BMCWEB_LOG_ERROR 1290 << "SetPIDValues: thermal mode " 1291 "iface invalid " 1292 << path; 1293 messages::internalError( 1294 self->asyncResp->res); 1295 return; 1296 } 1297 } 1298 if (key == "Supported") 1299 { 1300 supported = 1301 std::get_if<std::vector<std::string>>( 1302 &value); 1303 if (supported == nullptr) 1304 { 1305 BMCWEB_LOG_ERROR 1306 << "SetPIDValues: thermal mode " 1307 "iface invalid" 1308 << path; 1309 messages::internalError( 1310 self->asyncResp->res); 1311 return; 1312 } 1313 } 1314 } 1315 if (current == nullptr || supported == nullptr) 1316 { 1317 BMCWEB_LOG_ERROR << "SetPIDValues: thermal mode " 1318 "iface invalid " 1319 << path; 1320 messages::internalError(self->asyncResp->res); 1321 return; 1322 } 1323 self->currentProfile = *current; 1324 self->supportedProfiles = *supported; 1325 self->profileConnection = owner; 1326 self->profilePath = path; 1327 }, 1328 owner, path, "org.freedesktop.DBus.Properties", "GetAll", 1329 thermalModeIface); 1330 }, 1331 "xyz.openbmc_project.ObjectMapper", 1332 "/xyz/openbmc_project/object_mapper", 1333 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0, 1334 std::array<const char*, 1>{thermalModeIface}); 1335 } 1336 ~SetPIDValues() 1337 { 1338 if (asyncResp->res.result() != boost::beast::http::status::ok) 1339 { 1340 return; 1341 } 1342 1343 std::shared_ptr<AsyncResp> response = asyncResp; 1344 1345 if (profile) 1346 { 1347 if (std::find(supportedProfiles.begin(), supportedProfiles.end(), 1348 *profile) == supportedProfiles.end()) 1349 { 1350 messages::actionParameterUnknown(response->res, "Profile", 1351 *profile); 1352 return; 1353 } 1354 currentProfile = *profile; 1355 crow::connections::systemBus->async_method_call( 1356 [response](const boost::system::error_code ec) { 1357 if (ec) 1358 { 1359 BMCWEB_LOG_ERROR << "Error patching profile" << ec; 1360 messages::internalError(response->res); 1361 } 1362 }, 1363 profileConnection, profilePath, 1364 "org.freedesktop.DBus.Properties", "Set", thermalModeIface, 1365 "Current", std::variant<std::string>(*profile)); 1366 } 1367 1368 for (auto& containerPair : configuration) 1369 { 1370 auto& container = containerPair.second; 1371 if (!container) 1372 { 1373 continue; 1374 } 1375 BMCWEB_LOG_DEBUG << *container; 1376 1377 std::string& type = containerPair.first; 1378 1379 for (nlohmann::json::iterator it = container->begin(); 1380 it != container->end(); it++) 1381 { 1382 const auto& name = it.key(); 1383 BMCWEB_LOG_DEBUG << "looking for " << name; 1384 1385 auto pathItr = 1386 std::find_if(managedObj.begin(), managedObj.end(), 1387 [&name](const auto& obj) { 1388 return boost::algorithm::ends_with( 1389 obj.first.str, "/" + name); 1390 }); 1391 boost::container::flat_map<std::string, 1392 dbus::utility::DbusVariantType> 1393 output; 1394 1395 output.reserve(16); // The pid interface length 1396 1397 // determines if we're patching entity-manager or 1398 // creating a new object 1399 bool createNewObject = (pathItr == managedObj.end()); 1400 BMCWEB_LOG_DEBUG << "Found = " << !createNewObject; 1401 1402 std::string iface; 1403 if (type == "PidControllers" || type == "FanControllers") 1404 { 1405 iface = pidConfigurationIface; 1406 if (!createNewObject && 1407 pathItr->second.find(pidConfigurationIface) == 1408 pathItr->second.end()) 1409 { 1410 createNewObject = true; 1411 } 1412 } 1413 else if (type == "FanZones") 1414 { 1415 iface = pidZoneConfigurationIface; 1416 if (!createNewObject && 1417 pathItr->second.find(pidZoneConfigurationIface) == 1418 pathItr->second.end()) 1419 { 1420 1421 createNewObject = true; 1422 } 1423 } 1424 else if (type == "StepwiseControllers") 1425 { 1426 iface = stepwiseConfigurationIface; 1427 if (!createNewObject && 1428 pathItr->second.find(stepwiseConfigurationIface) == 1429 pathItr->second.end()) 1430 { 1431 createNewObject = true; 1432 } 1433 } 1434 1435 if (createNewObject && it.value() == nullptr) 1436 { 1437 // can't delete a non-existant object 1438 messages::invalidObject(response->res, name); 1439 continue; 1440 } 1441 1442 std::string path; 1443 if (pathItr != managedObj.end()) 1444 { 1445 path = pathItr->first.str; 1446 } 1447 1448 BMCWEB_LOG_DEBUG << "Create new = " << createNewObject << "\n"; 1449 output["Name"] = boost::replace_all_copy(name, "_", " "); 1450 1451 std::string chassis; 1452 CreatePIDRet ret = createPidInterface( 1453 response, type, it, path, managedObj, createNewObject, 1454 output, chassis, currentProfile); 1455 if (ret == CreatePIDRet::fail) 1456 { 1457 return; 1458 } 1459 else if (ret == CreatePIDRet::del) 1460 { 1461 continue; 1462 } 1463 1464 if (!createNewObject) 1465 { 1466 for (const auto& property : output) 1467 { 1468 crow::connections::systemBus->async_method_call( 1469 [response, 1470 propertyName{std::string(property.first)}]( 1471 const boost::system::error_code ec) { 1472 if (ec) 1473 { 1474 BMCWEB_LOG_ERROR << "Error patching " 1475 << propertyName << ": " 1476 << ec; 1477 messages::internalError(response->res); 1478 return; 1479 } 1480 messages::success(response->res); 1481 }, 1482 "xyz.openbmc_project.EntityManager", path, 1483 "org.freedesktop.DBus.Properties", "Set", iface, 1484 property.first, property.second); 1485 } 1486 } 1487 else 1488 { 1489 if (chassis.empty()) 1490 { 1491 BMCWEB_LOG_ERROR << "Failed to get chassis from config"; 1492 messages::invalidObject(response->res, name); 1493 return; 1494 } 1495 1496 bool foundChassis = false; 1497 for (const auto& obj : managedObj) 1498 { 1499 if (boost::algorithm::ends_with(obj.first.str, chassis)) 1500 { 1501 chassis = obj.first.str; 1502 foundChassis = true; 1503 break; 1504 } 1505 } 1506 if (!foundChassis) 1507 { 1508 BMCWEB_LOG_ERROR << "Failed to find chassis on dbus"; 1509 messages::resourceMissingAtURI( 1510 response->res, "/redfish/v1/Chassis/" + chassis); 1511 return; 1512 } 1513 1514 crow::connections::systemBus->async_method_call( 1515 [response](const boost::system::error_code ec) { 1516 if (ec) 1517 { 1518 BMCWEB_LOG_ERROR << "Error Adding Pid Object " 1519 << ec; 1520 messages::internalError(response->res); 1521 return; 1522 } 1523 messages::success(response->res); 1524 }, 1525 "xyz.openbmc_project.EntityManager", chassis, 1526 "xyz.openbmc_project.AddObject", "AddObject", output); 1527 } 1528 } 1529 } 1530 } 1531 std::shared_ptr<AsyncResp> asyncResp; 1532 std::vector<std::pair<std::string, std::optional<nlohmann::json>>> 1533 configuration; 1534 std::optional<std::string> profile; 1535 dbus::utility::ManagedObjectType managedObj; 1536 std::vector<std::string> supportedProfiles; 1537 std::string currentProfile; 1538 std::string profileConnection; 1539 std::string profilePath; 1540 }; 1541 1542 class Manager : public Node 1543 { 1544 public: 1545 Manager(CrowApp& app) : Node(app, "/redfish/v1/Managers/bmc/") 1546 { 1547 uuid = app.template getMiddleware<crow::persistent_data::Middleware>() 1548 .systemUuid; 1549 entityPrivileges = { 1550 {boost::beast::http::verb::get, {{"Login"}}}, 1551 {boost::beast::http::verb::head, {{"Login"}}}, 1552 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1553 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1554 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1555 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1556 } 1557 1558 private: 1559 void doGet(crow::Response& res, const crow::Request& req, 1560 const std::vector<std::string>& params) override 1561 { 1562 res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc"; 1563 res.jsonValue["@odata.type"] = "#Manager.v1_3_0.Manager"; 1564 res.jsonValue["@odata.context"] = 1565 "/redfish/v1/$metadata#Manager.Manager"; 1566 res.jsonValue["Id"] = "bmc"; 1567 res.jsonValue["Name"] = "OpenBmc Manager"; 1568 res.jsonValue["Description"] = "Baseboard Management Controller"; 1569 res.jsonValue["PowerState"] = "On"; 1570 res.jsonValue["Status"] = {{"State", "Enabled"}, {"Health", "OK"}}; 1571 res.jsonValue["ManagerType"] = "BMC"; 1572 res.jsonValue["UUID"] = systemd_utils::getUuid(); 1573 res.jsonValue["ServiceEntryPointUUID"] = uuid; 1574 res.jsonValue["Model"] = "OpenBmc"; // TODO(ed), get model 1575 1576 res.jsonValue["LogServices"] = { 1577 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices"}}; 1578 1579 res.jsonValue["NetworkProtocol"] = { 1580 {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol"}}; 1581 1582 res.jsonValue["EthernetInterfaces"] = { 1583 {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces"}}; 1584 1585 #ifdef BMCWEB_ENABLE_VM_NBDPROXY 1586 res.jsonValue["VirtualMedia"] = { 1587 {"@odata.id", "/redfish/v1/Managers/bmc/VirtualMedia"}}; 1588 #endif // BMCWEB_ENABLE_VM_NBDPROXY 1589 1590 // default oem data 1591 nlohmann::json& oem = res.jsonValue["Oem"]; 1592 nlohmann::json& oemOpenbmc = oem["OpenBmc"]; 1593 oem["@odata.type"] = "#OemManager.Oem"; 1594 oem["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem"; 1595 oem["@odata.context"] = "/redfish/v1/$metadata#OemManager.Oem"; 1596 oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc"; 1597 oemOpenbmc["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc"; 1598 oemOpenbmc["@odata.context"] = 1599 "/redfish/v1/$metadata#OemManager.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["@odata.context"] = 1806 "/redfish/v1/$metadata#ManagerCollection.ManagerCollection"; 1807 res.jsonValue["Name"] = "Manager Collection"; 1808 res.jsonValue["Members@odata.count"] = 1; 1809 res.jsonValue["Members"] = { 1810 {{"@odata.id", "/redfish/v1/Managers/bmc"}}}; 1811 res.end(); 1812 } 1813 }; 1814 } // namespace redfish 1815