CSIT-1325 - FIX: AVF tests failing on unbind
[csit.git] / resources / libraries / python / InterfaceUtil.py
index c0a8957..7fc7f85 100644 (file)
@@ -19,12 +19,14 @@ from robot.api import logger
 
 from resources.libraries.python.ssh import SSH
 from resources.libraries.python.IPUtil import convert_ipv4_netmask_prefix
+from resources.libraries.python.DUTSetup import DUTSetup
 from resources.libraries.python.ssh import exec_cmd_no_error
 from resources.libraries.python.topology import NodeType, Topology
 from resources.libraries.python.VatExecutor import VatExecutor, VatTerminal
 from resources.libraries.python.VatJsonUtil import VatJsonUtil
+from resources.libraries.python.VPPUtil import VPPUtil
 from resources.libraries.python.parsers.JsonParser import JsonParser
-
+from resources.libraries.python.CpuUtils import CpuUtils
 
 class InterfaceUtil(object):
     """General utilities for managing interfaces"""
@@ -67,9 +69,9 @@ class InterfaceUtil(object):
 
         if node['type'] == NodeType.DUT:
             if state == 'up':
-                state = 'admin-up'
+                state = 'admin-up link-up'
             elif state == 'down':
-                state = 'admin-down'
+                state = 'admin-down link-down'
             else:
                 raise ValueError('Unexpected interface state: {}'.format(state))
             VatExecutor.cmd_from_template(node, 'set_if_state.vat',
@@ -122,7 +124,54 @@ class InterfaceUtil(object):
             InterfaceUtil.set_interface_ethernet_mtu(node, ifc, 1500)
 
     @staticmethod
-    def vpp_node_interfaces_ready_wait(node, timeout=10):
+    def vpp_set_interface_mtu(node, interface, mtu=9200):
+        """Set Ethernet MTU on interface.
+
+        :param node: VPP node.
+        :param interface: Interface to setup MTU. Default: 9200.
+        :param mtu: Ethernet MTU size in Bytes.
+        :type node: dict
+        :type interface: str or int
+        :type mtu: int
+        """
+        if isinstance(interface, basestring):
+            sw_if_index = Topology.get_interface_sw_index(node, interface)
+        else:
+            sw_if_index = interface
+
+        if sw_if_index:
+            with VatTerminal(node, json_param=False) as vat:
+                vat.vat_terminal_exec_cmd_from_template(
+                    "hw_interface_set_mtu.vat", sw_if_index=sw_if_index,
+                    mtu=mtu)
+
+    @staticmethod
+    def vpp_set_interfaces_mtu_on_node(node, mtu=9200):
+        """Set Ethernet MTU on all interfaces.
+
+        :param node: VPP node.
+        :param mtu: Ethernet MTU size in Bytes. Default: 9200.
+        :type node: dict
+        :type mtu: int
+        """
+        for interface in node['interfaces']:
+            InterfaceUtil.vpp_set_interface_mtu(node, interface, mtu)
+
+    @staticmethod
+    def vpp_set_interfaces_mtu_on_all_duts(nodes, mtu=9200):
+        """Set Ethernet MTU on all interfaces on all DUTs.
+
+        :param nodes: VPP nodes.
+        :param mtu: Ethernet MTU size in Bytes. Default: 9200.
+        :type nodes: dict
+        :type mtu: int
+        """
+        for node in nodes.values():
+            if node['type'] == NodeType.DUT:
+                InterfaceUtil.vpp_set_interfaces_mtu_on_node(node, mtu)
+
+    @staticmethod
+    def vpp_node_interfaces_ready_wait(node, timeout=30):
         """Wait until all interfaces with admin-up are in link-up state.
 
         :param node: Node to wait on.
@@ -157,7 +206,7 @@ class InterfaceUtil(object):
                 sleep(1)
 
     @staticmethod
-    def vpp_nodes_interfaces_ready_wait(nodes, timeout=10):
+    def vpp_nodes_interfaces_ready_wait(nodes, timeout=30):
         """Wait until all interfaces with admin-up are in link-up state for
         listed nodes.
 
@@ -171,7 +220,7 @@ class InterfaceUtil(object):
             InterfaceUtil.vpp_node_interfaces_ready_wait(node, timeout)
 
     @staticmethod
-    def all_vpp_interfaces_ready_wait(nodes, timeout=10):
+    def all_vpp_interfaces_ready_wait(nodes, timeout=30):
         """Wait until all interfaces with admin-up are in link-up state for all
         nodes in the topology.
 
@@ -316,7 +365,6 @@ class InterfaceUtil(object):
         :type node: dict
         :type pci_addr: str
         :type driver: str
-        :returns: None.
         :raises RuntimeError: If unbinding from the current driver fails.
         :raises RuntimeError: If binding to the new driver fails.
         """
@@ -354,52 +402,9 @@ class InterfaceUtil(object):
         :type pci_addr: str
         :returns: Interface driver or None if not found.
         :rtype: str
-        :raises RuntimeError: If it is not possible to get the interface driver
-            information from the node.
-
-        .. note::
-            # lspci -vmmks 0000:00:05.0
-            Slot:   00:05.0
-            Class:  Ethernet controller
-            Vendor: Red Hat, Inc
-            Device: Virtio network device
-            SVendor:        Red Hat, Inc
-            SDevice:        Device 0001
-            PhySlot:        5
-            Driver: virtio-pci
+        :raises RuntimeError: If PCI rescan or lspci command execution failed.
         """
-        ssh = SSH()
-        ssh.connect(node)
-
-        for i in range(3):
-            logger.trace('Try {}: Get interface driver'.format(i))
-            cmd = 'sh -c "echo 1 > /sys/bus/pci/rescan"'
-            (ret_code, _, _) = ssh.exec_command_sudo(cmd)
-            if int(ret_code) != 0:
-                raise RuntimeError("'{0}' failed on '{1}'"
-                                   .format(cmd, node['host']))
-
-            cmd = 'lspci -vmmks {0}'.format(pci_addr)
-            (ret_code, stdout, _) = ssh.exec_command(cmd)
-            if int(ret_code) != 0:
-                raise RuntimeError("'{0}' failed on '{1}'"
-                                   .format(cmd, node['host']))
-
-            for line in stdout.splitlines():
-                if len(line) == 0:
-                    continue
-                try:
-                    (name, value) = line.split("\t", 1)
-                except ValueError:
-                    if name != "Driver:":
-                        pass
-                    else:
-                        return None
-                if name == 'Driver:':
-                    return value if value else None
-        else:
-            raise RuntimeError('Get interface driver for: {0}'
-                               .format(pci_addr))
+        return DUTSetup.get_pci_dev_driver(node, pci_addr)
 
     @staticmethod
     def tg_set_interfaces_udev_rules(node):
@@ -577,7 +582,10 @@ class InterfaceUtil(object):
                     try:
                         numa_node = int(out)
                         if numa_node < 0:
-                            raise ValueError
+                            if CpuUtils.cpu_node_count(node) == 1:
+                                numa_node = 0
+                            else:
+                                raise ValueError
                     except ValueError:
                         logger.trace('Reading numa location failed for: {0}'\
                             .format(if_pci))
@@ -779,11 +787,10 @@ class InterfaceUtil(object):
                 "tap_dump.vat")
         if name is None:
             return response[0]
-        else:
-            for item in response[0]:
-                if name == item['dev_name']:
-                    return item
-            return {}
+        for item in response[0]:
+            if name == item['dev_name']:
+                return item
+        return {}
 
     @staticmethod
     def create_subinterface(node, interface, sub_id, outer_vlan_id=None,
@@ -910,6 +917,151 @@ class InterfaceUtil(object):
             raise RuntimeError('Create loopback failed on node "{}"'
                                .format(node['host']))
 
+    @staticmethod
+    def vpp_create_bond_interface(node, mode, load_balance=None, mac=None):
+        """Create bond interface on VPP node.
+
+        :param node: DUT node from topology.
+        :param mode: Link bonding mode.
+        :param load_balance: Load balance (optional, valid for xor and lacp
+            modes, otherwise ignored).
+        :param mac: MAC address to assign to the bond interface (optional).
+        :type node: dict
+        :type mode: str
+        :type load_balance: str
+        :type mac: str
+        :returns: Interface key (name) in topology.
+        :rtype: str
+        :raises RuntimeError: If it is not possible to create bond interface on
+            the node.
+        """
+        hw_addr = '' if mac is None else 'hw-addr {mac}'.format(mac=mac)
+        ldb = '' if load_balance is None \
+            else 'lb {ldb}'.format(ldb=load_balance)
+
+        output = VatExecutor.cmd_from_template(
+            node, 'create_bond_interface.vat', mode=mode, lb=ldb, mac=hw_addr)
+
+        if output[0].get('retval') == 0:
+            sw_if_idx = output[0].get('sw_if_index')
+            InterfaceUtil.add_eth_interface(node, sw_if_idx=sw_if_idx,
+                                            ifc_pfx='eth_bond')
+            if_key = Topology.get_interface_by_sw_index(node, sw_if_idx)
+            return if_key
+        else:
+            raise RuntimeError('Create bond interface failed on "{host}"'.
+                               format(host=node['host']))
+
+    @staticmethod
+    def add_eth_interface(node, ifc_name=None, sw_if_idx=None, ifc_pfx=None):
+        """Add ethernet interface to current topology.
+
+        :param node: DUT node from topology.
+        :param ifc_name: Name of the interface.
+        :param sw_if_idx: SW interface index.
+        :param ifc_pfx: Interface key prefix.
+        :type node: dict
+        :type ifc_name: str
+        :type sw_if_idx: int
+        :type ifc_pfx: str
+        """
+        if_key = Topology.add_new_port(node, ifc_pfx)
+
+        vat_executor = VatExecutor()
+        vat_executor.execute_script_json_out("dump_interfaces.vat", node)
+        interface_dump_json = vat_executor.get_script_stdout()
+
+        if ifc_name and sw_if_idx is None:
+            sw_if_idx = VatJsonUtil.get_interface_sw_index_from_json(
+                interface_dump_json, ifc_name)
+        Topology.update_interface_sw_if_index(node, if_key, sw_if_idx)
+        if sw_if_idx and ifc_name is None:
+            ifc_name = InterfaceUtil.vpp_get_interface_name(node, sw_if_idx)
+        Topology.update_interface_name(node, if_key, ifc_name)
+        ifc_mac = VatJsonUtil.get_interface_mac_from_json(
+            interface_dump_json, sw_if_idx)
+        Topology.update_interface_mac_address(node, if_key, ifc_mac)
+
+    @staticmethod
+    def vpp_create_avf_interface(node, vf_pci_addr):
+        """Create AVF interface on VPP node.
+
+        :param node: DUT node from topology.
+        :param vf_pci_addr: Virtual Function PCI address.
+        :type node: dict
+        :type vf_pci_addr: str
+        :returns: Interface key (name) in topology.
+        :rtype: str
+        :raises RuntimeError: If it is not possible to create AVF interface on
+            the node.
+        """
+        with VatTerminal(node, json_param=False) as vat:
+            vat.vat_terminal_exec_cmd_from_template('create_avf_interface.vat',
+                                                    vf_pci_addr=vf_pci_addr)
+            output = vat.vat_stdout
+
+        if output is not None:
+            sw_if_idx = int(output.split()[4])
+            InterfaceUtil.add_eth_interface(node, sw_if_idx=sw_if_idx,
+                                            ifc_pfx='eth_avf')
+            if_key = Topology.get_interface_by_sw_index(node, sw_if_idx)
+            return if_key
+        else:
+            raise RuntimeError('Create AVF interface failed on {host}'.
+                               format(host=node['host']))
+
+    @staticmethod
+    def vpp_enslave_physical_interface(node, interface, bond_interface):
+        """Enslave physical interface to bond interface on VPP node.
+
+        :param node: DUT node from topology.
+        :param interface: Physical interface key from topology file.
+        :param bond_interface: Load balance
+        :type node: dict
+        :type interface: str
+        :type bond_interface: str
+        :raises RuntimeError: If it is not possible to enslave physical
+            interface to bond interface on the node.
+        """
+        ifc = Topology.get_interface_sw_index(node, interface)
+        bond_ifc = Topology.get_interface_sw_index(node, bond_interface)
+
+        output = VatExecutor.cmd_from_template(
+            node, 'enslave_physical_interface.vat', p_int=ifc, b_int=bond_ifc)
+
+        retval = output[0].get('retval', None)
+        if retval is None or int(retval) != 0:
+            raise RuntimeError('Enslave physical interface {ifc} to bond '
+                               'interface {bond} failed on node "{n}"'
+                               .format(ifc=interface, bond=bond_interface,
+                                       n=node['host']))
+
+    @staticmethod
+    def vpp_show_bond_data_on_node(node, details=False):
+        """Show (detailed) bond information on VPP node.
+
+        :param node: DUT node from topology.
+        :param details: If detailed information is required or not.
+        :type node: dict
+        :type details: bool
+        """
+        cmd = 'exec show bond details' if details else 'exec show bond'
+        with VatTerminal(node, json_param=False) as vat:
+            vat.vat_terminal_exec_cmd(cmd)
+
+    @staticmethod
+    def vpp_show_bond_data_on_all_nodes(nodes, details=False):
+        """Show (detailed) bond information on all VPP nodes in DICT__nodes.
+
+        :param nodes: Nodes in the topology.
+        :param details: If detailed information is required or not.
+        :type nodes: dict
+        :type details: bool
+        """
+        for node_data in nodes.values():
+            if node_data['type'] == NodeType.DUT:
+                InterfaceUtil.vpp_show_bond_data_on_node(node_data, details)
+
     @staticmethod
     def vpp_enable_input_acl_interface(node, interface, ip_version,
                                        table_index):
@@ -1092,21 +1244,141 @@ class InterfaceUtil(object):
                                .format(node))
 
     @staticmethod
-    def set_linux_interface_mac(node, interface, mac, namespace=None):
+    def set_linux_interface_mac(node, interface, mac, namespace=None,
+                                vf_id=None):
         """Set MAC address for interface in linux.
 
         :param node: Node where to execute command.
         :param interface: Interface in namespace.
         :param mac: MAC to be assigned to interface.
         :param namespace: Execute command in namespace. Optional
+        :param vf_id: Virtual Function id. Optional
         :type node: dict
         :type interface: str
         :type mac: str
         :type namespace: str
+        :type vf_id: int
         """
-        if namespace is not None:
-            cmd = 'ip netns exec {} ip link set {} address {}'.format(
-                namespace, interface, mac)
-        else:
-            cmd = 'ip link set {} address {}'.format(interface, mac)
+        mac_str = 'vf {vf_id} mac {mac}'.format(vf_id=vf_id, mac=mac) \
+            if vf_id is not None else 'address {mac}'.format(mac=mac)
+        ns_str = 'ip netns exec {ns}'.format(ns=namespace) if namespace else ''
+
+        cmd = ('{ns} ip link set {interface} {mac}'.
+               format(ns=ns_str, interface=interface, mac=mac_str))
+        exec_cmd_no_error(node, cmd, sudo=True)
+
+    @staticmethod
+    def set_linux_interface_trust_on(node, interface, namespace=None,
+                                     vf_id=None):
+        """Set trust on (promisc) for interface in linux.
+
+        :param node: Node where to execute command.
+        :param interface: Interface in namespace.
+        :param namespace: Execute command in namespace. Optional
+        :param vf_id: Virtual Function id. Optional
+        :type node: dict
+        :type interface: str
+        :type namespace: str
+        :type vf_id: int
+        """
+        trust_str = 'vf {vf_id} trust on'.format(vf_id=vf_id) \
+            if vf_id is not None else 'trust on'
+        ns_str = 'ip netns exec {ns}'.format(ns=namespace) if namespace else ''
+
+        cmd = ('{ns} ip link set dev {interface} {trust}'.
+               format(ns=ns_str, interface=interface, trust=trust_str))
         exec_cmd_no_error(node, cmd, sudo=True)
+
+    @staticmethod
+    def set_linux_interface_spoof_off(node, interface, namespace=None,
+                                      vf_id=None):
+        """Set spoof off for interface in linux.
+
+        :param node: Node where to execute command.
+        :param interface: Interface in namespace.
+        :param namespace: Execute command in namespace. Optional
+        :param vf_id: Virtual Function id. Optional
+        :type node: dict
+        :type interface: str
+        :type namespace: str
+        :type vf_id: int
+        """
+        spoof_str = 'vf {vf_id} spoof off'.format(vf_id=vf_id) \
+            if vf_id is not None else 'spoof off'
+        ns_str = 'ip netns exec {ns}'.format(ns=namespace) if namespace else ''
+
+        cmd = ('{ns} ip link set dev {interface} {spoof}'.
+               format(ns=ns_str, interface=interface, spoof=spoof_str))
+        exec_cmd_no_error(node, cmd, sudo=True)
+
+    @staticmethod
+    def init_avf_interface(node, ifc_key, numvfs=1, topology_type='L2'):
+        """Init PCI device by creating VFs and bind them to vfio-pci for AVF
+        driver testing on DUT.
+
+        :param node: DUT node.
+        :param iface_key: Interface key from topology file.
+        :param numvfs: Number of VFs to initialize, 0 - disable the VFs.
+        :param topology_type: Topology type.
+        :type node: dict
+        :iface_key: str
+        :type numvfs: int
+        :typ topology_type: str
+        :returns: Virtual Function topology interface keys.
+        :rtype: list
+        """
+        ssh = SSH()
+        ssh.connect(node)
+
+        # Read PCI address and driver.
+        pf_pci_addr = Topology.get_interface_pci_addr(node, ifc_key)
+        pf_mac_addr = Topology.get_interface_mac(node, ifc_key).split(":")
+        uio_driver = Topology.get_uio_driver(node)
+        kernel_driver = Topology.get_interface_driver(node, ifc_key)
+        current_driver = DUTSetup.get_pci_dev_driver(node,\
+            pf_pci_addr.replace(':', r'\:'))
+
+        VPPUtil.stop_vpp_service(node)
+        if current_driver != kernel_driver:
+            # PCI device must be re-bound to kernel driver before creating VFs.
+            DUTSetup.verify_kernel_module(node, kernel_driver, force_load=True)
+            # Stop VPP to prevent deadlock.
+            # Unbind from current driver.
+            DUTSetup.pci_driver_unbind(node, pf_pci_addr)
+            # Bind to kernel driver.
+            DUTSetup.pci_driver_bind(node, pf_pci_addr, kernel_driver)
+
+        # Initialize PCI VFs
+        DUTSetup.set_sriov_numvfs(node, pf_pci_addr, numvfs)
+
+        vf_ifc_keys = []
+        # Set MAC address and bind each virtual function to uio driver.
+        for vf_id in range(numvfs):
+            vf_mac_addr = ":".join([pf_mac_addr[0], pf_mac_addr[2],
+                                    pf_mac_addr[3], pf_mac_addr[4],
+                                    pf_mac_addr[5], "{:02x}".format(vf_id)])
+
+            pf_dev = '`basename /sys/bus/pci/devices/{pci}/net/*`'.\
+                format(pci=pf_pci_addr)
+            InterfaceUtil.set_linux_interface_trust_on(node, pf_dev,
+                                                       vf_id=vf_id)
+            if topology_type == 'L2':
+                InterfaceUtil.set_linux_interface_spoof_off(node, pf_dev,
+                                                            vf_id=vf_id)
+            InterfaceUtil.set_linux_interface_mac(node, pf_dev, vf_mac_addr,
+                                                  vf_id=vf_id)
+
+            DUTSetup.pci_vf_driver_unbind(node, pf_pci_addr, vf_id)
+            DUTSetup.pci_vf_driver_bind(node, pf_pci_addr, vf_id, uio_driver)
+
+            # Add newly created ports into topology file
+            vf_ifc_name = '{pf_if_key}_vf'.format(pf_if_key=ifc_key)
+            vf_pci_addr = DUTSetup.get_virtfn_pci_addr(node, pf_pci_addr, vf_id)
+            vf_ifc_key = Topology.add_new_port(node, vf_ifc_name)
+            Topology.update_interface_name(node, vf_ifc_key,
+                                           vf_ifc_name+str(vf_id+1))
+            Topology.update_interface_mac_address(node, vf_ifc_key, vf_mac_addr)
+            Topology.update_interface_pci_address(node, vf_ifc_key, vf_pci_addr)
+            vf_ifc_keys.append(vf_ifc_key)
+
+        return vf_ifc_keys