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