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