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