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