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