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