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