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