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