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