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 23 namespace redfish 24 { 25 26 /** 27 * ManagerActionsReset class supports handle POST method for Reset action. 28 * The class retrieves and sends data directly to dbus. 29 */ 30 class ManagerActionsReset : public Node 31 { 32 public: 33 ManagerActionsReset(CrowApp& app) : 34 Node(app, "/redfish/v1/Managers/bmc/Actions/Manager.Reset/") 35 { 36 entityPrivileges = { 37 {boost::beast::http::verb::get, {{"Login"}}}, 38 {boost::beast::http::verb::head, {{"Login"}}}, 39 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 40 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 41 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 42 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 43 } 44 45 private: 46 /** 47 * Function handles POST method request. 48 * Analyzes POST body message before sends Reset request data to dbus. 49 * OpenBMC allows for ResetType is GracefulRestart only. 50 */ 51 void doPost(crow::Response& res, const crow::Request& req, 52 const std::vector<std::string>& params) override 53 { 54 std::string resetType; 55 56 if (!json_util::readJson(req, res, "ResetType", resetType)) 57 { 58 return; 59 } 60 61 if (resetType != "GracefulRestart") 62 { 63 res.result(boost::beast::http::status::bad_request); 64 messages::actionParameterNotSupported(res, resetType, "ResetType"); 65 BMCWEB_LOG_ERROR << "Request incorrect action parameter: " 66 << resetType; 67 res.end(); 68 return; 69 } 70 doBMCGracefulRestart(res, req, params); 71 } 72 73 /** 74 * Function transceives data with dbus directly. 75 * All BMC state properties will be retrieved before sending reset request. 76 */ 77 void doBMCGracefulRestart(crow::Response& res, const crow::Request& req, 78 const std::vector<std::string>& params) 79 { 80 const char* processName = "xyz.openbmc_project.State.BMC"; 81 const char* objectPath = "/xyz/openbmc_project/state/bmc0"; 82 const char* interfaceName = "xyz.openbmc_project.State.BMC"; 83 const std::string& propertyValue = 84 "xyz.openbmc_project.State.BMC.Transition.Reboot"; 85 const char* destProperty = "RequestedBMCTransition"; 86 87 // Create the D-Bus variant for D-Bus call. 88 VariantType dbusPropertyValue(propertyValue); 89 90 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 91 92 crow::connections::systemBus->async_method_call( 93 [asyncResp](const boost::system::error_code ec) { 94 // Use "Set" method to set the property value. 95 if (ec) 96 { 97 BMCWEB_LOG_ERROR << "[Set] Bad D-Bus request error: " << ec; 98 messages::internalError(asyncResp->res); 99 return; 100 } 101 102 messages::success(asyncResp->res); 103 }, 104 processName, objectPath, "org.freedesktop.DBus.Properties", "Set", 105 interfaceName, destProperty, dbusPropertyValue); 106 } 107 }; 108 109 static constexpr const char* objectManagerIface = 110 "org.freedesktop.DBus.ObjectManager"; 111 static constexpr const char* pidConfigurationIface = 112 "xyz.openbmc_project.Configuration.Pid"; 113 static constexpr const char* pidZoneConfigurationIface = 114 "xyz.openbmc_project.Configuration.Pid.Zone"; 115 static constexpr const char* stepwiseConfigurationIface = 116 "xyz.openbmc_project.Configuration.Stepwise"; 117 118 static void asyncPopulatePid(const std::string& connection, 119 const std::string& path, 120 std::shared_ptr<AsyncResp> asyncResp) 121 { 122 123 crow::connections::systemBus->async_method_call( 124 [asyncResp](const boost::system::error_code ec, 125 const dbus::utility::ManagedObjectType& managedObj) { 126 if (ec) 127 { 128 BMCWEB_LOG_ERROR << ec; 129 asyncResp->res.jsonValue.clear(); 130 messages::internalError(asyncResp->res); 131 return; 132 } 133 nlohmann::json& configRoot = 134 asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"]; 135 nlohmann::json& fans = configRoot["FanControllers"]; 136 fans["@odata.type"] = "#OemManager.FanControllers"; 137 fans["@odata.context"] = 138 "/redfish/v1/$metadata#OemManager.FanControllers"; 139 fans["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc/" 140 "Fan/FanControllers"; 141 142 nlohmann::json& pids = configRoot["PidControllers"]; 143 pids["@odata.type"] = "#OemManager.PidControllers"; 144 pids["@odata.context"] = 145 "/redfish/v1/$metadata#OemManager.PidControllers"; 146 pids["@odata.id"] = 147 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/PidControllers"; 148 149 nlohmann::json& stepwise = configRoot["StepwiseControllers"]; 150 stepwise["@odata.type"] = "#OemManager.StepwiseControllers"; 151 stepwise["@odata.context"] = 152 "/redfish/v1/$metadata#OemManager.StepwiseControllers"; 153 stepwise["@odata.id"] = 154 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/StepwiseControllers"; 155 156 nlohmann::json& zones = configRoot["FanZones"]; 157 zones["@odata.id"] = 158 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones"; 159 zones["@odata.type"] = "#OemManager.FanZones"; 160 zones["@odata.context"] = 161 "/redfish/v1/$metadata#OemManager.FanZones"; 162 configRoot["@odata.id"] = 163 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan"; 164 configRoot["@odata.type"] = "#OemManager.Fan"; 165 configRoot["@odata.context"] = 166 "/redfish/v1/$metadata#OemManager.Fan"; 167 168 bool propertyError = false; 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 sdbusplus::message::variant_ns::get_if<std::string>( 188 &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 = sdbusplus::message::variant_ns::get_if< 244 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 sdbusplus::message::variant_ns::get_if<double>( 307 &propertyPair.second); 308 if (ptr == nullptr) 309 { 310 BMCWEB_LOG_ERROR << "Field Illegal " 311 << propertyPair.first; 312 messages::internalError(asyncResp->res); 313 return; 314 } 315 (*config)[propertyPair.first] = *ptr; 316 } 317 318 if (intfPair.first == stepwiseConfigurationIface) 319 { 320 if (propertyPair.first == "Reading" || 321 propertyPair.first == "Output") 322 { 323 const std::vector<double>* ptr = 324 sdbusplus::message::variant_ns::get_if< 325 std::vector<double>>( 326 &propertyPair.second); 327 328 if (ptr == nullptr) 329 { 330 BMCWEB_LOG_ERROR << "Field Illegal " 331 << propertyPair.first; 332 messages::internalError(asyncResp->res); 333 return; 334 } 335 336 if (propertyPair.first == "Reading") 337 { 338 keys = ptr; 339 } 340 else 341 { 342 values = ptr; 343 } 344 if (keys && values) 345 { 346 if (keys->size() != values->size()) 347 { 348 BMCWEB_LOG_ERROR 349 << "Reading and Output size don't " 350 "match "; 351 messages::internalError(asyncResp->res); 352 return; 353 } 354 nlohmann::json& steps = (*config)["Steps"]; 355 steps = nlohmann::json::array(); 356 for (size_t ii = 0; ii < keys->size(); ii++) 357 { 358 steps.push_back( 359 {{"Target", (*keys)[ii]}, 360 {"Output", (*values)[ii]}}); 361 } 362 } 363 } 364 if (propertyPair.first == "NegativeHysteresis" || 365 propertyPair.first == "PositiveHysteresis") 366 { 367 const double* ptr = 368 sdbusplus::message::variant_ns::get_if< 369 double>(&propertyPair.second); 370 if (ptr == nullptr) 371 { 372 BMCWEB_LOG_ERROR << "Field Illegal " 373 << propertyPair.first; 374 messages::internalError(asyncResp->res); 375 return; 376 } 377 (*config)[propertyPair.first] = *ptr; 378 } 379 } 380 381 // pid and fans are off the same configuration 382 if (intfPair.first == pidConfigurationIface || 383 intfPair.first == stepwiseConfigurationIface) 384 { 385 386 if (propertyPair.first == "Zones") 387 { 388 const std::vector<std::string>* inputs = 389 sdbusplus::message::variant_ns::get_if< 390 std::vector<std::string>>( 391 &propertyPair.second); 392 393 if (inputs == nullptr) 394 { 395 BMCWEB_LOG_ERROR 396 << "Zones Pid Field Illegal"; 397 messages::internalError(asyncResp->res); 398 return; 399 } 400 auto& data = (*config)[propertyPair.first]; 401 data = nlohmann::json::array(); 402 for (std::string itemCopy : *inputs) 403 { 404 dbus::utility::escapePathForDbus(itemCopy); 405 data.push_back( 406 {{"@odata.id", 407 "/redfish/v1/Managers/bmc#/Oem/" 408 "OpenBmc/Fan/FanZones/" + 409 itemCopy}}); 410 } 411 } 412 // todo(james): may never happen, but this 413 // assumes configuration data referenced in the 414 // PID config is provided by the same daemon, we 415 // could add another loop to cover all cases, 416 // but I'm okay kicking this can down the road a 417 // bit 418 419 else if (propertyPair.first == "Inputs" || 420 propertyPair.first == "Outputs") 421 { 422 auto& data = (*config)[propertyPair.first]; 423 const std::vector<std::string>* inputs = 424 sdbusplus::message::variant_ns::get_if< 425 std::vector<std::string>>( 426 &propertyPair.second); 427 428 if (inputs == nullptr) 429 { 430 BMCWEB_LOG_ERROR << "Field Illegal " 431 << propertyPair.first; 432 messages::internalError(asyncResp->res); 433 return; 434 } 435 data = *inputs; 436 } // doubles 437 else if (propertyPair.first == 438 "FFGainCoefficient" || 439 propertyPair.first == "FFOffCoefficient" || 440 propertyPair.first == "ICoefficient" || 441 propertyPair.first == "ILimitMax" || 442 propertyPair.first == "ILimitMin" || 443 propertyPair.first == "OutLimitMax" || 444 propertyPair.first == "OutLimitMin" || 445 propertyPair.first == "PCoefficient" || 446 propertyPair.first == "SlewNeg" || 447 propertyPair.first == "SlewPos") 448 { 449 const double* ptr = 450 sdbusplus::message::variant_ns::get_if< 451 double>(&propertyPair.second); 452 if (ptr == nullptr) 453 { 454 BMCWEB_LOG_ERROR << "Field Illegal " 455 << propertyPair.first; 456 messages::internalError(asyncResp->res); 457 return; 458 } 459 (*config)[propertyPair.first] = *ptr; 460 } 461 } 462 } 463 } 464 } 465 }, 466 connection, path, objectManagerIface, "GetManagedObjects"); 467 } 468 469 enum class CreatePIDRet 470 { 471 fail, 472 del, 473 patch 474 }; 475 476 static bool getZonesFromJsonReq(const std::shared_ptr<AsyncResp>& response, 477 std::vector<nlohmann::json>& config, 478 std::vector<std::string>& zones) 479 { 480 481 for (auto& odata : config) 482 { 483 std::string path; 484 if (!redfish::json_util::readJson(odata, response->res, "@odata.id", 485 path)) 486 { 487 return false; 488 } 489 std::string input; 490 if (!dbus::utility::getNthStringFromPath(path, 4, input)) 491 { 492 BMCWEB_LOG_ERROR << "Got invalid path " << path; 493 BMCWEB_LOG_ERROR << "Illegal Type Zones"; 494 messages::propertyValueFormatError(response->res, odata.dump(), 495 "Zones"); 496 return false; 497 } 498 boost::replace_all(input, "_", " "); 499 zones.emplace_back(std::move(input)); 500 } 501 return true; 502 } 503 504 static CreatePIDRet createPidInterface( 505 const std::shared_ptr<AsyncResp>& response, const std::string& type, 506 nlohmann::json&& record, const std::string& path, 507 const dbus::utility::ManagedObjectType& managedObj, bool createNewObject, 508 boost::container::flat_map<std::string, dbus::utility::DbusVariantType>& 509 output, 510 std::string& chassis) 511 { 512 513 // common deleter 514 if (record == nullptr) 515 { 516 std::string iface; 517 if (type == "PidControllers" || type == "FanControllers") 518 { 519 iface = pidConfigurationIface; 520 } 521 else if (type == "FanZones") 522 { 523 iface = pidZoneConfigurationIface; 524 } 525 else if (type == "StepwiseControllers") 526 { 527 iface = stepwiseConfigurationIface; 528 } 529 else 530 { 531 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type " 532 << type; 533 messages::propertyUnknown(response->res, type); 534 return CreatePIDRet::fail; 535 } 536 // delete interface 537 crow::connections::systemBus->async_method_call( 538 [response, path](const boost::system::error_code ec) { 539 if (ec) 540 { 541 BMCWEB_LOG_ERROR << "Error patching " << path << ": " << ec; 542 messages::internalError(response->res); 543 } 544 }, 545 "xyz.openbmc_project.EntityManager", path, iface, "Delete"); 546 return CreatePIDRet::del; 547 } 548 549 if (type == "PidControllers" || type == "FanControllers") 550 { 551 if (createNewObject) 552 { 553 output["Class"] = type == "PidControllers" ? std::string("temp") 554 : std::string("fan"); 555 output["Type"] = std::string("Pid"); 556 } 557 558 std::optional<std::vector<nlohmann::json>> zones; 559 std::optional<std::vector<std::string>> inputs; 560 std::optional<std::vector<std::string>> outputs; 561 std::map<std::string, std::optional<double>> doubles; 562 if (!redfish::json_util::readJson( 563 record, response->res, "Inputs", inputs, "Outputs", outputs, 564 "Zones", zones, "FFGainCoefficient", 565 doubles["FFGainCoefficient"], "FFOffCoefficient", 566 doubles["FFOffCoefficient"], "ICoefficient", 567 doubles["ICoefficient"], "ILimitMax", doubles["ILimitMax"], 568 "ILimitMin", doubles["ILimitMin"], "OutLimitMax", 569 doubles["OutLimitMax"], "OutLimitMin", doubles["OutLimitMin"], 570 "PCoefficient", doubles["PCoefficient"], "SetPoint", 571 doubles["SetPoint"], "SlewNeg", doubles["SlewNeg"], "SlewPos", 572 doubles["SlewPos"])) 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["ManagerType"] = "BMC"; 887 res.jsonValue["UUID"] = uuid; 888 res.jsonValue["Model"] = "OpenBmc"; // TODO(ed), get model 889 890 res.jsonValue["LogServices"] = { 891 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices"}}; 892 893 res.jsonValue["NetworkProtocol"] = { 894 {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol"}}; 895 896 res.jsonValue["EthernetInterfaces"] = { 897 {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces"}}; 898 // default oem data 899 nlohmann::json& oem = res.jsonValue["Oem"]; 900 nlohmann::json& oemOpenbmc = oem["OpenBmc"]; 901 oem["@odata.type"] = "#OemManager.Oem"; 902 oem["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem"; 903 oem["@odata.context"] = "/redfish/v1/$metadata#OemManager.Oem"; 904 oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc"; 905 oemOpenbmc["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc"; 906 oemOpenbmc["@odata.context"] = 907 "/redfish/v1/$metadata#OemManager.OpenBmc"; 908 909 // Update Actions object. 910 nlohmann::json& manager_reset = 911 res.jsonValue["Actions"]["#Manager.Reset"]; 912 manager_reset["target"] = 913 "/redfish/v1/Managers/bmc/Actions/Manager.Reset"; 914 manager_reset["ResetType@Redfish.AllowableValues"] = { 915 "GracefulRestart"}; 916 917 res.jsonValue["DateTime"] = getDateTime(); 918 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 919 920 crow::connections::systemBus->async_method_call( 921 [asyncResp](const boost::system::error_code ec, 922 const dbus::utility::ManagedObjectType& resp) { 923 if (ec) 924 { 925 BMCWEB_LOG_ERROR << "Error while getting Software Version"; 926 messages::internalError(asyncResp->res); 927 return; 928 } 929 930 for (auto& objpath : resp) 931 { 932 for (auto& interface : objpath.second) 933 { 934 // If interface is 935 // xyz.openbmc_project.Software.Version, this is 936 // what we're looking for. 937 if (interface.first == 938 "xyz.openbmc_project.Software.Version") 939 { 940 // Cut out everyting until last "/", ... 941 const std::string& iface_id = objpath.first; 942 for (auto& property : interface.second) 943 { 944 if (property.first == "Version") 945 { 946 const std::string* value = 947 sdbusplus::message::variant_ns::get_if< 948 std::string>(&property.second); 949 if (value == nullptr) 950 { 951 continue; 952 } 953 asyncResp->res 954 .jsonValue["FirmwareVersion"] = *value; 955 } 956 } 957 } 958 } 959 } 960 }, 961 "xyz.openbmc_project.Software.BMC.Updater", 962 "/xyz/openbmc_project/software", 963 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 964 getPidValues(asyncResp); 965 } 966 void setPidValues(std::shared_ptr<AsyncResp> response, nlohmann::json& data) 967 { 968 969 // todo(james): might make sense to do a mapper call here if this 970 // interface gets more traction 971 crow::connections::systemBus->async_method_call( 972 [response, 973 data](const boost::system::error_code ec, 974 const dbus::utility::ManagedObjectType& managedObj) { 975 if (ec) 976 { 977 BMCWEB_LOG_ERROR << "Error communicating to Entity Manager"; 978 messages::internalError(response->res); 979 return; 980 } 981 982 // todo(james) mutable doesn't work with asio bindings 983 nlohmann::json jsonData = data; 984 985 std::optional<nlohmann::json> pidControllers; 986 std::optional<nlohmann::json> fanControllers; 987 std::optional<nlohmann::json> fanZones; 988 std::optional<nlohmann::json> stepwiseControllers; 989 if (!redfish::json_util::readJson( 990 jsonData, response->res, "PidControllers", 991 pidControllers, "FanControllers", fanControllers, 992 "FanZones", fanZones, "StepwiseControllers", 993 stepwiseControllers)) 994 { 995 BMCWEB_LOG_ERROR << "Line:" << __LINE__ 996 << ", Illegal Property " 997 << jsonData.dump(); 998 return; 999 } 1000 std::array< 1001 std::pair<const char*, std::optional<nlohmann::json>*>, 4> 1002 sections = { 1003 std::make_pair("PidControllers", &pidControllers), 1004 std::make_pair("FanControllers", &fanControllers), 1005 std::make_pair("FanZones", &fanZones), 1006 std::make_pair("StepwiseControllers", 1007 &stepwiseControllers)}; 1008 1009 for (auto& containerPair : sections) 1010 { 1011 auto& container = *(containerPair.second); 1012 if (!container) 1013 { 1014 continue; 1015 } 1016 const char* type = containerPair.first; 1017 1018 for (auto& record : container->items()) 1019 { 1020 const auto& name = record.key(); 1021 auto pathItr = 1022 std::find_if(managedObj.begin(), managedObj.end(), 1023 [&name](const auto& obj) { 1024 return boost::algorithm::ends_with( 1025 obj.first.str, name); 1026 }); 1027 boost::container::flat_map< 1028 std::string, dbus::utility::DbusVariantType> 1029 output; 1030 1031 output.reserve(16); // The pid interface length 1032 1033 // determines if we're patching entity-manager or 1034 // creating a new object 1035 bool createNewObject = (pathItr == managedObj.end()); 1036 std::string iface; 1037 if (type == "PidControllers" || 1038 type == "FanControllers") 1039 { 1040 iface = pidConfigurationIface; 1041 if (!createNewObject && 1042 pathItr->second.find(pidConfigurationIface) == 1043 pathItr->second.end()) 1044 { 1045 createNewObject = true; 1046 } 1047 } 1048 else if (type == "FanZones") 1049 { 1050 iface = pidZoneConfigurationIface; 1051 if (!createNewObject && 1052 pathItr->second.find( 1053 pidZoneConfigurationIface) == 1054 pathItr->second.end()) 1055 { 1056 1057 createNewObject = true; 1058 } 1059 } 1060 else if (type == "StepwiseControllers") 1061 { 1062 iface = stepwiseConfigurationIface; 1063 if (!createNewObject && 1064 pathItr->second.find( 1065 stepwiseConfigurationIface) == 1066 pathItr->second.end()) 1067 { 1068 createNewObject = true; 1069 } 1070 } 1071 output["Name"] = 1072 boost::replace_all_copy(name, "_", " "); 1073 1074 std::string chassis; 1075 CreatePIDRet ret = createPidInterface( 1076 response, type, std::move(record.value()), 1077 pathItr->first.str, managedObj, createNewObject, 1078 output, chassis); 1079 if (ret == CreatePIDRet::fail) 1080 { 1081 return; 1082 } 1083 else if (ret == CreatePIDRet::del) 1084 { 1085 continue; 1086 } 1087 1088 if (!createNewObject) 1089 { 1090 for (const auto& property : output) 1091 { 1092 crow::connections::systemBus->async_method_call( 1093 [response, 1094 propertyName{std::string(property.first)}]( 1095 const boost::system::error_code ec) { 1096 if (ec) 1097 { 1098 BMCWEB_LOG_ERROR 1099 << "Error patching " 1100 << propertyName << ": " << ec; 1101 messages::internalError( 1102 response->res); 1103 } 1104 }, 1105 "xyz.openbmc_project.EntityManager", 1106 pathItr->first.str, 1107 "org.freedesktop.DBus.Properties", "Set", 1108 iface, property.first, property.second); 1109 } 1110 } 1111 else 1112 { 1113 if (chassis.empty()) 1114 { 1115 BMCWEB_LOG_ERROR 1116 << "Failed to get chassis from config"; 1117 messages::invalidObject(response->res, name); 1118 return; 1119 } 1120 1121 bool foundChassis = false; 1122 for (const auto& obj : managedObj) 1123 { 1124 if (boost::algorithm::ends_with(obj.first.str, 1125 chassis)) 1126 { 1127 chassis = obj.first.str; 1128 foundChassis = true; 1129 break; 1130 } 1131 } 1132 if (!foundChassis) 1133 { 1134 BMCWEB_LOG_ERROR 1135 << "Failed to find chassis on dbus"; 1136 messages::resourceMissingAtURI( 1137 response->res, 1138 "/redfish/v1/Chassis/" + chassis); 1139 return; 1140 } 1141 1142 crow::connections::systemBus->async_method_call( 1143 [response](const boost::system::error_code ec) { 1144 if (ec) 1145 { 1146 BMCWEB_LOG_ERROR 1147 << "Error Adding Pid Object " << ec; 1148 messages::internalError(response->res); 1149 } 1150 }, 1151 "xyz.openbmc_project.EntityManager", chassis, 1152 "xyz.openbmc_project.AddObject", "AddObject", 1153 output); 1154 } 1155 } 1156 } 1157 }, 1158 "xyz.openbmc_project.EntityManager", "/", objectManagerIface, 1159 "GetManagedObjects"); 1160 } 1161 1162 void doPatch(crow::Response& res, const crow::Request& req, 1163 const std::vector<std::string>& params) override 1164 { 1165 std::optional<nlohmann::json> oem; 1166 1167 if (!json_util::readJson(req, res, "Oem", oem)) 1168 { 1169 return; 1170 } 1171 1172 std::shared_ptr<AsyncResp> response = std::make_shared<AsyncResp>(res); 1173 1174 if (oem) 1175 { 1176 for (const auto& oemLevel : oem->items()) 1177 { 1178 std::optional<nlohmann::json> openbmc; 1179 if (!redfish::json_util::readJson(*oem, res, "OpenBmc", 1180 openbmc)) 1181 { 1182 BMCWEB_LOG_ERROR << "Line:" << __LINE__ 1183 << ", Illegal Property " << oem->dump(); 1184 return; 1185 } 1186 if (openbmc) 1187 { 1188 std::optional<nlohmann::json> fan; 1189 if (!redfish::json_util::readJson(*openbmc, res, "Fan", 1190 fan)) 1191 { 1192 BMCWEB_LOG_ERROR << "Line:" << __LINE__ 1193 << ", Illegal Property " 1194 << openbmc->dump(); 1195 return; 1196 } 1197 if (fan) 1198 { 1199 setPidValues(response, *fan); 1200 } 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