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