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