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