From f74f0f7df1ac4908f236bd319e9a545cadd02d29 Mon Sep 17 00:00:00 2001 From: "Houqi (Nick) Zuo" Date: Thu, 29 Aug 2024 16:15:16 +0800 Subject: [PATCH] add network system of multi-host migration add support for network subsystem. Signed-off-by: Houqi (Nick) Zuo --- .../managers/resource_backings/__init__.py | 8 ++ .../resource_backings/network/__init__.py | 1 + .../resource_backings/network/tap/__init__.py | 2 + .../network/tap/tap_backing.py | 78 +++++++++++++ .../network/tap/tap_network_connection.py | 47 ++++++++ virttest/vt_netmgr/__init__.py | 1 + virttest/vt_netmgr/vt_netmgr.py | 50 ++++++++ virttest/vt_resmgr/resources/__init__.py | 2 + .../vt_resmgr/resources/network/__init__.py | 6 + .../resources/network/port_resource.py | 66 +++++++++++ .../resources/network/tap/__init__.py | 2 + .../resources/network/tap/tap_network.py | 62 ++++++++++ .../resources/network/tap/tap_port.py | 108 ++++++++++++++++++ 13 files changed, 433 insertions(+) create mode 100644 virttest/vt_agent/managers/resource_backings/network/__init__.py create mode 100644 virttest/vt_agent/managers/resource_backings/network/tap/__init__.py create mode 100644 virttest/vt_agent/managers/resource_backings/network/tap/tap_backing.py create mode 100644 virttest/vt_agent/managers/resource_backings/network/tap/tap_network_connection.py create mode 100644 virttest/vt_netmgr/__init__.py create mode 100644 virttest/vt_netmgr/vt_netmgr.py create mode 100644 virttest/vt_resmgr/resources/network/__init__.py create mode 100644 virttest/vt_resmgr/resources/network/port_resource.py create mode 100644 virttest/vt_resmgr/resources/network/tap/__init__.py create mode 100644 virttest/vt_resmgr/resources/network/tap/tap_network.py create mode 100644 virttest/vt_resmgr/resources/network/tap/tap_port.py diff --git a/virttest/vt_agent/managers/resource_backings/__init__.py b/virttest/vt_agent/managers/resource_backings/__init__.py index 888e457049..545d437cc5 100644 --- a/virttest/vt_agent/managers/resource_backings/__init__.py +++ b/virttest/vt_agent/managers/resource_backings/__init__.py @@ -4,10 +4,15 @@ _NfsPoolConnection, _NfsVolumeBacking, ) +from .network import ( + _TapPortBacking, + _TapNetworkConnection, +) _pool_conn_classes = dict() _pool_conn_classes[_DirPoolConnection.get_pool_type()] = _DirPoolConnection _pool_conn_classes[_NfsPoolConnection.get_pool_type()] = _NfsPoolConnection +_pool_conn_classes[_TapNetworkConnection.get_pool_type()] = _TapNetworkConnection _backing_classes = dict() _backing_classes[_DirVolumeBacking.get_pool_type()] = { @@ -16,6 +21,9 @@ _backing_classes[_NfsVolumeBacking.get_pool_type()] = { _NfsVolumeBacking.get_resource_type(): _NfsVolumeBacking, } +_backing_classes[_TapPortBacking.get_pool_type()] = { + _TapPortBacking.get_resource_type(): _TapPortBacking, +} def get_resource_backing_class(pool_type, resource_type): diff --git a/virttest/vt_agent/managers/resource_backings/network/__init__.py b/virttest/vt_agent/managers/resource_backings/network/__init__.py new file mode 100644 index 0000000000..b9141d80f8 --- /dev/null +++ b/virttest/vt_agent/managers/resource_backings/network/__init__.py @@ -0,0 +1 @@ +from .tap import _TapPortBacking, _TapNetworkConnection diff --git a/virttest/vt_agent/managers/resource_backings/network/tap/__init__.py b/virttest/vt_agent/managers/resource_backings/network/tap/__init__.py new file mode 100644 index 0000000000..d2d2d2f283 --- /dev/null +++ b/virttest/vt_agent/managers/resource_backings/network/tap/__init__.py @@ -0,0 +1,2 @@ +from .tap_backing import _TapPortBacking +from .tap_network_connection import _TapNetworkConnection diff --git a/virttest/vt_agent/managers/resource_backings/network/tap/tap_backing.py b/virttest/vt_agent/managers/resource_backings/network/tap/tap_backing.py new file mode 100644 index 0000000000..cab691be20 --- /dev/null +++ b/virttest/vt_agent/managers/resource_backings/network/tap/tap_backing.py @@ -0,0 +1,78 @@ +import logging + +from virttest import utils_misc +from virttest.vt_utils.net import interface, tap +from virttest.vt_utils.net.drivers import bridge + +from ...backing import _ResourceBacking + +LOG = logging.getLogger("avocado.service.resource_backings.network.tap" + __name__) + + +class _TapPortBacking(_ResourceBacking): + _SOURCE_POOL_TYPE = "linux_bridge" + _BINDING_RESOURCE_TYPE = "port" + + def __init__(self, backing_config): + super().__init__(backing_config) + self.switch = None + self.tap_fd = None + self.tap_ifname = None + + def create(self, network_connection): + if not self.switch: + self.switch = network_connection.switch + + def destroy(self, network_connection): + super().destroy(network_connection) + self.switch = None + + def allocate_resource(self, network_connection, arguments=None): + """ + Create a tap device and put this device in to network_connection. + + :params network_connection: the _TapNetworkConnection object. + :type network_connection: class _TapNetworkConnection. + :params arguments: the device's params + :type arguments: dict. + + :return: the resource info. + :rtype: dict. + """ + if not self.tap_ifname: + self.tap_ifname = "tap_" + utils_misc.generate_random_string(8) + self.tap_fd = tap.open_tap("/dev/net/tun", self.tap_ifname, vnet_hdr=True) + self.tap_fd = self.tap_fd.split(":") + interface.bring_up_ifname(self.tap_ifname) + bridge.add_to_bridge(self.tap_ifname, self.switch) + + return self.get_resource_info(network_connection) + + def release_resource(self, network_connection, arguments=None): + bridge.del_from_bridge(self.tap_ifname) + interface.bring_down_ifname(self.tap_ifname) + self.tap_fd = None + self.tap_ifname = None + + return self.get_resource_info(network_connection) + + def get_resource_info(self, network_connection=None, arguments=None): + if self.switch and self.tap_fd and self.tap_ifname: + allocated = ( + True + if self.switch in (bridge.find_bridge_name(self.tap_ifname),) + else False + ) + else: + allocated = False + + return { + "meta": { + "allocated": allocated, + }, + "spec": { + "switch": self.switch, + "fds": self.tap_fd, + "ifname": self.tap_ifname, + }, + } diff --git a/virttest/vt_agent/managers/resource_backings/network/tap/tap_network_connection.py b/virttest/vt_agent/managers/resource_backings/network/tap/tap_network_connection.py new file mode 100644 index 0000000000..2c9d96eea3 --- /dev/null +++ b/virttest/vt_agent/managers/resource_backings/network/tap/tap_network_connection.py @@ -0,0 +1,47 @@ +import logging + +from virttest.vt_utils.net import interface +from virttest.vt_utils.net.drivers import bridge + +from ...pool_connection import _ResourcePoolConnection + +LOG = logging.getLogger("avocado.service.resource_backings.network.tap." + __name__) + + +class _TapNetworkConnection(_ResourcePoolConnection): + _CONNECT_POOL_TYPE = "linux_bridge" + + def __init__(self, pool_config): + super().__init__(pool_config) + self._switch = pool_config["spec"]["switch"] + self._export = pool_config["spec"]["export"] + + def open(self): + # TODO + pass + + def close(self): + # TODO + pass + + @property + def connected(self): + if_info = interface.net_get_iface_info(self._switch) + if ( + if_info[0] + and if_info[0]["operstate"] in ("UP",) + and ( + bridge.find_bridge_name(self._export) in (self._switch,) + or self._export is "" + ) + ): + return True + return False + + @property + def switch(self): + return self._switch + + @property + def export(self): + return self._export diff --git a/virttest/vt_netmgr/__init__.py b/virttest/vt_netmgr/__init__.py new file mode 100644 index 0000000000..058862d824 --- /dev/null +++ b/virttest/vt_netmgr/__init__.py @@ -0,0 +1 @@ +from .vt_netmgr import vt_netmgr diff --git a/virttest/vt_netmgr/vt_netmgr.py b/virttest/vt_netmgr/vt_netmgr.py new file mode 100644 index 0000000000..c9b1cc9d97 --- /dev/null +++ b/virttest/vt_netmgr/vt_netmgr.py @@ -0,0 +1,50 @@ +import logging + +from virttest.vt_resmgr import resmgr + +LOG = logging.getLogger("avocado." + __name__) + + +class _VTNetworkManager(object): + def __init__(self): + self._res_config = dict() + self._resource_id = dict() + + def define_and_create_network(self, network_name, params, net_type="port"): + LOG.debug(f"Defining the network configuration for {network_name}") + self._res_config[network_name] = resmgr.define_resource_config( + network_name, net_type, params + ) + LOG.debug(f"Done to define the network configuration for {network_name}") + + LOG.debug(f"Create the network object for {network_name}") + self._resource_id[network_name] = resmgr.create_resource_object( + self._res_config[network_name] + ) + LOG.debug(f"Done to create the network object for {network_name}") + + return self._resource_id[network_name] + + def operation(self, resource_id, operation_params): + resmgr.update_resource(resource_id, operation_params) + + def query_resource_id(self, network_name): + """ + Return all network names. + + :return: All network names. + :rtype: list + """ + return self._resource_id[network_name] + + def query_all_network_names(self): + """ + Return all network names. + + :return: All network names. + :rtype: list + """ + return list(self._resource_id.keys()) + + +vt_netmgr = _VTNetworkManager() diff --git a/virttest/vt_resmgr/resources/__init__.py b/virttest/vt_resmgr/resources/__init__.py index 0308f8dc5d..199c891373 100644 --- a/virttest/vt_resmgr/resources/__init__.py +++ b/virttest/vt_resmgr/resources/__init__.py @@ -2,6 +2,7 @@ # from .cvm import _TdxPool # from .storage import _CephPool from .storage import _DirPool, _NfsPool +from .network import _LinuxBridgeNetwork _pool_classes = dict() # _pool_classes[_SnpPool.get_pool_type()] = _SnpPool @@ -9,6 +10,7 @@ # _pool_classes[_CephPool.get_pool_type()] = _CephPool _pool_classes[_DirPool.get_pool_type()] = _DirPool _pool_classes[_NfsPool.get_pool_type()] = _NfsPool +_pool_classes[_LinuxBridgeNetwork.get_pool_type()] = _LinuxBridgeNetwork def get_resource_pool_class(pool_type): diff --git a/virttest/vt_resmgr/resources/network/__init__.py b/virttest/vt_resmgr/resources/network/__init__.py new file mode 100644 index 0000000000..0d1d19a7ca --- /dev/null +++ b/virttest/vt_resmgr/resources/network/__init__.py @@ -0,0 +1,6 @@ +from .tap import _LinuxBridgeNetwork + + +__all__ = ( + _LinuxBridgeNetwork, +) diff --git a/virttest/vt_resmgr/resources/network/port_resource.py b/virttest/vt_resmgr/resources/network/port_resource.py new file mode 100644 index 0000000000..dbfe549523 --- /dev/null +++ b/virttest/vt_resmgr/resources/network/port_resource.py @@ -0,0 +1,66 @@ +import uuid +from abc import abstractmethod + +from ..resource import _Resource + + +class _PortResource(_Resource): + """ + This class, inherited from _Resource, defines the port resource model. + """ + + _RESOURCE_TYPE = "port" + + def __init__(self, resource_config): + super().__init__(resource_config) + self._handlers = { + "bind": self.bind, + "unbind": self.unbind, + "allocate": self.allocate, + "release": self.release, + "sync": self.sync, + } + + def bind(self, arguments): + """ + Bind the port resource to one worker node. + """ + raise NotImplemented + + def unbind(self, arguments): + """ + Unbind the port resource from the worker node. + """ + raise NotImplemented + + def allocate(self, arguments): + raise NotImplemented + + def release(self, arguments): + raise NotImplemented + + def sync(self, arguments): + raise NotImplemented + + def create_object(self): + pass + + def destroy_object(self): + pass + + def define_config_from_self(self, pool_id): + pass + + @classmethod + def _define_config_legacy(cls, resource_name, resource_params): + return { + "meta": { + "name": resource_name, + "uuid": None, + "type": cls._RESOURCE_TYPE, + "pool": None, + "allocated": False, + "bindings": dict(), + }, + "spec": {}, + } diff --git a/virttest/vt_resmgr/resources/network/tap/__init__.py b/virttest/vt_resmgr/resources/network/tap/__init__.py new file mode 100644 index 0000000000..73225414a5 --- /dev/null +++ b/virttest/vt_resmgr/resources/network/tap/__init__.py @@ -0,0 +1,2 @@ +from .tap_network import _LinuxBridgeNetwork +from .tap_port import _TapPort diff --git a/virttest/vt_resmgr/resources/network/tap/tap_network.py b/virttest/vt_resmgr/resources/network/tap/tap_network.py new file mode 100644 index 0000000000..2a04041626 --- /dev/null +++ b/virttest/vt_resmgr/resources/network/tap/tap_network.py @@ -0,0 +1,62 @@ +import logging + +from virttest.vt_cluster import cluster + +from ...pool import _ResourcePool +from .tap_port import get_port_resource_class + +LOG = logging.getLogger("avocado." + __name__) + + +class _LinuxBridgeNetwork(_ResourcePool): + _POOL_TYPE = "linux_bridge" + + @classmethod + def define_config(cls, pool_name, pool_params): + config = super().define_config(pool_name, pool_params) + config["spec"].update( + { + "switch": pool_params["switch"], + "export": pool_params.get("export"), + } + ) + return config + + def customize_pool_config(self, node_name): + config = self.pool_config + config["spec"]["switch"] = config["spec"]["switch"][node_name]["ifname"] + config["spec"]["export"] = config["spec"]["export"][node_name]["ifname"] + return config + + @classmethod + def get_resource_class(cls, resource_type): + return get_port_resource_class(resource_type) + + def meet_resource_request(self, resource_type, resource_params): + if resource_type not in ("port",) or resource_params.get("nettype") not in ( + "bridge", + ): + return False + + if not self._check_nodes_access(resource_params): + return False + + return True + + def _check_nodes_access(self, resource_params): + # Note if you want the image is created from a specific pool or + # the image is handled on a specific worker node, you should + # specify its image_pool_name + vm_node_tag = resource_params.get("vm_node") + if vm_node_tag: + # Check if the pool can be accessed by the vm node + vm_node = cluster.get_node_by_tag(vm_node_tag) + if vm_node.name not in self.attaching_nodes: + return False + else: + # Check if the pool can be accessed by one of the partition nodes + node_names = [node.name for node in cluster.partition.nodes] + if not set(self.attaching_nodes).intersection(set(node_names)): + return False + + return True diff --git a/virttest/vt_resmgr/resources/network/tap/tap_port.py b/virttest/vt_resmgr/resources/network/tap/tap_port.py new file mode 100644 index 0000000000..def6b06627 --- /dev/null +++ b/virttest/vt_resmgr/resources/network/tap/tap_port.py @@ -0,0 +1,108 @@ +import logging + +from virttest.vt_cluster import cluster + +from ..port_resource import _PortResource + +LOG = logging.getLogger("avocado." + __name__) + + +class _TapPort(_PortResource): + """ + The tap port. + """ + + def bind(self, arguments): + """ + Bind the resource to a backing on a worker node. + """ + nodes = arguments.pop("nodes", list(self.resource_bindings.keys())) + for node_name in nodes: + if not self.resource_bindings.get(node_name): + LOG.info( + f"Bind the tap port {self.resource_id} to node {node_name}") + node = cluster.get_node(node_name) + r, o = node.proxy.resource.create_backing_object( + self.resource_config) + if r != 0: + raise Exception(o["out"]) + self.resource_bindings[node_name] = o["out"] + else: + LOG.info( + f"The tap port {self.resource_id} has already bound to {node_name}") + + def unbind(self, arguments): + """ + Unbind the tap port from a worker node + """ + nodes = arguments.pop("nodes", list(self.resource_bindings.keys())) + for node_name in nodes: + backing_id = self.resource_bindings.get(node_name) + if backing_id: + LOG.info( + f"Unbind the tap port {self.resource_id} from node {node_name}") + node = cluster.get_node(node_name) + r, o = node.proxy.resource.destroy_backing_object(backing_id) + if r != 0: + raise Exception(o["out"]) + self.resource_bindings[node_name] = None + else: + LOG.info( + f"The tap port {self.resource_id} has already unbound from {node_name}") + + def sync(self, arguments): + LOG.debug( + f"Sync up the configuration of the tap port {self.resource_id}") + node_name, backing_id = list(self.resource_bindings.items())[0] + node = cluster.get_node(node_name) + r, o = node.proxy.resource.update_resource_by_backing( + backing_id, {"sync": arguments} + ) + if r != 0: + raise Exception(o["out"]) + + config = o["out"] + self.resource_meta["allocated"] = config["meta"]["allocated"] + self.resource_spec["switch"] = config["spec"]["switch"] + self.resource_spec["fds"] = config["spec"]["fds"] + self.resource_spec["ifname"] = config["spec"]["ifname"] + + def allocate(self, arguments): + node_name, backing_id = list(self.resource_bindings.items())[0] + node = cluster.get_node(node_name) + LOG.debug( + f"Allocate the tap port {self.resource_id} from {node_name}.") + node = cluster.get_node(node_name) + r, o = node.proxy.resource.update_resource_by_backing( + backing_id, {"allocate": arguments} + ) + if r != 0: + raise Exception(o["out"]) + + config = o["out"] + self.resource_meta["allocated"] = config["meta"]["allocated"] + self.resource_spec["switch"] = config["spec"]["switch"] + self.resource_spec["fds"] = config["spec"]["fds"] + self.resource_spec["ifname"] = config["spec"]["ifname"] + + def release(self, arguments): + node_name, backing_id = list(self.resource_bindings.items())[0] + node = cluster.get_node(node_name) + LOG.debug(f"Release the tap port {self.resource_id} from {node_name}") + r, o = node.proxy.resource.update_resource_by_backing( + backing_id, {"release": arguments} + ) + if r != 0: + raise Exception(o["out"]) + self.resource_meta["allocated"] = False + self.resource_spec["switch"] = None + self.resource_spec["fds"] = None + self.resource_spec["ifname"] = None + + +def get_port_resource_class(resource_type): + mapping = { + "port": _TapPort, + } + + return mapping.get(resource_type)