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 == "SetPoint" || 447 propertyPair.first == "SlewNeg" || 448 propertyPair.first == "SlewPos") 449 { 450 const double* ptr = 451 sdbusplus::message::variant_ns::get_if< 452 double>(&propertyPair.second); 453 if (ptr == nullptr) 454 { 455 BMCWEB_LOG_ERROR << "Field Illegal " 456 << propertyPair.first; 457 messages::internalError(asyncResp->res); 458 return; 459 } 460 (*config)[propertyPair.first] = *ptr; 461 } 462 } 463 } 464 } 465 } 466 }, 467 connection, path, objectManagerIface, "GetManagedObjects"); 468 } 469 470 enum class CreatePIDRet 471 { 472 fail, 473 del, 474 patch 475 }; 476 477 static bool getZonesFromJsonReq(const std::shared_ptr<AsyncResp>& response, 478 std::vector<nlohmann::json>& config, 479 std::vector<std::string>& zones) 480 { 481 482 for (auto& odata : config) 483 { 484 std::string path; 485 if (!redfish::json_util::readJson(odata, response->res, "@odata.id", 486 path)) 487 { 488 return false; 489 } 490 std::string input; 491 if (!dbus::utility::getNthStringFromPath(path, 4, input)) 492 { 493 BMCWEB_LOG_ERROR << "Got invalid path " << path; 494 BMCWEB_LOG_ERROR << "Illegal Type Zones"; 495 messages::propertyValueFormatError(response->res, odata.dump(), 496 "Zones"); 497 return false; 498 } 499 boost::replace_all(input, "_", " "); 500 zones.emplace_back(std::move(input)); 501 } 502 return true; 503 } 504 505 static CreatePIDRet createPidInterface( 506 const std::shared_ptr<AsyncResp>& response, const std::string& type, 507 nlohmann::json&& record, const std::string& path, 508 const dbus::utility::ManagedObjectType& managedObj, bool createNewObject, 509 boost::container::flat_map<std::string, dbus::utility::DbusVariantType>& 510 output, 511 std::string& chassis) 512 { 513 514 // common deleter 515 if (record == nullptr) 516 { 517 std::string iface; 518 if (type == "PidControllers" || type == "FanControllers") 519 { 520 iface = pidConfigurationIface; 521 } 522 else if (type == "FanZones") 523 { 524 iface = pidZoneConfigurationIface; 525 } 526 else if (type == "StepwiseControllers") 527 { 528 iface = stepwiseConfigurationIface; 529 } 530 else 531 { 532 BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Type " 533 << type; 534 messages::propertyUnknown(response->res, type); 535 return CreatePIDRet::fail; 536 } 537 // delete interface 538 crow::connections::systemBus->async_method_call( 539 [response, path](const boost::system::error_code ec) { 540 if (ec) 541 { 542 BMCWEB_LOG_ERROR << "Error patching " << path << ": " << ec; 543 messages::internalError(response->res); 544 } 545 }, 546 "xyz.openbmc_project.EntityManager", path, iface, "Delete"); 547 return CreatePIDRet::del; 548 } 549 550 if (type == "PidControllers" || type == "FanControllers") 551 { 552 if (createNewObject) 553 { 554 output["Class"] = type == "PidControllers" ? std::string("temp") 555 : std::string("fan"); 556 output["Type"] = std::string("Pid"); 557 } 558 559 std::optional<std::vector<nlohmann::json>> zones; 560 std::optional<std::vector<std::string>> inputs; 561 std::optional<std::vector<std::string>> outputs; 562 std::map<std::string, std::optional<double>> doubles; 563 if (!redfish::json_util::readJson( 564 record, response->res, "Inputs", inputs, "Outputs", outputs, 565 "Zones", zones, "FFGainCoefficient", 566 doubles["FFGainCoefficient"], "FFOffCoefficient", 567 doubles["FFOffCoefficient"], "ICoefficient", 568 doubles["ICoefficient"], "ILimitMax", doubles["ILimitMax"], 569 "ILimitMin", doubles["ILimitMin"], "OutLimitMax", 570 doubles["OutLimitMax"], "OutLimitMin", doubles["OutLimitMin"], 571 "PCoefficient", doubles["PCoefficient"], "SetPoint", 572 doubles["SetPoint"], "SlewNeg", doubles["SlewNeg"], "SlewPos", 573 doubles["SlewPos"])) 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["ManagerType"] = "BMC"; 888 res.jsonValue["UUID"] = uuid; 889 res.jsonValue["Model"] = "OpenBmc"; // TODO(ed), get model 890 891 res.jsonValue["LogServices"] = { 892 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices"}}; 893 894 res.jsonValue["NetworkProtocol"] = { 895 {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol"}}; 896 897 res.jsonValue["EthernetInterfaces"] = { 898 {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces"}}; 899 // default oem data 900 nlohmann::json& oem = res.jsonValue["Oem"]; 901 nlohmann::json& oemOpenbmc = oem["OpenBmc"]; 902 oem["@odata.type"] = "#OemManager.Oem"; 903 oem["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem"; 904 oem["@odata.context"] = "/redfish/v1/$metadata#OemManager.Oem"; 905 oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc"; 906 oemOpenbmc["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc"; 907 oemOpenbmc["@odata.context"] = 908 "/redfish/v1/$metadata#OemManager.OpenBmc"; 909 910 // Update Actions object. 911 nlohmann::json& manager_reset = 912 res.jsonValue["Actions"]["#Manager.Reset"]; 913 manager_reset["target"] = 914 "/redfish/v1/Managers/bmc/Actions/Manager.Reset"; 915 manager_reset["ResetType@Redfish.AllowableValues"] = { 916 "GracefulRestart"}; 917 918 res.jsonValue["DateTime"] = getDateTime(); 919 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 920 921 crow::connections::systemBus->async_method_call( 922 [asyncResp](const boost::system::error_code ec, 923 const dbus::utility::ManagedObjectType& resp) { 924 if (ec) 925 { 926 BMCWEB_LOG_ERROR << "Error while getting Software Version"; 927 messages::internalError(asyncResp->res); 928 return; 929 } 930 931 for (auto& objpath : resp) 932 { 933 for (auto& interface : objpath.second) 934 { 935 // If interface is 936 // xyz.openbmc_project.Software.Version, this is 937 // what we're looking for. 938 if (interface.first == 939 "xyz.openbmc_project.Software.Version") 940 { 941 // Cut out everyting until last "/", ... 942 const std::string& iface_id = objpath.first; 943 for (auto& property : interface.second) 944 { 945 if (property.first == "Version") 946 { 947 const std::string* value = 948 sdbusplus::message::variant_ns::get_if< 949 std::string>(&property.second); 950 if (value == nullptr) 951 { 952 continue; 953 } 954 asyncResp->res 955 .jsonValue["FirmwareVersion"] = *value; 956 } 957 } 958 } 959 } 960 } 961 }, 962 "xyz.openbmc_project.Software.BMC.Updater", 963 "/xyz/openbmc_project/software", 964 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 965 getPidValues(asyncResp); 966 } 967 void setPidValues(std::shared_ptr<AsyncResp> response, nlohmann::json& data) 968 { 969 970 // todo(james): might make sense to do a mapper call here if this 971 // interface gets more traction 972 crow::connections::systemBus->async_method_call( 973 [response, 974 data](const boost::system::error_code ec, 975 const dbus::utility::ManagedObjectType& managedObj) { 976 if (ec) 977 { 978 BMCWEB_LOG_ERROR << "Error communicating to Entity Manager"; 979 messages::internalError(response->res); 980 return; 981 } 982 983 // todo(james) mutable doesn't work with asio bindings 984 nlohmann::json jsonData = data; 985 986 std::optional<nlohmann::json> pidControllers; 987 std::optional<nlohmann::json> fanControllers; 988 std::optional<nlohmann::json> fanZones; 989 std::optional<nlohmann::json> stepwiseControllers; 990 if (!redfish::json_util::readJson( 991 jsonData, response->res, "PidControllers", 992 pidControllers, "FanControllers", fanControllers, 993 "FanZones", fanZones, "StepwiseControllers", 994 stepwiseControllers)) 995 { 996 BMCWEB_LOG_ERROR << "Line:" << __LINE__ 997 << ", Illegal Property " 998 << jsonData.dump(); 999 return; 1000 } 1001 std::array< 1002 std::pair<const char*, std::optional<nlohmann::json>*>, 4> 1003 sections = { 1004 std::make_pair("PidControllers", &pidControllers), 1005 std::make_pair("FanControllers", &fanControllers), 1006 std::make_pair("FanZones", &fanZones), 1007 std::make_pair("StepwiseControllers", 1008 &stepwiseControllers)}; 1009 1010 for (auto& containerPair : sections) 1011 { 1012 auto& container = *(containerPair.second); 1013 if (!container) 1014 { 1015 continue; 1016 } 1017 const char* type = containerPair.first; 1018 1019 for (auto& record : container->items()) 1020 { 1021 const auto& name = record.key(); 1022 auto pathItr = 1023 std::find_if(managedObj.begin(), managedObj.end(), 1024 [&name](const auto& obj) { 1025 return boost::algorithm::ends_with( 1026 obj.first.str, name); 1027 }); 1028 boost::container::flat_map< 1029 std::string, dbus::utility::DbusVariantType> 1030 output; 1031 1032 output.reserve(16); // The pid interface length 1033 1034 // determines if we're patching entity-manager or 1035 // creating a new object 1036 bool createNewObject = (pathItr == managedObj.end()); 1037 std::string iface; 1038 if (type == "PidControllers" || 1039 type == "FanControllers") 1040 { 1041 iface = pidConfigurationIface; 1042 if (!createNewObject && 1043 pathItr->second.find(pidConfigurationIface) == 1044 pathItr->second.end()) 1045 { 1046 createNewObject = true; 1047 } 1048 } 1049 else if (type == "FanZones") 1050 { 1051 iface = pidZoneConfigurationIface; 1052 if (!createNewObject && 1053 pathItr->second.find( 1054 pidZoneConfigurationIface) == 1055 pathItr->second.end()) 1056 { 1057 1058 createNewObject = true; 1059 } 1060 } 1061 else if (type == "StepwiseControllers") 1062 { 1063 iface = stepwiseConfigurationIface; 1064 if (!createNewObject && 1065 pathItr->second.find( 1066 stepwiseConfigurationIface) == 1067 pathItr->second.end()) 1068 { 1069 createNewObject = true; 1070 } 1071 } 1072 output["Name"] = 1073 boost::replace_all_copy(name, "_", " "); 1074 1075 std::string chassis; 1076 CreatePIDRet ret = createPidInterface( 1077 response, type, std::move(record.value()), 1078 pathItr->first.str, managedObj, createNewObject, 1079 output, chassis); 1080 if (ret == CreatePIDRet::fail) 1081 { 1082 return; 1083 } 1084 else if (ret == CreatePIDRet::del) 1085 { 1086 continue; 1087 } 1088 1089 if (!createNewObject) 1090 { 1091 for (const auto& property : output) 1092 { 1093 crow::connections::systemBus->async_method_call( 1094 [response, 1095 propertyName{std::string(property.first)}]( 1096 const boost::system::error_code ec) { 1097 if (ec) 1098 { 1099 BMCWEB_LOG_ERROR 1100 << "Error patching " 1101 << propertyName << ": " << ec; 1102 messages::internalError( 1103 response->res); 1104 } 1105 }, 1106 "xyz.openbmc_project.EntityManager", 1107 pathItr->first.str, 1108 "org.freedesktop.DBus.Properties", "Set", 1109 iface, property.first, property.second); 1110 } 1111 } 1112 else 1113 { 1114 if (chassis.empty()) 1115 { 1116 BMCWEB_LOG_ERROR 1117 << "Failed to get chassis from config"; 1118 messages::invalidObject(response->res, name); 1119 return; 1120 } 1121 1122 bool foundChassis = false; 1123 for (const auto& obj : managedObj) 1124 { 1125 if (boost::algorithm::ends_with(obj.first.str, 1126 chassis)) 1127 { 1128 chassis = obj.first.str; 1129 foundChassis = true; 1130 break; 1131 } 1132 } 1133 if (!foundChassis) 1134 { 1135 BMCWEB_LOG_ERROR 1136 << "Failed to find chassis on dbus"; 1137 messages::resourceMissingAtURI( 1138 response->res, 1139 "/redfish/v1/Chassis/" + chassis); 1140 return; 1141 } 1142 1143 crow::connections::systemBus->async_method_call( 1144 [response](const boost::system::error_code ec) { 1145 if (ec) 1146 { 1147 BMCWEB_LOG_ERROR 1148 << "Error Adding Pid Object " << ec; 1149 messages::internalError(response->res); 1150 } 1151 }, 1152 "xyz.openbmc_project.EntityManager", chassis, 1153 "xyz.openbmc_project.AddObject", "AddObject", 1154 output); 1155 } 1156 } 1157 } 1158 }, 1159 "xyz.openbmc_project.EntityManager", "/", objectManagerIface, 1160 "GetManagedObjects"); 1161 } 1162 1163 void doPatch(crow::Response& res, const crow::Request& req, 1164 const std::vector<std::string>& params) override 1165 { 1166 std::optional<nlohmann::json> oem; 1167 1168 if (!json_util::readJson(req, res, "Oem", oem)) 1169 { 1170 return; 1171 } 1172 1173 std::shared_ptr<AsyncResp> response = std::make_shared<AsyncResp>(res); 1174 1175 if (oem) 1176 { 1177 for (const auto& oemLevel : oem->items()) 1178 { 1179 std::optional<nlohmann::json> openbmc; 1180 if (!redfish::json_util::readJson(*oem, res, "OpenBmc", 1181 openbmc)) 1182 { 1183 BMCWEB_LOG_ERROR << "Line:" << __LINE__ 1184 << ", Illegal Property " << oem->dump(); 1185 return; 1186 } 1187 if (openbmc) 1188 { 1189 std::optional<nlohmann::json> fan; 1190 if (!redfish::json_util::readJson(*openbmc, res, "Fan", 1191 fan)) 1192 { 1193 BMCWEB_LOG_ERROR << "Line:" << __LINE__ 1194 << ", Illegal Property " 1195 << openbmc->dump(); 1196 return; 1197 } 1198 if (fan) 1199 { 1200 setPidValues(response, *fan); 1201 } 1202 } 1203 } 1204 } 1205 } 1206 1207 std::string getDateTime() const 1208 { 1209 std::array<char, 128> dateTime; 1210 std::string redfishDateTime("0000-00-00T00:00:00Z00:00"); 1211 std::time_t time = std::time(nullptr); 1212 1213 if (std::strftime(dateTime.begin(), dateTime.size(), "%FT%T%z", 1214 std::localtime(&time))) 1215 { 1216 // insert the colon required by the ISO 8601 standard 1217 redfishDateTime = std::string(dateTime.data()); 1218 redfishDateTime.insert(redfishDateTime.end() - 2, ':'); 1219 } 1220 1221 return redfishDateTime; 1222 } 1223 1224 std::string uuid; 1225 }; 1226 1227 class ManagerCollection : public Node 1228 { 1229 public: 1230 ManagerCollection(CrowApp& app) : Node(app, "/redfish/v1/Managers/") 1231 { 1232 entityPrivileges = { 1233 {boost::beast::http::verb::get, {{"Login"}}}, 1234 {boost::beast::http::verb::head, {{"Login"}}}, 1235 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1236 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1237 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1238 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1239 } 1240 1241 private: 1242 void doGet(crow::Response& res, const crow::Request& req, 1243 const std::vector<std::string>& params) override 1244 { 1245 // Collections don't include the static data added by SubRoute 1246 // because it has a duplicate entry for members 1247 res.jsonValue["@odata.id"] = "/redfish/v1/Managers"; 1248 res.jsonValue["@odata.type"] = "#ManagerCollection.ManagerCollection"; 1249 res.jsonValue["@odata.context"] = 1250 "/redfish/v1/$metadata#ManagerCollection.ManagerCollection"; 1251 res.jsonValue["Name"] = "Manager Collection"; 1252 res.jsonValue["Members@odata.count"] = 1; 1253 res.jsonValue["Members"] = { 1254 {{"@odata.id", "/redfish/v1/Managers/bmc"}}}; 1255 res.end(); 1256 } 1257 }; 1258 } // namespace redfish 1259