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 <utils/fw_utils.hpp> 26 #include <utils/systemd_utils.hpp> 27 28 #include <cstdint> 29 #include <memory> 30 #include <sstream> 31 #include <variant> 32 33 namespace redfish 34 { 35 36 /** 37 * Function reboots the BMC. 38 * 39 * @param[in] asyncResp - Shared pointer for completing asynchronous calls 40 */ 41 inline void 42 doBMCGracefulRestart(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 43 { 44 const char* processName = "xyz.openbmc_project.State.BMC"; 45 const char* objectPath = "/xyz/openbmc_project/state/bmc0"; 46 const char* interfaceName = "xyz.openbmc_project.State.BMC"; 47 const std::string& propertyValue = 48 "xyz.openbmc_project.State.BMC.Transition.Reboot"; 49 const char* destProperty = "RequestedBMCTransition"; 50 51 // Create the D-Bus variant for D-Bus call. 52 VariantType dbusPropertyValue(propertyValue); 53 54 crow::connections::systemBus->async_method_call( 55 [asyncResp](const boost::system::error_code ec) { 56 // Use "Set" method to set the property value. 57 if (ec) 58 { 59 BMCWEB_LOG_DEBUG << "[Set] Bad D-Bus request error: " << ec; 60 messages::internalError(asyncResp->res); 61 return; 62 } 63 64 messages::success(asyncResp->res); 65 }, 66 processName, objectPath, "org.freedesktop.DBus.Properties", "Set", 67 interfaceName, destProperty, dbusPropertyValue); 68 } 69 70 inline void 71 doBMCForceRestart(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 72 { 73 const char* processName = "xyz.openbmc_project.State.BMC"; 74 const char* objectPath = "/xyz/openbmc_project/state/bmc0"; 75 const char* interfaceName = "xyz.openbmc_project.State.BMC"; 76 const std::string& propertyValue = 77 "xyz.openbmc_project.State.BMC.Transition.HardReboot"; 78 const char* destProperty = "RequestedBMCTransition"; 79 80 // Create the D-Bus variant for D-Bus call. 81 VariantType dbusPropertyValue(propertyValue); 82 83 crow::connections::systemBus->async_method_call( 84 [asyncResp](const boost::system::error_code ec) { 85 // Use "Set" method to set the property value. 86 if (ec) 87 { 88 BMCWEB_LOG_DEBUG << "[Set] Bad D-Bus request error: " << ec; 89 messages::internalError(asyncResp->res); 90 return; 91 } 92 93 messages::success(asyncResp->res); 94 }, 95 processName, objectPath, "org.freedesktop.DBus.Properties", "Set", 96 interfaceName, destProperty, dbusPropertyValue); 97 } 98 99 /** 100 * ManagerResetAction class supports the POST method for the Reset (reboot) 101 * action. 102 */ 103 class ManagerResetAction : public Node 104 { 105 public: 106 ManagerResetAction(App& app) : 107 Node(app, "/redfish/v1/Managers/bmc/Actions/Manager.Reset/") 108 { 109 entityPrivileges = { 110 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 111 } 112 113 private: 114 /** 115 * Function handles POST method request. 116 * Analyzes POST body before sending Reset (Reboot) request data to D-Bus. 117 * OpenBMC supports ResetType "GracefulRestart" and "ForceRestart". 118 */ 119 void doPost(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 120 const crow::Request& req, 121 const std::vector<std::string>&) override 122 { 123 BMCWEB_LOG_DEBUG << "Post Manager Reset."; 124 125 std::string resetType; 126 127 if (!json_util::readJson(req, asyncResp->res, "ResetType", resetType)) 128 { 129 return; 130 } 131 132 if (resetType == "GracefulRestart") 133 { 134 BMCWEB_LOG_DEBUG << "Proceeding with " << resetType; 135 doBMCGracefulRestart(asyncResp); 136 return; 137 } 138 if (resetType == "ForceRestart") 139 { 140 BMCWEB_LOG_DEBUG << "Proceeding with " << resetType; 141 doBMCForceRestart(asyncResp); 142 return; 143 } 144 BMCWEB_LOG_DEBUG << "Invalid property value for ResetType: " 145 << resetType; 146 messages::actionParameterNotSupported(asyncResp->res, resetType, 147 "ResetType"); 148 149 return; 150 } 151 }; 152 153 /** 154 * ManagerResetToDefaultsAction class supports POST method for factory reset 155 * action. 156 */ 157 class ManagerResetToDefaultsAction : public Node 158 { 159 public: 160 ManagerResetToDefaultsAction(App& app) : 161 Node(app, "/redfish/v1/Managers/bmc/Actions/Manager.ResetToDefaults/") 162 { 163 entityPrivileges = { 164 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 165 } 166 167 private: 168 /** 169 * Function handles ResetToDefaults POST method request. 170 * 171 * Analyzes POST body message and factory resets BMC by calling 172 * BMC code updater factory reset followed by a BMC reboot. 173 * 174 * BMC code updater factory reset wipes the whole BMC read-write 175 * filesystem which includes things like the network settings. 176 * 177 * OpenBMC only supports ResetToDefaultsType "ResetAll". 178 */ 179 void doPost(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 180 const crow::Request& req, 181 const std::vector<std::string>&) override 182 { 183 BMCWEB_LOG_DEBUG << "Post ResetToDefaults."; 184 185 std::string resetType; 186 187 if (!json_util::readJson(req, asyncResp->res, "ResetToDefaultsType", 188 resetType)) 189 { 190 BMCWEB_LOG_DEBUG << "Missing property ResetToDefaultsType."; 191 192 messages::actionParameterMissing(asyncResp->res, "ResetToDefaults", 193 "ResetToDefaultsType"); 194 return; 195 } 196 197 if (resetType != "ResetAll") 198 { 199 BMCWEB_LOG_DEBUG << "Invalid property value for " 200 "ResetToDefaultsType: " 201 << resetType; 202 messages::actionParameterNotSupported(asyncResp->res, resetType, 203 "ResetToDefaultsType"); 204 return; 205 } 206 207 crow::connections::systemBus->async_method_call( 208 [asyncResp](const boost::system::error_code ec) { 209 if (ec) 210 { 211 BMCWEB_LOG_DEBUG << "Failed to ResetToDefaults: " << ec; 212 messages::internalError(asyncResp->res); 213 return; 214 } 215 // Factory Reset doesn't actually happen until a reboot 216 // Can't erase what the BMC is running on 217 doBMCGracefulRestart(asyncResp); 218 }, 219 "xyz.openbmc_project.Software.BMC.Updater", 220 "/xyz/openbmc_project/software", 221 "xyz.openbmc_project.Common.FactoryReset", "Reset"); 222 } 223 }; 224 225 /** 226 * ManagerResetActionInfo derived class for delivering Manager 227 * ResetType AllowableValues using ResetInfo schema. 228 */ 229 class ManagerResetActionInfo : public Node 230 { 231 public: 232 /* 233 * Default Constructor 234 */ 235 ManagerResetActionInfo(App& app) : 236 Node(app, "/redfish/v1/Managers/bmc/ResetActionInfo/") 237 { 238 entityPrivileges = { 239 {boost::beast::http::verb::get, {{"Login"}}}, 240 {boost::beast::http::verb::head, {{"Login"}}}, 241 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 242 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 243 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 244 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 245 } 246 247 private: 248 /** 249 * Functions triggers appropriate requests on DBus 250 */ 251 void doGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 252 const crow::Request&, const std::vector<std::string>&) override 253 { 254 asyncResp->res.jsonValue = { 255 {"@odata.type", "#ActionInfo.v1_1_2.ActionInfo"}, 256 {"@odata.id", "/redfish/v1/Managers/bmc/ResetActionInfo"}, 257 {"Name", "Reset Action Info"}, 258 {"Id", "ResetActionInfo"}, 259 {"Parameters", 260 {{{"Name", "ResetType"}, 261 {"Required", true}, 262 {"DataType", "String"}, 263 {"AllowableValues", {"GracefulRestart", "ForceRestart"}}}}}}; 264 } 265 }; 266 267 static constexpr const char* objectManagerIface = 268 "org.freedesktop.DBus.ObjectManager"; 269 static constexpr const char* pidConfigurationIface = 270 "xyz.openbmc_project.Configuration.Pid"; 271 static constexpr const char* pidZoneConfigurationIface = 272 "xyz.openbmc_project.Configuration.Pid.Zone"; 273 static constexpr const char* stepwiseConfigurationIface = 274 "xyz.openbmc_project.Configuration.Stepwise"; 275 static constexpr const char* thermalModeIface = 276 "xyz.openbmc_project.Control.ThermalMode"; 277 278 inline void 279 asyncPopulatePid(const std::string& connection, const std::string& path, 280 const std::string& currentProfile, 281 const std::vector<std::string>& supportedProfiles, 282 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 283 { 284 285 crow::connections::systemBus->async_method_call( 286 [asyncResp, currentProfile, supportedProfiles]( 287 const boost::system::error_code ec, 288 const dbus::utility::ManagedObjectType& managedObj) { 289 if (ec) 290 { 291 BMCWEB_LOG_ERROR << ec; 292 asyncResp->res.jsonValue.clear(); 293 messages::internalError(asyncResp->res); 294 return; 295 } 296 nlohmann::json& configRoot = 297 asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"]; 298 nlohmann::json& fans = configRoot["FanControllers"]; 299 fans["@odata.type"] = "#OemManager.FanControllers"; 300 fans["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc/" 301 "Fan/FanControllers"; 302 303 nlohmann::json& pids = configRoot["PidControllers"]; 304 pids["@odata.type"] = "#OemManager.PidControllers"; 305 pids["@odata.id"] = 306 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/PidControllers"; 307 308 nlohmann::json& stepwise = configRoot["StepwiseControllers"]; 309 stepwise["@odata.type"] = "#OemManager.StepwiseControllers"; 310 stepwise["@odata.id"] = 311 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/StepwiseControllers"; 312 313 nlohmann::json& zones = configRoot["FanZones"]; 314 zones["@odata.id"] = 315 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones"; 316 zones["@odata.type"] = "#OemManager.FanZones"; 317 configRoot["@odata.id"] = 318 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan"; 319 configRoot["@odata.type"] = "#OemManager.Fan"; 320 configRoot["Profile@Redfish.AllowableValues"] = supportedProfiles; 321 322 if (!currentProfile.empty()) 323 { 324 configRoot["Profile"] = currentProfile; 325 } 326 BMCWEB_LOG_ERROR << "profile = " << currentProfile << " !"; 327 328 for (const auto& pathPair : managedObj) 329 { 330 for (const auto& intfPair : pathPair.second) 331 { 332 if (intfPair.first != pidConfigurationIface && 333 intfPair.first != pidZoneConfigurationIface && 334 intfPair.first != stepwiseConfigurationIface) 335 { 336 continue; 337 } 338 auto findName = intfPair.second.find("Name"); 339 if (findName == intfPair.second.end()) 340 { 341 BMCWEB_LOG_ERROR << "Pid Field missing Name"; 342 messages::internalError(asyncResp->res); 343 return; 344 } 345 346 const std::string* namePtr = 347 std::get_if<std::string>(&findName->second); 348 if (namePtr == nullptr) 349 { 350 BMCWEB_LOG_ERROR << "Pid Name Field illegal"; 351 messages::internalError(asyncResp->res); 352 return; 353 } 354 std::string name = *namePtr; 355 dbus::utility::escapePathForDbus(name); 356 357 auto findProfiles = intfPair.second.find("Profiles"); 358 if (findProfiles != intfPair.second.end()) 359 { 360 const std::vector<std::string>* profiles = 361 std::get_if<std::vector<std::string>>( 362 &findProfiles->second); 363 if (profiles == nullptr) 364 { 365 BMCWEB_LOG_ERROR << "Pid Profiles Field illegal"; 366 messages::internalError(asyncResp->res); 367 return; 368 } 369 if (std::find(profiles->begin(), profiles->end(), 370 currentProfile) == profiles->end()) 371 { 372 BMCWEB_LOG_INFO 373 << name << " not supported in current profile"; 374 continue; 375 } 376 } 377 nlohmann::json* config = nullptr; 378 379 const std::string* classPtr = nullptr; 380 auto findClass = intfPair.second.find("Class"); 381 if (findClass != intfPair.second.end()) 382 { 383 classPtr = std::get_if<std::string>(&findClass->second); 384 } 385 386 if (intfPair.first == pidZoneConfigurationIface) 387 { 388 std::string chassis; 389 if (!dbus::utility::getNthStringFromPath( 390 pathPair.first.str, 5, chassis)) 391 { 392 chassis = "#IllegalValue"; 393 } 394 nlohmann::json& zone = zones[name]; 395 zone["Chassis"] = { 396 {"@odata.id", "/redfish/v1/Chassis/" + chassis}}; 397 zone["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/" 398 "OpenBmc/Fan/FanZones/" + 399 name; 400 zone["@odata.type"] = "#OemManager.FanZone"; 401 config = &zone; 402 } 403 404 else if (intfPair.first == stepwiseConfigurationIface) 405 { 406 if (classPtr == nullptr) 407 { 408 BMCWEB_LOG_ERROR << "Pid Class Field illegal"; 409 messages::internalError(asyncResp->res); 410 return; 411 } 412 413 nlohmann::json& controller = stepwise[name]; 414 config = &controller; 415 416 controller["@odata.id"] = 417 "/redfish/v1/Managers/bmc#/Oem/" 418 "OpenBmc/Fan/StepwiseControllers/" + 419 name; 420 controller["@odata.type"] = 421 "#OemManager.StepwiseController"; 422 423 controller["Direction"] = *classPtr; 424 } 425 426 // pid and fans are off the same configuration 427 else if (intfPair.first == pidConfigurationIface) 428 { 429 430 if (classPtr == nullptr) 431 { 432 BMCWEB_LOG_ERROR << "Pid Class Field illegal"; 433 messages::internalError(asyncResp->res); 434 return; 435 } 436 bool isFan = *classPtr == "fan"; 437 nlohmann::json& element = 438 isFan ? fans[name] : pids[name]; 439 config = &element; 440 if (isFan) 441 { 442 element["@odata.id"] = 443 "/redfish/v1/Managers/bmc#/Oem/" 444 "OpenBmc/Fan/FanControllers/" + 445 name; 446 element["@odata.type"] = 447 "#OemManager.FanController"; 448 } 449 else 450 { 451 element["@odata.id"] = 452 "/redfish/v1/Managers/bmc#/Oem/" 453 "OpenBmc/Fan/PidControllers/" + 454 name; 455 element["@odata.type"] = 456 "#OemManager.PidController"; 457 } 458 } 459 else 460 { 461 BMCWEB_LOG_ERROR << "Unexpected configuration"; 462 messages::internalError(asyncResp->res); 463 return; 464 } 465 466 // used for making maps out of 2 vectors 467 const std::vector<double>* keys = nullptr; 468 const std::vector<double>* values = nullptr; 469 470 for (const auto& propertyPair : intfPair.second) 471 { 472 if (propertyPair.first == "Type" || 473 propertyPair.first == "Class" || 474 propertyPair.first == "Name") 475 { 476 continue; 477 } 478 479 // zones 480 if (intfPair.first == pidZoneConfigurationIface) 481 { 482 const double* ptr = 483 std::get_if<double>(&propertyPair.second); 484 if (ptr == nullptr) 485 { 486 BMCWEB_LOG_ERROR << "Field Illegal " 487 << propertyPair.first; 488 messages::internalError(asyncResp->res); 489 return; 490 } 491 (*config)[propertyPair.first] = *ptr; 492 } 493 494 if (intfPair.first == stepwiseConfigurationIface) 495 { 496 if (propertyPair.first == "Reading" || 497 propertyPair.first == "Output") 498 { 499 const std::vector<double>* ptr = 500 std::get_if<std::vector<double>>( 501 &propertyPair.second); 502 503 if (ptr == nullptr) 504 { 505 BMCWEB_LOG_ERROR << "Field Illegal " 506 << propertyPair.first; 507 messages::internalError(asyncResp->res); 508 return; 509 } 510 511 if (propertyPair.first == "Reading") 512 { 513 keys = ptr; 514 } 515 else 516 { 517 values = ptr; 518 } 519 if (keys && values) 520 { 521 if (keys->size() != values->size()) 522 { 523 BMCWEB_LOG_ERROR 524 << "Reading and Output size don't " 525 "match "; 526 messages::internalError(asyncResp->res); 527 return; 528 } 529 nlohmann::json& steps = (*config)["Steps"]; 530 steps = nlohmann::json::array(); 531 for (size_t ii = 0; ii < keys->size(); ii++) 532 { 533 steps.push_back( 534 {{"Target", (*keys)[ii]}, 535 {"Output", (*values)[ii]}}); 536 } 537 } 538 } 539 if (propertyPair.first == "NegativeHysteresis" || 540 propertyPair.first == "PositiveHysteresis") 541 { 542 const double* ptr = 543 std::get_if<double>(&propertyPair.second); 544 if (ptr == nullptr) 545 { 546 BMCWEB_LOG_ERROR << "Field Illegal " 547 << propertyPair.first; 548 messages::internalError(asyncResp->res); 549 return; 550 } 551 (*config)[propertyPair.first] = *ptr; 552 } 553 } 554 555 // pid and fans are off the same configuration 556 if (intfPair.first == pidConfigurationIface || 557 intfPair.first == stepwiseConfigurationIface) 558 { 559 560 if (propertyPair.first == "Zones") 561 { 562 const std::vector<std::string>* inputs = 563 std::get_if<std::vector<std::string>>( 564 &propertyPair.second); 565 566 if (inputs == nullptr) 567 { 568 BMCWEB_LOG_ERROR 569 << "Zones Pid Field Illegal"; 570 messages::internalError(asyncResp->res); 571 return; 572 } 573 auto& data = (*config)[propertyPair.first]; 574 data = nlohmann::json::array(); 575 for (std::string itemCopy : *inputs) 576 { 577 dbus::utility::escapePathForDbus(itemCopy); 578 data.push_back( 579 {{"@odata.id", 580 "/redfish/v1/Managers/bmc#/Oem/" 581 "OpenBmc/Fan/FanZones/" + 582 itemCopy}}); 583 } 584 } 585 // todo(james): may never happen, but this 586 // assumes configuration data referenced in the 587 // PID config is provided by the same daemon, we 588 // could add another loop to cover all cases, 589 // but I'm okay kicking this can down the road a 590 // bit 591 592 else if (propertyPair.first == "Inputs" || 593 propertyPair.first == "Outputs") 594 { 595 auto& data = (*config)[propertyPair.first]; 596 const std::vector<std::string>* inputs = 597 std::get_if<std::vector<std::string>>( 598 &propertyPair.second); 599 600 if (inputs == nullptr) 601 { 602 BMCWEB_LOG_ERROR << "Field Illegal " 603 << propertyPair.first; 604 messages::internalError(asyncResp->res); 605 return; 606 } 607 data = *inputs; 608 } 609 else if (propertyPair.first == "SetPointOffset") 610 { 611 const std::string* ptr = 612 std::get_if<std::string>( 613 &propertyPair.second); 614 615 if (ptr == nullptr) 616 { 617 BMCWEB_LOG_ERROR << "Field Illegal " 618 << propertyPair.first; 619 messages::internalError(asyncResp->res); 620 return; 621 } 622 // translate from dbus to redfish 623 if (*ptr == "WarningHigh") 624 { 625 (*config)["SetPointOffset"] = 626 "UpperThresholdNonCritical"; 627 } 628 else if (*ptr == "WarningLow") 629 { 630 (*config)["SetPointOffset"] = 631 "LowerThresholdNonCritical"; 632 } 633 else if (*ptr == "CriticalHigh") 634 { 635 (*config)["SetPointOffset"] = 636 "UpperThresholdCritical"; 637 } 638 else if (*ptr == "CriticalLow") 639 { 640 (*config)["SetPointOffset"] = 641 "LowerThresholdCritical"; 642 } 643 else 644 { 645 BMCWEB_LOG_ERROR << "Value Illegal " 646 << *ptr; 647 messages::internalError(asyncResp->res); 648 return; 649 } 650 } 651 // doubles 652 else if (propertyPair.first == 653 "FFGainCoefficient" || 654 propertyPair.first == "FFOffCoefficient" || 655 propertyPair.first == "ICoefficient" || 656 propertyPair.first == "ILimitMax" || 657 propertyPair.first == "ILimitMin" || 658 propertyPair.first == 659 "PositiveHysteresis" || 660 propertyPair.first == 661 "NegativeHysteresis" || 662 propertyPair.first == "OutLimitMax" || 663 propertyPair.first == "OutLimitMin" || 664 propertyPair.first == "PCoefficient" || 665 propertyPair.first == "SetPoint" || 666 propertyPair.first == "SlewNeg" || 667 propertyPair.first == "SlewPos") 668 { 669 const double* ptr = 670 std::get_if<double>(&propertyPair.second); 671 if (ptr == nullptr) 672 { 673 BMCWEB_LOG_ERROR << "Field Illegal " 674 << propertyPair.first; 675 messages::internalError(asyncResp->res); 676 return; 677 } 678 (*config)[propertyPair.first] = *ptr; 679 } 680 } 681 } 682 } 683 } 684 }, 685 connection, path, objectManagerIface, "GetManagedObjects"); 686 } 687 688 enum class CreatePIDRet 689 { 690 fail, 691 del, 692 patch 693 }; 694 695 inline bool 696 getZonesFromJsonReq(const std::shared_ptr<bmcweb::AsyncResp>& response, 697 std::vector<nlohmann::json>& config, 698 std::vector<std::string>& zones) 699 { 700 if (config.empty()) 701 { 702 BMCWEB_LOG_ERROR << "Empty Zones"; 703 messages::propertyValueFormatError(response->res, 704 nlohmann::json::array(), "Zones"); 705 return false; 706 } 707 for (auto& odata : config) 708 { 709 std::string path; 710 if (!redfish::json_util::readJson(odata, response->res, "@odata.id", 711 path)) 712 { 713 return false; 714 } 715 std::string input; 716 717 // 8 below comes from 718 // /redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones/Left 719 // 0 1 2 3 4 5 6 7 8 720 if (!dbus::utility::getNthStringFromPath(path, 8, input)) 721 { 722 BMCWEB_LOG_ERROR << "Got invalid path " << path; 723 BMCWEB_LOG_ERROR << "Illegal Type Zones"; 724 messages::propertyValueFormatError(response->res, odata.dump(), 725 "Zones"); 726 return false; 727 } 728 boost::replace_all(input, "_", " "); 729 zones.emplace_back(std::move(input)); 730 } 731 return true; 732 } 733 734 inline const dbus::utility::ManagedItem* 735 findChassis(const dbus::utility::ManagedObjectType& managedObj, 736 const std::string& value, std::string& chassis) 737 { 738 BMCWEB_LOG_DEBUG << "Find Chassis: " << value << "\n"; 739 740 std::string escaped = boost::replace_all_copy(value, " ", "_"); 741 escaped = "/" + escaped; 742 auto it = std::find_if( 743 managedObj.begin(), managedObj.end(), [&escaped](const auto& obj) { 744 if (boost::algorithm::ends_with(obj.first.str, escaped)) 745 { 746 BMCWEB_LOG_DEBUG << "Matched " << obj.first.str << "\n"; 747 return true; 748 } 749 return false; 750 }); 751 752 if (it == managedObj.end()) 753 { 754 return nullptr; 755 } 756 // 5 comes from <chassis-name> being the 5th element 757 // /xyz/openbmc_project/inventory/system/chassis/<chassis-name> 758 if (dbus::utility::getNthStringFromPath(it->first.str, 5, chassis)) 759 { 760 return &(*it); 761 } 762 763 return nullptr; 764 } 765 766 inline CreatePIDRet createPidInterface( 767 const std::shared_ptr<bmcweb::AsyncResp>& response, const std::string& type, 768 const nlohmann::json::iterator& it, const std::string& path, 769 const dbus::utility::ManagedObjectType& managedObj, bool createNewObject, 770 boost::container::flat_map<std::string, dbus::utility::DbusVariantType>& 771 output, 772 std::string& chassis, const std::string& profile) 773 { 774 775 // common deleter 776 if (it.value() == nullptr) 777 { 778 std::string iface; 779 if (type == "PidControllers" || type == "FanControllers") 780 { 781 iface = pidConfigurationIface; 782 } 783 else if (type == "FanZones") 784 { 785 iface = pidZoneConfigurationIface; 786 } 787 else if (type == "StepwiseControllers") 788 { 789 iface = stepwiseConfigurationIface; 790 } 791 else 792 { 793 BMCWEB_LOG_ERROR << "Illegal Type " << type; 794 messages::propertyUnknown(response->res, type); 795 return CreatePIDRet::fail; 796 } 797 798 BMCWEB_LOG_DEBUG << "del " << path << " " << iface << "\n"; 799 // delete interface 800 crow::connections::systemBus->async_method_call( 801 [response, path](const boost::system::error_code ec) { 802 if (ec) 803 { 804 BMCWEB_LOG_ERROR << "Error patching " << path << ": " << ec; 805 messages::internalError(response->res); 806 return; 807 } 808 messages::success(response->res); 809 }, 810 "xyz.openbmc_project.EntityManager", path, iface, "Delete"); 811 return CreatePIDRet::del; 812 } 813 814 const dbus::utility::ManagedItem* managedItem = nullptr; 815 if (!createNewObject) 816 { 817 // if we aren't creating a new object, we should be able to find it on 818 // d-bus 819 managedItem = findChassis(managedObj, it.key(), chassis); 820 if (managedItem == nullptr) 821 { 822 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch"; 823 messages::invalidObject(response->res, it.key()); 824 return CreatePIDRet::fail; 825 } 826 } 827 828 if (profile.size() && 829 (type == "PidControllers" || type == "FanControllers" || 830 type == "StepwiseControllers")) 831 { 832 if (managedItem == nullptr) 833 { 834 output["Profiles"] = std::vector<std::string>{profile}; 835 } 836 else 837 { 838 std::string interface; 839 if (type == "StepwiseControllers") 840 { 841 interface = stepwiseConfigurationIface; 842 } 843 else 844 { 845 interface = pidConfigurationIface; 846 } 847 auto findConfig = managedItem->second.find(interface); 848 if (findConfig == managedItem->second.end()) 849 { 850 BMCWEB_LOG_ERROR 851 << "Failed to find interface in managed object"; 852 messages::internalError(response->res); 853 return CreatePIDRet::fail; 854 } 855 auto findProfiles = findConfig->second.find("Profiles"); 856 if (findProfiles != findConfig->second.end()) 857 { 858 const std::vector<std::string>* curProfiles = 859 std::get_if<std::vector<std::string>>( 860 &(findProfiles->second)); 861 if (curProfiles == nullptr) 862 { 863 BMCWEB_LOG_ERROR << "Illegal profiles in managed object"; 864 messages::internalError(response->res); 865 return CreatePIDRet::fail; 866 } 867 if (std::find(curProfiles->begin(), curProfiles->end(), 868 profile) == curProfiles->end()) 869 { 870 std::vector<std::string> newProfiles = *curProfiles; 871 newProfiles.push_back(profile); 872 output["Profiles"] = newProfiles; 873 } 874 } 875 } 876 } 877 878 if (type == "PidControllers" || type == "FanControllers") 879 { 880 if (createNewObject) 881 { 882 output["Class"] = type == "PidControllers" ? std::string("temp") 883 : std::string("fan"); 884 output["Type"] = std::string("Pid"); 885 } 886 887 std::optional<std::vector<nlohmann::json>> zones; 888 std::optional<std::vector<std::string>> inputs; 889 std::optional<std::vector<std::string>> outputs; 890 std::map<std::string, std::optional<double>> doubles; 891 std::optional<std::string> setpointOffset; 892 if (!redfish::json_util::readJson( 893 it.value(), response->res, "Inputs", inputs, "Outputs", outputs, 894 "Zones", zones, "FFGainCoefficient", 895 doubles["FFGainCoefficient"], "FFOffCoefficient", 896 doubles["FFOffCoefficient"], "ICoefficient", 897 doubles["ICoefficient"], "ILimitMax", doubles["ILimitMax"], 898 "ILimitMin", doubles["ILimitMin"], "OutLimitMax", 899 doubles["OutLimitMax"], "OutLimitMin", doubles["OutLimitMin"], 900 "PCoefficient", doubles["PCoefficient"], "SetPoint", 901 doubles["SetPoint"], "SetPointOffset", setpointOffset, 902 "SlewNeg", doubles["SlewNeg"], "SlewPos", doubles["SlewPos"], 903 "PositiveHysteresis", doubles["PositiveHysteresis"], 904 "NegativeHysteresis", doubles["NegativeHysteresis"])) 905 { 906 BMCWEB_LOG_ERROR 907 << "Illegal Property " 908 << it.value().dump(2, ' ', true, 909 nlohmann::json::error_handler_t::replace); 910 return CreatePIDRet::fail; 911 } 912 if (zones) 913 { 914 std::vector<std::string> zonesStr; 915 if (!getZonesFromJsonReq(response, *zones, zonesStr)) 916 { 917 BMCWEB_LOG_ERROR << "Illegal Zones"; 918 return CreatePIDRet::fail; 919 } 920 if (chassis.empty() && 921 !findChassis(managedObj, zonesStr[0], chassis)) 922 { 923 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch"; 924 messages::invalidObject(response->res, it.key()); 925 return CreatePIDRet::fail; 926 } 927 928 output["Zones"] = std::move(zonesStr); 929 } 930 if (inputs || outputs) 931 { 932 std::array<std::optional<std::vector<std::string>>*, 2> containers = 933 {&inputs, &outputs}; 934 size_t index = 0; 935 for (const auto& containerPtr : containers) 936 { 937 std::optional<std::vector<std::string>>& container = 938 *containerPtr; 939 if (!container) 940 { 941 index++; 942 continue; 943 } 944 945 for (std::string& value : *container) 946 { 947 boost::replace_all(value, "_", " "); 948 } 949 std::string key; 950 if (index == 0) 951 { 952 key = "Inputs"; 953 } 954 else 955 { 956 key = "Outputs"; 957 } 958 output[key] = *container; 959 index++; 960 } 961 } 962 963 if (setpointOffset) 964 { 965 // translate between redfish and dbus names 966 if (*setpointOffset == "UpperThresholdNonCritical") 967 { 968 output["SetPointOffset"] = std::string("WarningLow"); 969 } 970 else if (*setpointOffset == "LowerThresholdNonCritical") 971 { 972 output["SetPointOffset"] = std::string("WarningHigh"); 973 } 974 else if (*setpointOffset == "LowerThresholdCritical") 975 { 976 output["SetPointOffset"] = std::string("CriticalLow"); 977 } 978 else if (*setpointOffset == "UpperThresholdCritical") 979 { 980 output["SetPointOffset"] = std::string("CriticalHigh"); 981 } 982 else 983 { 984 BMCWEB_LOG_ERROR << "Invalid setpointoffset " 985 << *setpointOffset; 986 messages::invalidObject(response->res, it.key()); 987 return CreatePIDRet::fail; 988 } 989 } 990 991 // doubles 992 for (const auto& pairs : doubles) 993 { 994 if (!pairs.second) 995 { 996 continue; 997 } 998 BMCWEB_LOG_DEBUG << pairs.first << " = " << *pairs.second; 999 output[pairs.first] = *(pairs.second); 1000 } 1001 } 1002 1003 else if (type == "FanZones") 1004 { 1005 output["Type"] = std::string("Pid.Zone"); 1006 1007 std::optional<nlohmann::json> chassisContainer; 1008 std::optional<double> failSafePercent; 1009 std::optional<double> minThermalOutput; 1010 if (!redfish::json_util::readJson(it.value(), response->res, "Chassis", 1011 chassisContainer, "FailSafePercent", 1012 failSafePercent, "MinThermalOutput", 1013 minThermalOutput)) 1014 { 1015 BMCWEB_LOG_ERROR 1016 << "Illegal Property " 1017 << it.value().dump(2, ' ', true, 1018 nlohmann::json::error_handler_t::replace); 1019 return CreatePIDRet::fail; 1020 } 1021 1022 if (chassisContainer) 1023 { 1024 1025 std::string chassisId; 1026 if (!redfish::json_util::readJson(*chassisContainer, response->res, 1027 "@odata.id", chassisId)) 1028 { 1029 BMCWEB_LOG_ERROR 1030 << "Illegal Property " 1031 << chassisContainer->dump( 1032 2, ' ', true, 1033 nlohmann::json::error_handler_t::replace); 1034 return CreatePIDRet::fail; 1035 } 1036 1037 // /redfish/v1/chassis/chassis_name/ 1038 if (!dbus::utility::getNthStringFromPath(chassisId, 3, chassis)) 1039 { 1040 BMCWEB_LOG_ERROR << "Got invalid path " << chassisId; 1041 messages::invalidObject(response->res, chassisId); 1042 return CreatePIDRet::fail; 1043 } 1044 } 1045 if (minThermalOutput) 1046 { 1047 output["MinThermalOutput"] = *minThermalOutput; 1048 } 1049 if (failSafePercent) 1050 { 1051 output["FailSafePercent"] = *failSafePercent; 1052 } 1053 } 1054 else if (type == "StepwiseControllers") 1055 { 1056 output["Type"] = std::string("Stepwise"); 1057 1058 std::optional<std::vector<nlohmann::json>> zones; 1059 std::optional<std::vector<nlohmann::json>> steps; 1060 std::optional<std::vector<std::string>> inputs; 1061 std::optional<double> positiveHysteresis; 1062 std::optional<double> negativeHysteresis; 1063 std::optional<std::string> direction; // upper clipping curve vs lower 1064 if (!redfish::json_util::readJson( 1065 it.value(), response->res, "Zones", zones, "Steps", steps, 1066 "Inputs", inputs, "PositiveHysteresis", positiveHysteresis, 1067 "NegativeHysteresis", negativeHysteresis, "Direction", 1068 direction)) 1069 { 1070 BMCWEB_LOG_ERROR 1071 << "Illegal Property " 1072 << it.value().dump(2, ' ', true, 1073 nlohmann::json::error_handler_t::replace); 1074 return CreatePIDRet::fail; 1075 } 1076 1077 if (zones) 1078 { 1079 std::vector<std::string> zonesStrs; 1080 if (!getZonesFromJsonReq(response, *zones, zonesStrs)) 1081 { 1082 BMCWEB_LOG_ERROR << "Illegal Zones"; 1083 return CreatePIDRet::fail; 1084 } 1085 if (chassis.empty() && 1086 !findChassis(managedObj, zonesStrs[0], chassis)) 1087 { 1088 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch"; 1089 messages::invalidObject(response->res, it.key()); 1090 return CreatePIDRet::fail; 1091 } 1092 output["Zones"] = std::move(zonesStrs); 1093 } 1094 if (steps) 1095 { 1096 std::vector<double> readings; 1097 std::vector<double> outputs; 1098 for (auto& step : *steps) 1099 { 1100 double target; 1101 double out; 1102 1103 if (!redfish::json_util::readJson(step, response->res, "Target", 1104 target, "Output", out)) 1105 { 1106 BMCWEB_LOG_ERROR 1107 << "Illegal Property " 1108 << it.value().dump( 1109 2, ' ', true, 1110 nlohmann::json::error_handler_t::replace); 1111 return CreatePIDRet::fail; 1112 } 1113 readings.emplace_back(target); 1114 outputs.emplace_back(out); 1115 } 1116 output["Reading"] = std::move(readings); 1117 output["Output"] = std::move(outputs); 1118 } 1119 if (inputs) 1120 { 1121 for (std::string& value : *inputs) 1122 { 1123 boost::replace_all(value, "_", " "); 1124 } 1125 output["Inputs"] = std::move(*inputs); 1126 } 1127 if (negativeHysteresis) 1128 { 1129 output["NegativeHysteresis"] = *negativeHysteresis; 1130 } 1131 if (positiveHysteresis) 1132 { 1133 output["PositiveHysteresis"] = *positiveHysteresis; 1134 } 1135 if (direction) 1136 { 1137 constexpr const std::array<const char*, 2> allowedDirections = { 1138 "Ceiling", "Floor"}; 1139 if (std::find(allowedDirections.begin(), allowedDirections.end(), 1140 *direction) == allowedDirections.end()) 1141 { 1142 messages::propertyValueTypeError(response->res, "Direction", 1143 *direction); 1144 return CreatePIDRet::fail; 1145 } 1146 output["Class"] = *direction; 1147 } 1148 } 1149 else 1150 { 1151 BMCWEB_LOG_ERROR << "Illegal Type " << type; 1152 messages::propertyUnknown(response->res, type); 1153 return CreatePIDRet::fail; 1154 } 1155 return CreatePIDRet::patch; 1156 } 1157 struct GetPIDValues : std::enable_shared_from_this<GetPIDValues> 1158 { 1159 1160 GetPIDValues(const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn) : 1161 asyncResp(asyncRespIn) 1162 1163 {} 1164 1165 void run() 1166 { 1167 std::shared_ptr<GetPIDValues> self = shared_from_this(); 1168 1169 // get all configurations 1170 crow::connections::systemBus->async_method_call( 1171 [self](const boost::system::error_code ec, 1172 const crow::openbmc_mapper::GetSubTreeType& subtreeLocal) { 1173 if (ec) 1174 { 1175 BMCWEB_LOG_ERROR << ec; 1176 messages::internalError(self->asyncResp->res); 1177 return; 1178 } 1179 self->subtree = subtreeLocal; 1180 }, 1181 "xyz.openbmc_project.ObjectMapper", 1182 "/xyz/openbmc_project/object_mapper", 1183 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0, 1184 std::array<const char*, 4>{ 1185 pidConfigurationIface, pidZoneConfigurationIface, 1186 objectManagerIface, stepwiseConfigurationIface}); 1187 1188 // at the same time get the selected profile 1189 crow::connections::systemBus->async_method_call( 1190 [self](const boost::system::error_code ec, 1191 const crow::openbmc_mapper::GetSubTreeType& subtreeLocal) { 1192 if (ec || subtreeLocal.empty()) 1193 { 1194 return; 1195 } 1196 if (subtreeLocal[0].second.size() != 1) 1197 { 1198 // invalid mapper response, should never happen 1199 BMCWEB_LOG_ERROR << "GetPIDValues: Mapper Error"; 1200 messages::internalError(self->asyncResp->res); 1201 return; 1202 } 1203 1204 const std::string& path = subtreeLocal[0].first; 1205 const std::string& owner = subtreeLocal[0].second[0].first; 1206 crow::connections::systemBus->async_method_call( 1207 [path, owner, self]( 1208 const boost::system::error_code ec2, 1209 const boost::container::flat_map< 1210 std::string, std::variant<std::vector<std::string>, 1211 std::string>>& resp) { 1212 if (ec2) 1213 { 1214 BMCWEB_LOG_ERROR << "GetPIDValues: Can't get " 1215 "thermalModeIface " 1216 << path; 1217 messages::internalError(self->asyncResp->res); 1218 return; 1219 } 1220 const std::string* current = nullptr; 1221 const std::vector<std::string>* supported = nullptr; 1222 for (auto& [key, value] : resp) 1223 { 1224 if (key == "Current") 1225 { 1226 current = std::get_if<std::string>(&value); 1227 if (current == nullptr) 1228 { 1229 BMCWEB_LOG_ERROR 1230 << "GetPIDValues: thermal mode " 1231 "iface invalid " 1232 << path; 1233 messages::internalError( 1234 self->asyncResp->res); 1235 return; 1236 } 1237 } 1238 if (key == "Supported") 1239 { 1240 supported = 1241 std::get_if<std::vector<std::string>>( 1242 &value); 1243 if (supported == nullptr) 1244 { 1245 BMCWEB_LOG_ERROR 1246 << "GetPIDValues: thermal mode " 1247 "iface invalid" 1248 << path; 1249 messages::internalError( 1250 self->asyncResp->res); 1251 return; 1252 } 1253 } 1254 } 1255 if (current == nullptr || supported == nullptr) 1256 { 1257 BMCWEB_LOG_ERROR << "GetPIDValues: thermal mode " 1258 "iface invalid " 1259 << path; 1260 messages::internalError(self->asyncResp->res); 1261 return; 1262 } 1263 self->currentProfile = *current; 1264 self->supportedProfiles = *supported; 1265 }, 1266 owner, path, "org.freedesktop.DBus.Properties", "GetAll", 1267 thermalModeIface); 1268 }, 1269 "xyz.openbmc_project.ObjectMapper", 1270 "/xyz/openbmc_project/object_mapper", 1271 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0, 1272 std::array<const char*, 1>{thermalModeIface}); 1273 } 1274 1275 ~GetPIDValues() 1276 { 1277 if (asyncResp->res.result() != boost::beast::http::status::ok) 1278 { 1279 return; 1280 } 1281 // create map of <connection, path to objMgr>> 1282 boost::container::flat_map<std::string, std::string> objectMgrPaths; 1283 boost::container::flat_set<std::string> calledConnections; 1284 for (const auto& pathGroup : subtree) 1285 { 1286 for (const auto& connectionGroup : pathGroup.second) 1287 { 1288 auto findConnection = 1289 calledConnections.find(connectionGroup.first); 1290 if (findConnection != calledConnections.end()) 1291 { 1292 break; 1293 } 1294 for (const std::string& interface : connectionGroup.second) 1295 { 1296 if (interface == objectManagerIface) 1297 { 1298 objectMgrPaths[connectionGroup.first] = pathGroup.first; 1299 } 1300 // this list is alphabetical, so we 1301 // should have found the objMgr by now 1302 if (interface == pidConfigurationIface || 1303 interface == pidZoneConfigurationIface || 1304 interface == stepwiseConfigurationIface) 1305 { 1306 auto findObjMgr = 1307 objectMgrPaths.find(connectionGroup.first); 1308 if (findObjMgr == objectMgrPaths.end()) 1309 { 1310 BMCWEB_LOG_DEBUG << connectionGroup.first 1311 << "Has no Object Manager"; 1312 continue; 1313 } 1314 1315 calledConnections.insert(connectionGroup.first); 1316 1317 asyncPopulatePid(findObjMgr->first, findObjMgr->second, 1318 currentProfile, supportedProfiles, 1319 asyncResp); 1320 break; 1321 } 1322 } 1323 } 1324 } 1325 } 1326 1327 std::vector<std::string> supportedProfiles; 1328 std::string currentProfile; 1329 crow::openbmc_mapper::GetSubTreeType subtree; 1330 std::shared_ptr<bmcweb::AsyncResp> asyncResp; 1331 }; 1332 1333 struct SetPIDValues : std::enable_shared_from_this<SetPIDValues> 1334 { 1335 1336 SetPIDValues(const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn, 1337 nlohmann::json& data) : 1338 asyncResp(asyncRespIn) 1339 { 1340 1341 std::optional<nlohmann::json> pidControllers; 1342 std::optional<nlohmann::json> fanControllers; 1343 std::optional<nlohmann::json> fanZones; 1344 std::optional<nlohmann::json> stepwiseControllers; 1345 1346 if (!redfish::json_util::readJson( 1347 data, asyncResp->res, "PidControllers", pidControllers, 1348 "FanControllers", fanControllers, "FanZones", fanZones, 1349 "StepwiseControllers", stepwiseControllers, "Profile", profile)) 1350 { 1351 BMCWEB_LOG_ERROR 1352 << "Illegal Property " 1353 << data.dump(2, ' ', true, 1354 nlohmann::json::error_handler_t::replace); 1355 return; 1356 } 1357 configuration.emplace_back("PidControllers", std::move(pidControllers)); 1358 configuration.emplace_back("FanControllers", std::move(fanControllers)); 1359 configuration.emplace_back("FanZones", std::move(fanZones)); 1360 configuration.emplace_back("StepwiseControllers", 1361 std::move(stepwiseControllers)); 1362 } 1363 void run() 1364 { 1365 if (asyncResp->res.result() != boost::beast::http::status::ok) 1366 { 1367 return; 1368 } 1369 1370 std::shared_ptr<SetPIDValues> self = shared_from_this(); 1371 1372 // todo(james): might make sense to do a mapper call here if this 1373 // interface gets more traction 1374 crow::connections::systemBus->async_method_call( 1375 [self](const boost::system::error_code ec, 1376 dbus::utility::ManagedObjectType& mObj) { 1377 if (ec) 1378 { 1379 BMCWEB_LOG_ERROR << "Error communicating to Entity Manager"; 1380 messages::internalError(self->asyncResp->res); 1381 return; 1382 } 1383 const std::array<const char*, 3> configurations = { 1384 pidConfigurationIface, pidZoneConfigurationIface, 1385 stepwiseConfigurationIface}; 1386 1387 for (const auto& [path, object] : mObj) 1388 { 1389 for (const auto& [interface, _] : object) 1390 { 1391 if (std::find(configurations.begin(), 1392 configurations.end(), 1393 interface) != configurations.end()) 1394 { 1395 self->objectCount++; 1396 break; 1397 } 1398 } 1399 } 1400 self->managedObj = std::move(mObj); 1401 }, 1402 "xyz.openbmc_project.EntityManager", "/", objectManagerIface, 1403 "GetManagedObjects"); 1404 1405 // at the same time get the profile information 1406 crow::connections::systemBus->async_method_call( 1407 [self](const boost::system::error_code ec, 1408 const crow::openbmc_mapper::GetSubTreeType& subtree) { 1409 if (ec || subtree.empty()) 1410 { 1411 return; 1412 } 1413 if (subtree[0].second.empty()) 1414 { 1415 // invalid mapper response, should never happen 1416 BMCWEB_LOG_ERROR << "SetPIDValues: Mapper Error"; 1417 messages::internalError(self->asyncResp->res); 1418 return; 1419 } 1420 1421 const std::string& path = subtree[0].first; 1422 const std::string& owner = subtree[0].second[0].first; 1423 crow::connections::systemBus->async_method_call( 1424 [self, path, owner]( 1425 const boost::system::error_code ec2, 1426 const boost::container::flat_map< 1427 std::string, std::variant<std::vector<std::string>, 1428 std::string>>& r) { 1429 if (ec2) 1430 { 1431 BMCWEB_LOG_ERROR << "SetPIDValues: Can't get " 1432 "thermalModeIface " 1433 << path; 1434 messages::internalError(self->asyncResp->res); 1435 return; 1436 } 1437 const std::string* current = nullptr; 1438 const std::vector<std::string>* supported = nullptr; 1439 for (auto& [key, value] : r) 1440 { 1441 if (key == "Current") 1442 { 1443 current = std::get_if<std::string>(&value); 1444 if (current == nullptr) 1445 { 1446 BMCWEB_LOG_ERROR 1447 << "SetPIDValues: thermal mode " 1448 "iface invalid " 1449 << path; 1450 messages::internalError( 1451 self->asyncResp->res); 1452 return; 1453 } 1454 } 1455 if (key == "Supported") 1456 { 1457 supported = 1458 std::get_if<std::vector<std::string>>( 1459 &value); 1460 if (supported == nullptr) 1461 { 1462 BMCWEB_LOG_ERROR 1463 << "SetPIDValues: thermal mode " 1464 "iface invalid" 1465 << path; 1466 messages::internalError( 1467 self->asyncResp->res); 1468 return; 1469 } 1470 } 1471 } 1472 if (current == nullptr || supported == nullptr) 1473 { 1474 BMCWEB_LOG_ERROR << "SetPIDValues: thermal mode " 1475 "iface invalid " 1476 << path; 1477 messages::internalError(self->asyncResp->res); 1478 return; 1479 } 1480 self->currentProfile = *current; 1481 self->supportedProfiles = *supported; 1482 self->profileConnection = owner; 1483 self->profilePath = path; 1484 }, 1485 owner, path, "org.freedesktop.DBus.Properties", "GetAll", 1486 thermalModeIface); 1487 }, 1488 "xyz.openbmc_project.ObjectMapper", 1489 "/xyz/openbmc_project/object_mapper", 1490 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0, 1491 std::array<const char*, 1>{thermalModeIface}); 1492 } 1493 ~SetPIDValues() 1494 { 1495 if (asyncResp->res.result() != boost::beast::http::status::ok) 1496 { 1497 return; 1498 } 1499 std::shared_ptr<bmcweb::AsyncResp> response = asyncResp; 1500 if (profile) 1501 { 1502 if (std::find(supportedProfiles.begin(), supportedProfiles.end(), 1503 *profile) == supportedProfiles.end()) 1504 { 1505 messages::actionParameterUnknown(response->res, "Profile", 1506 *profile); 1507 return; 1508 } 1509 currentProfile = *profile; 1510 crow::connections::systemBus->async_method_call( 1511 [response](const boost::system::error_code ec) { 1512 if (ec) 1513 { 1514 BMCWEB_LOG_ERROR << "Error patching profile" << ec; 1515 messages::internalError(response->res); 1516 } 1517 }, 1518 profileConnection, profilePath, 1519 "org.freedesktop.DBus.Properties", "Set", thermalModeIface, 1520 "Current", std::variant<std::string>(*profile)); 1521 } 1522 1523 for (auto& containerPair : configuration) 1524 { 1525 auto& container = containerPair.second; 1526 if (!container) 1527 { 1528 continue; 1529 } 1530 BMCWEB_LOG_DEBUG << *container; 1531 1532 std::string& type = containerPair.first; 1533 1534 for (nlohmann::json::iterator it = container->begin(); 1535 it != container->end(); ++it) 1536 { 1537 const auto& name = it.key(); 1538 BMCWEB_LOG_DEBUG << "looking for " << name; 1539 1540 auto pathItr = 1541 std::find_if(managedObj.begin(), managedObj.end(), 1542 [&name](const auto& obj) { 1543 return boost::algorithm::ends_with( 1544 obj.first.str, "/" + name); 1545 }); 1546 boost::container::flat_map<std::string, 1547 dbus::utility::DbusVariantType> 1548 output; 1549 1550 output.reserve(16); // The pid interface length 1551 1552 // determines if we're patching entity-manager or 1553 // creating a new object 1554 bool createNewObject = (pathItr == managedObj.end()); 1555 BMCWEB_LOG_DEBUG << "Found = " << !createNewObject; 1556 1557 std::string iface; 1558 if (type == "PidControllers" || type == "FanControllers") 1559 { 1560 iface = pidConfigurationIface; 1561 if (!createNewObject && 1562 pathItr->second.find(pidConfigurationIface) == 1563 pathItr->second.end()) 1564 { 1565 createNewObject = true; 1566 } 1567 } 1568 else if (type == "FanZones") 1569 { 1570 iface = pidZoneConfigurationIface; 1571 if (!createNewObject && 1572 pathItr->second.find(pidZoneConfigurationIface) == 1573 pathItr->second.end()) 1574 { 1575 1576 createNewObject = true; 1577 } 1578 } 1579 else if (type == "StepwiseControllers") 1580 { 1581 iface = stepwiseConfigurationIface; 1582 if (!createNewObject && 1583 pathItr->second.find(stepwiseConfigurationIface) == 1584 pathItr->second.end()) 1585 { 1586 createNewObject = true; 1587 } 1588 } 1589 1590 if (createNewObject && it.value() == nullptr) 1591 { 1592 // can't delete a non-existent object 1593 messages::invalidObject(response->res, name); 1594 continue; 1595 } 1596 1597 std::string path; 1598 if (pathItr != managedObj.end()) 1599 { 1600 path = pathItr->first.str; 1601 } 1602 1603 BMCWEB_LOG_DEBUG << "Create new = " << createNewObject << "\n"; 1604 1605 // arbitrary limit to avoid attacks 1606 constexpr const size_t controllerLimit = 500; 1607 if (createNewObject && objectCount >= controllerLimit) 1608 { 1609 messages::resourceExhaustion(response->res, type); 1610 continue; 1611 } 1612 1613 output["Name"] = boost::replace_all_copy(name, "_", " "); 1614 1615 std::string chassis; 1616 CreatePIDRet ret = createPidInterface( 1617 response, type, it, path, managedObj, createNewObject, 1618 output, chassis, currentProfile); 1619 if (ret == CreatePIDRet::fail) 1620 { 1621 return; 1622 } 1623 if (ret == CreatePIDRet::del) 1624 { 1625 continue; 1626 } 1627 1628 if (!createNewObject) 1629 { 1630 for (const auto& property : output) 1631 { 1632 crow::connections::systemBus->async_method_call( 1633 [response, 1634 propertyName{std::string(property.first)}]( 1635 const boost::system::error_code ec) { 1636 if (ec) 1637 { 1638 BMCWEB_LOG_ERROR << "Error patching " 1639 << propertyName << ": " 1640 << ec; 1641 messages::internalError(response->res); 1642 return; 1643 } 1644 messages::success(response->res); 1645 }, 1646 "xyz.openbmc_project.EntityManager", path, 1647 "org.freedesktop.DBus.Properties", "Set", iface, 1648 property.first, property.second); 1649 } 1650 } 1651 else 1652 { 1653 if (chassis.empty()) 1654 { 1655 BMCWEB_LOG_ERROR << "Failed to get chassis from config"; 1656 messages::invalidObject(response->res, name); 1657 return; 1658 } 1659 1660 bool foundChassis = false; 1661 for (const auto& obj : managedObj) 1662 { 1663 if (boost::algorithm::ends_with(obj.first.str, chassis)) 1664 { 1665 chassis = obj.first.str; 1666 foundChassis = true; 1667 break; 1668 } 1669 } 1670 if (!foundChassis) 1671 { 1672 BMCWEB_LOG_ERROR << "Failed to find chassis on dbus"; 1673 messages::resourceMissingAtURI( 1674 response->res, "/redfish/v1/Chassis/" + chassis); 1675 return; 1676 } 1677 1678 crow::connections::systemBus->async_method_call( 1679 [response](const boost::system::error_code ec) { 1680 if (ec) 1681 { 1682 BMCWEB_LOG_ERROR << "Error Adding Pid Object " 1683 << ec; 1684 messages::internalError(response->res); 1685 return; 1686 } 1687 messages::success(response->res); 1688 }, 1689 "xyz.openbmc_project.EntityManager", chassis, 1690 "xyz.openbmc_project.AddObject", "AddObject", output); 1691 } 1692 } 1693 } 1694 } 1695 std::shared_ptr<bmcweb::AsyncResp> asyncResp; 1696 std::vector<std::pair<std::string, std::optional<nlohmann::json>>> 1697 configuration; 1698 std::optional<std::string> profile; 1699 dbus::utility::ManagedObjectType managedObj; 1700 std::vector<std::string> supportedProfiles; 1701 std::string currentProfile; 1702 std::string profileConnection; 1703 std::string profilePath; 1704 size_t objectCount = 0; 1705 }; 1706 1707 /** 1708 * @brief Retrieves BMC manager location data over DBus 1709 * 1710 * @param[in] aResp Shared pointer for completing asynchronous calls 1711 * @param[in] connectionName - service name 1712 * @param[in] path - object path 1713 * @return none 1714 */ 1715 inline void getLocation(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 1716 const std::string& connectionName, 1717 const std::string& path) 1718 { 1719 BMCWEB_LOG_DEBUG << "Get BMC manager Location data."; 1720 1721 crow::connections::systemBus->async_method_call( 1722 [aResp](const boost::system::error_code ec, 1723 const std::variant<std::string>& property) { 1724 if (ec) 1725 { 1726 BMCWEB_LOG_DEBUG << "DBUS response error for " 1727 "Location"; 1728 messages::internalError(aResp->res); 1729 return; 1730 } 1731 1732 const std::string* value = std::get_if<std::string>(&property); 1733 1734 if (value == nullptr) 1735 { 1736 // illegal value 1737 messages::internalError(aResp->res); 1738 return; 1739 } 1740 1741 aResp->res.jsonValue["Location"]["PartLocation"]["ServiceLabel"] = 1742 *value; 1743 }, 1744 connectionName, path, "org.freedesktop.DBus.Properties", "Get", 1745 "xyz.openbmc_project.Inventory.Decorator." 1746 "LocationCode", 1747 "LocationCode"); 1748 } 1749 1750 class Manager : public Node 1751 { 1752 public: 1753 Manager(App& app) : Node(app, "/redfish/v1/Managers/bmc/") 1754 { 1755 1756 uuid = persistent_data::getConfig().systemUuid; 1757 entityPrivileges = { 1758 {boost::beast::http::verb::get, {{"Login"}}}, 1759 {boost::beast::http::verb::head, {{"Login"}}}, 1760 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1761 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1762 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1763 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1764 } 1765 1766 private: 1767 void doGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1768 const crow::Request&, const std::vector<std::string>&) override 1769 { 1770 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc"; 1771 asyncResp->res.jsonValue["@odata.type"] = "#Manager.v1_11_0.Manager"; 1772 asyncResp->res.jsonValue["Id"] = "bmc"; 1773 asyncResp->res.jsonValue["Name"] = "OpenBmc Manager"; 1774 asyncResp->res.jsonValue["Description"] = 1775 "Baseboard Management Controller"; 1776 asyncResp->res.jsonValue["PowerState"] = "On"; 1777 asyncResp->res.jsonValue["Status"] = {{"State", "Enabled"}, 1778 {"Health", "OK"}}; 1779 asyncResp->res.jsonValue["ManagerType"] = "BMC"; 1780 asyncResp->res.jsonValue["UUID"] = systemd_utils::getUuid(); 1781 asyncResp->res.jsonValue["ServiceEntryPointUUID"] = uuid; 1782 asyncResp->res.jsonValue["Model"] = "OpenBmc"; // TODO(ed), get model 1783 1784 asyncResp->res.jsonValue["LogServices"] = { 1785 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices"}}; 1786 1787 asyncResp->res.jsonValue["NetworkProtocol"] = { 1788 {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol"}}; 1789 1790 asyncResp->res.jsonValue["EthernetInterfaces"] = { 1791 {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces"}}; 1792 1793 #ifdef BMCWEB_ENABLE_VM_NBDPROXY 1794 asyncResp->res.jsonValue["VirtualMedia"] = { 1795 {"@odata.id", "/redfish/v1/Managers/bmc/VirtualMedia"}}; 1796 #endif // BMCWEB_ENABLE_VM_NBDPROXY 1797 1798 // default oem data 1799 nlohmann::json& oem = asyncResp->res.jsonValue["Oem"]; 1800 nlohmann::json& oemOpenbmc = oem["OpenBmc"]; 1801 oem["@odata.type"] = "#OemManager.Oem"; 1802 oem["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem"; 1803 oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc"; 1804 oemOpenbmc["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc"; 1805 oemOpenbmc["Certificates"] = { 1806 {"@odata.id", "/redfish/v1/Managers/bmc/Truststore/Certificates"}}; 1807 1808 // Manager.Reset (an action) can be many values, OpenBMC only supports 1809 // BMC reboot. 1810 nlohmann::json& managerReset = 1811 asyncResp->res.jsonValue["Actions"]["#Manager.Reset"]; 1812 managerReset["target"] = 1813 "/redfish/v1/Managers/bmc/Actions/Manager.Reset"; 1814 managerReset["@Redfish.ActionInfo"] = 1815 "/redfish/v1/Managers/bmc/ResetActionInfo"; 1816 1817 // ResetToDefaults (Factory Reset) has values like 1818 // PreserveNetworkAndUsers and PreserveNetwork that aren't supported 1819 // on OpenBMC 1820 nlohmann::json& resetToDefaults = 1821 asyncResp->res.jsonValue["Actions"]["#Manager.ResetToDefaults"]; 1822 resetToDefaults["target"] = 1823 "/redfish/v1/Managers/bmc/Actions/Manager.ResetToDefaults"; 1824 resetToDefaults["ResetType@Redfish.AllowableValues"] = {"ResetAll"}; 1825 1826 asyncResp->res.jsonValue["DateTime"] = crow::utility::dateTimeNow(); 1827 1828 // Fill in SerialConsole info 1829 asyncResp->res.jsonValue["SerialConsole"]["ServiceEnabled"] = true; 1830 asyncResp->res.jsonValue["SerialConsole"]["MaxConcurrentSessions"] = 15; 1831 asyncResp->res.jsonValue["SerialConsole"]["ConnectTypesSupported"] = { 1832 "IPMI", "SSH"}; 1833 #ifdef BMCWEB_ENABLE_KVM 1834 // Fill in GraphicalConsole info 1835 asyncResp->res.jsonValue["GraphicalConsole"]["ServiceEnabled"] = true; 1836 asyncResp->res.jsonValue["GraphicalConsole"]["MaxConcurrentSessions"] = 1837 4; 1838 asyncResp->res 1839 .jsonValue["GraphicalConsole"]["ConnectTypesSupported"] = {"KVMIP"}; 1840 #endif // BMCWEB_ENABLE_KVM 1841 1842 asyncResp->res.jsonValue["Links"]["ManagerForServers@odata.count"] = 1; 1843 asyncResp->res.jsonValue["Links"]["ManagerForServers"] = { 1844 {{"@odata.id", "/redfish/v1/Systems/system"}}}; 1845 1846 auto health = std::make_shared<HealthPopulate>(asyncResp); 1847 health->isManagersHealth = true; 1848 health->populate(); 1849 1850 fw_util::populateFirmwareInformation(asyncResp, fw_util::bmcPurpose, 1851 "FirmwareVersion", true); 1852 1853 getLastResetTime(asyncResp); 1854 1855 auto pids = std::make_shared<GetPIDValues>(asyncResp); 1856 pids->run(); 1857 1858 getMainChassisId( 1859 asyncResp, [](const std::string& chassisId, 1860 const std::shared_ptr<bmcweb::AsyncResp>& aRsp) { 1861 aRsp->res.jsonValue["Links"]["ManagerForChassis@odata.count"] = 1862 1; 1863 aRsp->res.jsonValue["Links"]["ManagerForChassis"] = { 1864 {{"@odata.id", "/redfish/v1/Chassis/" + chassisId}}}; 1865 aRsp->res.jsonValue["Links"]["ManagerInChassis"] = { 1866 {"@odata.id", "/redfish/v1/Chassis/" + chassisId}}; 1867 }); 1868 1869 static bool started = false; 1870 1871 if (!started) 1872 { 1873 crow::connections::systemBus->async_method_call( 1874 [asyncResp](const boost::system::error_code ec, 1875 const std::variant<double>& resp) { 1876 if (ec) 1877 { 1878 BMCWEB_LOG_ERROR << "Error while getting progress"; 1879 messages::internalError(asyncResp->res); 1880 return; 1881 } 1882 const double* val = std::get_if<double>(&resp); 1883 if (val == nullptr) 1884 { 1885 BMCWEB_LOG_ERROR 1886 << "Invalid response while getting progress"; 1887 messages::internalError(asyncResp->res); 1888 return; 1889 } 1890 if (*val < 1.0) 1891 { 1892 asyncResp->res.jsonValue["Status"]["State"] = 1893 "Starting"; 1894 started = true; 1895 } 1896 }, 1897 "org.freedesktop.systemd1", "/org/freedesktop/systemd1", 1898 "org.freedesktop.DBus.Properties", "Get", 1899 "org.freedesktop.systemd1.Manager", "Progress"); 1900 } 1901 1902 crow::connections::systemBus->async_method_call( 1903 [asyncResp]( 1904 const boost::system::error_code ec, 1905 const std::vector<std::pair< 1906 std::string, std::vector<std::pair< 1907 std::string, std::vector<std::string>>>>>& 1908 subtree) { 1909 if (ec) 1910 { 1911 BMCWEB_LOG_DEBUG << "D-Bus response error on GetSubTree " 1912 << ec; 1913 return; 1914 } 1915 if (subtree.size() == 0) 1916 { 1917 BMCWEB_LOG_DEBUG << "Can't find bmc D-Bus object!"; 1918 return; 1919 } 1920 // Assume only 1 bmc D-Bus object 1921 // Throw an error if there is more than 1 1922 if (subtree.size() > 1) 1923 { 1924 BMCWEB_LOG_DEBUG << "Found more than 1 bmc D-Bus object!"; 1925 messages::internalError(asyncResp->res); 1926 return; 1927 } 1928 1929 if (subtree[0].first.empty() || subtree[0].second.size() != 1) 1930 { 1931 BMCWEB_LOG_DEBUG << "Error getting bmc D-Bus object!"; 1932 messages::internalError(asyncResp->res); 1933 return; 1934 } 1935 1936 const std::string& path = subtree[0].first; 1937 const std::string& connectionName = subtree[0].second[0].first; 1938 1939 for (const auto& interfaceName : subtree[0].second[0].second) 1940 { 1941 if (interfaceName == 1942 "xyz.openbmc_project.Inventory.Decorator.Asset") 1943 { 1944 crow::connections::systemBus->async_method_call( 1945 [asyncResp]( 1946 const boost::system::error_code ec, 1947 const std::vector<std::pair< 1948 std::string, std::variant<std::string>>>& 1949 propertiesList) { 1950 if (ec) 1951 { 1952 BMCWEB_LOG_DEBUG << "Can't get bmc asset!"; 1953 return; 1954 } 1955 for (const std::pair<std::string, 1956 std::variant<std::string>>& 1957 property : propertiesList) 1958 { 1959 const std::string& propertyName = 1960 property.first; 1961 1962 if ((propertyName == "PartNumber") || 1963 (propertyName == "SerialNumber") || 1964 (propertyName == "Manufacturer") || 1965 (propertyName == "Model") || 1966 (propertyName == "SparePartNumber")) 1967 { 1968 const std::string* value = 1969 std::get_if<std::string>( 1970 &property.second); 1971 if (value == nullptr) 1972 { 1973 // illegal property 1974 messages::internalError( 1975 asyncResp->res); 1976 return; 1977 } 1978 asyncResp->res.jsonValue[propertyName] = 1979 *value; 1980 } 1981 } 1982 }, 1983 connectionName, path, 1984 "org.freedesktop.DBus.Properties", "GetAll", 1985 "xyz.openbmc_project.Inventory.Decorator.Asset"); 1986 } 1987 else if (interfaceName == "xyz.openbmc_project.Inventory." 1988 "Decorator.LocationCode") 1989 { 1990 getLocation(asyncResp, connectionName, path); 1991 } 1992 } 1993 }, 1994 "xyz.openbmc_project.ObjectMapper", 1995 "/xyz/openbmc_project/object_mapper", 1996 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 1997 "/xyz/openbmc_project/inventory", int32_t(0), 1998 std::array<const char*, 1>{ 1999 "xyz.openbmc_project.Inventory.Item.Bmc"}); 2000 } 2001 2002 void doPatch(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2003 const crow::Request& req, 2004 const std::vector<std::string>&) override 2005 { 2006 std::optional<nlohmann::json> oem; 2007 std::optional<nlohmann::json> links; 2008 std::optional<std::string> datetime; 2009 2010 if (!json_util::readJson(req, asyncResp->res, "Oem", oem, "DateTime", 2011 datetime, "Links", links)) 2012 { 2013 return; 2014 } 2015 2016 if (oem) 2017 { 2018 std::optional<nlohmann::json> openbmc; 2019 if (!redfish::json_util::readJson(*oem, asyncResp->res, "OpenBmc", 2020 openbmc)) 2021 { 2022 BMCWEB_LOG_ERROR 2023 << "Illegal Property " 2024 << oem->dump(2, ' ', true, 2025 nlohmann::json::error_handler_t::replace); 2026 return; 2027 } 2028 if (openbmc) 2029 { 2030 std::optional<nlohmann::json> fan; 2031 if (!redfish::json_util::readJson(*openbmc, asyncResp->res, 2032 "Fan", fan)) 2033 { 2034 BMCWEB_LOG_ERROR 2035 << "Illegal Property " 2036 << openbmc->dump( 2037 2, ' ', true, 2038 nlohmann::json::error_handler_t::replace); 2039 return; 2040 } 2041 if (fan) 2042 { 2043 auto pid = std::make_shared<SetPIDValues>(asyncResp, *fan); 2044 pid->run(); 2045 } 2046 } 2047 } 2048 if (links) 2049 { 2050 std::optional<nlohmann::json> activeSoftwareImage; 2051 if (!redfish::json_util::readJson(*links, asyncResp->res, 2052 "ActiveSoftwareImage", 2053 activeSoftwareImage)) 2054 { 2055 return; 2056 } 2057 if (activeSoftwareImage) 2058 { 2059 std::optional<std::string> odataId; 2060 if (!json_util::readJson(*activeSoftwareImage, asyncResp->res, 2061 "@odata.id", odataId)) 2062 { 2063 return; 2064 } 2065 2066 if (odataId) 2067 { 2068 setActiveFirmwareImage(asyncResp, *odataId); 2069 } 2070 } 2071 } 2072 if (datetime) 2073 { 2074 setDateTime(asyncResp, std::move(*datetime)); 2075 } 2076 } 2077 2078 void getLastResetTime(const std::shared_ptr<bmcweb::AsyncResp>& aResp) 2079 { 2080 BMCWEB_LOG_DEBUG << "Getting Manager Last Reset Time"; 2081 2082 crow::connections::systemBus->async_method_call( 2083 [aResp](const boost::system::error_code ec, 2084 std::variant<uint64_t>& lastResetTime) { 2085 if (ec) 2086 { 2087 BMCWEB_LOG_DEBUG << "D-BUS response error " << ec; 2088 return; 2089 } 2090 2091 const uint64_t* lastResetTimePtr = 2092 std::get_if<uint64_t>(&lastResetTime); 2093 2094 if (!lastResetTimePtr) 2095 { 2096 messages::internalError(aResp->res); 2097 return; 2098 } 2099 // LastRebootTime is epoch time, in milliseconds 2100 // https://github.com/openbmc/phosphor-dbus-interfaces/blob/7f9a128eb9296e926422ddc312c148b625890bb6/xyz/openbmc_project/State/BMC.interface.yaml#L19 2101 time_t lastResetTimeStamp = 2102 static_cast<time_t>(*lastResetTimePtr / 1000); 2103 2104 // Convert to ISO 8601 standard 2105 aResp->res.jsonValue["LastResetTime"] = 2106 crow::utility::getDateTime(lastResetTimeStamp); 2107 }, 2108 "xyz.openbmc_project.State.BMC", "/xyz/openbmc_project/state/bmc0", 2109 "org.freedesktop.DBus.Properties", "Get", 2110 "xyz.openbmc_project.State.BMC", "LastRebootTime"); 2111 } 2112 2113 /** 2114 * @brief Set the running firmware image 2115 * 2116 * @param[i,o] aResp - Async response object 2117 * @param[i] runningFirmwareTarget - Image to make the running image 2118 * 2119 * @return void 2120 */ 2121 void setActiveFirmwareImage(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 2122 const std::string& runningFirmwareTarget) 2123 { 2124 // Get the Id from /redfish/v1/UpdateService/FirmwareInventory/<Id> 2125 std::string::size_type idPos = runningFirmwareTarget.rfind('/'); 2126 if (idPos == std::string::npos) 2127 { 2128 messages::propertyValueNotInList(aResp->res, runningFirmwareTarget, 2129 "@odata.id"); 2130 BMCWEB_LOG_DEBUG << "Can't parse firmware ID!"; 2131 return; 2132 } 2133 idPos++; 2134 if (idPos >= runningFirmwareTarget.size()) 2135 { 2136 messages::propertyValueNotInList(aResp->res, runningFirmwareTarget, 2137 "@odata.id"); 2138 BMCWEB_LOG_DEBUG << "Invalid firmware ID."; 2139 return; 2140 } 2141 std::string firmwareId = runningFirmwareTarget.substr(idPos); 2142 2143 // Make sure the image is valid before setting priority 2144 crow::connections::systemBus->async_method_call( 2145 [aResp, firmwareId, 2146 runningFirmwareTarget](const boost::system::error_code ec, 2147 ManagedObjectType& subtree) { 2148 if (ec) 2149 { 2150 BMCWEB_LOG_DEBUG << "D-Bus response error getting objects."; 2151 messages::internalError(aResp->res); 2152 return; 2153 } 2154 2155 if (subtree.size() == 0) 2156 { 2157 BMCWEB_LOG_DEBUG << "Can't find image!"; 2158 messages::internalError(aResp->res); 2159 return; 2160 } 2161 2162 bool foundImage = false; 2163 for (auto& object : subtree) 2164 { 2165 const std::string& path = 2166 static_cast<const std::string&>(object.first); 2167 std::size_t idPos2 = path.rfind('/'); 2168 2169 if (idPos2 == std::string::npos) 2170 { 2171 continue; 2172 } 2173 2174 idPos2++; 2175 if (idPos2 >= path.size()) 2176 { 2177 continue; 2178 } 2179 2180 if (path.substr(idPos2) == firmwareId) 2181 { 2182 foundImage = true; 2183 break; 2184 } 2185 } 2186 2187 if (!foundImage) 2188 { 2189 messages::propertyValueNotInList( 2190 aResp->res, runningFirmwareTarget, "@odata.id"); 2191 BMCWEB_LOG_DEBUG << "Invalid firmware ID."; 2192 return; 2193 } 2194 2195 BMCWEB_LOG_DEBUG << "Setting firmware version " + firmwareId + 2196 " to priority 0."; 2197 2198 // Only support Immediate 2199 // An addition could be a Redfish Setting like 2200 // ActiveSoftwareImageApplyTime and support OnReset 2201 crow::connections::systemBus->async_method_call( 2202 [aResp](const boost::system::error_code ec) { 2203 if (ec) 2204 { 2205 BMCWEB_LOG_DEBUG << "D-Bus response error setting."; 2206 messages::internalError(aResp->res); 2207 return; 2208 } 2209 doBMCGracefulRestart(aResp); 2210 }, 2211 2212 "xyz.openbmc_project.Software.BMC.Updater", 2213 "/xyz/openbmc_project/software/" + firmwareId, 2214 "org.freedesktop.DBus.Properties", "Set", 2215 "xyz.openbmc_project.Software.RedundancyPriority", 2216 "Priority", std::variant<uint8_t>(static_cast<uint8_t>(0))); 2217 }, 2218 "xyz.openbmc_project.Software.BMC.Updater", 2219 "/xyz/openbmc_project/software", 2220 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 2221 } 2222 2223 void setDateTime(std::shared_ptr<bmcweb::AsyncResp> aResp, 2224 std::string datetime) const 2225 { 2226 BMCWEB_LOG_DEBUG << "Set date time: " << datetime; 2227 2228 std::stringstream stream(datetime); 2229 // Convert from ISO 8601 to boost local_time 2230 // (BMC only has time in UTC) 2231 boost::posix_time::ptime posixTime; 2232 boost::posix_time::ptime epoch(boost::gregorian::date(1970, 1, 1)); 2233 // Facet gets deleted with the stringsteam 2234 auto ifc = std::make_unique<boost::local_time::local_time_input_facet>( 2235 "%Y-%m-%d %H:%M:%S%F %ZP"); 2236 stream.imbue(std::locale(stream.getloc(), ifc.release())); 2237 2238 boost::local_time::local_date_time ldt( 2239 boost::local_time::not_a_date_time); 2240 2241 if (stream >> ldt) 2242 { 2243 posixTime = ldt.utc_time(); 2244 boost::posix_time::time_duration dur = posixTime - epoch; 2245 uint64_t durMicroSecs = 2246 static_cast<uint64_t>(dur.total_microseconds()); 2247 crow::connections::systemBus->async_method_call( 2248 [aResp{std::move(aResp)}, datetime{std::move(datetime)}]( 2249 const boost::system::error_code ec) { 2250 if (ec) 2251 { 2252 BMCWEB_LOG_DEBUG << "Failed to set elapsed time. " 2253 "DBUS response error " 2254 << ec; 2255 messages::internalError(aResp->res); 2256 return; 2257 } 2258 aResp->res.jsonValue["DateTime"] = datetime; 2259 }, 2260 "xyz.openbmc_project.Time.Manager", 2261 "/xyz/openbmc_project/time/bmc", 2262 "org.freedesktop.DBus.Properties", "Set", 2263 "xyz.openbmc_project.Time.EpochTime", "Elapsed", 2264 std::variant<uint64_t>(durMicroSecs)); 2265 } 2266 else 2267 { 2268 messages::propertyValueFormatError(aResp->res, datetime, 2269 "DateTime"); 2270 return; 2271 } 2272 } 2273 2274 std::string uuid; 2275 }; 2276 2277 class ManagerCollection : public Node 2278 { 2279 public: 2280 ManagerCollection(App& app) : Node(app, "/redfish/v1/Managers/") 2281 { 2282 entityPrivileges = { 2283 {boost::beast::http::verb::get, {{"Login"}}}, 2284 {boost::beast::http::verb::head, {{"Login"}}}, 2285 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2286 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2287 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2288 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2289 } 2290 2291 private: 2292 void doGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2293 const crow::Request&, const std::vector<std::string>&) override 2294 { 2295 // Collections don't include the static data added by SubRoute 2296 // because it has a duplicate entry for members 2297 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Managers"; 2298 asyncResp->res.jsonValue["@odata.type"] = 2299 "#ManagerCollection.ManagerCollection"; 2300 asyncResp->res.jsonValue["Name"] = "Manager Collection"; 2301 asyncResp->res.jsonValue["Members@odata.count"] = 1; 2302 asyncResp->res.jsonValue["Members"] = { 2303 {{"@odata.id", "/redfish/v1/Managers/bmc"}}}; 2304 } 2305 }; 2306 } // namespace redfish 2307