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