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