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