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