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 "node.hpp" 19 20 #include <boost/algorithm/string/replace.hpp> 21 #include <boost/date_time.hpp> 22 #include <dbus_utility.hpp> 23 #include <memory> 24 #include <sstream> 25 #include <utils/systemd_utils.hpp> 26 #include <variant> 27 28 namespace redfish 29 { 30 31 /** 32 * ManagerActionsReset class supports handle POST method for Reset action. 33 * The class retrieves and sends data directly to dbus. 34 */ 35 class ManagerActionsReset : public Node 36 { 37 public: 38 ManagerActionsReset(CrowApp& app) : 39 Node(app, "/redfish/v1/Managers/bmc/Actions/Manager.Reset/") 40 { 41 entityPrivileges = { 42 {boost::beast::http::verb::get, {{"Login"}}}, 43 {boost::beast::http::verb::head, {{"Login"}}}, 44 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 45 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 46 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 47 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 48 } 49 50 private: 51 /** 52 * Function handles POST method request. 53 * Analyzes POST body message before sends Reset request data to dbus. 54 * OpenBMC allows for ResetType is GracefulRestart only. 55 */ 56 void doPost(crow::Response& res, const crow::Request& req, 57 const std::vector<std::string>& params) override 58 { 59 std::string resetType; 60 61 if (!json_util::readJson(req, res, "ResetType", resetType)) 62 { 63 return; 64 } 65 66 if (resetType != "GracefulRestart") 67 { 68 res.result(boost::beast::http::status::bad_request); 69 messages::actionParameterNotSupported(res, resetType, "ResetType"); 70 BMCWEB_LOG_ERROR << "Request incorrect action parameter: " 71 << resetType; 72 res.end(); 73 return; 74 } 75 doBMCGracefulRestart(res, req, params); 76 } 77 78 /** 79 * Function transceives data with dbus directly. 80 * All BMC state properties will be retrieved before sending reset request. 81 */ 82 void doBMCGracefulRestart(crow::Response& res, const crow::Request& req, 83 const std::vector<std::string>& params) 84 { 85 const char* processName = "xyz.openbmc_project.State.BMC"; 86 const char* objectPath = "/xyz/openbmc_project/state/bmc0"; 87 const char* interfaceName = "xyz.openbmc_project.State.BMC"; 88 const std::string& propertyValue = 89 "xyz.openbmc_project.State.BMC.Transition.Reboot"; 90 const char* destProperty = "RequestedBMCTransition"; 91 92 // Create the D-Bus variant for D-Bus call. 93 VariantType dbusPropertyValue(propertyValue); 94 95 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 96 97 crow::connections::systemBus->async_method_call( 98 [asyncResp](const boost::system::error_code ec) { 99 // Use "Set" method to set the property value. 100 if (ec) 101 { 102 BMCWEB_LOG_ERROR << "[Set] Bad D-Bus request error: " << ec; 103 messages::internalError(asyncResp->res); 104 return; 105 } 106 107 messages::success(asyncResp->res); 108 }, 109 processName, objectPath, "org.freedesktop.DBus.Properties", "Set", 110 interfaceName, destProperty, dbusPropertyValue); 111 } 112 }; 113 114 static constexpr const char* objectManagerIface = 115 "org.freedesktop.DBus.ObjectManager"; 116 static constexpr const char* pidConfigurationIface = 117 "xyz.openbmc_project.Configuration.Pid"; 118 static constexpr const char* pidZoneConfigurationIface = 119 "xyz.openbmc_project.Configuration.Pid.Zone"; 120 static constexpr const char* stepwiseConfigurationIface = 121 "xyz.openbmc_project.Configuration.Stepwise"; 122 static constexpr const char* thermalModeIface = 123 "xyz.openbmc_project.Control.ThermalMode"; 124 125 static void asyncPopulatePid(const std::string& connection, 126 const std::string& path, 127 const std::string& currentProfile, 128 const std::vector<std::string>& supportedProfiles, 129 std::shared_ptr<AsyncResp> asyncResp) 130 { 131 132 crow::connections::systemBus->async_method_call( 133 [asyncResp, currentProfile, supportedProfiles]( 134 const boost::system::error_code ec, 135 const dbus::utility::ManagedObjectType& managedObj) { 136 if (ec) 137 { 138 BMCWEB_LOG_ERROR << ec; 139 asyncResp->res.jsonValue.clear(); 140 messages::internalError(asyncResp->res); 141 return; 142 } 143 nlohmann::json& configRoot = 144 asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"]; 145 nlohmann::json& fans = configRoot["FanControllers"]; 146 fans["@odata.type"] = "#OemManager.FanControllers"; 147 fans["@odata.context"] = 148 "/redfish/v1/$metadata#OemManager.FanControllers"; 149 fans["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc/" 150 "Fan/FanControllers"; 151 152 nlohmann::json& pids = configRoot["PidControllers"]; 153 pids["@odata.type"] = "#OemManager.PidControllers"; 154 pids["@odata.context"] = 155 "/redfish/v1/$metadata#OemManager.PidControllers"; 156 pids["@odata.id"] = 157 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/PidControllers"; 158 159 nlohmann::json& stepwise = configRoot["StepwiseControllers"]; 160 stepwise["@odata.type"] = "#OemManager.StepwiseControllers"; 161 stepwise["@odata.context"] = 162 "/redfish/v1/$metadata#OemManager.StepwiseControllers"; 163 stepwise["@odata.id"] = 164 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/StepwiseControllers"; 165 166 nlohmann::json& zones = configRoot["FanZones"]; 167 zones["@odata.id"] = 168 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones"; 169 zones["@odata.type"] = "#OemManager.FanZones"; 170 zones["@odata.context"] = 171 "/redfish/v1/$metadata#OemManager.FanZones"; 172 configRoot["@odata.id"] = 173 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan"; 174 configRoot["@odata.type"] = "#OemManager.Fan"; 175 configRoot["@odata.context"] = 176 "/redfish/v1/$metadata#OemManager.Fan"; 177 configRoot["Profile@Redfish.AllowableValues"] = supportedProfiles; 178 179 if (!currentProfile.empty()) 180 { 181 configRoot["Profile"] = currentProfile; 182 } 183 BMCWEB_LOG_ERROR << "profile = " << currentProfile << " !"; 184 185 for (const auto& pathPair : managedObj) 186 { 187 for (const auto& intfPair : pathPair.second) 188 { 189 if (intfPair.first != pidConfigurationIface && 190 intfPair.first != pidZoneConfigurationIface && 191 intfPair.first != stepwiseConfigurationIface) 192 { 193 continue; 194 } 195 auto findName = intfPair.second.find("Name"); 196 if (findName == intfPair.second.end()) 197 { 198 BMCWEB_LOG_ERROR << "Pid Field missing Name"; 199 messages::internalError(asyncResp->res); 200 return; 201 } 202 203 const std::string* namePtr = 204 std::get_if<std::string>(&findName->second); 205 if (namePtr == nullptr) 206 { 207 BMCWEB_LOG_ERROR << "Pid Name Field illegal"; 208 messages::internalError(asyncResp->res); 209 return; 210 } 211 std::string name = *namePtr; 212 dbus::utility::escapePathForDbus(name); 213 214 auto findProfiles = intfPair.second.find("Profiles"); 215 if (findProfiles != intfPair.second.end()) 216 { 217 const std::vector<std::string>* profiles = 218 std::get_if<std::vector<std::string>>( 219 &findProfiles->second); 220 if (profiles == nullptr) 221 { 222 BMCWEB_LOG_ERROR << "Pid Profiles Field illegal"; 223 messages::internalError(asyncResp->res); 224 return; 225 } 226 if (std::find(profiles->begin(), profiles->end(), 227 currentProfile) == profiles->end()) 228 { 229 BMCWEB_LOG_INFO 230 << name << " not supported in current profile"; 231 continue; 232 } 233 } 234 nlohmann::json* config = nullptr; 235 236 const std::string* classPtr = nullptr; 237 auto findClass = intfPair.second.find("Class"); 238 if (findClass != intfPair.second.end()) 239 { 240 classPtr = std::get_if<std::string>(&findClass->second); 241 } 242 243 if (intfPair.first == pidZoneConfigurationIface) 244 { 245 std::string chassis; 246 if (!dbus::utility::getNthStringFromPath( 247 pathPair.first.str, 5, chassis)) 248 { 249 chassis = "#IllegalValue"; 250 } 251 nlohmann::json& zone = zones[name]; 252 zone["Chassis"] = { 253 {"@odata.id", "/redfish/v1/Chassis/" + chassis}}; 254 zone["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/" 255 "OpenBmc/Fan/FanZones/" + 256 name; 257 zone["@odata.type"] = "#OemManager.FanZone"; 258 zone["@odata.context"] = 259 "/redfish/v1/$metadata#OemManager.FanZone"; 260 config = &zone; 261 } 262 263 else if (intfPair.first == stepwiseConfigurationIface) 264 { 265 if (classPtr == nullptr) 266 { 267 BMCWEB_LOG_ERROR << "Pid Class Field illegal"; 268 messages::internalError(asyncResp->res); 269 return; 270 } 271 272 nlohmann::json& controller = stepwise[name]; 273 config = &controller; 274 275 controller["@odata.id"] = 276 "/redfish/v1/Managers/bmc#/Oem/" 277 "OpenBmc/Fan/StepwiseControllers/" + 278 std::string(name); 279 controller["@odata.type"] = 280 "#OemManager.StepwiseController"; 281 282 controller["@odata.context"] = 283 "/redfish/v1/" 284 "$metadata#OemManager.StepwiseController"; 285 controller["Direction"] = *classPtr; 286 } 287 288 // pid and fans are off the same configuration 289 else if (intfPair.first == pidConfigurationIface) 290 { 291 292 if (classPtr == nullptr) 293 { 294 BMCWEB_LOG_ERROR << "Pid Class Field illegal"; 295 messages::internalError(asyncResp->res); 296 return; 297 } 298 bool isFan = *classPtr == "fan"; 299 nlohmann::json& element = 300 isFan ? fans[name] : pids[name]; 301 config = &element; 302 if (isFan) 303 { 304 element["@odata.id"] = 305 "/redfish/v1/Managers/bmc#/Oem/" 306 "OpenBmc/Fan/FanControllers/" + 307 std::string(name); 308 element["@odata.type"] = 309 "#OemManager.FanController"; 310 311 element["@odata.context"] = 312 "/redfish/v1/" 313 "$metadata#OemManager.FanController"; 314 } 315 else 316 { 317 element["@odata.id"] = 318 "/redfish/v1/Managers/bmc#/Oem/" 319 "OpenBmc/Fan/PidControllers/" + 320 std::string(name); 321 element["@odata.type"] = 322 "#OemManager.PidController"; 323 element["@odata.context"] = 324 "/redfish/v1/$metadata" 325 "#OemManager.PidController"; 326 } 327 } 328 else 329 { 330 BMCWEB_LOG_ERROR << "Unexpected configuration"; 331 messages::internalError(asyncResp->res); 332 return; 333 } 334 335 // used for making maps out of 2 vectors 336 const std::vector<double>* keys = nullptr; 337 const std::vector<double>* values = nullptr; 338 339 for (const auto& propertyPair : intfPair.second) 340 { 341 if (propertyPair.first == "Type" || 342 propertyPair.first == "Class" || 343 propertyPair.first == "Name") 344 { 345 continue; 346 } 347 348 // zones 349 if (intfPair.first == pidZoneConfigurationIface) 350 { 351 const double* ptr = 352 std::get_if<double>(&propertyPair.second); 353 if (ptr == nullptr) 354 { 355 BMCWEB_LOG_ERROR << "Field Illegal " 356 << propertyPair.first; 357 messages::internalError(asyncResp->res); 358 return; 359 } 360 (*config)[propertyPair.first] = *ptr; 361 } 362 363 if (intfPair.first == stepwiseConfigurationIface) 364 { 365 if (propertyPair.first == "Reading" || 366 propertyPair.first == "Output") 367 { 368 const std::vector<double>* ptr = 369 std::get_if<std::vector<double>>( 370 &propertyPair.second); 371 372 if (ptr == nullptr) 373 { 374 BMCWEB_LOG_ERROR << "Field Illegal " 375 << propertyPair.first; 376 messages::internalError(asyncResp->res); 377 return; 378 } 379 380 if (propertyPair.first == "Reading") 381 { 382 keys = ptr; 383 } 384 else 385 { 386 values = ptr; 387 } 388 if (keys && values) 389 { 390 if (keys->size() != values->size()) 391 { 392 BMCWEB_LOG_ERROR 393 << "Reading and Output size don't " 394 "match "; 395 messages::internalError(asyncResp->res); 396 return; 397 } 398 nlohmann::json& steps = (*config)["Steps"]; 399 steps = nlohmann::json::array(); 400 for (size_t ii = 0; ii < keys->size(); ii++) 401 { 402 steps.push_back( 403 {{"Target", (*keys)[ii]}, 404 {"Output", (*values)[ii]}}); 405 } 406 } 407 } 408 if (propertyPair.first == "NegativeHysteresis" || 409 propertyPair.first == "PositiveHysteresis") 410 { 411 const double* ptr = 412 std::get_if<double>(&propertyPair.second); 413 if (ptr == nullptr) 414 { 415 BMCWEB_LOG_ERROR << "Field Illegal " 416 << propertyPair.first; 417 messages::internalError(asyncResp->res); 418 return; 419 } 420 (*config)[propertyPair.first] = *ptr; 421 } 422 } 423 424 // pid and fans are off the same configuration 425 if (intfPair.first == pidConfigurationIface || 426 intfPair.first == stepwiseConfigurationIface) 427 { 428 429 if (propertyPair.first == "Zones") 430 { 431 const std::vector<std::string>* inputs = 432 std::get_if<std::vector<std::string>>( 433 &propertyPair.second); 434 435 if (inputs == nullptr) 436 { 437 BMCWEB_LOG_ERROR 438 << "Zones Pid Field Illegal"; 439 messages::internalError(asyncResp->res); 440 return; 441 } 442 auto& data = (*config)[propertyPair.first]; 443 data = nlohmann::json::array(); 444 for (std::string itemCopy : *inputs) 445 { 446 dbus::utility::escapePathForDbus(itemCopy); 447 data.push_back( 448 {{"@odata.id", 449 "/redfish/v1/Managers/bmc#/Oem/" 450 "OpenBmc/Fan/FanZones/" + 451 itemCopy}}); 452 } 453 } 454 // todo(james): may never happen, but this 455 // assumes configuration data referenced in the 456 // PID config is provided by the same daemon, we 457 // could add another loop to cover all cases, 458 // but I'm okay kicking this can down the road a 459 // bit 460 461 else if (propertyPair.first == "Inputs" || 462 propertyPair.first == "Outputs") 463 { 464 auto& data = (*config)[propertyPair.first]; 465 const std::vector<std::string>* inputs = 466 std::get_if<std::vector<std::string>>( 467 &propertyPair.second); 468 469 if (inputs == nullptr) 470 { 471 BMCWEB_LOG_ERROR << "Field Illegal " 472 << propertyPair.first; 473 messages::internalError(asyncResp->res); 474 return; 475 } 476 data = *inputs; 477 } // doubles 478 else if (propertyPair.first == 479 "FFGainCoefficient" || 480 propertyPair.first == "FFOffCoefficient" || 481 propertyPair.first == "ICoefficient" || 482 propertyPair.first == "ILimitMax" || 483 propertyPair.first == "ILimitMin" || 484 propertyPair.first == 485 "PositiveHysteresis" || 486 propertyPair.first == 487 "NegativeHysteresis" || 488 propertyPair.first == "OutLimitMax" || 489 propertyPair.first == "OutLimitMin" || 490 propertyPair.first == "PCoefficient" || 491 propertyPair.first == "SetPoint" || 492 propertyPair.first == "SlewNeg" || 493 propertyPair.first == "SlewPos") 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 } 508 } 509 } 510 }, 511 connection, path, objectManagerIface, "GetManagedObjects"); 512 } 513 514 enum class CreatePIDRet 515 { 516 fail, 517 del, 518 patch 519 }; 520 521 static bool getZonesFromJsonReq(const std::shared_ptr<AsyncResp>& response, 522 std::vector<nlohmann::json>& config, 523 std::vector<std::string>& zones) 524 { 525 if (config.empty()) 526 { 527 BMCWEB_LOG_ERROR << "Empty Zones"; 528 messages::propertyValueFormatError(response->res, 529 nlohmann::json::array(), "Zones"); 530 return false; 531 } 532 for (auto& odata : config) 533 { 534 std::string path; 535 if (!redfish::json_util::readJson(odata, response->res, "@odata.id", 536 path)) 537 { 538 return false; 539 } 540 std::string input; 541 542 // 8 below comes from 543 // /redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones/Left 544 // 0 1 2 3 4 5 6 7 8 545 if (!dbus::utility::getNthStringFromPath(path, 8, input)) 546 { 547 BMCWEB_LOG_ERROR << "Got invalid path " << path; 548 BMCWEB_LOG_ERROR << "Illegal Type Zones"; 549 messages::propertyValueFormatError(response->res, odata.dump(), 550 "Zones"); 551 return false; 552 } 553 boost::replace_all(input, "_", " "); 554 zones.emplace_back(std::move(input)); 555 } 556 return true; 557 } 558 559 static const dbus::utility::ManagedItem* 560 findChassis(const dbus::utility::ManagedObjectType& managedObj, 561 const std::string& value, std::string& chassis) 562 { 563 BMCWEB_LOG_DEBUG << "Find Chassis: " << value << "\n"; 564 565 std::string escaped = boost::replace_all_copy(value, " ", "_"); 566 escaped = "/" + escaped; 567 auto it = std::find_if( 568 managedObj.begin(), managedObj.end(), [&escaped](const auto& obj) { 569 if (boost::algorithm::ends_with(obj.first.str, escaped)) 570 { 571 BMCWEB_LOG_DEBUG << "Matched " << obj.first.str << "\n"; 572 return true; 573 } 574 return false; 575 }); 576 577 if (it == managedObj.end()) 578 { 579 return nullptr; 580 } 581 // 5 comes from <chassis-name> being the 5th element 582 // /xyz/openbmc_project/inventory/system/chassis/<chassis-name> 583 if (dbus::utility::getNthStringFromPath(it->first.str, 5, chassis)) 584 { 585 return &(*it); 586 } 587 588 return nullptr; 589 } 590 591 static CreatePIDRet createPidInterface( 592 const std::shared_ptr<AsyncResp>& response, const std::string& type, 593 nlohmann::json::iterator it, const std::string& path, 594 const dbus::utility::ManagedObjectType& managedObj, bool createNewObject, 595 boost::container::flat_map<std::string, dbus::utility::DbusVariantType>& 596 output, 597 std::string& chassis, const std::string& profile) 598 { 599 600 // common deleter 601 if (it.value() == nullptr) 602 { 603 std::string iface; 604 if (type == "PidControllers" || type == "FanControllers") 605 { 606 iface = pidConfigurationIface; 607 } 608 else if (type == "FanZones") 609 { 610 iface = pidZoneConfigurationIface; 611 } 612 else if (type == "StepwiseControllers") 613 { 614 iface = stepwiseConfigurationIface; 615 } 616 else 617 { 618 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type " 619 << type; 620 messages::propertyUnknown(response->res, type); 621 return CreatePIDRet::fail; 622 } 623 // delete interface 624 crow::connections::systemBus->async_method_call( 625 [response, path](const boost::system::error_code ec) { 626 if (ec) 627 { 628 BMCWEB_LOG_ERROR << "Error patching " << path << ": " << ec; 629 messages::internalError(response->res); 630 return; 631 } 632 messages::success(response->res); 633 }, 634 "xyz.openbmc_project.EntityManager", path, iface, "Delete"); 635 return CreatePIDRet::del; 636 } 637 638 const dbus::utility::ManagedItem* managedItem = nullptr; 639 if (!createNewObject) 640 { 641 // if we aren't creating a new object, we should be able to find it on 642 // d-bus 643 managedItem = findChassis(managedObj, it.key(), chassis); 644 if (managedItem == nullptr) 645 { 646 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch"; 647 messages::invalidObject(response->res, it.key()); 648 return CreatePIDRet::fail; 649 } 650 } 651 652 if (profile.size() && 653 (type == "PidControllers" || type == "FanControllers" || 654 type == "StepwiseControllers")) 655 { 656 if (managedItem == nullptr) 657 { 658 output["Profiles"] = std::vector<std::string>{profile}; 659 } 660 else 661 { 662 std::string interface; 663 if (type == "StepwiseControllers") 664 { 665 interface = stepwiseConfigurationIface; 666 } 667 else 668 { 669 interface = pidConfigurationIface; 670 } 671 auto findConfig = managedItem->second.find(interface); 672 if (findConfig == managedItem->second.end()) 673 { 674 BMCWEB_LOG_ERROR 675 << "Failed to find interface in managed object"; 676 messages::internalError(response->res); 677 return CreatePIDRet::fail; 678 } 679 auto findProfiles = findConfig->second.find("Profiles"); 680 if (findProfiles != findConfig->second.end()) 681 { 682 const std::vector<std::string>* curProfiles = 683 std::get_if<std::vector<std::string>>( 684 &(findProfiles->second)); 685 if (curProfiles == nullptr) 686 { 687 BMCWEB_LOG_ERROR << "Illegal profiles in managed object"; 688 messages::internalError(response->res); 689 return CreatePIDRet::fail; 690 } 691 if (std::find(curProfiles->begin(), curProfiles->end(), 692 profile) == curProfiles->end()) 693 { 694 std::vector<std::string> newProfiles = *curProfiles; 695 newProfiles.push_back(profile); 696 output["Profiles"] = newProfiles; 697 } 698 } 699 } 700 } 701 702 if (type == "PidControllers" || type == "FanControllers") 703 { 704 if (createNewObject) 705 { 706 output["Class"] = type == "PidControllers" ? std::string("temp") 707 : std::string("fan"); 708 output["Type"] = std::string("Pid"); 709 } 710 711 std::optional<std::vector<nlohmann::json>> zones; 712 std::optional<std::vector<std::string>> inputs; 713 std::optional<std::vector<std::string>> outputs; 714 std::map<std::string, std::optional<double>> doubles; 715 if (!redfish::json_util::readJson( 716 it.value(), response->res, "Inputs", inputs, "Outputs", outputs, 717 "Zones", zones, "FFGainCoefficient", 718 doubles["FFGainCoefficient"], "FFOffCoefficient", 719 doubles["FFOffCoefficient"], "ICoefficient", 720 doubles["ICoefficient"], "ILimitMax", doubles["ILimitMax"], 721 "ILimitMin", doubles["ILimitMin"], "OutLimitMax", 722 doubles["OutLimitMax"], "OutLimitMin", doubles["OutLimitMin"], 723 "PCoefficient", doubles["PCoefficient"], "SetPoint", 724 doubles["SetPoint"], "SlewNeg", doubles["SlewNeg"], "SlewPos", 725 doubles["SlewPos"], "PositiveHysteresis", 726 doubles["PositiveHysteresis"], "NegativeHysteresis", 727 doubles["NegativeHysteresis"])) 728 { 729 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 730 << it.value().dump(); 731 return CreatePIDRet::fail; 732 } 733 if (zones) 734 { 735 std::vector<std::string> zonesStr; 736 if (!getZonesFromJsonReq(response, *zones, zonesStr)) 737 { 738 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones"; 739 return CreatePIDRet::fail; 740 } 741 if (chassis.empty() && 742 !findChassis(managedObj, zonesStr[0], chassis)) 743 { 744 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch"; 745 messages::invalidObject(response->res, it.key()); 746 return CreatePIDRet::fail; 747 } 748 749 output["Zones"] = std::move(zonesStr); 750 } 751 if (inputs || outputs) 752 { 753 std::array<std::optional<std::vector<std::string>>*, 2> containers = 754 {&inputs, &outputs}; 755 size_t index = 0; 756 for (const auto& containerPtr : containers) 757 { 758 std::optional<std::vector<std::string>>& container = 759 *containerPtr; 760 if (!container) 761 { 762 index++; 763 continue; 764 } 765 766 for (std::string& value : *container) 767 { 768 boost::replace_all(value, "_", " "); 769 } 770 std::string key; 771 if (index == 0) 772 { 773 key = "Inputs"; 774 } 775 else 776 { 777 key = "Outputs"; 778 } 779 output[key] = *container; 780 index++; 781 } 782 } 783 784 // doubles 785 for (const auto& pairs : doubles) 786 { 787 if (!pairs.second) 788 { 789 continue; 790 } 791 BMCWEB_LOG_DEBUG << pairs.first << " = " << *pairs.second; 792 output[pairs.first] = *(pairs.second); 793 } 794 } 795 796 else if (type == "FanZones") 797 { 798 output["Type"] = std::string("Pid.Zone"); 799 800 std::optional<nlohmann::json> chassisContainer; 801 std::optional<double> failSafePercent; 802 std::optional<double> minThermalOutput; 803 if (!redfish::json_util::readJson(it.value(), response->res, "Chassis", 804 chassisContainer, "FailSafePercent", 805 failSafePercent, "MinThermalOutput", 806 minThermalOutput)) 807 { 808 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 809 << it.value().dump(); 810 return CreatePIDRet::fail; 811 } 812 813 if (chassisContainer) 814 { 815 816 std::string chassisId; 817 if (!redfish::json_util::readJson(*chassisContainer, response->res, 818 "@odata.id", chassisId)) 819 { 820 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 821 << chassisContainer->dump(); 822 return CreatePIDRet::fail; 823 } 824 825 // /refish/v1/chassis/chassis_name/ 826 if (!dbus::utility::getNthStringFromPath(chassisId, 3, chassis)) 827 { 828 BMCWEB_LOG_ERROR << "Got invalid path " << chassisId; 829 messages::invalidObject(response->res, chassisId); 830 return CreatePIDRet::fail; 831 } 832 } 833 if (minThermalOutput) 834 { 835 output["MinThermalOutput"] = *minThermalOutput; 836 } 837 if (failSafePercent) 838 { 839 output["FailSafePercent"] = *failSafePercent; 840 } 841 } 842 else if (type == "StepwiseControllers") 843 { 844 output["Type"] = std::string("Stepwise"); 845 846 std::optional<std::vector<nlohmann::json>> zones; 847 std::optional<std::vector<nlohmann::json>> steps; 848 std::optional<std::vector<std::string>> inputs; 849 std::optional<double> positiveHysteresis; 850 std::optional<double> negativeHysteresis; 851 std::optional<std::string> direction; // upper clipping curve vs lower 852 if (!redfish::json_util::readJson( 853 it.value(), response->res, "Zones", zones, "Steps", steps, 854 "Inputs", inputs, "PositiveHysteresis", positiveHysteresis, 855 "NegativeHysteresis", negativeHysteresis, "Direction", 856 direction)) 857 { 858 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 859 << it.value().dump(); 860 return CreatePIDRet::fail; 861 } 862 863 if (zones) 864 { 865 std::vector<std::string> zonesStrs; 866 if (!getZonesFromJsonReq(response, *zones, zonesStrs)) 867 { 868 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones"; 869 return CreatePIDRet::fail; 870 } 871 if (chassis.empty() && 872 !findChassis(managedObj, zonesStrs[0], chassis)) 873 { 874 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch"; 875 messages::invalidObject(response->res, it.key()); 876 return CreatePIDRet::fail; 877 } 878 output["Zones"] = std::move(zonesStrs); 879 } 880 if (steps) 881 { 882 std::vector<double> readings; 883 std::vector<double> outputs; 884 for (auto& step : *steps) 885 { 886 double target; 887 double output; 888 889 if (!redfish::json_util::readJson(step, response->res, "Target", 890 target, "Output", output)) 891 { 892 BMCWEB_LOG_ERROR << "Line:" << __LINE__ 893 << ", Illegal Property " 894 << it.value().dump(); 895 return CreatePIDRet::fail; 896 } 897 readings.emplace_back(target); 898 outputs.emplace_back(output); 899 } 900 output["Reading"] = std::move(readings); 901 output["Output"] = std::move(outputs); 902 } 903 if (inputs) 904 { 905 for (std::string& value : *inputs) 906 { 907 boost::replace_all(value, "_", " "); 908 } 909 output["Inputs"] = std::move(*inputs); 910 } 911 if (negativeHysteresis) 912 { 913 output["NegativeHysteresis"] = *negativeHysteresis; 914 } 915 if (positiveHysteresis) 916 { 917 output["PositiveHysteresis"] = *positiveHysteresis; 918 } 919 if (direction) 920 { 921 constexpr const std::array<const char*, 2> allowedDirections = { 922 "Ceiling", "Floor"}; 923 if (std::find(allowedDirections.begin(), allowedDirections.end(), 924 *direction) == allowedDirections.end()) 925 { 926 messages::propertyValueTypeError(response->res, "Direction", 927 *direction); 928 return CreatePIDRet::fail; 929 } 930 output["Class"] = *direction; 931 } 932 } 933 else 934 { 935 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type " << type; 936 messages::propertyUnknown(response->res, type); 937 return CreatePIDRet::fail; 938 } 939 return CreatePIDRet::patch; 940 } 941 struct GetPIDValues : std::enable_shared_from_this<GetPIDValues> 942 { 943 944 GetPIDValues(const std::shared_ptr<AsyncResp>& asyncResp) : 945 asyncResp(asyncResp) 946 947 { 948 } 949 950 void run() 951 { 952 std::shared_ptr<GetPIDValues> self = shared_from_this(); 953 954 // get all configurations 955 crow::connections::systemBus->async_method_call( 956 [self](const boost::system::error_code ec, 957 const crow::openbmc_mapper::GetSubTreeType& subtree) { 958 if (ec) 959 { 960 BMCWEB_LOG_ERROR << ec; 961 messages::internalError(self->asyncResp->res); 962 return; 963 } 964 self->subtree = subtree; 965 }, 966 "xyz.openbmc_project.ObjectMapper", 967 "/xyz/openbmc_project/object_mapper", 968 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0, 969 std::array<const char*, 4>{ 970 pidConfigurationIface, pidZoneConfigurationIface, 971 objectManagerIface, stepwiseConfigurationIface}); 972 973 // at the same time get the selected profile 974 crow::connections::systemBus->async_method_call( 975 [self](const boost::system::error_code ec, 976 const crow::openbmc_mapper::GetSubTreeType& subtree) { 977 if (ec || subtree.empty()) 978 { 979 return; 980 } 981 if (subtree[0].second.size() != 1) 982 { 983 // invalid mapper response, should never happen 984 BMCWEB_LOG_ERROR << "GetPIDValues: Mapper Error"; 985 messages::internalError(self->asyncResp->res); 986 return; 987 } 988 989 const std::string& path = subtree[0].first; 990 const std::string& owner = subtree[0].second[0].first; 991 crow::connections::systemBus->async_method_call( 992 [path, owner, self]( 993 const boost::system::error_code ec, 994 const boost::container::flat_map< 995 std::string, std::variant<std::vector<std::string>, 996 std::string>>& resp) { 997 if (ec) 998 { 999 BMCWEB_LOG_ERROR << "GetPIDValues: Can't get " 1000 "thermalModeIface " 1001 << path; 1002 messages::internalError(self->asyncResp->res); 1003 return; 1004 } 1005 const std::string* current; 1006 const std::vector<std::string>* supported; 1007 for (auto& [key, value] : resp) 1008 { 1009 if (key == "Current") 1010 { 1011 current = std::get_if<std::string>(&value); 1012 if (current == nullptr) 1013 { 1014 BMCWEB_LOG_ERROR 1015 << "GetPIDValues: thermal mode " 1016 "iface invalid " 1017 << path; 1018 messages::internalError( 1019 self->asyncResp->res); 1020 return; 1021 } 1022 } 1023 if (key == "Supported") 1024 { 1025 supported = 1026 std::get_if<std::vector<std::string>>( 1027 &value); 1028 if (supported == nullptr) 1029 { 1030 BMCWEB_LOG_ERROR 1031 << "GetPIDValues: thermal mode " 1032 "iface invalid" 1033 << path; 1034 messages::internalError( 1035 self->asyncResp->res); 1036 return; 1037 } 1038 } 1039 } 1040 if (current == nullptr || supported == nullptr) 1041 { 1042 BMCWEB_LOG_ERROR << "GetPIDValues: thermal mode " 1043 "iface invalid " 1044 << path; 1045 messages::internalError(self->asyncResp->res); 1046 return; 1047 } 1048 self->currentProfile = *current; 1049 self->supportedProfiles = *supported; 1050 }, 1051 owner, path, "org.freedesktop.DBus.Properties", "GetAll", 1052 thermalModeIface); 1053 }, 1054 "xyz.openbmc_project.ObjectMapper", 1055 "/xyz/openbmc_project/object_mapper", 1056 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0, 1057 std::array<const char*, 1>{thermalModeIface}); 1058 } 1059 1060 ~GetPIDValues() 1061 { 1062 if (asyncResp->res.result() != boost::beast::http::status::ok) 1063 { 1064 return; 1065 } 1066 // create map of <connection, path to objMgr>> 1067 boost::container::flat_map<std::string, std::string> objectMgrPaths; 1068 boost::container::flat_set<std::string> calledConnections; 1069 for (const auto& pathGroup : subtree) 1070 { 1071 for (const auto& connectionGroup : pathGroup.second) 1072 { 1073 auto findConnection = 1074 calledConnections.find(connectionGroup.first); 1075 if (findConnection != calledConnections.end()) 1076 { 1077 break; 1078 } 1079 for (const std::string& interface : connectionGroup.second) 1080 { 1081 if (interface == objectManagerIface) 1082 { 1083 objectMgrPaths[connectionGroup.first] = pathGroup.first; 1084 } 1085 // this list is alphabetical, so we 1086 // should have found the objMgr by now 1087 if (interface == pidConfigurationIface || 1088 interface == pidZoneConfigurationIface || 1089 interface == stepwiseConfigurationIface) 1090 { 1091 auto findObjMgr = 1092 objectMgrPaths.find(connectionGroup.first); 1093 if (findObjMgr == objectMgrPaths.end()) 1094 { 1095 BMCWEB_LOG_DEBUG << connectionGroup.first 1096 << "Has no Object Manager"; 1097 continue; 1098 } 1099 1100 calledConnections.insert(connectionGroup.first); 1101 1102 asyncPopulatePid(findObjMgr->first, findObjMgr->second, 1103 currentProfile, supportedProfiles, 1104 asyncResp); 1105 break; 1106 } 1107 } 1108 } 1109 } 1110 } 1111 1112 std::vector<std::string> supportedProfiles; 1113 std::string currentProfile; 1114 crow::openbmc_mapper::GetSubTreeType subtree; 1115 std::shared_ptr<AsyncResp> asyncResp; 1116 }; 1117 1118 struct SetPIDValues : std::enable_shared_from_this<SetPIDValues> 1119 { 1120 1121 SetPIDValues(const std::shared_ptr<AsyncResp>& asyncResp, 1122 nlohmann::json& data) : 1123 asyncResp(asyncResp) 1124 { 1125 1126 std::optional<nlohmann::json> pidControllers; 1127 std::optional<nlohmann::json> fanControllers; 1128 std::optional<nlohmann::json> fanZones; 1129 std::optional<nlohmann::json> stepwiseControllers; 1130 1131 if (!redfish::json_util::readJson( 1132 data, asyncResp->res, "PidControllers", pidControllers, 1133 "FanControllers", fanControllers, "FanZones", fanZones, 1134 "StepwiseControllers", stepwiseControllers, "Profile", profile)) 1135 { 1136 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 1137 << data.dump(); 1138 return; 1139 } 1140 configuration.emplace_back("PidControllers", std::move(pidControllers)); 1141 configuration.emplace_back("FanControllers", std::move(fanControllers)); 1142 configuration.emplace_back("FanZones", std::move(fanZones)); 1143 configuration.emplace_back("StepwiseControllers", 1144 std::move(stepwiseControllers)); 1145 } 1146 void run() 1147 { 1148 if (asyncResp->res.result() != boost::beast::http::status::ok) 1149 { 1150 return; 1151 } 1152 1153 std::shared_ptr<SetPIDValues> self = shared_from_this(); 1154 1155 // todo(james): might make sense to do a mapper call here if this 1156 // interface gets more traction 1157 crow::connections::systemBus->async_method_call( 1158 [self](const boost::system::error_code ec, 1159 dbus::utility::ManagedObjectType& managedObj) { 1160 if (ec) 1161 { 1162 BMCWEB_LOG_ERROR << "Error communicating to Entity Manager"; 1163 messages::internalError(self->asyncResp->res); 1164 return; 1165 } 1166 self->managedObj = std::move(managedObj); 1167 }, 1168 "xyz.openbmc_project.EntityManager", "/", objectManagerIface, 1169 "GetManagedObjects"); 1170 1171 // at the same time get the profile information 1172 crow::connections::systemBus->async_method_call( 1173 [self](const boost::system::error_code ec, 1174 const crow::openbmc_mapper::GetSubTreeType& subtree) { 1175 if (ec || subtree.empty()) 1176 { 1177 return; 1178 } 1179 if (subtree[0].second.empty()) 1180 { 1181 // invalid mapper response, should never happen 1182 BMCWEB_LOG_ERROR << "SetPIDValues: Mapper Error"; 1183 messages::internalError(self->asyncResp->res); 1184 return; 1185 } 1186 1187 const std::string& path = subtree[0].first; 1188 const std::string& owner = subtree[0].second[0].first; 1189 crow::connections::systemBus->async_method_call( 1190 [self, path, owner]( 1191 const boost::system::error_code ec, 1192 const boost::container::flat_map< 1193 std::string, std::variant<std::vector<std::string>, 1194 std::string>>& resp) { 1195 if (ec) 1196 { 1197 BMCWEB_LOG_ERROR << "SetPIDValues: Can't get " 1198 "thermalModeIface " 1199 << path; 1200 messages::internalError(self->asyncResp->res); 1201 return; 1202 } 1203 const std::string* current; 1204 const std::vector<std::string>* supported; 1205 for (auto& [key, value] : resp) 1206 { 1207 if (key == "Current") 1208 { 1209 current = std::get_if<std::string>(&value); 1210 if (current == nullptr) 1211 { 1212 BMCWEB_LOG_ERROR 1213 << "SetPIDValues: thermal mode " 1214 "iface invalid " 1215 << path; 1216 messages::internalError( 1217 self->asyncResp->res); 1218 return; 1219 } 1220 } 1221 if (key == "Supported") 1222 { 1223 supported = 1224 std::get_if<std::vector<std::string>>( 1225 &value); 1226 if (supported == nullptr) 1227 { 1228 BMCWEB_LOG_ERROR 1229 << "SetPIDValues: thermal mode " 1230 "iface invalid" 1231 << path; 1232 messages::internalError( 1233 self->asyncResp->res); 1234 return; 1235 } 1236 } 1237 } 1238 if (current == nullptr || supported == nullptr) 1239 { 1240 BMCWEB_LOG_ERROR << "SetPIDValues: thermal mode " 1241 "iface invalid " 1242 << path; 1243 messages::internalError(self->asyncResp->res); 1244 return; 1245 } 1246 self->currentProfile = *current; 1247 self->supportedProfiles = *supported; 1248 self->profileConnection = owner; 1249 self->profilePath = path; 1250 }, 1251 owner, path, "org.freedesktop.DBus.Properties", "GetAll", 1252 thermalModeIface); 1253 }, 1254 "xyz.openbmc_project.ObjectMapper", 1255 "/xyz/openbmc_project/object_mapper", 1256 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0, 1257 std::array<const char*, 1>{thermalModeIface}); 1258 } 1259 ~SetPIDValues() 1260 { 1261 if (asyncResp->res.result() != boost::beast::http::status::ok) 1262 { 1263 return; 1264 } 1265 1266 std::shared_ptr<AsyncResp> response = asyncResp; 1267 1268 if (profile) 1269 { 1270 if (std::find(supportedProfiles.begin(), supportedProfiles.end(), 1271 *profile) == supportedProfiles.end()) 1272 { 1273 messages::actionParameterUnknown(response->res, "Profile", 1274 *profile); 1275 return; 1276 } 1277 currentProfile = *profile; 1278 crow::connections::systemBus->async_method_call( 1279 [response](const boost::system::error_code ec) { 1280 if (ec) 1281 { 1282 BMCWEB_LOG_ERROR << "Error patching profile" << ec; 1283 messages::internalError(response->res); 1284 } 1285 }, 1286 profileConnection, profilePath, 1287 "org.freedesktop.DBus.Properties", "Set", thermalModeIface, 1288 "Current", std::variant<std::string>(*profile)); 1289 } 1290 1291 for (auto& containerPair : configuration) 1292 { 1293 auto& container = containerPair.second; 1294 if (!container) 1295 { 1296 continue; 1297 } 1298 std::string& type = containerPair.first; 1299 1300 for (nlohmann::json::iterator it = container->begin(); 1301 it != container->end(); it++) 1302 { 1303 const auto& name = it.key(); 1304 auto pathItr = 1305 std::find_if(managedObj.begin(), managedObj.end(), 1306 [&name](const auto& obj) { 1307 return boost::algorithm::ends_with( 1308 obj.first.str, "/" + name); 1309 }); 1310 boost::container::flat_map<std::string, 1311 dbus::utility::DbusVariantType> 1312 output; 1313 1314 output.reserve(16); // The pid interface length 1315 1316 // determines if we're patching entity-manager or 1317 // creating a new object 1318 bool createNewObject = (pathItr == managedObj.end()); 1319 std::string iface; 1320 if (type == "PidControllers" || type == "FanControllers") 1321 { 1322 iface = pidConfigurationIface; 1323 if (!createNewObject && 1324 pathItr->second.find(pidConfigurationIface) == 1325 pathItr->second.end()) 1326 { 1327 createNewObject = true; 1328 } 1329 } 1330 else if (type == "FanZones") 1331 { 1332 iface = pidZoneConfigurationIface; 1333 if (!createNewObject && 1334 pathItr->second.find(pidZoneConfigurationIface) == 1335 pathItr->second.end()) 1336 { 1337 1338 createNewObject = true; 1339 } 1340 } 1341 else if (type == "StepwiseControllers") 1342 { 1343 iface = stepwiseConfigurationIface; 1344 if (!createNewObject && 1345 pathItr->second.find(stepwiseConfigurationIface) == 1346 pathItr->second.end()) 1347 { 1348 createNewObject = true; 1349 } 1350 } 1351 BMCWEB_LOG_DEBUG << "Create new = " << createNewObject << "\n"; 1352 output["Name"] = boost::replace_all_copy(name, "_", " "); 1353 1354 std::string chassis; 1355 CreatePIDRet ret = createPidInterface( 1356 response, type, it, pathItr->first.str, managedObj, 1357 createNewObject, output, chassis, currentProfile); 1358 if (ret == CreatePIDRet::fail) 1359 { 1360 return; 1361 } 1362 else if (ret == CreatePIDRet::del) 1363 { 1364 continue; 1365 } 1366 1367 if (!createNewObject) 1368 { 1369 for (const auto& property : output) 1370 { 1371 crow::connections::systemBus->async_method_call( 1372 [response, 1373 propertyName{std::string(property.first)}]( 1374 const boost::system::error_code ec) { 1375 if (ec) 1376 { 1377 BMCWEB_LOG_ERROR << "Error patching " 1378 << propertyName << ": " 1379 << ec; 1380 messages::internalError(response->res); 1381 return; 1382 } 1383 messages::success(response->res); 1384 }, 1385 "xyz.openbmc_project.EntityManager", 1386 pathItr->first.str, 1387 "org.freedesktop.DBus.Properties", "Set", iface, 1388 property.first, property.second); 1389 } 1390 } 1391 else 1392 { 1393 if (chassis.empty()) 1394 { 1395 BMCWEB_LOG_ERROR << "Failed to get chassis from config"; 1396 messages::invalidObject(response->res, name); 1397 return; 1398 } 1399 1400 bool foundChassis = false; 1401 for (const auto& obj : managedObj) 1402 { 1403 if (boost::algorithm::ends_with(obj.first.str, chassis)) 1404 { 1405 chassis = obj.first.str; 1406 foundChassis = true; 1407 break; 1408 } 1409 } 1410 if (!foundChassis) 1411 { 1412 BMCWEB_LOG_ERROR << "Failed to find chassis on dbus"; 1413 messages::resourceMissingAtURI( 1414 response->res, "/redfish/v1/Chassis/" + chassis); 1415 return; 1416 } 1417 1418 crow::connections::systemBus->async_method_call( 1419 [response](const boost::system::error_code ec) { 1420 if (ec) 1421 { 1422 BMCWEB_LOG_ERROR << "Error Adding Pid Object " 1423 << ec; 1424 messages::internalError(response->res); 1425 return; 1426 } 1427 messages::success(response->res); 1428 }, 1429 "xyz.openbmc_project.EntityManager", chassis, 1430 "xyz.openbmc_project.AddObject", "AddObject", output); 1431 } 1432 } 1433 } 1434 } 1435 std::shared_ptr<AsyncResp> asyncResp; 1436 std::vector<std::pair<std::string, std::optional<nlohmann::json>>> 1437 configuration; 1438 std::optional<std::string> profile; 1439 dbus::utility::ManagedObjectType managedObj; 1440 std::vector<std::string> supportedProfiles; 1441 std::string currentProfile; 1442 std::string profileConnection; 1443 std::string profilePath; 1444 }; 1445 1446 class Manager : public Node 1447 { 1448 public: 1449 Manager(CrowApp& app) : Node(app, "/redfish/v1/Managers/bmc/") 1450 { 1451 uuid = app.template getMiddleware<crow::persistent_data::Middleware>() 1452 .systemUuid; 1453 entityPrivileges = { 1454 {boost::beast::http::verb::get, {{"Login"}}}, 1455 {boost::beast::http::verb::head, {{"Login"}}}, 1456 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1457 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1458 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1459 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1460 } 1461 1462 private: 1463 void doGet(crow::Response& res, const crow::Request& req, 1464 const std::vector<std::string>& params) override 1465 { 1466 res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc"; 1467 res.jsonValue["@odata.type"] = "#Manager.v1_3_0.Manager"; 1468 res.jsonValue["@odata.context"] = 1469 "/redfish/v1/$metadata#Manager.Manager"; 1470 res.jsonValue["Id"] = "bmc"; 1471 res.jsonValue["Name"] = "OpenBmc Manager"; 1472 res.jsonValue["Description"] = "Baseboard Management Controller"; 1473 res.jsonValue["PowerState"] = "On"; 1474 res.jsonValue["Status"] = {{"State", "Enabled"}, {"Health", "OK"}}; 1475 res.jsonValue["ManagerType"] = "BMC"; 1476 res.jsonValue["UUID"] = systemd_utils::getUuid(); 1477 res.jsonValue["ServiceEntryPointUUID"] = uuid; 1478 res.jsonValue["Model"] = "OpenBmc"; // TODO(ed), get model 1479 1480 res.jsonValue["LogServices"] = { 1481 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices"}}; 1482 1483 res.jsonValue["NetworkProtocol"] = { 1484 {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol"}}; 1485 1486 res.jsonValue["EthernetInterfaces"] = { 1487 {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces"}}; 1488 // default oem data 1489 nlohmann::json& oem = res.jsonValue["Oem"]; 1490 nlohmann::json& oemOpenbmc = oem["OpenBmc"]; 1491 oem["@odata.type"] = "#OemManager.Oem"; 1492 oem["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem"; 1493 oem["@odata.context"] = "/redfish/v1/$metadata#OemManager.Oem"; 1494 oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc"; 1495 oemOpenbmc["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc"; 1496 oemOpenbmc["@odata.context"] = 1497 "/redfish/v1/$metadata#OemManager.OpenBmc"; 1498 1499 // Update Actions object. 1500 nlohmann::json& manager_reset = 1501 res.jsonValue["Actions"]["#Manager.Reset"]; 1502 manager_reset["target"] = 1503 "/redfish/v1/Managers/bmc/Actions/Manager.Reset"; 1504 manager_reset["ResetType@Redfish.AllowableValues"] = { 1505 "GracefulRestart"}; 1506 1507 res.jsonValue["DateTime"] = crow::utility::dateTimeNow(); 1508 1509 // Fill in GraphicalConsole and SerialConsole info 1510 res.jsonValue["SerialConsole"]["ServiceEnabled"] = true; 1511 res.jsonValue["SerialConsole"]["ConnectTypesSupported"] = {"IPMI", 1512 "SSH"}; 1513 #ifdef BMCWEB_ENABLE_KVM 1514 res.jsonValue["GraphicalConsole"]["ServiceEnabled"] = true; 1515 res.jsonValue["GraphicalConsole"]["ConnectTypesSupported"] = {"KVMIP"}; 1516 #endif // BMCWEB_ENABLE_KVM 1517 1518 res.jsonValue["Links"]["ManagerForServers@odata.count"] = 1; 1519 res.jsonValue["Links"]["ManagerForServers"] = { 1520 {{"@odata.id", "/redfish/v1/Systems/system"}}}; 1521 1522 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1523 1524 crow::connections::systemBus->async_method_call( 1525 [asyncResp](const boost::system::error_code ec, 1526 const dbus::utility::ManagedObjectType& resp) { 1527 if (ec) 1528 { 1529 BMCWEB_LOG_ERROR << "Error while getting Software Version"; 1530 messages::internalError(asyncResp->res); 1531 return; 1532 } 1533 1534 for (auto& objpath : resp) 1535 { 1536 for (auto& interface : objpath.second) 1537 { 1538 // If interface is 1539 // xyz.openbmc_project.Software.Version, this is 1540 // what we're looking for. 1541 if (interface.first == 1542 "xyz.openbmc_project.Software.Version") 1543 { 1544 // Cut out everyting until last "/", ... 1545 for (auto& property : interface.second) 1546 { 1547 if (property.first == "Version") 1548 { 1549 const std::string* value = 1550 std::get_if<std::string>( 1551 &property.second); 1552 if (value == nullptr) 1553 { 1554 continue; 1555 } 1556 asyncResp->res 1557 .jsonValue["FirmwareVersion"] = *value; 1558 } 1559 } 1560 } 1561 } 1562 } 1563 }, 1564 "xyz.openbmc_project.Software.BMC.Updater", 1565 "/xyz/openbmc_project/software", 1566 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1567 auto pids = std::make_shared<GetPIDValues>(asyncResp); 1568 pids->run(); 1569 } 1570 1571 void doPatch(crow::Response& res, const crow::Request& req, 1572 const std::vector<std::string>& params) override 1573 { 1574 std::optional<nlohmann::json> oem; 1575 std::optional<std::string> datetime; 1576 1577 if (!json_util::readJson(req, res, "Oem", oem, "DateTime", datetime)) 1578 { 1579 return; 1580 } 1581 1582 std::shared_ptr<AsyncResp> response = std::make_shared<AsyncResp>(res); 1583 1584 if (oem) 1585 { 1586 std::optional<nlohmann::json> openbmc; 1587 if (!redfish::json_util::readJson(*oem, res, "OpenBmc", openbmc)) 1588 { 1589 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 1590 << oem->dump(); 1591 return; 1592 } 1593 if (openbmc) 1594 { 1595 std::optional<nlohmann::json> fan; 1596 if (!redfish::json_util::readJson(*openbmc, res, "Fan", fan)) 1597 { 1598 BMCWEB_LOG_ERROR << "Line:" << __LINE__ 1599 << ", Illegal Property " 1600 << openbmc->dump(); 1601 return; 1602 } 1603 if (fan) 1604 { 1605 auto pid = std::make_shared<SetPIDValues>(response, *fan); 1606 pid->run(); 1607 } 1608 } 1609 } 1610 if (datetime) 1611 { 1612 setDateTime(response, std::move(*datetime)); 1613 } 1614 } 1615 1616 void setDateTime(std::shared_ptr<AsyncResp> aResp, 1617 std::string datetime) const 1618 { 1619 BMCWEB_LOG_DEBUG << "Set date time: " << datetime; 1620 1621 std::stringstream stream(datetime); 1622 // Convert from ISO 8601 to boost local_time 1623 // (BMC only has time in UTC) 1624 boost::posix_time::ptime posixTime; 1625 boost::posix_time::ptime epoch(boost::gregorian::date(1970, 1, 1)); 1626 // Facet gets deleted with the stringsteam 1627 auto ifc = std::make_unique<boost::local_time::local_time_input_facet>( 1628 "%Y-%m-%d %H:%M:%S%F %ZP"); 1629 stream.imbue(std::locale(stream.getloc(), ifc.release())); 1630 1631 boost::local_time::local_date_time ldt( 1632 boost::local_time::not_a_date_time); 1633 1634 if (stream >> ldt) 1635 { 1636 posixTime = ldt.utc_time(); 1637 boost::posix_time::time_duration dur = posixTime - epoch; 1638 uint64_t durMicroSecs = 1639 static_cast<uint64_t>(dur.total_microseconds()); 1640 crow::connections::systemBus->async_method_call( 1641 [aResp{std::move(aResp)}, datetime{std::move(datetime)}]( 1642 const boost::system::error_code ec) { 1643 if (ec) 1644 { 1645 BMCWEB_LOG_DEBUG << "Failed to set elapsed time. " 1646 "DBUS response error " 1647 << ec; 1648 messages::internalError(aResp->res); 1649 return; 1650 } 1651 aResp->res.jsonValue["DateTime"] = datetime; 1652 }, 1653 "xyz.openbmc_project.Time.Manager", 1654 "/xyz/openbmc_project/time/bmc", 1655 "org.freedesktop.DBus.Properties", "Set", 1656 "xyz.openbmc_project.Time.EpochTime", "Elapsed", 1657 std::variant<uint64_t>(durMicroSecs)); 1658 } 1659 else 1660 { 1661 messages::propertyValueFormatError(aResp->res, datetime, 1662 "DateTime"); 1663 return; 1664 } 1665 } 1666 1667 std::string uuid; 1668 }; 1669 1670 class ManagerCollection : public Node 1671 { 1672 public: 1673 ManagerCollection(CrowApp& app) : Node(app, "/redfish/v1/Managers/") 1674 { 1675 entityPrivileges = { 1676 {boost::beast::http::verb::get, {{"Login"}}}, 1677 {boost::beast::http::verb::head, {{"Login"}}}, 1678 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1679 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1680 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1681 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1682 } 1683 1684 private: 1685 void doGet(crow::Response& res, const crow::Request& req, 1686 const std::vector<std::string>& params) override 1687 { 1688 // Collections don't include the static data added by SubRoute 1689 // because it has a duplicate entry for members 1690 res.jsonValue["@odata.id"] = "/redfish/v1/Managers"; 1691 res.jsonValue["@odata.type"] = "#ManagerCollection.ManagerCollection"; 1692 res.jsonValue["@odata.context"] = 1693 "/redfish/v1/$metadata#ManagerCollection.ManagerCollection"; 1694 res.jsonValue["Name"] = "Manager Collection"; 1695 res.jsonValue["Members@odata.count"] = 1; 1696 res.jsonValue["Members"] = { 1697 {{"@odata.id", "/redfish/v1/Managers/bmc"}}}; 1698 res.end(); 1699 } 1700 }; 1701 } // namespace redfish 1702