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