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 if (!dbus::utility::getNthStringFromPath(path, 4, input)) 505 { 506 BMCWEB_LOG_ERROR << "Got invalid path " << path; 507 BMCWEB_LOG_ERROR << "Illegal Type Zones"; 508 messages::propertyValueFormatError(response->res, odata.dump(), 509 "Zones"); 510 return false; 511 } 512 boost::replace_all(input, "_", " "); 513 zones.emplace_back(std::move(input)); 514 } 515 return true; 516 } 517 518 static bool findChassis(const dbus::utility::ManagedObjectType& managedObj, 519 const std::string& value, std::string& chassis) 520 { 521 BMCWEB_LOG_DEBUG << "Find Chassis: " << value << "\n"; 522 523 std::string escaped = boost::replace_all_copy(value, " ", "_"); 524 escaped = "/" + escaped; 525 auto it = std::find_if( 526 managedObj.begin(), managedObj.end(), [&escaped](const auto& obj) { 527 if (boost::algorithm::ends_with(obj.first.str, escaped)) 528 { 529 BMCWEB_LOG_DEBUG << "Matched " << obj.first.str << "\n"; 530 return true; 531 } 532 return false; 533 }); 534 535 if (it == managedObj.end()) 536 { 537 return false; 538 } 539 // 5 comes from <chassis-name> being the 5th element 540 // /xyz/openbmc_project/inventory/system/chassis/<chassis-name> 541 return dbus::utility::getNthStringFromPath(it->first.str, 5, chassis); 542 } 543 544 static CreatePIDRet createPidInterface( 545 const std::shared_ptr<AsyncResp>& response, const std::string& type, 546 nlohmann::json::iterator it, const std::string& path, 547 const dbus::utility::ManagedObjectType& managedObj, bool createNewObject, 548 boost::container::flat_map<std::string, dbus::utility::DbusVariantType>& 549 output, 550 std::string& chassis) 551 { 552 553 // common deleter 554 if (it.value() == nullptr) 555 { 556 std::string iface; 557 if (type == "PidControllers" || type == "FanControllers") 558 { 559 iface = pidConfigurationIface; 560 } 561 else if (type == "FanZones") 562 { 563 iface = pidZoneConfigurationIface; 564 } 565 else if (type == "StepwiseControllers") 566 { 567 iface = stepwiseConfigurationIface; 568 } 569 else 570 { 571 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type " 572 << type; 573 messages::propertyUnknown(response->res, type); 574 return CreatePIDRet::fail; 575 } 576 // delete interface 577 crow::connections::systemBus->async_method_call( 578 [response, path](const boost::system::error_code ec) { 579 if (ec) 580 { 581 BMCWEB_LOG_ERROR << "Error patching " << path << ": " << ec; 582 messages::internalError(response->res); 583 return; 584 } 585 messages::success(response->res); 586 }, 587 "xyz.openbmc_project.EntityManager", path, iface, "Delete"); 588 return CreatePIDRet::del; 589 } 590 591 if (!createNewObject) 592 { 593 // if we aren't creating a new object, we should be able to find it on 594 // d-bus 595 if (!findChassis(managedObj, it.key(), chassis)) 596 { 597 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch"; 598 messages::invalidObject(response->res, it.key()); 599 return CreatePIDRet::fail; 600 } 601 } 602 603 if (type == "PidControllers" || type == "FanControllers") 604 { 605 if (createNewObject) 606 { 607 output["Class"] = type == "PidControllers" ? std::string("temp") 608 : std::string("fan"); 609 output["Type"] = std::string("Pid"); 610 } 611 612 std::optional<std::vector<nlohmann::json>> zones; 613 std::optional<std::vector<std::string>> inputs; 614 std::optional<std::vector<std::string>> outputs; 615 std::map<std::string, std::optional<double>> doubles; 616 if (!redfish::json_util::readJson( 617 it.value(), response->res, "Inputs", inputs, "Outputs", outputs, 618 "Zones", zones, "FFGainCoefficient", 619 doubles["FFGainCoefficient"], "FFOffCoefficient", 620 doubles["FFOffCoefficient"], "ICoefficient", 621 doubles["ICoefficient"], "ILimitMax", doubles["ILimitMax"], 622 "ILimitMin", doubles["ILimitMin"], "OutLimitMax", 623 doubles["OutLimitMax"], "OutLimitMin", doubles["OutLimitMin"], 624 "PCoefficient", doubles["PCoefficient"], "SetPoint", 625 doubles["SetPoint"], "SlewNeg", doubles["SlewNeg"], "SlewPos", 626 doubles["SlewPos"], "PositiveHysteresis", 627 doubles["PositiveHysteresis"], "NegativeHysteresis", 628 doubles["NegativeHysteresis"])) 629 { 630 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 631 << it.value().dump(); 632 return CreatePIDRet::fail; 633 } 634 if (zones) 635 { 636 std::vector<std::string> zonesStr; 637 if (!getZonesFromJsonReq(response, *zones, zonesStr)) 638 { 639 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones"; 640 return CreatePIDRet::fail; 641 } 642 if (chassis.empty() && 643 !findChassis(managedObj, zonesStr[0], chassis)) 644 { 645 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch"; 646 messages::invalidObject(response->res, it.key()); 647 return CreatePIDRet::fail; 648 } 649 650 output["Zones"] = std::move(zonesStr); 651 } 652 if (inputs || outputs) 653 { 654 std::array<std::optional<std::vector<std::string>>*, 2> containers = 655 {&inputs, &outputs}; 656 size_t index = 0; 657 for (const auto& containerPtr : containers) 658 { 659 std::optional<std::vector<std::string>>& container = 660 *containerPtr; 661 if (!container) 662 { 663 index++; 664 continue; 665 } 666 667 for (std::string& value : *container) 668 { 669 boost::replace_all(value, "_", " "); 670 } 671 std::string key; 672 if (index == 0) 673 { 674 key = "Inputs"; 675 } 676 else 677 { 678 key = "Outputs"; 679 } 680 output[key] = *container; 681 index++; 682 } 683 } 684 685 // doubles 686 for (const auto& pairs : doubles) 687 { 688 if (!pairs.second) 689 { 690 continue; 691 } 692 BMCWEB_LOG_DEBUG << pairs.first << " = " << *pairs.second; 693 output[pairs.first] = *(pairs.second); 694 } 695 } 696 697 else if (type == "FanZones") 698 { 699 output["Type"] = std::string("Pid.Zone"); 700 701 std::optional<nlohmann::json> chassisContainer; 702 std::optional<double> failSafePercent; 703 std::optional<double> minThermalOutput; 704 if (!redfish::json_util::readJson(it.value(), response->res, "Chassis", 705 chassisContainer, "FailSafePercent", 706 failSafePercent, "MinThermalOutput", 707 minThermalOutput)) 708 { 709 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 710 << it.value().dump(); 711 return CreatePIDRet::fail; 712 } 713 714 if (chassisContainer) 715 { 716 717 std::string chassisId; 718 if (!redfish::json_util::readJson(*chassisContainer, response->res, 719 "@odata.id", chassisId)) 720 { 721 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 722 << chassisContainer->dump(); 723 return CreatePIDRet::fail; 724 } 725 726 // /refish/v1/chassis/chassis_name/ 727 if (!dbus::utility::getNthStringFromPath(chassisId, 3, chassis)) 728 { 729 BMCWEB_LOG_ERROR << "Got invalid path " << chassisId; 730 messages::invalidObject(response->res, chassisId); 731 return CreatePIDRet::fail; 732 } 733 } 734 if (minThermalOutput) 735 { 736 output["MinThermalOutput"] = *minThermalOutput; 737 } 738 if (failSafePercent) 739 { 740 output["FailSafePercent"] = *failSafePercent; 741 } 742 } 743 else if (type == "StepwiseControllers") 744 { 745 output["Type"] = std::string("Stepwise"); 746 747 std::optional<std::vector<nlohmann::json>> zones; 748 std::optional<std::vector<nlohmann::json>> steps; 749 std::optional<std::vector<std::string>> inputs; 750 std::optional<double> positiveHysteresis; 751 std::optional<double> negativeHysteresis; 752 std::optional<std::string> direction; // upper clipping curve vs lower 753 if (!redfish::json_util::readJson( 754 it.value(), response->res, "Zones", zones, "Steps", steps, 755 "Inputs", inputs, "PositiveHysteresis", positiveHysteresis, 756 "NegativeHysteresis", negativeHysteresis, "Direction", 757 direction)) 758 { 759 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 760 << it.value().dump(); 761 return CreatePIDRet::fail; 762 } 763 764 if (zones) 765 { 766 std::vector<std::string> zonesStrs; 767 if (!getZonesFromJsonReq(response, *zones, zonesStrs)) 768 { 769 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones"; 770 return CreatePIDRet::fail; 771 } 772 if (chassis.empty() && 773 !findChassis(managedObj, zonesStrs[0], chassis)) 774 { 775 BMCWEB_LOG_ERROR << "Failed to get chassis from config patch"; 776 messages::invalidObject(response->res, it.key()); 777 return CreatePIDRet::fail; 778 } 779 output["Zones"] = std::move(zonesStrs); 780 } 781 if (steps) 782 { 783 std::vector<double> readings; 784 std::vector<double> outputs; 785 for (auto& step : *steps) 786 { 787 double target; 788 double output; 789 790 if (!redfish::json_util::readJson(step, response->res, "Target", 791 target, "Output", output)) 792 { 793 BMCWEB_LOG_ERROR << "Line:" << __LINE__ 794 << ", Illegal Property " 795 << it.value().dump(); 796 return CreatePIDRet::fail; 797 } 798 readings.emplace_back(target); 799 outputs.emplace_back(output); 800 } 801 output["Reading"] = std::move(readings); 802 output["Output"] = std::move(outputs); 803 } 804 if (inputs) 805 { 806 for (std::string& value : *inputs) 807 { 808 boost::replace_all(value, "_", " "); 809 } 810 output["Inputs"] = std::move(*inputs); 811 } 812 if (negativeHysteresis) 813 { 814 output["NegativeHysteresis"] = *negativeHysteresis; 815 } 816 if (positiveHysteresis) 817 { 818 output["PositiveHysteresis"] = *positiveHysteresis; 819 } 820 if (direction) 821 { 822 constexpr const std::array<const char*, 2> allowedDirections = { 823 "Ceiling", "Floor"}; 824 if (std::find(allowedDirections.begin(), allowedDirections.end(), 825 *direction) == allowedDirections.end()) 826 { 827 messages::propertyValueTypeError(response->res, "Direction", 828 *direction); 829 return CreatePIDRet::fail; 830 } 831 output["Class"] = *direction; 832 } 833 } 834 else 835 { 836 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type " << type; 837 messages::propertyUnknown(response->res, type); 838 return CreatePIDRet::fail; 839 } 840 return CreatePIDRet::patch; 841 } 842 843 class Manager : public Node 844 { 845 public: 846 Manager(CrowApp& app) : Node(app, "/redfish/v1/Managers/bmc/") 847 { 848 uuid = app.template getMiddleware<crow::persistent_data::Middleware>() 849 .systemUuid; 850 entityPrivileges = { 851 {boost::beast::http::verb::get, {{"Login"}}}, 852 {boost::beast::http::verb::head, {{"Login"}}}, 853 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 854 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 855 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 856 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 857 } 858 859 private: 860 void getPidValues(std::shared_ptr<AsyncResp> asyncResp) 861 { 862 crow::connections::systemBus->async_method_call( 863 [asyncResp](const boost::system::error_code ec, 864 const crow::openbmc_mapper::GetSubTreeType& subtree) { 865 if (ec) 866 { 867 BMCWEB_LOG_ERROR << ec; 868 messages::internalError(asyncResp->res); 869 return; 870 } 871 872 // create map of <connection, path to objMgr>> 873 boost::container::flat_map<std::string, std::string> 874 objectMgrPaths; 875 boost::container::flat_set<std::string> calledConnections; 876 for (const auto& pathGroup : subtree) 877 { 878 for (const auto& connectionGroup : pathGroup.second) 879 { 880 auto findConnection = 881 calledConnections.find(connectionGroup.first); 882 if (findConnection != calledConnections.end()) 883 { 884 break; 885 } 886 for (const std::string& interface : 887 connectionGroup.second) 888 { 889 if (interface == objectManagerIface) 890 { 891 objectMgrPaths[connectionGroup.first] = 892 pathGroup.first; 893 } 894 // this list is alphabetical, so we 895 // should have found the objMgr by now 896 if (interface == pidConfigurationIface || 897 interface == pidZoneConfigurationIface || 898 interface == stepwiseConfigurationIface) 899 { 900 auto findObjMgr = 901 objectMgrPaths.find(connectionGroup.first); 902 if (findObjMgr == objectMgrPaths.end()) 903 { 904 BMCWEB_LOG_DEBUG << connectionGroup.first 905 << "Has no Object Manager"; 906 continue; 907 } 908 909 calledConnections.insert(connectionGroup.first); 910 911 asyncPopulatePid(findObjMgr->first, 912 findObjMgr->second, asyncResp); 913 break; 914 } 915 } 916 } 917 } 918 }, 919 "xyz.openbmc_project.ObjectMapper", 920 "/xyz/openbmc_project/object_mapper", 921 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0, 922 std::array<const char*, 4>{ 923 pidConfigurationIface, pidZoneConfigurationIface, 924 objectManagerIface, stepwiseConfigurationIface}); 925 } 926 927 void doGet(crow::Response& res, const crow::Request& req, 928 const std::vector<std::string>& params) override 929 { 930 res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc"; 931 res.jsonValue["@odata.type"] = "#Manager.v1_3_0.Manager"; 932 res.jsonValue["@odata.context"] = 933 "/redfish/v1/$metadata#Manager.Manager"; 934 res.jsonValue["Id"] = "bmc"; 935 res.jsonValue["Name"] = "OpenBmc Manager"; 936 res.jsonValue["Description"] = "Baseboard Management Controller"; 937 res.jsonValue["PowerState"] = "On"; 938 res.jsonValue["Status"] = {{"State", "Enabled"}, {"Health", "OK"}}; 939 res.jsonValue["ManagerType"] = "BMC"; 940 res.jsonValue["UUID"] = uuid; 941 res.jsonValue["Model"] = "OpenBmc"; // TODO(ed), get model 942 943 res.jsonValue["LogServices"] = { 944 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices"}}; 945 946 res.jsonValue["NetworkProtocol"] = { 947 {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol"}}; 948 949 res.jsonValue["EthernetInterfaces"] = { 950 {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces"}}; 951 // default oem data 952 nlohmann::json& oem = res.jsonValue["Oem"]; 953 nlohmann::json& oemOpenbmc = oem["OpenBmc"]; 954 oem["@odata.type"] = "#OemManager.Oem"; 955 oem["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem"; 956 oem["@odata.context"] = "/redfish/v1/$metadata#OemManager.Oem"; 957 oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc"; 958 oemOpenbmc["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc"; 959 oemOpenbmc["@odata.context"] = 960 "/redfish/v1/$metadata#OemManager.OpenBmc"; 961 962 // Update Actions object. 963 nlohmann::json& manager_reset = 964 res.jsonValue["Actions"]["#Manager.Reset"]; 965 manager_reset["target"] = 966 "/redfish/v1/Managers/bmc/Actions/Manager.Reset"; 967 manager_reset["ResetType@Redfish.AllowableValues"] = { 968 "GracefulRestart"}; 969 970 res.jsonValue["DateTime"] = getDateTime(); 971 res.jsonValue["Links"]["ManagerForServers@odata.count"] = 1; 972 res.jsonValue["Links"]["ManagerForServers"] = { 973 {{"@odata.id", "/redfish/v1/Systems/system"}}}; 974 #ifdef BMCWEB_ENABLE_REDFISH_ONE_CHASSIS 975 res.jsonValue["Links"]["ManagerForChassis@odata.count"] = 1; 976 res.jsonValue["Links"]["ManagerForChassis"] = { 977 {{"@odata.id", "/redfish/v1/Chassis/chassis"}}}; 978 #endif 979 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 980 981 crow::connections::systemBus->async_method_call( 982 [asyncResp](const boost::system::error_code ec, 983 const dbus::utility::ManagedObjectType& resp) { 984 if (ec) 985 { 986 BMCWEB_LOG_ERROR << "Error while getting Software Version"; 987 messages::internalError(asyncResp->res); 988 return; 989 } 990 991 for (auto& objpath : resp) 992 { 993 for (auto& interface : objpath.second) 994 { 995 // If interface is 996 // xyz.openbmc_project.Software.Version, this is 997 // what we're looking for. 998 if (interface.first == 999 "xyz.openbmc_project.Software.Version") 1000 { 1001 // Cut out everyting until last "/", ... 1002 for (auto& property : interface.second) 1003 { 1004 if (property.first == "Version") 1005 { 1006 const std::string* value = 1007 std::get_if<std::string>( 1008 &property.second); 1009 if (value == nullptr) 1010 { 1011 continue; 1012 } 1013 asyncResp->res 1014 .jsonValue["FirmwareVersion"] = *value; 1015 } 1016 } 1017 } 1018 } 1019 } 1020 }, 1021 "xyz.openbmc_project.Software.BMC.Updater", 1022 "/xyz/openbmc_project/software", 1023 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1024 getPidValues(asyncResp); 1025 } 1026 void setPidValues(std::shared_ptr<AsyncResp> response, nlohmann::json& data) 1027 { 1028 1029 // todo(james): might make sense to do a mapper call here if this 1030 // interface gets more traction 1031 crow::connections::systemBus->async_method_call( 1032 [response, 1033 data](const boost::system::error_code ec, 1034 const dbus::utility::ManagedObjectType& managedObj) { 1035 if (ec) 1036 { 1037 BMCWEB_LOG_ERROR << "Error communicating to Entity Manager"; 1038 messages::internalError(response->res); 1039 return; 1040 } 1041 1042 // todo(james) mutable doesn't work with asio bindings 1043 nlohmann::json jsonData = data; 1044 1045 std::optional<nlohmann::json> pidControllers; 1046 std::optional<nlohmann::json> fanControllers; 1047 std::optional<nlohmann::json> fanZones; 1048 std::optional<nlohmann::json> stepwiseControllers; 1049 if (!redfish::json_util::readJson( 1050 jsonData, response->res, "PidControllers", 1051 pidControllers, "FanControllers", fanControllers, 1052 "FanZones", fanZones, "StepwiseControllers", 1053 stepwiseControllers)) 1054 { 1055 BMCWEB_LOG_ERROR << "Line:" << __LINE__ 1056 << ", Illegal Property " 1057 << jsonData.dump(); 1058 return; 1059 } 1060 std::array< 1061 std::pair<std::string, std::optional<nlohmann::json>*>, 4> 1062 sections = { 1063 std::make_pair("PidControllers", &pidControllers), 1064 std::make_pair("FanControllers", &fanControllers), 1065 std::make_pair("FanZones", &fanZones), 1066 std::make_pair("StepwiseControllers", 1067 &stepwiseControllers)}; 1068 1069 for (auto& containerPair : sections) 1070 { 1071 auto& container = *(containerPair.second); 1072 if (!container) 1073 { 1074 continue; 1075 } 1076 std::string& type = containerPair.first; 1077 1078 for (nlohmann::json::iterator it = container->begin(); 1079 it != container->end(); it++) 1080 { 1081 const auto& name = it.key(); 1082 auto pathItr = 1083 std::find_if(managedObj.begin(), managedObj.end(), 1084 [&name](const auto& obj) { 1085 return boost::algorithm::ends_with( 1086 obj.first.str, "/" + name); 1087 }); 1088 boost::container::flat_map< 1089 std::string, dbus::utility::DbusVariantType> 1090 output; 1091 1092 output.reserve(16); // The pid interface length 1093 1094 // determines if we're patching entity-manager or 1095 // creating a new object 1096 bool createNewObject = (pathItr == managedObj.end()); 1097 std::string iface; 1098 if (type == "PidControllers" || 1099 type == "FanControllers") 1100 { 1101 iface = pidConfigurationIface; 1102 if (!createNewObject && 1103 pathItr->second.find(pidConfigurationIface) == 1104 pathItr->second.end()) 1105 { 1106 createNewObject = true; 1107 } 1108 } 1109 else if (type == "FanZones") 1110 { 1111 iface = pidZoneConfigurationIface; 1112 if (!createNewObject && 1113 pathItr->second.find( 1114 pidZoneConfigurationIface) == 1115 pathItr->second.end()) 1116 { 1117 1118 createNewObject = true; 1119 } 1120 } 1121 else if (type == "StepwiseControllers") 1122 { 1123 iface = stepwiseConfigurationIface; 1124 if (!createNewObject && 1125 pathItr->second.find( 1126 stepwiseConfigurationIface) == 1127 pathItr->second.end()) 1128 { 1129 createNewObject = true; 1130 } 1131 } 1132 BMCWEB_LOG_DEBUG << "Create new = " << createNewObject 1133 << "\n"; 1134 output["Name"] = 1135 boost::replace_all_copy(name, "_", " "); 1136 1137 std::string chassis; 1138 CreatePIDRet ret = createPidInterface( 1139 response, type, it, pathItr->first.str, managedObj, 1140 createNewObject, output, chassis); 1141 if (ret == CreatePIDRet::fail) 1142 { 1143 return; 1144 } 1145 else if (ret == CreatePIDRet::del) 1146 { 1147 continue; 1148 } 1149 1150 if (!createNewObject) 1151 { 1152 for (const auto& property : output) 1153 { 1154 crow::connections::systemBus->async_method_call( 1155 [response, 1156 propertyName{std::string(property.first)}]( 1157 const boost::system::error_code ec) { 1158 if (ec) 1159 { 1160 BMCWEB_LOG_ERROR 1161 << "Error patching " 1162 << propertyName << ": " << ec; 1163 messages::internalError( 1164 response->res); 1165 return; 1166 } 1167 messages::success(response->res); 1168 }, 1169 "xyz.openbmc_project.EntityManager", 1170 pathItr->first.str, 1171 "org.freedesktop.DBus.Properties", "Set", 1172 iface, property.first, property.second); 1173 } 1174 } 1175 else 1176 { 1177 if (chassis.empty()) 1178 { 1179 BMCWEB_LOG_ERROR 1180 << "Failed to get chassis from config"; 1181 messages::invalidObject(response->res, name); 1182 return; 1183 } 1184 1185 bool foundChassis = false; 1186 for (const auto& obj : managedObj) 1187 { 1188 if (boost::algorithm::ends_with(obj.first.str, 1189 chassis)) 1190 { 1191 chassis = obj.first.str; 1192 foundChassis = true; 1193 break; 1194 } 1195 } 1196 if (!foundChassis) 1197 { 1198 BMCWEB_LOG_ERROR 1199 << "Failed to find chassis on dbus"; 1200 messages::resourceMissingAtURI( 1201 response->res, 1202 "/redfish/v1/Chassis/" + chassis); 1203 return; 1204 } 1205 1206 crow::connections::systemBus->async_method_call( 1207 [response](const boost::system::error_code ec) { 1208 if (ec) 1209 { 1210 BMCWEB_LOG_ERROR 1211 << "Error Adding Pid Object " << ec; 1212 messages::internalError(response->res); 1213 return; 1214 } 1215 messages::success(response->res); 1216 }, 1217 "xyz.openbmc_project.EntityManager", chassis, 1218 "xyz.openbmc_project.AddObject", "AddObject", 1219 output); 1220 } 1221 } 1222 } 1223 }, 1224 "xyz.openbmc_project.EntityManager", "/", objectManagerIface, 1225 "GetManagedObjects"); 1226 } 1227 1228 void doPatch(crow::Response& res, const crow::Request& req, 1229 const std::vector<std::string>& params) override 1230 { 1231 std::optional<nlohmann::json> oem; 1232 1233 if (!json_util::readJson(req, res, "Oem", oem)) 1234 { 1235 return; 1236 } 1237 1238 std::shared_ptr<AsyncResp> response = std::make_shared<AsyncResp>(res); 1239 1240 if (oem) 1241 { 1242 std::optional<nlohmann::json> openbmc; 1243 if (!redfish::json_util::readJson(*oem, res, "OpenBmc", openbmc)) 1244 { 1245 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 1246 << oem->dump(); 1247 return; 1248 } 1249 if (openbmc) 1250 { 1251 std::optional<nlohmann::json> fan; 1252 if (!redfish::json_util::readJson(*openbmc, res, "Fan", fan)) 1253 { 1254 BMCWEB_LOG_ERROR << "Line:" << __LINE__ 1255 << ", Illegal Property " 1256 << openbmc->dump(); 1257 return; 1258 } 1259 if (fan) 1260 { 1261 setPidValues(response, *fan); 1262 } 1263 } 1264 } 1265 } 1266 1267 std::string getDateTime() const 1268 { 1269 std::array<char, 128> dateTime; 1270 std::string redfishDateTime("0000-00-00T00:00:00Z00:00"); 1271 std::time_t time = std::time(nullptr); 1272 1273 if (std::strftime(dateTime.begin(), dateTime.size(), "%FT%T%z", 1274 std::localtime(&time))) 1275 { 1276 // insert the colon required by the ISO 8601 standard 1277 redfishDateTime = std::string(dateTime.data()); 1278 redfishDateTime.insert(redfishDateTime.end() - 2, ':'); 1279 } 1280 1281 return redfishDateTime; 1282 } 1283 1284 std::string uuid; 1285 }; 1286 1287 class ManagerCollection : public Node 1288 { 1289 public: 1290 ManagerCollection(CrowApp& app) : Node(app, "/redfish/v1/Managers/") 1291 { 1292 entityPrivileges = { 1293 {boost::beast::http::verb::get, {{"Login"}}}, 1294 {boost::beast::http::verb::head, {{"Login"}}}, 1295 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1296 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1297 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1298 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1299 } 1300 1301 private: 1302 void doGet(crow::Response& res, const crow::Request& req, 1303 const std::vector<std::string>& params) override 1304 { 1305 // Collections don't include the static data added by SubRoute 1306 // because it has a duplicate entry for members 1307 res.jsonValue["@odata.id"] = "/redfish/v1/Managers"; 1308 res.jsonValue["@odata.type"] = "#ManagerCollection.ManagerCollection"; 1309 res.jsonValue["@odata.context"] = 1310 "/redfish/v1/$metadata#ManagerCollection.ManagerCollection"; 1311 res.jsonValue["Name"] = "Manager Collection"; 1312 res.jsonValue["Members@odata.count"] = 1; 1313 res.jsonValue["Members"] = { 1314 {{"@odata.id", "/redfish/v1/Managers/bmc"}}}; 1315 res.end(); 1316 } 1317 }; 1318 } // namespace redfish 1319