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 <dbus_utility.hpp> 22 #include <variant> 23 24 namespace redfish 25 { 26 27 /** 28 * ManagerActionsReset class supports handle POST method for Reset action. 29 * The class retrieves and sends data directly to dbus. 30 */ 31 class ManagerActionsReset : public Node 32 { 33 public: 34 ManagerActionsReset(CrowApp& app) : 35 Node(app, "/redfish/v1/Managers/bmc/Actions/Manager.Reset/") 36 { 37 entityPrivileges = { 38 {boost::beast::http::verb::get, {{"Login"}}}, 39 {boost::beast::http::verb::head, {{"Login"}}}, 40 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 41 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 42 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 43 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 44 } 45 46 private: 47 /** 48 * Function handles POST method request. 49 * Analyzes POST body message before sends Reset request data to dbus. 50 * OpenBMC allows for ResetType is GracefulRestart only. 51 */ 52 void doPost(crow::Response& res, const crow::Request& req, 53 const std::vector<std::string>& params) override 54 { 55 std::string resetType; 56 57 if (!json_util::readJson(req, res, "ResetType", resetType)) 58 { 59 return; 60 } 61 62 if (resetType != "GracefulRestart") 63 { 64 res.result(boost::beast::http::status::bad_request); 65 messages::actionParameterNotSupported(res, resetType, "ResetType"); 66 BMCWEB_LOG_ERROR << "Request incorrect action parameter: " 67 << resetType; 68 res.end(); 69 return; 70 } 71 doBMCGracefulRestart(res, req, params); 72 } 73 74 /** 75 * Function transceives data with dbus directly. 76 * All BMC state properties will be retrieved before sending reset request. 77 */ 78 void doBMCGracefulRestart(crow::Response& res, const crow::Request& req, 79 const std::vector<std::string>& params) 80 { 81 const char* processName = "xyz.openbmc_project.State.BMC"; 82 const char* objectPath = "/xyz/openbmc_project/state/bmc0"; 83 const char* interfaceName = "xyz.openbmc_project.State.BMC"; 84 const std::string& propertyValue = 85 "xyz.openbmc_project.State.BMC.Transition.Reboot"; 86 const char* destProperty = "RequestedBMCTransition"; 87 88 // Create the D-Bus variant for D-Bus call. 89 VariantType dbusPropertyValue(propertyValue); 90 91 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 92 93 crow::connections::systemBus->async_method_call( 94 [asyncResp](const boost::system::error_code ec) { 95 // Use "Set" method to set the property value. 96 if (ec) 97 { 98 BMCWEB_LOG_ERROR << "[Set] Bad D-Bus request error: " << ec; 99 messages::internalError(asyncResp->res); 100 return; 101 } 102 103 messages::success(asyncResp->res); 104 }, 105 processName, objectPath, "org.freedesktop.DBus.Properties", "Set", 106 interfaceName, destProperty, dbusPropertyValue); 107 } 108 }; 109 110 static constexpr const char* objectManagerIface = 111 "org.freedesktop.DBus.ObjectManager"; 112 static constexpr const char* pidConfigurationIface = 113 "xyz.openbmc_project.Configuration.Pid"; 114 static constexpr const char* pidZoneConfigurationIface = 115 "xyz.openbmc_project.Configuration.Pid.Zone"; 116 static constexpr const char* stepwiseConfigurationIface = 117 "xyz.openbmc_project.Configuration.Stepwise"; 118 119 static void asyncPopulatePid(const std::string& connection, 120 const std::string& path, 121 std::shared_ptr<AsyncResp> asyncResp) 122 { 123 124 crow::connections::systemBus->async_method_call( 125 [asyncResp](const boost::system::error_code ec, 126 const dbus::utility::ManagedObjectType& managedObj) { 127 if (ec) 128 { 129 BMCWEB_LOG_ERROR << ec; 130 asyncResp->res.jsonValue.clear(); 131 messages::internalError(asyncResp->res); 132 return; 133 } 134 nlohmann::json& configRoot = 135 asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"]; 136 nlohmann::json& fans = configRoot["FanControllers"]; 137 fans["@odata.type"] = "#OemManager.FanControllers"; 138 fans["@odata.context"] = 139 "/redfish/v1/$metadata#OemManager.FanControllers"; 140 fans["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc/" 141 "Fan/FanControllers"; 142 143 nlohmann::json& pids = configRoot["PidControllers"]; 144 pids["@odata.type"] = "#OemManager.PidControllers"; 145 pids["@odata.context"] = 146 "/redfish/v1/$metadata#OemManager.PidControllers"; 147 pids["@odata.id"] = 148 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/PidControllers"; 149 150 nlohmann::json& stepwise = configRoot["StepwiseControllers"]; 151 stepwise["@odata.type"] = "#OemManager.StepwiseControllers"; 152 stepwise["@odata.context"] = 153 "/redfish/v1/$metadata#OemManager.StepwiseControllers"; 154 stepwise["@odata.id"] = 155 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/StepwiseControllers"; 156 157 nlohmann::json& zones = configRoot["FanZones"]; 158 zones["@odata.id"] = 159 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones"; 160 zones["@odata.type"] = "#OemManager.FanZones"; 161 zones["@odata.context"] = 162 "/redfish/v1/$metadata#OemManager.FanZones"; 163 configRoot["@odata.id"] = 164 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan"; 165 configRoot["@odata.type"] = "#OemManager.Fan"; 166 configRoot["@odata.context"] = 167 "/redfish/v1/$metadata#OemManager.Fan"; 168 169 for (const auto& pathPair : managedObj) 170 { 171 for (const auto& intfPair : pathPair.second) 172 { 173 if (intfPair.first != pidConfigurationIface && 174 intfPair.first != pidZoneConfigurationIface && 175 intfPair.first != stepwiseConfigurationIface) 176 { 177 continue; 178 } 179 auto findName = intfPair.second.find("Name"); 180 if (findName == intfPair.second.end()) 181 { 182 BMCWEB_LOG_ERROR << "Pid Field missing Name"; 183 messages::internalError(asyncResp->res); 184 return; 185 } 186 const std::string* namePtr = 187 std::get_if<std::string>(&findName->second); 188 if (namePtr == nullptr) 189 { 190 BMCWEB_LOG_ERROR << "Pid Name Field illegal"; 191 messages::internalError(asyncResp->res); 192 return; 193 } 194 195 std::string name = *namePtr; 196 dbus::utility::escapePathForDbus(name); 197 nlohmann::json* config = nullptr; 198 if (intfPair.first == pidZoneConfigurationIface) 199 { 200 std::string chassis; 201 if (!dbus::utility::getNthStringFromPath( 202 pathPair.first.str, 5, chassis)) 203 { 204 chassis = "#IllegalValue"; 205 } 206 nlohmann::json& zone = zones[name]; 207 zone["Chassis"] = { 208 {"@odata.id", "/redfish/v1/Chassis/" + chassis}}; 209 zone["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/" 210 "OpenBmc/Fan/FanZones/" + 211 name; 212 zone["@odata.type"] = "#OemManager.FanZone"; 213 zone["@odata.context"] = 214 "/redfish/v1/$metadata#OemManager.FanZone"; 215 config = &zone; 216 } 217 218 else if (intfPair.first == stepwiseConfigurationIface) 219 { 220 nlohmann::json& controller = stepwise[name]; 221 config = &controller; 222 223 controller["@odata.id"] = 224 "/redfish/v1/Managers/bmc#/Oem/" 225 "OpenBmc/Fan/StepwiseControllers/" + 226 std::string(name); 227 controller["@odata.type"] = 228 "#OemManager.StepwiseController"; 229 230 controller["@odata.context"] = 231 "/redfish/v1/" 232 "$metadata#OemManager.StepwiseController"; 233 } 234 235 // pid and fans are off the same configuration 236 else if (intfPair.first == pidConfigurationIface) 237 { 238 const std::string* classPtr = nullptr; 239 auto findClass = intfPair.second.find("Class"); 240 if (findClass != intfPair.second.end()) 241 { 242 classPtr = 243 std::get_if<std::string>(&findClass->second); 244 } 245 if (classPtr == nullptr) 246 { 247 BMCWEB_LOG_ERROR << "Pid Class Field illegal"; 248 messages::internalError(asyncResp->res); 249 return; 250 } 251 bool isFan = *classPtr == "fan"; 252 nlohmann::json& element = 253 isFan ? fans[name] : pids[name]; 254 config = &element; 255 if (isFan) 256 { 257 element["@odata.id"] = 258 "/redfish/v1/Managers/bmc#/Oem/" 259 "OpenBmc/Fan/FanControllers/" + 260 std::string(name); 261 element["@odata.type"] = 262 "#OemManager.FanController"; 263 264 element["@odata.context"] = 265 "/redfish/v1/" 266 "$metadata#OemManager.FanController"; 267 } 268 else 269 { 270 element["@odata.id"] = 271 "/redfish/v1/Managers/bmc#/Oem/" 272 "OpenBmc/Fan/PidControllers/" + 273 std::string(name); 274 element["@odata.type"] = 275 "#OemManager.PidController"; 276 element["@odata.context"] = 277 "/redfish/v1/$metadata" 278 "#OemManager.PidController"; 279 } 280 } 281 else 282 { 283 BMCWEB_LOG_ERROR << "Unexpected configuration"; 284 messages::internalError(asyncResp->res); 285 return; 286 } 287 288 // used for making maps out of 2 vectors 289 const std::vector<double>* keys = nullptr; 290 const std::vector<double>* values = nullptr; 291 292 for (const auto& propertyPair : intfPair.second) 293 { 294 if (propertyPair.first == "Type" || 295 propertyPair.first == "Class" || 296 propertyPair.first == "Name") 297 { 298 continue; 299 } 300 301 // zones 302 if (intfPair.first == pidZoneConfigurationIface) 303 { 304 const double* ptr = 305 std::get_if<double>(&propertyPair.second); 306 if (ptr == nullptr) 307 { 308 BMCWEB_LOG_ERROR << "Field Illegal " 309 << propertyPair.first; 310 messages::internalError(asyncResp->res); 311 return; 312 } 313 (*config)[propertyPair.first] = *ptr; 314 } 315 316 if (intfPair.first == stepwiseConfigurationIface) 317 { 318 if (propertyPair.first == "Reading" || 319 propertyPair.first == "Output") 320 { 321 const std::vector<double>* ptr = 322 std::get_if<std::vector<double>>( 323 &propertyPair.second); 324 325 if (ptr == nullptr) 326 { 327 BMCWEB_LOG_ERROR << "Field Illegal " 328 << propertyPair.first; 329 messages::internalError(asyncResp->res); 330 return; 331 } 332 333 if (propertyPair.first == "Reading") 334 { 335 keys = ptr; 336 } 337 else 338 { 339 values = ptr; 340 } 341 if (keys && values) 342 { 343 if (keys->size() != values->size()) 344 { 345 BMCWEB_LOG_ERROR 346 << "Reading and Output size don't " 347 "match "; 348 messages::internalError(asyncResp->res); 349 return; 350 } 351 nlohmann::json& steps = (*config)["Steps"]; 352 steps = nlohmann::json::array(); 353 for (size_t ii = 0; ii < keys->size(); ii++) 354 { 355 steps.push_back( 356 {{"Target", (*keys)[ii]}, 357 {"Output", (*values)[ii]}}); 358 } 359 } 360 } 361 if (propertyPair.first == "NegativeHysteresis" || 362 propertyPair.first == "PositiveHysteresis") 363 { 364 const double* ptr = 365 std::get_if<double>(&propertyPair.second); 366 if (ptr == nullptr) 367 { 368 BMCWEB_LOG_ERROR << "Field Illegal " 369 << propertyPair.first; 370 messages::internalError(asyncResp->res); 371 return; 372 } 373 (*config)[propertyPair.first] = *ptr; 374 } 375 } 376 377 // pid and fans are off the same configuration 378 if (intfPair.first == pidConfigurationIface || 379 intfPair.first == stepwiseConfigurationIface) 380 { 381 382 if (propertyPair.first == "Zones") 383 { 384 const std::vector<std::string>* inputs = 385 std::get_if<std::vector<std::string>>( 386 &propertyPair.second); 387 388 if (inputs == nullptr) 389 { 390 BMCWEB_LOG_ERROR 391 << "Zones Pid Field Illegal"; 392 messages::internalError(asyncResp->res); 393 return; 394 } 395 auto& data = (*config)[propertyPair.first]; 396 data = nlohmann::json::array(); 397 for (std::string itemCopy : *inputs) 398 { 399 dbus::utility::escapePathForDbus(itemCopy); 400 data.push_back( 401 {{"@odata.id", 402 "/redfish/v1/Managers/bmc#/Oem/" 403 "OpenBmc/Fan/FanZones/" + 404 itemCopy}}); 405 } 406 } 407 // todo(james): may never happen, but this 408 // assumes configuration data referenced in the 409 // PID config is provided by the same daemon, we 410 // could add another loop to cover all cases, 411 // but I'm okay kicking this can down the road a 412 // bit 413 414 else if (propertyPair.first == "Inputs" || 415 propertyPair.first == "Outputs") 416 { 417 auto& data = (*config)[propertyPair.first]; 418 const std::vector<std::string>* inputs = 419 std::get_if<std::vector<std::string>>( 420 &propertyPair.second); 421 422 if (inputs == nullptr) 423 { 424 BMCWEB_LOG_ERROR << "Field Illegal " 425 << propertyPair.first; 426 messages::internalError(asyncResp->res); 427 return; 428 } 429 data = *inputs; 430 } // doubles 431 else if (propertyPair.first == 432 "FFGainCoefficient" || 433 propertyPair.first == "FFOffCoefficient" || 434 propertyPair.first == "ICoefficient" || 435 propertyPair.first == "ILimitMax" || 436 propertyPair.first == "ILimitMin" || 437 propertyPair.first == 438 "PositiveHysteresis" || 439 propertyPair.first == 440 "NegativeHysteresis" || 441 propertyPair.first == "OutLimitMax" || 442 propertyPair.first == "OutLimitMin" || 443 propertyPair.first == "PCoefficient" || 444 propertyPair.first == "SetPoint" || 445 propertyPair.first == "SlewNeg" || 446 propertyPair.first == "SlewPos") 447 { 448 const double* ptr = 449 std::get_if<double>(&propertyPair.second); 450 if (ptr == nullptr) 451 { 452 BMCWEB_LOG_ERROR << "Field Illegal " 453 << propertyPair.first; 454 messages::internalError(asyncResp->res); 455 return; 456 } 457 (*config)[propertyPair.first] = *ptr; 458 } 459 } 460 } 461 } 462 } 463 }, 464 connection, path, objectManagerIface, "GetManagedObjects"); 465 } 466 467 enum class CreatePIDRet 468 { 469 fail, 470 del, 471 patch 472 }; 473 474 static bool getZonesFromJsonReq(const std::shared_ptr<AsyncResp>& response, 475 std::vector<nlohmann::json>& config, 476 std::vector<std::string>& zones) 477 { 478 if (config.empty()) 479 { 480 BMCWEB_LOG_ERROR << "Empty Zones"; 481 messages::propertyValueFormatError(response->res, 482 nlohmann::json::array(), "Zones"); 483 return false; 484 } 485 for (auto& odata : config) 486 { 487 std::string path; 488 if (!redfish::json_util::readJson(odata, response->res, "@odata.id", 489 path)) 490 { 491 return false; 492 } 493 std::string input; 494 if (!dbus::utility::getNthStringFromPath(path, 4, input)) 495 { 496 BMCWEB_LOG_ERROR << "Got invalid path " << path; 497 BMCWEB_LOG_ERROR << "Illegal Type Zones"; 498 messages::propertyValueFormatError(response->res, odata.dump(), 499 "Zones"); 500 return false; 501 } 502 boost::replace_all(input, "_", " "); 503 zones.emplace_back(std::move(input)); 504 } 505 return true; 506 } 507 508 static bool findChassis(const dbus::utility::ManagedObjectType& managedObj, 509 const std::string& value, std::string& chassis) 510 { 511 BMCWEB_LOG_DEBUG << "Find Chassis: " << value << "\n"; 512 513 std::string escaped = boost::replace_all_copy(value, " ", "_"); 514 escaped = "/" + escaped; 515 auto it = std::find_if( 516 managedObj.begin(), managedObj.end(), [&escaped](const auto& obj) { 517 if (boost::algorithm::ends_with(obj.first.str, escaped)) 518 { 519 BMCWEB_LOG_DEBUG << "Matched " << obj.first.str << "\n"; 520 return true; 521 } 522 return false; 523 }); 524 525 if (it == managedObj.end()) 526 { 527 return false; 528 } 529 // 5 comes from <chassis-name> being the 5th element 530 // /xyz/openbmc_project/inventory/system/chassis/<chassis-name> 531 return dbus::utility::getNthStringFromPath(it->first.str, 5, chassis); 532 } 533 534 static CreatePIDRet createPidInterface( 535 const std::shared_ptr<AsyncResp>& response, const std::string& type, 536 nlohmann::json::iterator it, const std::string& path, 537 const dbus::utility::ManagedObjectType& managedObj, bool createNewObject, 538 boost::container::flat_map<std::string, dbus::utility::DbusVariantType>& 539 output, 540 std::string& chassis) 541 { 542 543 // common deleter 544 if (it.value() == nullptr) 545 { 546 std::string iface; 547 if (type == "PidControllers" || type == "FanControllers") 548 { 549 iface = pidConfigurationIface; 550 } 551 else if (type == "FanZones") 552 { 553 iface = pidZoneConfigurationIface; 554 } 555 else if (type == "StepwiseControllers") 556 { 557 iface = stepwiseConfigurationIface; 558 } 559 else 560 { 561 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type " 562 << type; 563 messages::propertyUnknown(response->res, type); 564 return CreatePIDRet::fail; 565 } 566 // delete interface 567 crow::connections::systemBus->async_method_call( 568 [response, path](const boost::system::error_code ec) { 569 if (ec) 570 { 571 BMCWEB_LOG_ERROR << "Error patching " << path << ": " << ec; 572 messages::internalError(response->res); 573 return; 574 } 575 messages::success(response->res); 576 }, 577 "xyz.openbmc_project.EntityManager", path, iface, "Delete"); 578 return CreatePIDRet::del; 579 } 580 581 if (!createNewObject) 582 { 583 // if we aren't creating a new object, we should be able to find it on 584 // d-bus 585 if (!findChassis(managedObj, it.key(), chassis)) 586 { 587 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch"; 588 messages::invalidObject(response->res, it.key()); 589 return CreatePIDRet::fail; 590 } 591 } 592 593 if (type == "PidControllers" || type == "FanControllers") 594 { 595 if (createNewObject) 596 { 597 output["Class"] = type == "PidControllers" ? std::string("temp") 598 : std::string("fan"); 599 output["Type"] = std::string("Pid"); 600 } 601 602 std::optional<std::vector<nlohmann::json>> zones; 603 std::optional<std::vector<std::string>> inputs; 604 std::optional<std::vector<std::string>> outputs; 605 std::map<std::string, std::optional<double>> doubles; 606 if (!redfish::json_util::readJson( 607 it.value(), response->res, "Inputs", inputs, "Outputs", outputs, 608 "Zones", zones, "FFGainCoefficient", 609 doubles["FFGainCoefficient"], "FFOffCoefficient", 610 doubles["FFOffCoefficient"], "ICoefficient", 611 doubles["ICoefficient"], "ILimitMax", doubles["ILimitMax"], 612 "ILimitMin", doubles["ILimitMin"], "OutLimitMax", 613 doubles["OutLimitMax"], "OutLimitMin", doubles["OutLimitMin"], 614 "PCoefficient", doubles["PCoefficient"], "SetPoint", 615 doubles["SetPoint"], "SlewNeg", doubles["SlewNeg"], "SlewPos", 616 doubles["SlewPos"], "PositiveHysteresis", 617 doubles["PositiveHysteresis"], "NegativeHysteresis", 618 doubles["NegativeHysteresis"])) 619 { 620 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 621 << it.value().dump(); 622 return CreatePIDRet::fail; 623 } 624 if (zones) 625 { 626 std::vector<std::string> zonesStr; 627 if (!getZonesFromJsonReq(response, *zones, zonesStr)) 628 { 629 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones"; 630 return CreatePIDRet::fail; 631 } 632 if (chassis.empty() && 633 !findChassis(managedObj, zonesStr[0], chassis)) 634 { 635 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch"; 636 messages::invalidObject(response->res, it.key()); 637 return CreatePIDRet::fail; 638 } 639 640 output["Zones"] = std::move(zonesStr); 641 } 642 if (inputs || outputs) 643 { 644 std::array<std::optional<std::vector<std::string>>*, 2> containers = 645 {&inputs, &outputs}; 646 size_t index = 0; 647 for (const auto& containerPtr : containers) 648 { 649 std::optional<std::vector<std::string>>& container = 650 *containerPtr; 651 if (!container) 652 { 653 index++; 654 continue; 655 } 656 657 for (std::string& value : *container) 658 { 659 boost::replace_all(value, "_", " "); 660 } 661 std::string key; 662 if (index == 0) 663 { 664 key = "Inputs"; 665 } 666 else 667 { 668 key = "Outputs"; 669 } 670 output[key] = *container; 671 index++; 672 } 673 } 674 675 // doubles 676 for (const auto& pairs : doubles) 677 { 678 if (!pairs.second) 679 { 680 continue; 681 } 682 BMCWEB_LOG_DEBUG << pairs.first << " = " << *pairs.second; 683 output[pairs.first] = *(pairs.second); 684 } 685 } 686 687 else if (type == "FanZones") 688 { 689 output["Type"] = std::string("Pid.Zone"); 690 691 std::optional<nlohmann::json> chassisContainer; 692 std::optional<double> failSafePercent; 693 std::optional<double> minThermalRpm; 694 if (!redfish::json_util::readJson(it.value(), response->res, "Chassis", 695 chassisContainer, "FailSafePercent", 696 failSafePercent, "MinThermalRpm", 697 minThermalRpm)) 698 { 699 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 700 << it.value().dump(); 701 return CreatePIDRet::fail; 702 } 703 704 if (chassisContainer) 705 { 706 707 std::string chassisId; 708 if (!redfish::json_util::readJson(*chassisContainer, response->res, 709 "@odata.id", chassisId)) 710 { 711 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 712 << chassisContainer->dump(); 713 return CreatePIDRet::fail; 714 } 715 716 // /refish/v1/chassis/chassis_name/ 717 if (!dbus::utility::getNthStringFromPath(chassisId, 3, chassis)) 718 { 719 BMCWEB_LOG_ERROR << "Got invalid path " << chassisId; 720 messages::invalidObject(response->res, chassisId); 721 return CreatePIDRet::fail; 722 } 723 } 724 if (minThermalRpm) 725 { 726 output["MinThermalRpm"] = *minThermalRpm; 727 } 728 if (failSafePercent) 729 { 730 output["FailSafePercent"] = *failSafePercent; 731 } 732 } 733 else if (type == "StepwiseControllers") 734 { 735 output["Type"] = std::string("Stepwise"); 736 737 std::optional<std::vector<nlohmann::json>> zones; 738 std::optional<std::vector<nlohmann::json>> steps; 739 std::optional<std::vector<std::string>> inputs; 740 std::optional<double> positiveHysteresis; 741 std::optional<double> negativeHysteresis; 742 if (!redfish::json_util::readJson( 743 it.value(), response->res, "Zones", zones, "Steps", steps, 744 "Inputs", inputs, "PositiveHysteresis", positiveHysteresis, 745 "NegativeHysteresis", negativeHysteresis)) 746 { 747 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 748 << it.value().dump(); 749 return CreatePIDRet::fail; 750 } 751 752 if (zones) 753 { 754 std::vector<std::string> zonesStrs; 755 if (!getZonesFromJsonReq(response, *zones, zonesStrs)) 756 { 757 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones"; 758 return CreatePIDRet::fail; 759 } 760 if (chassis.empty() && 761 !findChassis(managedObj, zonesStrs[0], chassis)) 762 { 763 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch"; 764 messages::invalidObject(response->res, it.key()); 765 return CreatePIDRet::fail; 766 } 767 output["Zones"] = std::move(zonesStrs); 768 } 769 if (steps) 770 { 771 std::vector<double> readings; 772 std::vector<double> outputs; 773 for (auto& step : *steps) 774 { 775 double target; 776 double output; 777 778 if (!redfish::json_util::readJson(step, response->res, "Target", 779 target, "Output", output)) 780 { 781 BMCWEB_LOG_ERROR << "Line:" << __LINE__ 782 << ", Illegal Property " 783 << it.value().dump(); 784 return CreatePIDRet::fail; 785 } 786 readings.emplace_back(target); 787 outputs.emplace_back(output); 788 } 789 output["Reading"] = std::move(readings); 790 output["Output"] = std::move(outputs); 791 } 792 if (inputs) 793 { 794 for (std::string& value : *inputs) 795 { 796 boost::replace_all(value, "_", " "); 797 } 798 output["Inputs"] = std::move(*inputs); 799 } 800 if (negativeHysteresis) 801 { 802 output["NegativeHysteresis"] = *negativeHysteresis; 803 } 804 if (positiveHysteresis) 805 { 806 output["PositiveHysteresis"] = *positiveHysteresis; 807 } 808 } 809 else 810 { 811 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type " << type; 812 messages::propertyUnknown(response->res, type); 813 return CreatePIDRet::fail; 814 } 815 return CreatePIDRet::patch; 816 } 817 818 class Manager : public Node 819 { 820 public: 821 Manager(CrowApp& app) : Node(app, "/redfish/v1/Managers/bmc/") 822 { 823 uuid = app.template getMiddleware<crow::persistent_data::Middleware>() 824 .systemUuid; 825 entityPrivileges = { 826 {boost::beast::http::verb::get, {{"Login"}}}, 827 {boost::beast::http::verb::head, {{"Login"}}}, 828 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 829 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 830 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 831 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 832 } 833 834 private: 835 void getPidValues(std::shared_ptr<AsyncResp> asyncResp) 836 { 837 crow::connections::systemBus->async_method_call( 838 [asyncResp](const boost::system::error_code ec, 839 const crow::openbmc_mapper::GetSubTreeType& subtree) { 840 if (ec) 841 { 842 BMCWEB_LOG_ERROR << ec; 843 messages::internalError(asyncResp->res); 844 return; 845 } 846 847 // create map of <connection, path to objMgr>> 848 boost::container::flat_map<std::string, std::string> 849 objectMgrPaths; 850 boost::container::flat_set<std::string> calledConnections; 851 for (const auto& pathGroup : subtree) 852 { 853 for (const auto& connectionGroup : pathGroup.second) 854 { 855 auto findConnection = 856 calledConnections.find(connectionGroup.first); 857 if (findConnection != calledConnections.end()) 858 { 859 break; 860 } 861 for (const std::string& interface : 862 connectionGroup.second) 863 { 864 if (interface == objectManagerIface) 865 { 866 objectMgrPaths[connectionGroup.first] = 867 pathGroup.first; 868 } 869 // this list is alphabetical, so we 870 // should have found the objMgr by now 871 if (interface == pidConfigurationIface || 872 interface == pidZoneConfigurationIface || 873 interface == stepwiseConfigurationIface) 874 { 875 auto findObjMgr = 876 objectMgrPaths.find(connectionGroup.first); 877 if (findObjMgr == objectMgrPaths.end()) 878 { 879 BMCWEB_LOG_DEBUG << connectionGroup.first 880 << "Has no Object Manager"; 881 continue; 882 } 883 884 calledConnections.insert(connectionGroup.first); 885 886 asyncPopulatePid(findObjMgr->first, 887 findObjMgr->second, asyncResp); 888 break; 889 } 890 } 891 } 892 } 893 }, 894 "xyz.openbmc_project.ObjectMapper", 895 "/xyz/openbmc_project/object_mapper", 896 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0, 897 std::array<const char*, 4>{ 898 pidConfigurationIface, pidZoneConfigurationIface, 899 objectManagerIface, stepwiseConfigurationIface}); 900 } 901 902 void doGet(crow::Response& res, const crow::Request& req, 903 const std::vector<std::string>& params) override 904 { 905 res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc"; 906 res.jsonValue["@odata.type"] = "#Manager.v1_3_0.Manager"; 907 res.jsonValue["@odata.context"] = 908 "/redfish/v1/$metadata#Manager.Manager"; 909 res.jsonValue["Id"] = "bmc"; 910 res.jsonValue["Name"] = "OpenBmc Manager"; 911 res.jsonValue["Description"] = "Baseboard Management Controller"; 912 res.jsonValue["PowerState"] = "On"; 913 res.jsonValue["Status"] = {{"State", "Enabled"}, {"Health", "OK"}}; 914 res.jsonValue["ManagerType"] = "BMC"; 915 res.jsonValue["UUID"] = uuid; 916 res.jsonValue["Model"] = "OpenBmc"; // TODO(ed), get model 917 918 res.jsonValue["LogServices"] = { 919 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices"}}; 920 921 res.jsonValue["NetworkProtocol"] = { 922 {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol"}}; 923 924 res.jsonValue["EthernetInterfaces"] = { 925 {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces"}}; 926 // default oem data 927 nlohmann::json& oem = res.jsonValue["Oem"]; 928 nlohmann::json& oemOpenbmc = oem["OpenBmc"]; 929 oem["@odata.type"] = "#OemManager.Oem"; 930 oem["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem"; 931 oem["@odata.context"] = "/redfish/v1/$metadata#OemManager.Oem"; 932 oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc"; 933 oemOpenbmc["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc"; 934 oemOpenbmc["@odata.context"] = 935 "/redfish/v1/$metadata#OemManager.OpenBmc"; 936 937 // Update Actions object. 938 nlohmann::json& manager_reset = 939 res.jsonValue["Actions"]["#Manager.Reset"]; 940 manager_reset["target"] = 941 "/redfish/v1/Managers/bmc/Actions/Manager.Reset"; 942 manager_reset["ResetType@Redfish.AllowableValues"] = { 943 "GracefulRestart"}; 944 945 res.jsonValue["DateTime"] = getDateTime(); 946 res.jsonValue["Links"] = { 947 {"ManagerForServers@odata.count", 1}, 948 {"ManagerForServers", 949 {{{"@odata.id", "/redfish/v1/Systems/system"}}}}, 950 {"ManagerForServers", nlohmann::json::array()}}; 951 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 952 953 crow::connections::systemBus->async_method_call( 954 [asyncResp](const boost::system::error_code ec, 955 const dbus::utility::ManagedObjectType& resp) { 956 if (ec) 957 { 958 BMCWEB_LOG_ERROR << "Error while getting Software Version"; 959 messages::internalError(asyncResp->res); 960 return; 961 } 962 963 for (auto& objpath : resp) 964 { 965 for (auto& interface : objpath.second) 966 { 967 // If interface is 968 // xyz.openbmc_project.Software.Version, this is 969 // what we're looking for. 970 if (interface.first == 971 "xyz.openbmc_project.Software.Version") 972 { 973 // Cut out everyting until last "/", ... 974 for (auto& property : interface.second) 975 { 976 if (property.first == "Version") 977 { 978 const std::string* value = 979 std::get_if<std::string>( 980 &property.second); 981 if (value == nullptr) 982 { 983 continue; 984 } 985 asyncResp->res 986 .jsonValue["FirmwareVersion"] = *value; 987 } 988 } 989 } 990 } 991 } 992 }, 993 "xyz.openbmc_project.Software.BMC.Updater", 994 "/xyz/openbmc_project/software", 995 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 996 getPidValues(asyncResp); 997 } 998 void setPidValues(std::shared_ptr<AsyncResp> response, nlohmann::json& data) 999 { 1000 1001 // todo(james): might make sense to do a mapper call here if this 1002 // interface gets more traction 1003 crow::connections::systemBus->async_method_call( 1004 [response, 1005 data](const boost::system::error_code ec, 1006 const dbus::utility::ManagedObjectType& managedObj) { 1007 if (ec) 1008 { 1009 BMCWEB_LOG_ERROR << "Error communicating to Entity Manager"; 1010 messages::internalError(response->res); 1011 return; 1012 } 1013 1014 // todo(james) mutable doesn't work with asio bindings 1015 nlohmann::json jsonData = data; 1016 1017 std::optional<nlohmann::json> pidControllers; 1018 std::optional<nlohmann::json> fanControllers; 1019 std::optional<nlohmann::json> fanZones; 1020 std::optional<nlohmann::json> stepwiseControllers; 1021 if (!redfish::json_util::readJson( 1022 jsonData, response->res, "PidControllers", 1023 pidControllers, "FanControllers", fanControllers, 1024 "FanZones", fanZones, "StepwiseControllers", 1025 stepwiseControllers)) 1026 { 1027 BMCWEB_LOG_ERROR << "Line:" << __LINE__ 1028 << ", Illegal Property " 1029 << jsonData.dump(); 1030 return; 1031 } 1032 std::array< 1033 std::pair<std::string, std::optional<nlohmann::json>*>, 4> 1034 sections = { 1035 std::make_pair("PidControllers", &pidControllers), 1036 std::make_pair("FanControllers", &fanControllers), 1037 std::make_pair("FanZones", &fanZones), 1038 std::make_pair("StepwiseControllers", 1039 &stepwiseControllers)}; 1040 1041 for (auto& containerPair : sections) 1042 { 1043 auto& container = *(containerPair.second); 1044 if (!container) 1045 { 1046 continue; 1047 } 1048 std::string& type = containerPair.first; 1049 1050 for (nlohmann::json::iterator it = container->begin(); 1051 it != container->end(); it++) 1052 { 1053 const auto& name = it.key(); 1054 auto pathItr = 1055 std::find_if(managedObj.begin(), managedObj.end(), 1056 [&name](const auto& obj) { 1057 return boost::algorithm::ends_with( 1058 obj.first.str, "/" + name); 1059 }); 1060 boost::container::flat_map< 1061 std::string, dbus::utility::DbusVariantType> 1062 output; 1063 1064 output.reserve(16); // The pid interface length 1065 1066 // determines if we're patching entity-manager or 1067 // creating a new object 1068 bool createNewObject = (pathItr == managedObj.end()); 1069 std::string iface; 1070 if (type == "PidControllers" || 1071 type == "FanControllers") 1072 { 1073 iface = pidConfigurationIface; 1074 if (!createNewObject && 1075 pathItr->second.find(pidConfigurationIface) == 1076 pathItr->second.end()) 1077 { 1078 createNewObject = true; 1079 } 1080 } 1081 else if (type == "FanZones") 1082 { 1083 iface = pidZoneConfigurationIface; 1084 if (!createNewObject && 1085 pathItr->second.find( 1086 pidZoneConfigurationIface) == 1087 pathItr->second.end()) 1088 { 1089 1090 createNewObject = true; 1091 } 1092 } 1093 else if (type == "StepwiseControllers") 1094 { 1095 iface = stepwiseConfigurationIface; 1096 if (!createNewObject && 1097 pathItr->second.find( 1098 stepwiseConfigurationIface) == 1099 pathItr->second.end()) 1100 { 1101 createNewObject = true; 1102 } 1103 } 1104 BMCWEB_LOG_DEBUG << "Create new = " << createNewObject 1105 << "\n"; 1106 output["Name"] = 1107 boost::replace_all_copy(name, "_", " "); 1108 1109 std::string chassis; 1110 CreatePIDRet ret = createPidInterface( 1111 response, type, it, pathItr->first.str, managedObj, 1112 createNewObject, output, chassis); 1113 if (ret == CreatePIDRet::fail) 1114 { 1115 return; 1116 } 1117 else if (ret == CreatePIDRet::del) 1118 { 1119 continue; 1120 } 1121 1122 if (!createNewObject) 1123 { 1124 for (const auto& property : output) 1125 { 1126 crow::connections::systemBus->async_method_call( 1127 [response, 1128 propertyName{std::string(property.first)}]( 1129 const boost::system::error_code ec) { 1130 if (ec) 1131 { 1132 BMCWEB_LOG_ERROR 1133 << "Error patching " 1134 << propertyName << ": " << ec; 1135 messages::internalError( 1136 response->res); 1137 return; 1138 } 1139 messages::success(response->res); 1140 }, 1141 "xyz.openbmc_project.EntityManager", 1142 pathItr->first.str, 1143 "org.freedesktop.DBus.Properties", "Set", 1144 iface, property.first, property.second); 1145 } 1146 } 1147 else 1148 { 1149 if (chassis.empty()) 1150 { 1151 BMCWEB_LOG_ERROR 1152 << "Failed to get chassis from config"; 1153 messages::invalidObject(response->res, name); 1154 return; 1155 } 1156 1157 bool foundChassis = false; 1158 for (const auto& obj : managedObj) 1159 { 1160 if (boost::algorithm::ends_with(obj.first.str, 1161 chassis)) 1162 { 1163 chassis = obj.first.str; 1164 foundChassis = true; 1165 break; 1166 } 1167 } 1168 if (!foundChassis) 1169 { 1170 BMCWEB_LOG_ERROR 1171 << "Failed to find chassis on dbus"; 1172 messages::resourceMissingAtURI( 1173 response->res, 1174 "/redfish/v1/Chassis/" + chassis); 1175 return; 1176 } 1177 1178 crow::connections::systemBus->async_method_call( 1179 [response](const boost::system::error_code ec) { 1180 if (ec) 1181 { 1182 BMCWEB_LOG_ERROR 1183 << "Error Adding Pid Object " << ec; 1184 messages::internalError(response->res); 1185 return; 1186 } 1187 messages::success(response->res); 1188 }, 1189 "xyz.openbmc_project.EntityManager", chassis, 1190 "xyz.openbmc_project.AddObject", "AddObject", 1191 output); 1192 } 1193 } 1194 } 1195 }, 1196 "xyz.openbmc_project.EntityManager", "/", objectManagerIface, 1197 "GetManagedObjects"); 1198 } 1199 1200 void doPatch(crow::Response& res, const crow::Request& req, 1201 const std::vector<std::string>& params) override 1202 { 1203 std::optional<nlohmann::json> oem; 1204 1205 if (!json_util::readJson(req, res, "Oem", oem)) 1206 { 1207 return; 1208 } 1209 1210 std::shared_ptr<AsyncResp> response = std::make_shared<AsyncResp>(res); 1211 1212 if (oem) 1213 { 1214 std::optional<nlohmann::json> openbmc; 1215 if (!redfish::json_util::readJson(*oem, res, "OpenBmc", openbmc)) 1216 { 1217 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 1218 << oem->dump(); 1219 return; 1220 } 1221 if (openbmc) 1222 { 1223 std::optional<nlohmann::json> fan; 1224 if (!redfish::json_util::readJson(*openbmc, res, "Fan", fan)) 1225 { 1226 BMCWEB_LOG_ERROR << "Line:" << __LINE__ 1227 << ", Illegal Property " 1228 << openbmc->dump(); 1229 return; 1230 } 1231 if (fan) 1232 { 1233 setPidValues(response, *fan); 1234 } 1235 } 1236 } 1237 } 1238 1239 std::string getDateTime() const 1240 { 1241 std::array<char, 128> dateTime; 1242 std::string redfishDateTime("0000-00-00T00:00:00Z00:00"); 1243 std::time_t time = std::time(nullptr); 1244 1245 if (std::strftime(dateTime.begin(), dateTime.size(), "%FT%T%z", 1246 std::localtime(&time))) 1247 { 1248 // insert the colon required by the ISO 8601 standard 1249 redfishDateTime = std::string(dateTime.data()); 1250 redfishDateTime.insert(redfishDateTime.end() - 2, ':'); 1251 } 1252 1253 return redfishDateTime; 1254 } 1255 1256 std::string uuid; 1257 }; 1258 1259 class ManagerCollection : public Node 1260 { 1261 public: 1262 ManagerCollection(CrowApp& app) : Node(app, "/redfish/v1/Managers/") 1263 { 1264 entityPrivileges = { 1265 {boost::beast::http::verb::get, {{"Login"}}}, 1266 {boost::beast::http::verb::head, {{"Login"}}}, 1267 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1268 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1269 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1270 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1271 } 1272 1273 private: 1274 void doGet(crow::Response& res, const crow::Request& req, 1275 const std::vector<std::string>& params) override 1276 { 1277 // Collections don't include the static data added by SubRoute 1278 // because it has a duplicate entry for members 1279 res.jsonValue["@odata.id"] = "/redfish/v1/Managers"; 1280 res.jsonValue["@odata.type"] = "#ManagerCollection.ManagerCollection"; 1281 res.jsonValue["@odata.context"] = 1282 "/redfish/v1/$metadata#ManagerCollection.ManagerCollection"; 1283 res.jsonValue["Name"] = "Manager Collection"; 1284 res.jsonValue["Members@odata.count"] = 1; 1285 res.jsonValue["Members"] = { 1286 {{"@odata.id", "/redfish/v1/Managers/bmc"}}}; 1287 res.end(); 1288 } 1289 }; 1290 } // namespace redfish 1291