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