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