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