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