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