1 #pragma once 2 3 #include <boost/container/flat_map.hpp> 4 #include <boost/container/flat_set.hpp> 5 #include <dbus_singleton.hpp> 6 #include <error_messages.hpp> 7 #include <node.hpp> 8 #include <utils/json_utils.hpp> 9 10 #include <optional> 11 #include <utility> 12 #include <variant> 13 14 namespace redfish 15 { 16 17 /** 18 * @brief Retrieves hypervisor state properties over dbus 19 * 20 * The hypervisor state object is optional so this function will only set the 21 * state variables if the object is found 22 * 23 * @param[in] aResp Shared pointer for completing asynchronous calls. 24 * 25 * @return None. 26 */ 27 inline void getHypervisorState(const std::shared_ptr<AsyncResp>& aResp) 28 { 29 BMCWEB_LOG_DEBUG << "Get hypervisor state information."; 30 crow::connections::systemBus->async_method_call( 31 [aResp](const boost::system::error_code ec, 32 const std::variant<std::string>& hostState) { 33 if (ec) 34 { 35 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 36 // This is an optional D-Bus object so just return if 37 // error occurs 38 return; 39 } 40 41 const std::string* s = std::get_if<std::string>(&hostState); 42 if (s == nullptr) 43 { 44 messages::internalError(aResp->res); 45 return; 46 } 47 48 BMCWEB_LOG_DEBUG << "Hypervisor state: " << *s; 49 // Verify Host State 50 if (*s == "xyz.openbmc_project.State.Host.HostState.Running") 51 { 52 aResp->res.jsonValue["PowerState"] = "On"; 53 aResp->res.jsonValue["Status"]["State"] = "Enabled"; 54 } 55 else if (*s == "xyz.openbmc_project.State.Host.HostState." 56 "Quiesced") 57 { 58 aResp->res.jsonValue["PowerState"] = "On"; 59 aResp->res.jsonValue["Status"]["State"] = "Quiesced"; 60 } 61 else if (*s == "xyz.openbmc_project.State.Host.HostState." 62 "Standby") 63 { 64 aResp->res.jsonValue["PowerState"] = "On"; 65 aResp->res.jsonValue["Status"]["State"] = "StandbyOffline"; 66 } 67 else if (*s == "xyz.openbmc_project.State.Host.HostState." 68 "TransitioningToRunning") 69 { 70 aResp->res.jsonValue["PowerState"] = "PoweringOn"; 71 aResp->res.jsonValue["Status"]["State"] = "Starting"; 72 } 73 else if (*s == "xyz.openbmc_project.State.Host.HostState." 74 "TransitioningToOff") 75 { 76 aResp->res.jsonValue["PowerState"] = "PoweringOff"; 77 aResp->res.jsonValue["Status"]["State"] = "Enabled"; 78 } 79 else if (*s == "xyz.openbmc_project.State.Host.HostState.Off") 80 { 81 aResp->res.jsonValue["PowerState"] = "Off"; 82 aResp->res.jsonValue["Status"]["State"] = "Disabled"; 83 } 84 else 85 { 86 messages::internalError(aResp->res); 87 return; 88 } 89 }, 90 "xyz.openbmc_project.State.Hypervisor", 91 "/xyz/openbmc_project/state/hypervisor0", 92 "org.freedesktop.DBus.Properties", "Get", 93 "xyz.openbmc_project.State.Host", "CurrentHostState"); 94 } 95 96 /** 97 * @brief Populate Actions if any are valid for hypervisor object 98 * 99 * The hypervisor state object is optional so this function will only set the 100 * Action if the object is found 101 * 102 * @param[in] aResp Shared pointer for completing asynchronous calls. 103 * 104 * @return None. 105 */ 106 inline void getHypervisorActions(const std::shared_ptr<AsyncResp>& aResp) 107 { 108 BMCWEB_LOG_DEBUG << "Get hypervisor actions."; 109 crow::connections::systemBus->async_method_call( 110 [aResp]( 111 const boost::system::error_code ec, 112 const std::vector<std::pair<std::string, std::vector<std::string>>>& 113 objInfo) { 114 if (ec) 115 { 116 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 117 // This is an optional D-Bus object so just return if 118 // error occurs 119 return; 120 } 121 122 if (objInfo.size() == 0) 123 { 124 // As noted above, this is an optional interface so just return 125 // if there is no instance found 126 return; 127 } 128 129 if (objInfo.size() > 1) 130 { 131 // More then one hypervisor object is not supported and is an 132 // error 133 messages::internalError(aResp->res); 134 return; 135 } 136 137 // Object present so system support limited ComputerSystem Action 138 aResp->res.jsonValue["Actions"]["#ComputerSystem.Reset"] = { 139 {"target", 140 "/redfish/v1/Systems/hypervisor/Actions/ComputerSystem.Reset"}, 141 {"@Redfish.ActionInfo", 142 "/redfish/v1/Systems/hypervisor/ResetActionInfo"}}; 143 }, 144 "xyz.openbmc_project.ObjectMapper", 145 "/xyz/openbmc_project/object_mapper", 146 "xyz.openbmc_project.ObjectMapper", "GetObject", 147 "/xyz/openbmc_project/state/hypervisor0", 148 std::array<const char*, 1>{"xyz.openbmc_project.State.Host"}); 149 } 150 151 /** 152 * Hypervisor Systems derived class for delivering Computer Systems Schema. 153 */ 154 class HypervisorSystem : public Node 155 { 156 public: 157 /* 158 * Default Constructor 159 */ 160 HypervisorSystem(App& app) : Node(app, "/redfish/v1/Systems/hypervisor/") 161 { 162 entityPrivileges = { 163 {boost::beast::http::verb::get, {{"Login"}}}, 164 {boost::beast::http::verb::head, {{"Login"}}}, 165 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}}; 166 } 167 168 private: 169 /** 170 * Functions triggers appropriate requests on DBus 171 */ 172 void doGet(crow::Response& res, const crow::Request&, 173 const std::vector<std::string>&) override 174 { 175 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 176 crow::connections::systemBus->async_method_call( 177 [asyncResp](const boost::system::error_code ec, 178 const std::variant<std::string>& /*hostName*/) { 179 if (ec) 180 { 181 messages::resourceNotFound(asyncResp->res, "System", 182 "hypervisor"); 183 return; 184 } 185 BMCWEB_LOG_DEBUG << "Hypervisor is available"; 186 187 asyncResp->res.jsonValue["@odata.type"] = 188 "#ComputerSystem.v1_6_0.ComputerSystem"; 189 asyncResp->res.jsonValue["@odata.id"] = 190 "/redfish/v1/Systems/hypervisor"; 191 asyncResp->res.jsonValue["Description"] = "Hypervisor"; 192 asyncResp->res.jsonValue["Name"] = "Hypervisor"; 193 asyncResp->res.jsonValue["Id"] = "hypervisor"; 194 asyncResp->res.jsonValue["Links"]["ManagedBy"] = { 195 {{"@odata.id", "/redfish/v1/Managers/bmc"}}}; 196 asyncResp->res.jsonValue["EthernetInterfaces"] = { 197 {"@odata.id", "/redfish/v1/Systems/hypervisor/" 198 "EthernetInterfaces"}}; 199 getHypervisorState(asyncResp); 200 getHypervisorActions(asyncResp); 201 // TODO: Add "SystemType" : "hypervisor" 202 }, 203 "xyz.openbmc_project.Settings", 204 "/xyz/openbmc_project/network/hypervisor", 205 "org.freedesktop.DBus.Properties", "Get", 206 "xyz.openbmc_project.Network.SystemConfiguration", "HostName"); 207 } 208 }; 209 210 /** 211 * HypervisorInterfaceCollection class to handle the GET and PATCH on Hypervisor 212 * Interface 213 */ 214 class HypervisorInterfaceCollection : public Node 215 { 216 public: 217 HypervisorInterfaceCollection(App& app) : 218 Node(app, "/redfish/v1/Systems/hypervisor/EthernetInterfaces/") 219 { 220 entityPrivileges = { 221 {boost::beast::http::verb::get, {{"Login"}}}, 222 {boost::beast::http::verb::head, {{"Login"}}}, 223 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}}; 224 } 225 226 private: 227 /** 228 * Functions triggers appropriate requests on DBus 229 */ 230 void doGet(crow::Response& res, const crow::Request&, 231 const std::vector<std::string>&) override 232 { 233 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 234 const std::array<const char*, 1> interfaces = { 235 "xyz.openbmc_project.Network.EthernetInterface"}; 236 237 crow::connections::systemBus->async_method_call( 238 [asyncResp](const boost::system::error_code error, 239 const std::vector<std::string>& ifaceList) { 240 if (error) 241 { 242 messages::resourceNotFound(asyncResp->res, "System", 243 "hypervisor"); 244 return; 245 } 246 asyncResp->res.jsonValue["@odata.type"] = 247 "#EthernetInterfaceCollection." 248 "EthernetInterfaceCollection"; 249 asyncResp->res.jsonValue["@odata.id"] = 250 "/redfish/v1/Systems/hypervisor/EthernetInterfaces"; 251 asyncResp->res.jsonValue["Name"] = "Hypervisor Ethernet " 252 "Interface Collection"; 253 asyncResp->res.jsonValue["Description"] = 254 "Collection of Virtual Management " 255 "Interfaces for the hypervisor"; 256 257 nlohmann::json& ifaceArray = 258 asyncResp->res.jsonValue["Members"]; 259 ifaceArray = nlohmann::json::array(); 260 for (const std::string& iface : ifaceList) 261 { 262 sdbusplus::message::object_path path(iface); 263 std::string name = path.filename(); 264 if (name.empty()) 265 { 266 continue; 267 } 268 269 ifaceArray.push_back( 270 {{"@odata.id", "/redfish/v1/Systems/hypervisor/" 271 "EthernetInterfaces/" + 272 name}}); 273 } 274 asyncResp->res.jsonValue["Members@odata.count"] = 275 ifaceArray.size(); 276 }, 277 "xyz.openbmc_project.ObjectMapper", 278 "/xyz/openbmc_project/object_mapper", 279 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", 280 "/xyz/openbmc_project/network/hypervisor", 0, interfaces); 281 } 282 }; 283 284 inline bool extractHypervisorInterfaceData( 285 const std::string& ethIfaceId, const GetManagedObjects& dbusData, 286 EthernetInterfaceData& ethData, 287 boost::container::flat_set<IPv4AddressData>& ipv4Config) 288 { 289 bool idFound = false; 290 for (const auto& objpath : dbusData) 291 { 292 for (const auto& ifacePair : objpath.second) 293 { 294 if (objpath.first == 295 "/xyz/openbmc_project/network/hypervisor/" + ethIfaceId) 296 { 297 idFound = true; 298 if (ifacePair.first == "xyz.openbmc_project.Network.MACAddress") 299 { 300 for (const auto& propertyPair : ifacePair.second) 301 { 302 if (propertyPair.first == "MACAddress") 303 { 304 const std::string* mac = 305 std::get_if<std::string>(&propertyPair.second); 306 if (mac != nullptr) 307 { 308 ethData.mac_address = *mac; 309 } 310 } 311 } 312 } 313 else if (ifacePair.first == 314 "xyz.openbmc_project.Network.EthernetInterface") 315 { 316 for (const auto& propertyPair : ifacePair.second) 317 { 318 if (propertyPair.first == "DHCPEnabled") 319 { 320 const std::string* dhcp = 321 std::get_if<std::string>(&propertyPair.second); 322 if (dhcp != nullptr) 323 { 324 ethData.DHCPEnabled = *dhcp; 325 break; // Interested on only "DHCPEnabled". 326 // Stop parsing since we got the 327 // "DHCPEnabled" value. 328 } 329 } 330 } 331 } 332 } 333 if (objpath.first == "/xyz/openbmc_project/network/hypervisor/" + 334 ethIfaceId + "/ipv4/addr0") 335 { 336 std::pair<boost::container::flat_set<IPv4AddressData>::iterator, 337 bool> 338 it = ipv4Config.insert(IPv4AddressData{}); 339 IPv4AddressData& ipv4Address = *it.first; 340 if (ifacePair.first == "xyz.openbmc_project.Object.Enable") 341 { 342 for (auto& property : ifacePair.second) 343 { 344 if (property.first == "Enabled") 345 { 346 const bool* intfEnable = 347 std::get_if<bool>(&property.second); 348 if (intfEnable != nullptr) 349 { 350 ipv4Address.isActive = *intfEnable; 351 break; 352 } 353 } 354 } 355 } 356 if (ifacePair.first == "xyz.openbmc_project.Network.IP") 357 { 358 for (auto& property : ifacePair.second) 359 { 360 if (property.first == "Address") 361 { 362 const std::string* address = 363 std::get_if<std::string>(&property.second); 364 if (address != nullptr) 365 { 366 ipv4Address.address = *address; 367 } 368 } 369 else if (property.first == "Origin") 370 { 371 const std::string* origin = 372 std::get_if<std::string>(&property.second); 373 if (origin != nullptr) 374 { 375 ipv4Address.origin = 376 translateAddressOriginDbusToRedfish(*origin, 377 true); 378 } 379 } 380 else if (property.first == "PrefixLength") 381 { 382 const uint8_t* mask = 383 std::get_if<uint8_t>(&property.second); 384 if (mask != nullptr) 385 { 386 // convert it to the string 387 ipv4Address.netmask = getNetmask(*mask); 388 } 389 } 390 else 391 { 392 BMCWEB_LOG_ERROR 393 << "Got extra property: " << property.first 394 << " on the " << objpath.first.str << " object"; 395 } 396 } 397 } 398 } 399 if (objpath.first == "/xyz/openbmc_project/network/hypervisor") 400 { 401 // System configuration shows up in the global namespace, so no 402 // need to check eth number 403 if (ifacePair.first == 404 "xyz.openbmc_project.Network.SystemConfiguration") 405 { 406 for (const auto& propertyPair : ifacePair.second) 407 { 408 if (propertyPair.first == "HostName") 409 { 410 const std::string* hostName = 411 std::get_if<std::string>(&propertyPair.second); 412 if (hostName != nullptr) 413 { 414 ethData.hostname = *hostName; 415 } 416 } 417 else if (propertyPair.first == "DefaultGateway") 418 { 419 const std::string* defaultGateway = 420 std::get_if<std::string>(&propertyPair.second); 421 if (defaultGateway != nullptr) 422 { 423 ethData.default_gateway = *defaultGateway; 424 } 425 } 426 } 427 } 428 } 429 } 430 } 431 return idFound; 432 } 433 /** 434 * Function that retrieves all properties for given Hypervisor Ethernet 435 * Interface Object from Settings Manager 436 * @param ethIfaceId Hypervisor ethernet interface id to query on DBus 437 * @param callback a function that shall be called to convert Dbus output 438 * into JSON 439 */ 440 template <typename CallbackFunc> 441 void getHypervisorIfaceData(const std::string& ethIfaceId, 442 CallbackFunc&& callback) 443 { 444 crow::connections::systemBus->async_method_call( 445 [ethIfaceId{std::string{ethIfaceId}}, 446 callback{std::move(callback)}](const boost::system::error_code error, 447 const GetManagedObjects& resp) { 448 EthernetInterfaceData ethData{}; 449 boost::container::flat_set<IPv4AddressData> ipv4Data; 450 if (error) 451 { 452 callback(false, ethData, ipv4Data); 453 return; 454 } 455 456 bool found = extractHypervisorInterfaceData(ethIfaceId, resp, 457 ethData, ipv4Data); 458 if (!found) 459 { 460 BMCWEB_LOG_INFO << "Hypervisor Interface not found"; 461 } 462 callback(found, ethData, ipv4Data); 463 }, 464 "xyz.openbmc_project.Settings", "/", 465 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 466 } 467 468 /** 469 * @brief Sets the Hypervisor Interface IPAddress DBUS 470 * 471 * @param[in] aResp Shared pointer for generating response message. 472 * @param[in] ipv4Address Address from the incoming request 473 * @param[in] ethIfaceId Hypervisor Interface Id 474 * 475 * @return None. 476 */ 477 inline void setHypervisorIPv4Address(const std::shared_ptr<AsyncResp>& aResp, 478 const std::string& ethIfaceId, 479 const std::string& ipv4Address) 480 { 481 BMCWEB_LOG_DEBUG << "Setting the Hypervisor IPaddress : " << ipv4Address 482 << " on Iface: " << ethIfaceId; 483 crow::connections::systemBus->async_method_call( 484 [aResp](const boost::system::error_code ec) { 485 if (ec) 486 { 487 BMCWEB_LOG_ERROR << "DBUS response error " << ec; 488 return; 489 } 490 BMCWEB_LOG_DEBUG << "Hypervisor IPaddress is Set"; 491 }, 492 "xyz.openbmc_project.Settings", 493 "/xyz/openbmc_project/network/hypervisor/" + ethIfaceId + "/ipv4/addr0", 494 "org.freedesktop.DBus.Properties", "Set", 495 "xyz.openbmc_project.Network.IP", "Address", 496 std::variant<std::string>(ipv4Address)); 497 } 498 499 /** 500 * @brief Sets the Hypervisor Interface SubnetMask DBUS 501 * 502 * @param[in] aResp Shared pointer for generating response message. 503 * @param[in] subnet SubnetMask from the incoming request 504 * @param[in] ethIfaceId Hypervisor Interface Id 505 * 506 * @return None. 507 */ 508 inline void setHypervisorIPv4Subnet(const std::shared_ptr<AsyncResp>& aResp, 509 const std::string& ethIfaceId, 510 const uint8_t subnet) 511 { 512 BMCWEB_LOG_DEBUG << "Setting the Hypervisor subnet : " << subnet 513 << " on Iface: " << ethIfaceId; 514 515 crow::connections::systemBus->async_method_call( 516 [aResp](const boost::system::error_code ec) { 517 if (ec) 518 { 519 BMCWEB_LOG_ERROR << "DBUS response error " << ec; 520 return; 521 } 522 BMCWEB_LOG_DEBUG << "SubnetMask is Set"; 523 }, 524 "xyz.openbmc_project.Settings", 525 "/xyz/openbmc_project/network/hypervisor/" + ethIfaceId + "/ipv4/addr0", 526 "org.freedesktop.DBus.Properties", "Set", 527 "xyz.openbmc_project.Network.IP", "PrefixLength", 528 std::variant<uint8_t>(subnet)); 529 } 530 531 /** 532 * @brief Sets the Hypervisor Interface Gateway DBUS 533 * 534 * @param[in] aResp Shared pointer for generating response message. 535 * @param[in] gateway Gateway from the incoming request 536 * @param[in] ethIfaceId Hypervisor Interface Id 537 * 538 * @return None. 539 */ 540 inline void setHypervisorIPv4Gateway(const std::shared_ptr<AsyncResp>& aResp, 541 const std::string& gateway) 542 { 543 BMCWEB_LOG_DEBUG 544 << "Setting the DefaultGateway to the last configured gateway"; 545 546 crow::connections::systemBus->async_method_call( 547 [aResp](const boost::system::error_code ec) { 548 if (ec) 549 { 550 BMCWEB_LOG_ERROR << "DBUS response error " << ec; 551 return; 552 } 553 BMCWEB_LOG_DEBUG << "Default Gateway is Set"; 554 }, 555 "xyz.openbmc_project.Settings", 556 "/xyz/openbmc_project/network/hypervisor", 557 "org.freedesktop.DBus.Properties", "Set", 558 "xyz.openbmc_project.Network.SystemConfiguration", "DefaultGateway", 559 std::variant<std::string>(gateway)); 560 } 561 562 /** 563 * @brief Creates a static IPv4 entry 564 * 565 * @param[in] ifaceId Id of interface upon which to create the IPv4 entry 566 * @param[in] prefixLength IPv4 prefix syntax for the subnet mask 567 * @param[in] gateway IPv4 address of this interfaces gateway 568 * @param[in] address IPv4 address to assign to this interface 569 * @param[io] asyncResp Response object that will be returned to client 570 * 571 * @return None 572 */ 573 inline void createHypervisorIPv4(const std::string& ifaceId, 574 uint8_t prefixLength, 575 const std::string& gateway, 576 const std::string& address, 577 const std::shared_ptr<AsyncResp>& asyncResp) 578 { 579 setHypervisorIPv4Address(asyncResp, ifaceId, address); 580 setHypervisorIPv4Gateway(asyncResp, gateway); 581 setHypervisorIPv4Subnet(asyncResp, ifaceId, prefixLength); 582 } 583 584 /** 585 * @brief Deletes given IPv4 interface 586 * 587 * @param[in] ifaceId Id of interface whose IP should be deleted 588 * @param[io] asyncResp Response object that will be returned to client 589 * 590 * @return None 591 */ 592 inline void deleteHypervisorIPv4(const std::string& ifaceId, 593 const std::shared_ptr<AsyncResp>& asyncResp) 594 { 595 std::string address = "0.0.0.0"; 596 std::string gateway = "0.0.0.0"; 597 const uint8_t prefixLength = 0; 598 setHypervisorIPv4Address(asyncResp, ifaceId, address); 599 setHypervisorIPv4Gateway(asyncResp, gateway); 600 setHypervisorIPv4Subnet(asyncResp, ifaceId, prefixLength); 601 } 602 603 /** 604 * HypervisorInterface derived class for delivering Ethernet Schema 605 */ 606 class HypervisorInterface : public Node 607 { 608 public: 609 /* 610 * Default Constructor 611 */ 612 HypervisorInterface(App& app) : 613 Node(app, "/redfish/v1/Systems/hypervisor/EthernetInterfaces/<str>/", 614 std::string()) 615 { 616 entityPrivileges = { 617 {boost::beast::http::verb::get, {{"Login"}}}, 618 {boost::beast::http::verb::head, {{"Login"}}}, 619 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}}; 620 } 621 622 private: 623 void parseInterfaceData( 624 nlohmann::json& jsonResponse, const std::string& ifaceId, 625 const EthernetInterfaceData& ethData, 626 const boost::container::flat_set<IPv4AddressData>& ipv4Data) 627 { 628 jsonResponse["Id"] = ifaceId; 629 jsonResponse["@odata.id"] = 630 "/redfish/v1/Systems/hypervisor/EthernetInterfaces/" + ifaceId; 631 jsonResponse["InterfaceEnabled"] = true; 632 jsonResponse["MACAddress"] = ethData.mac_address; 633 634 jsonResponse["HostName"] = ethData.hostname; 635 jsonResponse["DHCPv4"]["DHCPEnabled"] = 636 translateDHCPEnabledToBool(ethData.DHCPEnabled, true); 637 638 nlohmann::json& ipv4Array = jsonResponse["IPv4Addresses"]; 639 nlohmann::json& ipv4StaticArray = jsonResponse["IPv4StaticAddresses"]; 640 ipv4Array = nlohmann::json::array(); 641 ipv4StaticArray = nlohmann::json::array(); 642 for (auto& ipv4Config : ipv4Data) 643 { 644 if (ipv4Config.isActive) 645 { 646 647 ipv4Array.push_back({{"AddressOrigin", ipv4Config.origin}, 648 {"SubnetMask", ipv4Config.netmask}, 649 {"Address", ipv4Config.address}, 650 {"Gateway", ethData.default_gateway}}); 651 if (ipv4Config.origin == "Static") 652 { 653 ipv4StaticArray.push_back( 654 {{"AddressOrigin", ipv4Config.origin}, 655 {"SubnetMask", ipv4Config.netmask}, 656 {"Address", ipv4Config.address}, 657 {"Gateway", ethData.default_gateway}}); 658 } 659 } 660 } 661 } 662 663 void handleHypervisorIPv4StaticPatch( 664 const std::string& ifaceId, const nlohmann::json& input, 665 const std::shared_ptr<AsyncResp>& asyncResp) 666 { 667 if ((!input.is_array()) || input.empty()) 668 { 669 messages::propertyValueTypeError(asyncResp->res, input.dump(), 670 "IPv4StaticAddresses"); 671 return; 672 } 673 674 // Hypervisor considers the first IP address in the array list 675 // as the Hypervisor's virtual management interface supports single IPv4 676 // address 677 const nlohmann::json& thisJson = input[0]; 678 679 // For the error string 680 std::string pathString = "IPv4StaticAddresses/1"; 681 682 if (!thisJson.is_null() && !thisJson.empty()) 683 { 684 std::optional<std::string> address; 685 std::optional<std::string> subnetMask; 686 std::optional<std::string> gateway; 687 nlohmann::json thisJsonCopy = thisJson; 688 if (!json_util::readJson(thisJsonCopy, asyncResp->res, "Address", 689 address, "SubnetMask", subnetMask, 690 "Gateway", gateway)) 691 { 692 messages::propertyValueFormatError( 693 asyncResp->res, 694 thisJson.dump(2, ' ', true, 695 nlohmann::json::error_handler_t::replace), 696 pathString); 697 return; 698 } 699 700 uint8_t prefixLength = 0; 701 bool errorInEntry = false; 702 if (address) 703 { 704 if (!ipv4VerifyIpAndGetBitcount(*address)) 705 { 706 messages::propertyValueFormatError(asyncResp->res, *address, 707 pathString + "/Address"); 708 errorInEntry = true; 709 } 710 } 711 else 712 { 713 messages::propertyMissing(asyncResp->res, 714 pathString + "/Address"); 715 errorInEntry = true; 716 } 717 718 if (subnetMask) 719 { 720 if (!ipv4VerifyIpAndGetBitcount(*subnetMask, &prefixLength)) 721 { 722 messages::propertyValueFormatError( 723 asyncResp->res, *subnetMask, 724 pathString + "/SubnetMask"); 725 errorInEntry = true; 726 } 727 } 728 else 729 { 730 messages::propertyMissing(asyncResp->res, 731 pathString + "/SubnetMask"); 732 errorInEntry = true; 733 } 734 735 if (gateway) 736 { 737 if (!ipv4VerifyIpAndGetBitcount(*gateway)) 738 { 739 messages::propertyValueFormatError(asyncResp->res, *gateway, 740 pathString + "/Gateway"); 741 errorInEntry = true; 742 } 743 } 744 else 745 { 746 messages::propertyMissing(asyncResp->res, 747 pathString + "/Gateway"); 748 errorInEntry = true; 749 } 750 751 if (errorInEntry) 752 { 753 return; 754 } 755 756 BMCWEB_LOG_DEBUG << "Calling createHypervisorIPv4 on : " << ifaceId 757 << "," << *address; 758 createHypervisorIPv4(ifaceId, prefixLength, *gateway, *address, 759 asyncResp); 760 // Set the DHCPEnabled to false since the Static IPv4 is set 761 setDHCPEnabled(ifaceId, false, asyncResp); 762 } 763 else 764 { 765 if (thisJson.is_null()) 766 { 767 deleteHypervisorIPv4(ifaceId, asyncResp); 768 } 769 } 770 } 771 772 bool isHostnameValid(const std::string& hostName) 773 { 774 // As per RFC 1123 775 // Allow up to 255 characters 776 if (hostName.length() > 255) 777 { 778 return false; 779 } 780 // Validate the regex 781 const std::regex pattern( 782 "^[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]$"); 783 784 return std::regex_match(hostName, pattern); 785 } 786 787 void handleHostnamePatch(const std::string& hostName, 788 const std::shared_ptr<AsyncResp>& asyncResp) 789 { 790 if (!isHostnameValid(hostName)) 791 { 792 messages::propertyValueFormatError(asyncResp->res, hostName, 793 "HostName"); 794 return; 795 } 796 797 asyncResp->res.jsonValue["HostName"] = hostName; 798 crow::connections::systemBus->async_method_call( 799 [asyncResp](const boost::system::error_code ec) { 800 if (ec) 801 { 802 messages::internalError(asyncResp->res); 803 } 804 }, 805 "xyz.openbmc_project.Settings", 806 "/xyz/openbmc_project/network/hypervisor", 807 "org.freedesktop.DBus.Properties", "Set", 808 "xyz.openbmc_project.Network.SystemConfiguration", "HostName", 809 std::variant<std::string>(hostName)); 810 } 811 812 void setIPv4InterfaceEnabled(const std::string& ifaceId, 813 const bool& isActive, 814 const std::shared_ptr<AsyncResp>& asyncResp) 815 { 816 crow::connections::systemBus->async_method_call( 817 [asyncResp](const boost::system::error_code ec) { 818 if (ec) 819 { 820 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 821 messages::internalError(asyncResp->res); 822 return; 823 } 824 }, 825 "xyz.openbmc_project.Settings", 826 "/xyz/openbmc_project/network/hypervisor/" + ifaceId + 827 "/ipv4/addr0", 828 "org.freedesktop.DBus.Properties", "Set", 829 "xyz.openbmc_project.Object.Enable", "Enabled", 830 std::variant<bool>(isActive)); 831 } 832 833 void setDHCPEnabled(const std::string& ifaceId, const bool& ipv4DHCPEnabled, 834 const std::shared_ptr<AsyncResp>& asyncResp) 835 { 836 const std::string dhcp = 837 getDhcpEnabledEnumeration(ipv4DHCPEnabled, false); 838 crow::connections::systemBus->async_method_call( 839 [asyncResp](const boost::system::error_code ec) { 840 if (ec) 841 { 842 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 843 messages::internalError(asyncResp->res); 844 return; 845 } 846 }, 847 "xyz.openbmc_project.Settings", 848 "/xyz/openbmc_project/network/hypervisor/" + ifaceId, 849 "org.freedesktop.DBus.Properties", "Set", 850 "xyz.openbmc_project.Network.EthernetInterface", "DHCPEnabled", 851 std::variant<std::string>{dhcp}); 852 853 // Set the IPv4 address origin to the DHCP / Static as per the new value 854 // of the DHCPEnabled property 855 std::string origin; 856 if (ipv4DHCPEnabled == false) 857 { 858 origin = "xyz.openbmc_project.Network.IP.AddressOrigin.Static"; 859 } 860 else 861 { 862 // DHCPEnabled is set to true. Delete the current IPv4 settings 863 // to receive the new values from DHCP server. 864 deleteHypervisorIPv4(ifaceId, asyncResp); 865 origin = "xyz.openbmc_project.Network.IP.AddressOrigin.DHCP"; 866 } 867 crow::connections::systemBus->async_method_call( 868 [asyncResp](const boost::system::error_code ec) { 869 if (ec) 870 { 871 BMCWEB_LOG_ERROR << "DBUS response error " << ec; 872 messages::internalError(asyncResp->res); 873 return; 874 } 875 BMCWEB_LOG_DEBUG << "Hypervisor IPaddress Origin is Set"; 876 }, 877 "xyz.openbmc_project.Settings", 878 "/xyz/openbmc_project/network/hypervisor/" + ifaceId + 879 "/ipv4/addr0", 880 "org.freedesktop.DBus.Properties", "Set", 881 "xyz.openbmc_project.Network.IP", "Origin", 882 std::variant<std::string>(origin)); 883 } 884 885 /** 886 * Functions triggers appropriate requests on DBus 887 */ 888 void doGet(crow::Response& res, const crow::Request&, 889 const std::vector<std::string>& params) override 890 { 891 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 892 if (params.size() != 1) 893 { 894 messages::internalError(asyncResp->res); 895 return; 896 } 897 898 getHypervisorIfaceData( 899 params[0], 900 [this, asyncResp, ifaceId{std::string(params[0])}]( 901 const bool& success, const EthernetInterfaceData& ethData, 902 const boost::container::flat_set<IPv4AddressData>& ipv4Data) { 903 if (!success) 904 { 905 messages::resourceNotFound(asyncResp->res, 906 "EthernetInterface", ifaceId); 907 return; 908 } 909 asyncResp->res.jsonValue["@odata.type"] = 910 "#EthernetInterface.v1_5_1.EthernetInterface"; 911 asyncResp->res.jsonValue["Name"] = 912 "Hypervisor Ethernet Interface"; 913 asyncResp->res.jsonValue["Description"] = 914 "Hypervisor's Virtual Management Ethernet Interface"; 915 parseInterfaceData(asyncResp->res.jsonValue, ifaceId, ethData, 916 ipv4Data); 917 }); 918 } 919 920 void doPatch(crow::Response& res, const crow::Request& req, 921 const std::vector<std::string>& params) override 922 { 923 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 924 if (params.size() != 1) 925 { 926 messages::internalError(asyncResp->res); 927 return; 928 } 929 930 const std::string& ifaceId = params[0]; 931 std::optional<std::string> hostName; 932 std::optional<std::vector<nlohmann::json>> ipv4StaticAddresses; 933 std::optional<nlohmann::json> ipv4Addresses; 934 std::optional<nlohmann::json> dhcpv4; 935 std::optional<bool> ipv4DHCPEnabled; 936 937 if (!json_util::readJson(req, res, "HostName", hostName, 938 "IPv4StaticAddresses", ipv4StaticAddresses, 939 "IPv4Addresses", ipv4Addresses, "DHCPv4", 940 dhcpv4)) 941 { 942 return; 943 } 944 945 if (ipv4Addresses) 946 { 947 messages::propertyNotWritable(asyncResp->res, "IPv4Addresses"); 948 } 949 950 if (dhcpv4) 951 { 952 if (!json_util::readJson(*dhcpv4, res, "DHCPEnabled", 953 ipv4DHCPEnabled)) 954 { 955 return; 956 } 957 } 958 959 getHypervisorIfaceData( 960 ifaceId, 961 [this, asyncResp, ifaceId, hostName = std::move(hostName), 962 ipv4StaticAddresses = std::move(ipv4StaticAddresses), 963 ipv4DHCPEnabled, dhcpv4 = std::move(dhcpv4)]( 964 const bool& success, const EthernetInterfaceData& ethData, 965 const boost::container::flat_set<IPv4AddressData>&) { 966 if (!success) 967 { 968 messages::resourceNotFound(asyncResp->res, 969 "EthernetInterface", ifaceId); 970 return; 971 } 972 973 if (ipv4StaticAddresses) 974 { 975 const nlohmann::json& ipv4Static = *ipv4StaticAddresses; 976 if (ipv4Static.begin() == ipv4Static.end()) 977 { 978 messages::propertyValueTypeError( 979 asyncResp->res, 980 ipv4Static.dump( 981 2, ' ', true, 982 nlohmann::json::error_handler_t::replace), 983 "IPv4StaticAddresses"); 984 return; 985 } 986 987 // One and only one hypervisor instance supported 988 if (ipv4Static.size() != 1) 989 { 990 messages::propertyValueFormatError( 991 asyncResp->res, 992 ipv4Static.dump( 993 2, ' ', true, 994 nlohmann::json::error_handler_t::replace), 995 "IPv4StaticAddresses"); 996 return; 997 } 998 999 const nlohmann::json& ipv4Json = ipv4Static[0]; 1000 // Check if the param is 'null'. If its null, it means that 1001 // user wants to delete the IP address. Deleting the IP 1002 // address is allowed only if its statically configured. 1003 // Deleting the address originated from DHCP is not allowed. 1004 if ((ipv4Json.is_null()) && 1005 (translateDHCPEnabledToBool(ethData.DHCPEnabled, true))) 1006 { 1007 BMCWEB_LOG_INFO 1008 << "Ignoring the delete on ipv4StaticAddresses " 1009 "as the interface is DHCP enabled"; 1010 } 1011 else 1012 { 1013 handleHypervisorIPv4StaticPatch(ifaceId, ipv4Static, 1014 asyncResp); 1015 } 1016 } 1017 1018 if (hostName) 1019 { 1020 handleHostnamePatch(*hostName, asyncResp); 1021 } 1022 1023 if (dhcpv4) 1024 { 1025 setDHCPEnabled(ifaceId, *ipv4DHCPEnabled, asyncResp); 1026 } 1027 1028 // Set this interface to disabled/inactive. This will be set to 1029 // enabled/active by the pldm once the hypervisor consumes the 1030 // updated settings from the user. 1031 setIPv4InterfaceEnabled(ifaceId, false, asyncResp); 1032 }); 1033 res.result(boost::beast::http::status::accepted); 1034 } 1035 }; 1036 1037 /** 1038 * HypervisorResetActionInfo derived class for delivering Computer Systems 1039 * ResetType AllowableValues using ResetInfo schema. 1040 */ 1041 class HypervisorResetActionInfo : public Node 1042 { 1043 public: 1044 /* 1045 * Default Constructor 1046 */ 1047 HypervisorResetActionInfo(App& app) : 1048 Node(app, "/redfish/v1/Systems/hypervisor/ResetActionInfo/") 1049 { 1050 entityPrivileges = { 1051 {boost::beast::http::verb::get, {{"Login"}}}, 1052 {boost::beast::http::verb::head, {{"Login"}}}, 1053 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1054 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1055 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1056 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1057 } 1058 1059 private: 1060 /** 1061 * Functions triggers appropriate requests on DBus 1062 */ 1063 void doGet(crow::Response& res, const crow::Request&, 1064 const std::vector<std::string>&) override 1065 { 1066 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1067 1068 // Only return action info if hypervisor D-Bus object present 1069 crow::connections::systemBus->async_method_call( 1070 [asyncResp](const boost::system::error_code ec, 1071 const std::vector<std::pair< 1072 std::string, std::vector<std::string>>>& objInfo) { 1073 if (ec) 1074 { 1075 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 1076 1077 // No hypervisor objects found by mapper 1078 if (ec.value() == boost::system::errc::io_error) 1079 { 1080 messages::resourceNotFound(asyncResp->res, "hypervisor", 1081 "ResetActionInfo"); 1082 return; 1083 } 1084 1085 messages::internalError(asyncResp->res); 1086 return; 1087 } 1088 1089 // One and only one hypervisor instance supported 1090 if (objInfo.size() != 1) 1091 { 1092 messages::internalError(asyncResp->res); 1093 return; 1094 } 1095 1096 // The hypervisor object only support the ability to turn On 1097 // The system object Action should be utilized for other 1098 // operations 1099 asyncResp->res.jsonValue = { 1100 {"@odata.type", "#ActionInfo.v1_1_2.ActionInfo"}, 1101 {"@odata.id", 1102 "/redfish/v1/Systems/hypervisor/ResetActionInfo"}, 1103 {"Name", "Reset Action Info"}, 1104 {"Id", "ResetActionInfo"}, 1105 {"Parameters", 1106 {{{"Name", "ResetType"}, 1107 {"Required", true}, 1108 {"DataType", "String"}, 1109 {"AllowableValues", {"On"}}}}}}; 1110 }, 1111 "xyz.openbmc_project.ObjectMapper", 1112 "/xyz/openbmc_project/object_mapper", 1113 "xyz.openbmc_project.ObjectMapper", "GetObject", 1114 "/xyz/openbmc_project/state/hypervisor0", 1115 std::array<const char*, 1>{"xyz.openbmc_project.State.Host"}); 1116 } 1117 }; 1118 1119 /** 1120 * HypervisorActionsReset class supports the POST method for Reset action. 1121 * The class sends data directly to D-Bus. 1122 */ 1123 class HypervisorActionsReset : public Node 1124 { 1125 public: 1126 HypervisorActionsReset(App& app) : 1127 Node(app, 1128 "/redfish/v1/Systems/hypervisor/Actions/ComputerSystem.Reset/") 1129 { 1130 entityPrivileges = { 1131 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1132 } 1133 1134 private: 1135 /** 1136 * Function handles POST method request. 1137 * Analyzes POST body message before sends Reset request data to D-Bus. 1138 */ 1139 void doPost(crow::Response& res, const crow::Request& req, 1140 const std::vector<std::string>&) override 1141 { 1142 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1143 1144 std::optional<std::string> resetType; 1145 if (!json_util::readJson(req, res, "ResetType", resetType)) 1146 { 1147 // readJson adds appropriate error to response 1148 return; 1149 } 1150 1151 if (!resetType) 1152 { 1153 messages::actionParameterMissing( 1154 asyncResp->res, "ComputerSystem.Reset", "ResetType"); 1155 return; 1156 } 1157 1158 // Hypervisor object only support On operation 1159 if (resetType != "On") 1160 { 1161 messages::propertyValueNotInList(asyncResp->res, *resetType, 1162 "ResetType"); 1163 return; 1164 } 1165 1166 std::string command = "xyz.openbmc_project.State.Host.Transition.On"; 1167 1168 crow::connections::systemBus->async_method_call( 1169 [asyncResp, resetType](const boost::system::error_code ec) { 1170 if (ec) 1171 { 1172 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 1173 if (ec.value() == boost::asio::error::invalid_argument) 1174 { 1175 messages::actionParameterNotSupported( 1176 asyncResp->res, *resetType, "Reset"); 1177 return; 1178 } 1179 1180 if (ec.value() == boost::asio::error::host_unreachable) 1181 { 1182 messages::resourceNotFound(asyncResp->res, "Actions", 1183 "Reset"); 1184 return; 1185 } 1186 1187 messages::internalError(asyncResp->res); 1188 return; 1189 } 1190 messages::success(asyncResp->res); 1191 }, 1192 "xyz.openbmc_project.State.Hypervisor", 1193 "/xyz/openbmc_project/state/hypervisor0", 1194 "org.freedesktop.DBus.Properties", "Set", 1195 "xyz.openbmc_project.State.Host", "RequestedHostTransition", 1196 std::variant<std::string>{std::move(command)}); 1197 } 1198 }; 1199 } // namespace redfish 1200