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 == "OutLimitMax" || 439 propertyPair.first == "OutLimitMin" || 440 propertyPair.first == "PCoefficient" || 441 propertyPair.first == "SetPoint" || 442 propertyPair.first == "SlewNeg" || 443 propertyPair.first == "SlewPos") 444 { 445 const double* ptr = 446 std::get_if<double>(&propertyPair.second); 447 if (ptr == nullptr) 448 { 449 BMCWEB_LOG_ERROR << "Field Illegal " 450 << propertyPair.first; 451 messages::internalError(asyncResp->res); 452 return; 453 } 454 (*config)[propertyPair.first] = *ptr; 455 } 456 } 457 } 458 } 459 } 460 }, 461 connection, path, objectManagerIface, "GetManagedObjects"); 462 } 463 464 enum class CreatePIDRet 465 { 466 fail, 467 del, 468 patch 469 }; 470 471 static bool getZonesFromJsonReq(const std::shared_ptr<AsyncResp>& response, 472 std::vector<nlohmann::json>& config, 473 std::vector<std::string>& zones) 474 { 475 476 for (auto& odata : config) 477 { 478 std::string path; 479 if (!redfish::json_util::readJson(odata, response->res, "@odata.id", 480 path)) 481 { 482 return false; 483 } 484 std::string input; 485 if (!dbus::utility::getNthStringFromPath(path, 4, input)) 486 { 487 BMCWEB_LOG_ERROR << "Got invalid path " << path; 488 BMCWEB_LOG_ERROR << "Illegal Type Zones"; 489 messages::propertyValueFormatError(response->res, odata.dump(), 490 "Zones"); 491 return false; 492 } 493 boost::replace_all(input, "_", " "); 494 zones.emplace_back(std::move(input)); 495 } 496 return true; 497 } 498 499 static CreatePIDRet createPidInterface( 500 const std::shared_ptr<AsyncResp>& response, const std::string& type, 501 nlohmann::json&& record, const std::string& path, 502 const dbus::utility::ManagedObjectType& managedObj, bool createNewObject, 503 boost::container::flat_map<std::string, dbus::utility::DbusVariantType>& 504 output, 505 std::string& chassis) 506 { 507 508 // common deleter 509 if (record == nullptr) 510 { 511 std::string iface; 512 if (type == "PidControllers" || type == "FanControllers") 513 { 514 iface = pidConfigurationIface; 515 } 516 else if (type == "FanZones") 517 { 518 iface = pidZoneConfigurationIface; 519 } 520 else if (type == "StepwiseControllers") 521 { 522 iface = stepwiseConfigurationIface; 523 } 524 else 525 { 526 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type " 527 << type; 528 messages::propertyUnknown(response->res, type); 529 return CreatePIDRet::fail; 530 } 531 // delete interface 532 crow::connections::systemBus->async_method_call( 533 [response, path](const boost::system::error_code ec) { 534 if (ec) 535 { 536 BMCWEB_LOG_ERROR << "Error patching " << path << ": " << ec; 537 messages::internalError(response->res); 538 } 539 }, 540 "xyz.openbmc_project.EntityManager", path, iface, "Delete"); 541 return CreatePIDRet::del; 542 } 543 544 if (type == "PidControllers" || type == "FanControllers") 545 { 546 if (createNewObject) 547 { 548 output["Class"] = type == "PidControllers" ? std::string("temp") 549 : std::string("fan"); 550 output["Type"] = std::string("Pid"); 551 } 552 553 std::optional<std::vector<nlohmann::json>> zones; 554 std::optional<std::vector<std::string>> inputs; 555 std::optional<std::vector<std::string>> outputs; 556 std::map<std::string, std::optional<double>> doubles; 557 if (!redfish::json_util::readJson( 558 record, response->res, "Inputs", inputs, "Outputs", outputs, 559 "Zones", zones, "FFGainCoefficient", 560 doubles["FFGainCoefficient"], "FFOffCoefficient", 561 doubles["FFOffCoefficient"], "ICoefficient", 562 doubles["ICoefficient"], "ILimitMax", doubles["ILimitMax"], 563 "ILimitMin", doubles["ILimitMin"], "OutLimitMax", 564 doubles["OutLimitMax"], "OutLimitMin", doubles["OutLimitMin"], 565 "PCoefficient", doubles["PCoefficient"], "SetPoint", 566 doubles["SetPoint"], "SlewNeg", doubles["SlewNeg"], "SlewPos", 567 doubles["SlewPos"])) 568 { 569 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 570 << record.dump(); 571 return CreatePIDRet::fail; 572 } 573 if (zones) 574 { 575 std::vector<std::string> zonesStr; 576 if (!getZonesFromJsonReq(response, *zones, zonesStr)) 577 { 578 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones"; 579 return CreatePIDRet::fail; 580 } 581 output["Zones"] = std::move(zonesStr); 582 } 583 if (inputs || outputs) 584 { 585 std::array<std::optional<std::vector<std::string>>*, 2> containers = 586 {&inputs, &outputs}; 587 size_t index = 0; 588 for (const auto& containerPtr : containers) 589 { 590 std::optional<std::vector<std::string>>& container = 591 *containerPtr; 592 if (!container) 593 { 594 index++; 595 continue; 596 } 597 598 for (std::string& value : *container) 599 { 600 601 // try to find the sensor in the 602 // configuration 603 if (chassis.empty()) 604 { 605 std::string escaped = 606 boost::replace_all_copy(value, " ", "_"); 607 std::find_if( 608 managedObj.begin(), managedObj.end(), 609 [&chassis, &escaped](const auto& obj) { 610 if (boost::algorithm::ends_with(obj.first.str, 611 escaped)) 612 { 613 return dbus::utility::getNthStringFromPath( 614 obj.first.str, 5, chassis); 615 } 616 return false; 617 }); 618 } 619 boost::replace_all(value, "_", " "); 620 } 621 std::string key; 622 if (index == 0) 623 { 624 key = "Inputs"; 625 } 626 else 627 { 628 key = "Outputs"; 629 } 630 output[key] = *container; 631 index++; 632 } 633 } 634 635 // doubles 636 for (const auto& pairs : doubles) 637 { 638 if (!pairs.second) 639 { 640 continue; 641 } 642 BMCWEB_LOG_DEBUG << pairs.first << " = " << *pairs.second; 643 output[pairs.first] = *(pairs.second); 644 } 645 } 646 647 else if (type == "FanZones") 648 { 649 output["Type"] = std::string("Pid.Zone"); 650 651 std::optional<nlohmann::json> chassisContainer; 652 std::optional<double> failSafePercent; 653 std::optional<double> minThermalRpm; 654 if (!redfish::json_util::readJson(record, response->res, "Chassis", 655 chassisContainer, "FailSafePercent", 656 failSafePercent, "MinThermalRpm", 657 minThermalRpm)) 658 { 659 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 660 << record.dump(); 661 return CreatePIDRet::fail; 662 } 663 664 if (chassisContainer) 665 { 666 667 std::string chassisId; 668 if (!redfish::json_util::readJson(*chassisContainer, response->res, 669 "@odata.id", chassisId)) 670 { 671 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 672 << chassisContainer->dump(); 673 return CreatePIDRet::fail; 674 } 675 676 // /refish/v1/chassis/chassis_name/ 677 if (!dbus::utility::getNthStringFromPath(chassisId, 3, chassis)) 678 { 679 BMCWEB_LOG_ERROR << "Got invalid path " << chassisId; 680 messages::invalidObject(response->res, chassisId); 681 return CreatePIDRet::fail; 682 } 683 } 684 if (minThermalRpm) 685 { 686 output["MinThermalRpm"] = *minThermalRpm; 687 } 688 if (failSafePercent) 689 { 690 output["FailSafePercent"] = *failSafePercent; 691 } 692 } 693 else if (type == "StepwiseControllers") 694 { 695 output["Type"] = std::string("Stepwise"); 696 697 std::optional<std::vector<nlohmann::json>> zones; 698 std::optional<std::vector<nlohmann::json>> steps; 699 std::optional<std::vector<std::string>> inputs; 700 std::optional<double> positiveHysteresis; 701 std::optional<double> negativeHysteresis; 702 if (!redfish::json_util::readJson( 703 record, response->res, "Zones", zones, "Steps", steps, "Inputs", 704 inputs, "PositiveHysteresis", positiveHysteresis, 705 "NegativeHysteresis", negativeHysteresis)) 706 { 707 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property " 708 << record.dump(); 709 return CreatePIDRet::fail; 710 } 711 712 if (zones) 713 { 714 std::vector<std::string> zoneStrs; 715 if (!getZonesFromJsonReq(response, *zones, zoneStrs)) 716 { 717 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Zones"; 718 return CreatePIDRet::fail; 719 } 720 output["Zones"] = std::move(zoneStrs); 721 } 722 if (steps) 723 { 724 std::vector<double> readings; 725 std::vector<double> outputs; 726 for (auto& step : *steps) 727 { 728 double target; 729 double output; 730 731 if (!redfish::json_util::readJson(step, response->res, "Target", 732 target, "Output", output)) 733 { 734 BMCWEB_LOG_ERROR << "Line:" << __LINE__ 735 << ", Illegal Property " << record.dump(); 736 return CreatePIDRet::fail; 737 } 738 readings.emplace_back(target); 739 outputs.emplace_back(output); 740 } 741 output["Reading"] = std::move(readings); 742 output["Output"] = std::move(outputs); 743 } 744 if (inputs) 745 { 746 for (std::string& value : *inputs) 747 { 748 if (chassis.empty()) 749 { 750 std::string escaped = 751 boost::replace_all_copy(value, " ", "_"); 752 std::find_if( 753 managedObj.begin(), managedObj.end(), 754 [&chassis, &escaped](const auto& obj) { 755 if (boost::algorithm::ends_with(obj.first.str, 756 escaped)) 757 { 758 return dbus::utility::getNthStringFromPath( 759 obj.first.str, 5, chassis); 760 } 761 return false; 762 }); 763 } 764 boost::replace_all(value, "_", " "); 765 } 766 output["Inputs"] = std::move(*inputs); 767 } 768 if (negativeHysteresis) 769 { 770 output["NegativeHysteresis"] = *negativeHysteresis; 771 } 772 if (positiveHysteresis) 773 { 774 output["PositiveHysteresis"] = *positiveHysteresis; 775 } 776 } 777 else 778 { 779 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type " << type; 780 messages::propertyUnknown(response->res, type); 781 return CreatePIDRet::fail; 782 } 783 return CreatePIDRet::patch; 784 } 785 786 class Manager : public Node 787 { 788 public: 789 Manager(CrowApp& app) : Node(app, "/redfish/v1/Managers/bmc/") 790 { 791 uuid = app.template getMiddleware<crow::persistent_data::Middleware>() 792 .systemUuid; 793 entityPrivileges = { 794 {boost::beast::http::verb::get, {{"Login"}}}, 795 {boost::beast::http::verb::head, {{"Login"}}}, 796 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 797 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 798 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 799 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 800 } 801 802 private: 803 void getPidValues(std::shared_ptr<AsyncResp> asyncResp) 804 { 805 crow::connections::systemBus->async_method_call( 806 [asyncResp](const boost::system::error_code ec, 807 const crow::openbmc_mapper::GetSubTreeType& subtree) { 808 if (ec) 809 { 810 BMCWEB_LOG_ERROR << ec; 811 messages::internalError(asyncResp->res); 812 return; 813 } 814 815 // create map of <connection, path to objMgr>> 816 boost::container::flat_map<std::string, std::string> 817 objectMgrPaths; 818 boost::container::flat_set<std::string> calledConnections; 819 for (const auto& pathGroup : subtree) 820 { 821 for (const auto& connectionGroup : pathGroup.second) 822 { 823 auto findConnection = 824 calledConnections.find(connectionGroup.first); 825 if (findConnection != calledConnections.end()) 826 { 827 break; 828 } 829 for (const std::string& interface : 830 connectionGroup.second) 831 { 832 if (interface == objectManagerIface) 833 { 834 objectMgrPaths[connectionGroup.first] = 835 pathGroup.first; 836 } 837 // this list is alphabetical, so we 838 // should have found the objMgr by now 839 if (interface == pidConfigurationIface || 840 interface == pidZoneConfigurationIface || 841 interface == stepwiseConfigurationIface) 842 { 843 auto findObjMgr = 844 objectMgrPaths.find(connectionGroup.first); 845 if (findObjMgr == objectMgrPaths.end()) 846 { 847 BMCWEB_LOG_DEBUG << connectionGroup.first 848 << "Has no Object Manager"; 849 continue; 850 } 851 852 calledConnections.insert(connectionGroup.first); 853 854 asyncPopulatePid(findObjMgr->first, 855 findObjMgr->second, asyncResp); 856 break; 857 } 858 } 859 } 860 } 861 }, 862 "xyz.openbmc_project.ObjectMapper", 863 "/xyz/openbmc_project/object_mapper", 864 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0, 865 std::array<const char*, 4>{ 866 pidConfigurationIface, pidZoneConfigurationIface, 867 objectManagerIface, stepwiseConfigurationIface}); 868 } 869 870 void doGet(crow::Response& res, const crow::Request& req, 871 const std::vector<std::string>& params) override 872 { 873 res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc"; 874 res.jsonValue["@odata.type"] = "#Manager.v1_3_0.Manager"; 875 res.jsonValue["@odata.context"] = 876 "/redfish/v1/$metadata#Manager.Manager"; 877 res.jsonValue["Id"] = "bmc"; 878 res.jsonValue["Name"] = "OpenBmc Manager"; 879 res.jsonValue["Description"] = "Baseboard Management Controller"; 880 res.jsonValue["PowerState"] = "On"; 881 res.jsonValue["ManagerType"] = "BMC"; 882 res.jsonValue["UUID"] = uuid; 883 res.jsonValue["Model"] = "OpenBmc"; // TODO(ed), get model 884 885 res.jsonValue["LogServices"] = { 886 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices"}}; 887 888 res.jsonValue["NetworkProtocol"] = { 889 {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol"}}; 890 891 res.jsonValue["EthernetInterfaces"] = { 892 {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces"}}; 893 // default oem data 894 nlohmann::json& oem = res.jsonValue["Oem"]; 895 nlohmann::json& oemOpenbmc = oem["OpenBmc"]; 896 oem["@odata.type"] = "#OemManager.Oem"; 897 oem["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem"; 898 oem["@odata.context"] = "/redfish/v1/$metadata#OemManager.Oem"; 899 oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc"; 900 oemOpenbmc["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc"; 901 oemOpenbmc["@odata.context"] = 902 "/redfish/v1/$metadata#OemManager.OpenBmc"; 903 904 // Update Actions object. 905 nlohmann::json& manager_reset = 906 res.jsonValue["Actions"]["#Manager.Reset"]; 907 manager_reset["target"] = 908 "/redfish/v1/Managers/bmc/Actions/Manager.Reset"; 909 manager_reset["ResetType@Redfish.AllowableValues"] = { 910 "GracefulRestart"}; 911 912 res.jsonValue["DateTime"] = getDateTime(); 913 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 914 915 crow::connections::systemBus->async_method_call( 916 [asyncResp](const boost::system::error_code ec, 917 const dbus::utility::ManagedObjectType& resp) { 918 if (ec) 919 { 920 BMCWEB_LOG_ERROR << "Error while getting Software Version"; 921 messages::internalError(asyncResp->res); 922 return; 923 } 924 925 for (auto& objpath : resp) 926 { 927 for (auto& interface : objpath.second) 928 { 929 // If interface is 930 // xyz.openbmc_project.Software.Version, this is 931 // what we're looking for. 932 if (interface.first == 933 "xyz.openbmc_project.Software.Version") 934 { 935 // Cut out everyting until last "/", ... 936 const std::string& iface_id = objpath.first; 937 for (auto& property : interface.second) 938 { 939 if (property.first == "Version") 940 { 941 const std::string* value = 942 std::get_if<std::string>( 943 &property.second); 944 if (value == nullptr) 945 { 946 continue; 947 } 948 asyncResp->res 949 .jsonValue["FirmwareVersion"] = *value; 950 } 951 } 952 } 953 } 954 } 955 }, 956 "xyz.openbmc_project.Software.BMC.Updater", 957 "/xyz/openbmc_project/software", 958 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 959 getPidValues(asyncResp); 960 } 961 void setPidValues(std::shared_ptr<AsyncResp> response, nlohmann::json& data) 962 { 963 964 // todo(james): might make sense to do a mapper call here if this 965 // interface gets more traction 966 crow::connections::systemBus->async_method_call( 967 [response, 968 data](const boost::system::error_code ec, 969 const dbus::utility::ManagedObjectType& managedObj) { 970 if (ec) 971 { 972 BMCWEB_LOG_ERROR << "Error communicating to Entity Manager"; 973 messages::internalError(response->res); 974 return; 975 } 976 977 // todo(james) mutable doesn't work with asio bindings 978 nlohmann::json jsonData = data; 979 980 std::optional<nlohmann::json> pidControllers; 981 std::optional<nlohmann::json> fanControllers; 982 std::optional<nlohmann::json> fanZones; 983 std::optional<nlohmann::json> stepwiseControllers; 984 if (!redfish::json_util::readJson( 985 jsonData, response->res, "PidControllers", 986 pidControllers, "FanControllers", fanControllers, 987 "FanZones", fanZones, "StepwiseControllers", 988 stepwiseControllers)) 989 { 990 BMCWEB_LOG_ERROR << "Line:" << __LINE__ 991 << ", Illegal Property " 992 << jsonData.dump(); 993 return; 994 } 995 std::array< 996 std::pair<const char*, std::optional<nlohmann::json>*>, 4> 997 sections = { 998 std::make_pair("PidControllers", &pidControllers), 999 std::make_pair("FanControllers", &fanControllers), 1000 std::make_pair("FanZones", &fanZones), 1001 std::make_pair("StepwiseControllers", 1002 &stepwiseControllers)}; 1003 1004 for (auto& containerPair : sections) 1005 { 1006 auto& container = *(containerPair.second); 1007 if (!container) 1008 { 1009 continue; 1010 } 1011 const char* type = containerPair.first; 1012 1013 for (auto& record : container->items()) 1014 { 1015 const auto& name = record.key(); 1016 auto pathItr = 1017 std::find_if(managedObj.begin(), managedObj.end(), 1018 [&name](const auto& obj) { 1019 return boost::algorithm::ends_with( 1020 obj.first.str, name); 1021 }); 1022 boost::container::flat_map< 1023 std::string, dbus::utility::DbusVariantType> 1024 output; 1025 1026 output.reserve(16); // The pid interface length 1027 1028 // determines if we're patching entity-manager or 1029 // creating a new object 1030 bool createNewObject = (pathItr == managedObj.end()); 1031 std::string iface; 1032 if (type == "PidControllers" || 1033 type == "FanControllers") 1034 { 1035 iface = pidConfigurationIface; 1036 if (!createNewObject && 1037 pathItr->second.find(pidConfigurationIface) == 1038 pathItr->second.end()) 1039 { 1040 createNewObject = true; 1041 } 1042 } 1043 else if (type == "FanZones") 1044 { 1045 iface = pidZoneConfigurationIface; 1046 if (!createNewObject && 1047 pathItr->second.find( 1048 pidZoneConfigurationIface) == 1049 pathItr->second.end()) 1050 { 1051 1052 createNewObject = true; 1053 } 1054 } 1055 else if (type == "StepwiseControllers") 1056 { 1057 iface = stepwiseConfigurationIface; 1058 if (!createNewObject && 1059 pathItr->second.find( 1060 stepwiseConfigurationIface) == 1061 pathItr->second.end()) 1062 { 1063 createNewObject = true; 1064 } 1065 } 1066 output["Name"] = 1067 boost::replace_all_copy(name, "_", " "); 1068 1069 std::string chassis; 1070 CreatePIDRet ret = createPidInterface( 1071 response, type, std::move(record.value()), 1072 pathItr->first.str, managedObj, createNewObject, 1073 output, chassis); 1074 if (ret == CreatePIDRet::fail) 1075 { 1076 return; 1077 } 1078 else if (ret == CreatePIDRet::del) 1079 { 1080 continue; 1081 } 1082 1083 if (!createNewObject) 1084 { 1085 for (const auto& property : output) 1086 { 1087 crow::connections::systemBus->async_method_call( 1088 [response, 1089 propertyName{std::string(property.first)}]( 1090 const boost::system::error_code ec) { 1091 if (ec) 1092 { 1093 BMCWEB_LOG_ERROR 1094 << "Error patching " 1095 << propertyName << ": " << ec; 1096 messages::internalError( 1097 response->res); 1098 } 1099 }, 1100 "xyz.openbmc_project.EntityManager", 1101 pathItr->first.str, 1102 "org.freedesktop.DBus.Properties", "Set", 1103 iface, property.first, property.second); 1104 } 1105 } 1106 else 1107 { 1108 if (chassis.empty()) 1109 { 1110 BMCWEB_LOG_ERROR 1111 << "Failed to get chassis from config"; 1112 messages::invalidObject(response->res, name); 1113 return; 1114 } 1115 1116 bool foundChassis = false; 1117 for (const auto& obj : managedObj) 1118 { 1119 if (boost::algorithm::ends_with(obj.first.str, 1120 chassis)) 1121 { 1122 chassis = obj.first.str; 1123 foundChassis = true; 1124 break; 1125 } 1126 } 1127 if (!foundChassis) 1128 { 1129 BMCWEB_LOG_ERROR 1130 << "Failed to find chassis on dbus"; 1131 messages::resourceMissingAtURI( 1132 response->res, 1133 "/redfish/v1/Chassis/" + chassis); 1134 return; 1135 } 1136 1137 crow::connections::systemBus->async_method_call( 1138 [response](const boost::system::error_code ec) { 1139 if (ec) 1140 { 1141 BMCWEB_LOG_ERROR 1142 << "Error Adding Pid Object " << ec; 1143 messages::internalError(response->res); 1144 } 1145 }, 1146 "xyz.openbmc_project.EntityManager", chassis, 1147 "xyz.openbmc_project.AddObject", "AddObject", 1148 output); 1149 } 1150 } 1151 } 1152 }, 1153 "xyz.openbmc_project.EntityManager", "/", objectManagerIface, 1154 "GetManagedObjects"); 1155 } 1156 1157 void doPatch(crow::Response& res, const crow::Request& req, 1158 const std::vector<std::string>& params) override 1159 { 1160 std::optional<nlohmann::json> oem; 1161 1162 if (!json_util::readJson(req, res, "Oem", oem)) 1163 { 1164 return; 1165 } 1166 1167 std::shared_ptr<AsyncResp> response = std::make_shared<AsyncResp>(res); 1168 1169 if (oem) 1170 { 1171 for (const auto& oemLevel : oem->items()) 1172 { 1173 std::optional<nlohmann::json> openbmc; 1174 if (!redfish::json_util::readJson(*oem, res, "OpenBmc", 1175 openbmc)) 1176 { 1177 BMCWEB_LOG_ERROR << "Line:" << __LINE__ 1178 << ", Illegal Property " << oem->dump(); 1179 return; 1180 } 1181 if (openbmc) 1182 { 1183 std::optional<nlohmann::json> fan; 1184 if (!redfish::json_util::readJson(*openbmc, res, "Fan", 1185 fan)) 1186 { 1187 BMCWEB_LOG_ERROR << "Line:" << __LINE__ 1188 << ", Illegal Property " 1189 << openbmc->dump(); 1190 return; 1191 } 1192 if (fan) 1193 { 1194 setPidValues(response, *fan); 1195 } 1196 } 1197 } 1198 } 1199 } 1200 1201 std::string getDateTime() const 1202 { 1203 std::array<char, 128> dateTime; 1204 std::string redfishDateTime("0000-00-00T00:00:00Z00:00"); 1205 std::time_t time = std::time(nullptr); 1206 1207 if (std::strftime(dateTime.begin(), dateTime.size(), "%FT%T%z", 1208 std::localtime(&time))) 1209 { 1210 // insert the colon required by the ISO 8601 standard 1211 redfishDateTime = std::string(dateTime.data()); 1212 redfishDateTime.insert(redfishDateTime.end() - 2, ':'); 1213 } 1214 1215 return redfishDateTime; 1216 } 1217 1218 std::string uuid; 1219 }; 1220 1221 class ManagerCollection : public Node 1222 { 1223 public: 1224 ManagerCollection(CrowApp& app) : Node(app, "/redfish/v1/Managers/") 1225 { 1226 entityPrivileges = { 1227 {boost::beast::http::verb::get, {{"Login"}}}, 1228 {boost::beast::http::verb::head, {{"Login"}}}, 1229 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1230 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1231 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1232 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1233 } 1234 1235 private: 1236 void doGet(crow::Response& res, const crow::Request& req, 1237 const std::vector<std::string>& params) override 1238 { 1239 // Collections don't include the static data added by SubRoute 1240 // because it has a duplicate entry for members 1241 res.jsonValue["@odata.id"] = "/redfish/v1/Managers"; 1242 res.jsonValue["@odata.type"] = "#ManagerCollection.ManagerCollection"; 1243 res.jsonValue["@odata.context"] = 1244 "/redfish/v1/$metadata#ManagerCollection.ManagerCollection"; 1245 res.jsonValue["Name"] = "Manager Collection"; 1246 res.jsonValue["Members@odata.count"] = 1; 1247 res.jsonValue["Members"] = { 1248 {{"@odata.id", "/redfish/v1/Managers/bmc"}}}; 1249 res.end(); 1250 } 1251 }; 1252 } // namespace redfish 1253