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 116 static void asyncPopulatePid(const std::string& connection, 117 const std::string& path, 118 std::shared_ptr<AsyncResp> asyncResp) 119 { 120 121 crow::connections::systemBus->async_method_call( 122 [asyncResp](const boost::system::error_code ec, 123 const dbus::utility::ManagedObjectType& managedObj) { 124 if (ec) 125 { 126 BMCWEB_LOG_ERROR << ec; 127 asyncResp->res.jsonValue.clear(); 128 messages::internalError(asyncResp->res); 129 return; 130 } 131 nlohmann::json& configRoot = 132 asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"]; 133 nlohmann::json& fans = configRoot["FanControllers"]; 134 fans["@odata.type"] = "#OemManager.FanControllers"; 135 fans["@odata.context"] = 136 "/redfish/v1/$metadata#OemManager.FanControllers"; 137 fans["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc/" 138 "Fan/FanControllers"; 139 140 nlohmann::json& pids = configRoot["PidControllers"]; 141 pids["@odata.type"] = "#OemManager.PidControllers"; 142 pids["@odata.context"] = 143 "/redfish/v1/$metadata#OemManager.PidControllers"; 144 pids["@odata.id"] = 145 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/PidControllers"; 146 147 nlohmann::json& zones = configRoot["FanZones"]; 148 zones["@odata.id"] = 149 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones"; 150 zones["@odata.type"] = "#OemManager.FanZones"; 151 zones["@odata.context"] = 152 "/redfish/v1/$metadata#OemManager.FanZones"; 153 configRoot["@odata.id"] = 154 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan"; 155 configRoot["@odata.type"] = "#OemManager.Fan"; 156 configRoot["@odata.context"] = 157 "/redfish/v1/$metadata#OemManager.Fan"; 158 159 bool propertyError = false; 160 for (const auto& pathPair : managedObj) 161 { 162 for (const auto& intfPair : pathPair.second) 163 { 164 if (intfPair.first != pidConfigurationIface && 165 intfPair.first != pidZoneConfigurationIface) 166 { 167 continue; 168 } 169 auto findName = intfPair.second.find("Name"); 170 if (findName == intfPair.second.end()) 171 { 172 BMCWEB_LOG_ERROR << "Pid Field missing Name"; 173 messages::internalError(asyncResp->res); 174 return; 175 } 176 const std::string* namePtr = 177 sdbusplus::message::variant_ns::get_if<std::string>( 178 &findName->second); 179 if (namePtr == nullptr) 180 { 181 BMCWEB_LOG_ERROR << "Pid Name Field illegal"; 182 return; 183 } 184 185 std::string name = *namePtr; 186 dbus::utility::escapePathForDbus(name); 187 if (intfPair.first == pidZoneConfigurationIface) 188 { 189 std::string chassis; 190 if (!dbus::utility::getNthStringFromPath( 191 pathPair.first.str, 5, chassis)) 192 { 193 chassis = "#IllegalValue"; 194 } 195 nlohmann::json& zone = zones[name]; 196 zone["Chassis"] = { 197 {"@odata.id", "/redfish/v1/Chassis/" + chassis}}; 198 zone["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/" 199 "OpenBmc/Fan/FanZones/" + 200 name; 201 zone["@odata.type"] = "#OemManager.FanZone"; 202 zone["@odata.context"] = 203 "/redfish/v1/$metadata#OemManager.FanZone"; 204 } 205 206 for (const auto& propertyPair : intfPair.second) 207 { 208 if (propertyPair.first == "Type" || 209 propertyPair.first == "Class" || 210 propertyPair.first == "Name") 211 { 212 continue; 213 } 214 215 // zones 216 if (intfPair.first == pidZoneConfigurationIface) 217 { 218 const double* ptr = 219 sdbusplus::message::variant_ns::get_if<double>( 220 &propertyPair.second); 221 if (ptr == nullptr) 222 { 223 BMCWEB_LOG_ERROR << "Field Illegal " 224 << propertyPair.first; 225 messages::internalError(asyncResp->res); 226 return; 227 } 228 zones[name][propertyPair.first] = *ptr; 229 } 230 231 // pid and fans are off the same configuration 232 if (intfPair.first == pidConfigurationIface) 233 { 234 const std::string* classPtr = nullptr; 235 auto findClass = intfPair.second.find("Class"); 236 if (findClass != intfPair.second.end()) 237 { 238 classPtr = 239 sdbusplus::message::variant_ns::get_if< 240 std::string>(&findClass->second); 241 } 242 if (classPtr == nullptr) 243 { 244 BMCWEB_LOG_ERROR << "Pid Class Field illegal"; 245 messages::internalError(asyncResp->res); 246 return; 247 } 248 bool isFan = *classPtr == "fan"; 249 nlohmann::json& element = 250 isFan ? fans[name] : pids[name]; 251 if (isFan) 252 { 253 element["@odata.id"] = 254 "/redfish/v1/Managers/bmc#/Oem/" 255 "OpenBmc/Fan/FanControllers/" + 256 std::string(name); 257 element["@odata.type"] = 258 "#OemManager.FanController"; 259 260 element["@odata.context"] = 261 "/redfish/v1/" 262 "$metadata#OemManager.FanController"; 263 } 264 else 265 { 266 element["@odata.id"] = 267 "/redfish/v1/Managers/bmc#/Oem/" 268 "OpenBmc/Fan/PidControllers/" + 269 std::string(name); 270 element["@odata.type"] = 271 "#OemManager.PidController"; 272 element["@odata.context"] = 273 "/redfish/v1/$metadata" 274 "#OemManager.PidController"; 275 } 276 277 if (propertyPair.first == "Zones") 278 { 279 const std::vector<std::string>* inputs = 280 sdbusplus::message::variant_ns::get_if< 281 std::vector<std::string>>( 282 &propertyPair.second); 283 284 if (inputs == nullptr) 285 { 286 BMCWEB_LOG_ERROR 287 << "Zones Pid Field Illegal"; 288 messages::internalError(asyncResp->res); 289 return; 290 } 291 auto& data = element[propertyPair.first]; 292 data = nlohmann::json::array(); 293 for (std::string itemCopy : *inputs) 294 { 295 dbus::utility::escapePathForDbus(itemCopy); 296 data.push_back( 297 {{"@odata.id", 298 "/redfish/v1/Managers/bmc#/Oem/" 299 "OpenBmc/Fan/FanZones/" + 300 itemCopy}}); 301 } 302 } 303 // todo(james): may never happen, but this 304 // assumes configuration data referenced in the 305 // PID config is provided by the same daemon, we 306 // could add another loop to cover all cases, 307 // but I'm okay kicking this can down the road a 308 // bit 309 310 else if (propertyPair.first == "Inputs" || 311 propertyPair.first == "Outputs") 312 { 313 auto& data = element[propertyPair.first]; 314 const std::vector<std::string>* inputs = 315 sdbusplus::message::variant_ns::get_if< 316 std::vector<std::string>>( 317 &propertyPair.second); 318 319 if (inputs == nullptr) 320 { 321 BMCWEB_LOG_ERROR << "Field Illegal " 322 << propertyPair.first; 323 messages::internalError(asyncResp->res); 324 return; 325 } 326 data = *inputs; 327 } // doubles 328 else if (propertyPair.first == 329 "FFGainCoefficient" || 330 propertyPair.first == "FFOffCoefficient" || 331 propertyPair.first == "ICoefficient" || 332 propertyPair.first == "ILimitMax" || 333 propertyPair.first == "ILimitMin" || 334 propertyPair.first == "OutLimitMax" || 335 propertyPair.first == "OutLimitMin" || 336 propertyPair.first == "PCoefficient" || 337 propertyPair.first == "SlewNeg" || 338 propertyPair.first == "SlewPos") 339 { 340 const double* ptr = 341 sdbusplus::message::variant_ns::get_if< 342 double>(&propertyPair.second); 343 if (ptr == nullptr) 344 { 345 BMCWEB_LOG_ERROR << "Field Illegal " 346 << propertyPair.first; 347 messages::internalError(asyncResp->res); 348 return; 349 } 350 element[propertyPair.first] = *ptr; 351 } 352 } 353 } 354 } 355 } 356 }, 357 connection, path, objectManagerIface, "GetManagedObjects"); 358 } 359 360 enum class CreatePIDRet 361 { 362 fail, 363 del, 364 patch 365 }; 366 367 static CreatePIDRet createPidInterface( 368 const std::shared_ptr<AsyncResp>& response, const std::string& type, 369 const nlohmann::json& record, const std::string& path, 370 const dbus::utility::ManagedObjectType& managedObj, bool createNewObject, 371 boost::container::flat_map<std::string, dbus::utility::DbusVariantType>& 372 output, 373 std::string& chassis) 374 { 375 376 if (type == "PidControllers" || type == "FanControllers") 377 { 378 if (createNewObject) 379 { 380 output["Class"] = type == "PidControllers" ? std::string("temp") 381 : std::string("fan"); 382 output["Type"] = std::string("Pid"); 383 } 384 else if (record == nullptr) 385 { 386 // delete interface 387 crow::connections::systemBus->async_method_call( 388 [response, 389 path{std::string(path)}](const boost::system::error_code ec) { 390 if (ec) 391 { 392 BMCWEB_LOG_ERROR << "Error patching " << path << ": " 393 << ec; 394 messages::internalError(response->res); 395 } 396 }, 397 "xyz.openbmc_project.EntityManager", path, 398 pidConfigurationIface, "Delete"); 399 return CreatePIDRet::del; 400 } 401 402 for (auto& field : record.items()) 403 { 404 if (field.key() == "Zones") 405 { 406 if (!field.value().is_array()) 407 { 408 BMCWEB_LOG_ERROR << "Illegal Type " << field.key(); 409 messages::propertyValueFormatError( 410 response->res, field.value(), field.key()); 411 return CreatePIDRet::fail; 412 } 413 std::vector<std::string> inputs; 414 for (const auto& odata : field.value().items()) 415 { 416 for (const auto& value : odata.value().items()) 417 { 418 const std::string* path = 419 value.value().get_ptr<const std::string*>(); 420 if (path == nullptr) 421 { 422 BMCWEB_LOG_ERROR << "Illegal Type " << field.key(); 423 messages::propertyValueFormatError( 424 response->res, field.value().dump(), 425 field.key()); 426 return CreatePIDRet::fail; 427 } 428 std::string input; 429 if (!dbus::utility::getNthStringFromPath(*path, 4, 430 input)) 431 { 432 BMCWEB_LOG_ERROR << "Got invalid path " << *path; 433 messages::propertyValueFormatError( 434 response->res, field.value().dump(), 435 field.key()); 436 return CreatePIDRet::fail; 437 } 438 boost::replace_all(input, "_", " "); 439 inputs.emplace_back(std::move(input)); 440 } 441 } 442 output["Zones"] = std::move(inputs); 443 } 444 else if (field.key() == "Inputs" || field.key() == "Outputs") 445 { 446 if (!field.value().is_array()) 447 { 448 BMCWEB_LOG_ERROR << "Illegal Type " << field.key(); 449 messages::propertyValueFormatError( 450 response->res, field.value().dump(), field.key()); 451 return CreatePIDRet::fail; 452 } 453 std::vector<std::string> inputs; 454 for (const auto& value : field.value().items()) 455 { 456 const std::string* sensor = 457 value.value().get_ptr<const std::string*>(); 458 459 if (sensor == nullptr) 460 { 461 BMCWEB_LOG_ERROR << "Illegal Type " 462 << field.value().dump(); 463 messages::propertyValueFormatError( 464 response->res, field.value().dump(), field.key()); 465 return CreatePIDRet::fail; 466 } 467 468 std::string input = 469 boost::replace_all_copy(*sensor, "_", " "); 470 inputs.push_back(std::move(input)); 471 // try to find the sensor in the 472 // configuration 473 if (chassis.empty()) 474 { 475 std::find_if( 476 managedObj.begin(), managedObj.end(), 477 [&chassis, sensor](const auto& obj) { 478 if (boost::algorithm::ends_with(obj.first.str, 479 *sensor)) 480 { 481 return dbus::utility::getNthStringFromPath( 482 obj.first.str, 5, chassis); 483 } 484 return false; 485 }); 486 } 487 } 488 output[field.key()] = inputs; 489 } 490 491 // doubles 492 else if (field.key() == "FFGainCoefficient" || 493 field.key() == "FFOffCoefficient" || 494 field.key() == "ICoefficient" || 495 field.key() == "ILimitMax" || field.key() == "ILimitMin" || 496 field.key() == "OutLimitMax" || 497 field.key() == "OutLimitMin" || 498 field.key() == "PCoefficient" || 499 field.key() == "SetPoint" || field.key() == "SlewNeg" || 500 field.key() == "SlewPos") 501 { 502 const double* ptr = field.value().get_ptr<const double*>(); 503 if (ptr == nullptr) 504 { 505 BMCWEB_LOG_ERROR << "Illegal Type " << field.key(); 506 messages::propertyValueFormatError( 507 response->res, field.value().dump(), field.key()); 508 return CreatePIDRet::fail; 509 } 510 output[field.key()] = *ptr; 511 } 512 513 else 514 { 515 BMCWEB_LOG_ERROR << "Illegal Type " << field.key(); 516 messages::propertyUnknown(response->res, field.key()); 517 return CreatePIDRet::fail; 518 } 519 } 520 } 521 else if (type == "FanZones") 522 { 523 if (!createNewObject && record == nullptr) 524 { 525 // delete interface 526 crow::connections::systemBus->async_method_call( 527 [response, 528 path{std::string(path)}](const boost::system::error_code ec) { 529 if (ec) 530 { 531 BMCWEB_LOG_ERROR << "Error patching " << path << ": " 532 << ec; 533 messages::internalError(response->res); 534 } 535 }, 536 "xyz.openbmc_project.EntityManager", path, 537 pidZoneConfigurationIface, "Delete"); 538 return CreatePIDRet::del; 539 } 540 output["Type"] = std::string("Pid.Zone"); 541 542 for (auto& field : record.items()) 543 { 544 if (field.key() == "Chassis") 545 { 546 const std::string* chassisId = nullptr; 547 for (const auto& id : field.value().items()) 548 { 549 if (id.key() != "@odata.id") 550 { 551 BMCWEB_LOG_ERROR << "Illegal Type " << id.key(); 552 messages::propertyUnknown(response->res, field.key()); 553 return CreatePIDRet::fail; 554 } 555 chassisId = id.value().get_ptr<const std::string*>(); 556 if (chassisId == nullptr) 557 { 558 messages::createFailedMissingReqProperties( 559 response->res, field.key()); 560 return CreatePIDRet::fail; 561 } 562 } 563 564 // /refish/v1/chassis/chassis_name/ 565 if (!dbus::utility::getNthStringFromPath(*chassisId, 3, 566 chassis)) 567 { 568 BMCWEB_LOG_ERROR << "Got invalid path " << *chassisId; 569 messages::invalidObject(response->res, *chassisId); 570 return CreatePIDRet::fail; 571 } 572 } 573 else if (field.key() == "FailSafePercent" || 574 field.key() == "MinThermalRpm") 575 { 576 const double* ptr = field.value().get_ptr<const double*>(); 577 if (ptr == nullptr) 578 { 579 BMCWEB_LOG_ERROR << "Illegal Type " << field.key(); 580 messages::propertyValueFormatError( 581 response->res, field.value().dump(), field.key()); 582 return CreatePIDRet::fail; 583 } 584 output[field.key()] = *ptr; 585 } 586 else 587 { 588 BMCWEB_LOG_ERROR << "Illegal Type " << field.key(); 589 messages::propertyUnknown(response->res, field.key()); 590 return CreatePIDRet::fail; 591 } 592 } 593 } 594 else 595 { 596 BMCWEB_LOG_ERROR << "Illegal Type " << type; 597 messages::propertyUnknown(response->res, type); 598 return CreatePIDRet::fail; 599 } 600 return CreatePIDRet::patch; 601 } 602 603 class Manager : public Node 604 { 605 public: 606 Manager(CrowApp& app) : Node(app, "/redfish/v1/Managers/bmc/") 607 { 608 uuid = app.template getMiddleware<crow::persistent_data::Middleware>() 609 .systemUuid; 610 entityPrivileges = { 611 {boost::beast::http::verb::get, {{"Login"}}}, 612 {boost::beast::http::verb::head, {{"Login"}}}, 613 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 614 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 615 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 616 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 617 } 618 619 private: 620 void getPidValues(std::shared_ptr<AsyncResp> asyncResp) 621 { 622 crow::connections::systemBus->async_method_call( 623 [asyncResp](const boost::system::error_code ec, 624 const crow::openbmc_mapper::GetSubTreeType& subtree) { 625 if (ec) 626 { 627 BMCWEB_LOG_ERROR << ec; 628 messages::internalError(asyncResp->res); 629 return; 630 } 631 632 // create map of <connection, path to objMgr>> 633 boost::container::flat_map<std::string, std::string> 634 objectMgrPaths; 635 boost::container::flat_set<std::string> calledConnections; 636 for (const auto& pathGroup : subtree) 637 { 638 for (const auto& connectionGroup : pathGroup.second) 639 { 640 auto findConnection = 641 calledConnections.find(connectionGroup.first); 642 if (findConnection != calledConnections.end()) 643 { 644 break; 645 } 646 for (const std::string& interface : 647 connectionGroup.second) 648 { 649 if (interface == objectManagerIface) 650 { 651 objectMgrPaths[connectionGroup.first] = 652 pathGroup.first; 653 } 654 // this list is alphabetical, so we 655 // should have found the objMgr by now 656 if (interface == pidConfigurationIface || 657 interface == pidZoneConfigurationIface) 658 { 659 auto findObjMgr = 660 objectMgrPaths.find(connectionGroup.first); 661 if (findObjMgr == objectMgrPaths.end()) 662 { 663 BMCWEB_LOG_DEBUG << connectionGroup.first 664 << "Has no Object Manager"; 665 continue; 666 } 667 668 calledConnections.insert(connectionGroup.first); 669 670 asyncPopulatePid(findObjMgr->first, 671 findObjMgr->second, asyncResp); 672 break; 673 } 674 } 675 } 676 } 677 }, 678 "xyz.openbmc_project.ObjectMapper", 679 "/xyz/openbmc_project/object_mapper", 680 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0, 681 std::array<const char*, 3>{pidConfigurationIface, 682 pidZoneConfigurationIface, 683 objectManagerIface}); 684 } 685 686 void doGet(crow::Response& res, const crow::Request& req, 687 const std::vector<std::string>& params) override 688 { 689 res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc"; 690 res.jsonValue["@odata.type"] = "#Manager.v1_3_0.Manager"; 691 res.jsonValue["@odata.context"] = 692 "/redfish/v1/$metadata#Manager.Manager"; 693 res.jsonValue["Id"] = "bmc"; 694 res.jsonValue["Name"] = "OpenBmc Manager"; 695 res.jsonValue["Description"] = "Baseboard Management Controller"; 696 res.jsonValue["PowerState"] = "On"; 697 res.jsonValue["ManagerType"] = "BMC"; 698 res.jsonValue["UUID"] = uuid; 699 res.jsonValue["Model"] = "OpenBmc"; // TODO(ed), get model 700 701 res.jsonValue["LogServices"] = { 702 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices"}}; 703 704 res.jsonValue["NetworkProtocol"] = { 705 {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol"}}; 706 707 res.jsonValue["EthernetInterfaces"] = { 708 {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces"}}; 709 // default oem data 710 nlohmann::json& oem = res.jsonValue["Oem"]; 711 nlohmann::json& oemOpenbmc = oem["OpenBmc"]; 712 oem["@odata.type"] = "#OemManager.Oem"; 713 oem["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem"; 714 oem["@odata.context"] = "/redfish/v1/$metadata#OemManager.Oem"; 715 oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc"; 716 oemOpenbmc["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc"; 717 oemOpenbmc["@odata.context"] = 718 "/redfish/v1/$metadata#OemManager.OpenBmc"; 719 720 // Update Actions object. 721 nlohmann::json& manager_reset = 722 res.jsonValue["Actions"]["#Manager.Reset"]; 723 manager_reset["target"] = 724 "/redfish/v1/Managers/bmc/Actions/Manager.Reset"; 725 manager_reset["ResetType@Redfish.AllowableValues"] = { 726 "GracefulRestart"}; 727 728 res.jsonValue["DateTime"] = getDateTime(); 729 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 730 731 crow::connections::systemBus->async_method_call( 732 [asyncResp](const boost::system::error_code ec, 733 const dbus::utility::ManagedObjectType& resp) { 734 if (ec) 735 { 736 BMCWEB_LOG_ERROR << "Error while getting Software Version"; 737 messages::internalError(asyncResp->res); 738 return; 739 } 740 741 for (auto& objpath : resp) 742 { 743 for (auto& interface : objpath.second) 744 { 745 // If interface is xyz.openbmc_project.Software.Version, 746 // this is what we're looking for. 747 if (interface.first == 748 "xyz.openbmc_project.Software.Version") 749 { 750 // Cut out everyting until last "/", ... 751 const std::string& iface_id = objpath.first; 752 for (auto& property : interface.second) 753 { 754 if (property.first == "Version") 755 { 756 const std::string* value = 757 sdbusplus::message::variant_ns::get_if< 758 std::string>(&property.second); 759 if (value == nullptr) 760 { 761 continue; 762 } 763 asyncResp->res 764 .jsonValue["FirmwareVersion"] = *value; 765 } 766 } 767 } 768 } 769 } 770 }, 771 "xyz.openbmc_project.Software.BMC.Updater", 772 "/xyz/openbmc_project/software", 773 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 774 getPidValues(asyncResp); 775 } 776 void setPidValues(std::shared_ptr<AsyncResp> response, 777 const nlohmann::json& data) 778 { 779 // todo(james): might make sense to do a mapper call here if this 780 // interface gets more traction 781 crow::connections::systemBus->async_method_call( 782 [response, 783 data](const boost::system::error_code ec, 784 const dbus::utility::ManagedObjectType& managedObj) { 785 if (ec) 786 { 787 BMCWEB_LOG_ERROR << "Error communicating to Entity Manager"; 788 messages::internalError(response->res); 789 return; 790 } 791 for (const auto& type : data.items()) 792 { 793 if (!type.value().is_object()) 794 { 795 BMCWEB_LOG_ERROR << "Illegal Type " << type.key(); 796 messages::propertyValueFormatError( 797 response->res, type.value(), type.key()); 798 return; 799 } 800 for (const auto& record : type.value().items()) 801 { 802 const std::string& name = record.key(); 803 auto pathItr = 804 std::find_if(managedObj.begin(), managedObj.end(), 805 [&name](const auto& obj) { 806 return boost::algorithm::ends_with( 807 obj.first.str, name); 808 }); 809 boost::container::flat_map< 810 std::string, dbus::utility::DbusVariantType> 811 output; 812 813 output.reserve(16); // The pid interface length 814 815 // determines if we're patching entity-manager or 816 // creating a new object 817 bool createNewObject = (pathItr == managedObj.end()); 818 if (type.key() == "PidControllers" || 819 type.key() == "FanControllers") 820 { 821 if (!createNewObject && 822 pathItr->second.find(pidConfigurationIface) == 823 pathItr->second.end()) 824 { 825 createNewObject = true; 826 } 827 } 828 else if (!createNewObject && 829 pathItr->second.find( 830 pidZoneConfigurationIface) == 831 pathItr->second.end()) 832 { 833 createNewObject = true; 834 } 835 output["Name"] = 836 boost::replace_all_copy(name, "_", " "); 837 838 std::string chassis; 839 CreatePIDRet ret = createPidInterface( 840 response, type.key(), record.value(), 841 pathItr->first.str, managedObj, createNewObject, 842 output, chassis); 843 if (ret == CreatePIDRet::fail) 844 { 845 return; 846 } 847 else if (ret == CreatePIDRet::del) 848 { 849 continue; 850 } 851 852 if (!createNewObject) 853 { 854 for (const auto& property : output) 855 { 856 const char* iface = 857 type.key() == "FanZones" 858 ? pidZoneConfigurationIface 859 : pidConfigurationIface; 860 crow::connections::systemBus->async_method_call( 861 [response, 862 propertyName{std::string(property.first)}]( 863 const boost::system::error_code ec) { 864 if (ec) 865 { 866 BMCWEB_LOG_ERROR 867 << "Error patching " 868 << propertyName << ": " << ec; 869 messages::internalError( 870 response->res); 871 } 872 }, 873 "xyz.openbmc_project.EntityManager", 874 pathItr->first.str, 875 "org.freedesktop.DBus.Properties", "Set", 876 std::string(iface), property.first, 877 property.second); 878 } 879 } 880 else 881 { 882 if (chassis.empty()) 883 { 884 BMCWEB_LOG_ERROR 885 << "Failed to get chassis from config"; 886 messages::invalidObject(response->res, name); 887 return; 888 } 889 890 bool foundChassis = false; 891 for (const auto& obj : managedObj) 892 { 893 if (boost::algorithm::ends_with(obj.first.str, 894 chassis)) 895 { 896 chassis = obj.first.str; 897 foundChassis = true; 898 break; 899 } 900 } 901 if (!foundChassis) 902 { 903 BMCWEB_LOG_ERROR 904 << "Failed to find chassis on dbus"; 905 messages::resourceMissingAtURI( 906 response->res, 907 "/redfish/v1/Chassis/" + chassis); 908 return; 909 } 910 911 crow::connections::systemBus->async_method_call( 912 [response](const boost::system::error_code ec) { 913 if (ec) 914 { 915 BMCWEB_LOG_ERROR 916 << "Error Adding Pid Object " << ec; 917 messages::internalError(response->res); 918 } 919 }, 920 "xyz.openbmc_project.EntityManager", chassis, 921 "xyz.openbmc_project.AddObject", "AddObject", 922 output); 923 } 924 } 925 } 926 }, 927 "xyz.openbmc_project.EntityManager", "/", objectManagerIface, 928 "GetManagedObjects"); 929 } 930 931 void doPatch(crow::Response& res, const crow::Request& req, 932 const std::vector<std::string>& params) override 933 { 934 std::optional<nlohmann::json> oem; 935 936 if (!json_util::readJson(req, res, "Oem", oem)) 937 { 938 return; 939 } 940 941 std::shared_ptr<AsyncResp> response = std::make_shared<AsyncResp>(res); 942 943 if (oem) 944 { 945 for (const auto& oemLevel : oem->items()) 946 { 947 if (oemLevel.key() == "OpenBmc") 948 { 949 if (!oemLevel.value().is_object()) 950 { 951 BMCWEB_LOG_ERROR << "Bad Patch " << oemLevel.key(); 952 messages::propertyValueFormatError( 953 response->res, "Oem", "OemManager.OpenBmc"); 954 return; 955 } 956 for (const auto& typeLevel : oemLevel.value().items()) 957 { 958 959 if (typeLevel.key() == "Fan") 960 { 961 if (!typeLevel.value().is_object()) 962 { 963 BMCWEB_LOG_ERROR << "Bad Patch " 964 << typeLevel.key(); 965 messages::propertyValueFormatError( 966 response->res, typeLevel.value().dump(), 967 typeLevel.key()); 968 return; 969 } 970 setPidValues(response, 971 std::move(typeLevel.value())); 972 } 973 else 974 { 975 BMCWEB_LOG_ERROR << "Bad Patch " << typeLevel.key(); 976 messages::propertyUnknown(response->res, 977 typeLevel.key()); 978 return; 979 } 980 } 981 } 982 else 983 { 984 BMCWEB_LOG_ERROR << "Bad Patch " << oemLevel.key(); 985 messages::propertyUnknown(response->res, oemLevel.key()); 986 return; 987 } 988 } 989 } 990 } 991 992 std::string getDateTime() const 993 { 994 std::array<char, 128> dateTime; 995 std::string redfishDateTime("0000-00-00T00:00:00Z00:00"); 996 std::time_t time = std::time(nullptr); 997 998 if (std::strftime(dateTime.begin(), dateTime.size(), "%FT%T%z", 999 std::localtime(&time))) 1000 { 1001 // insert the colon required by the ISO 8601 standard 1002 redfishDateTime = std::string(dateTime.data()); 1003 redfishDateTime.insert(redfishDateTime.end() - 2, ':'); 1004 } 1005 1006 return redfishDateTime; 1007 } 1008 1009 std::string uuid; 1010 }; 1011 1012 class ManagerCollection : public Node 1013 { 1014 public: 1015 ManagerCollection(CrowApp& app) : Node(app, "/redfish/v1/Managers/") 1016 { 1017 entityPrivileges = { 1018 {boost::beast::http::verb::get, {{"Login"}}}, 1019 {boost::beast::http::verb::head, {{"Login"}}}, 1020 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1021 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1022 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1023 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1024 } 1025 1026 private: 1027 void doGet(crow::Response& res, const crow::Request& req, 1028 const std::vector<std::string>& params) override 1029 { 1030 // Collections don't include the static data added by SubRoute 1031 // because it has a duplicate entry for members 1032 res.jsonValue["@odata.id"] = "/redfish/v1/Managers"; 1033 res.jsonValue["@odata.type"] = "#ManagerCollection.ManagerCollection"; 1034 res.jsonValue["@odata.context"] = 1035 "/redfish/v1/$metadata#ManagerCollection.ManagerCollection"; 1036 res.jsonValue["Name"] = "Manager Collection"; 1037 res.jsonValue["Members@odata.count"] = 1; 1038 res.jsonValue["Members"] = { 1039 {{"@odata.id", "/redfish/v1/Managers/bmc"}}}; 1040 res.end(); 1041 } 1042 }; 1043 } // namespace redfish 1044