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