VPP link bonding - lacp mode
[csit.git] / resources / libraries / python / InterfaceUtil.py
index 793f908..25540dc 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2016 Cisco and/or its affiliates.
+# Copyright (c) 2018 Cisco and/or its affiliates.
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # You may obtain a copy of the License at:
@@ -130,7 +130,7 @@ class InterfaceUtil(object):
         :type node: dict
         :type timeout: int
         :returns: Nothing.
-        :raises: RuntimeError if the timeout period value has elapsed.
+        :raises RuntimeError: If the timeout period value has elapsed.
         """
         if_ready = False
         not_ready = []
@@ -196,10 +196,10 @@ class InterfaceUtil(object):
         :type node: dict
         :type interface: int or str
         :returns: List of dictionaries containing data for each interface, or a
-        single dictionary for the specified interface.
+            single dictionary for the specified interface.
         :rtype: list or dict
         :raises TypeError: if the data type of interface is neither basestring
-        nor int.
+            nor int.
         """
         with VatTerminal(node) as vat:
             response = vat.vat_terminal_exec_cmd_from_template(
@@ -220,6 +220,29 @@ class InterfaceUtil(object):
             return dict()
         return data
 
+    @staticmethod
+    def vpp_get_interface_name(node, sw_if_index):
+        """Get interface name for the given SW interface index from actual
+        interface dump.
+
+        :param node: VPP node to get interface data from.
+        :param sw_if_index: SW interface index of the specific interface.
+        :type node: dict
+        :type sw_if_index: int
+        :returns: Name of the given interface.
+        :rtype: str
+        """
+
+        if_data = InterfaceUtil.vpp_get_interface_data(node, sw_if_index)
+        if if_data['sup_sw_if_index'] != if_data['sw_if_index']:
+            if_data = InterfaceUtil.vpp_get_interface_data(
+                node, if_data['sup_sw_if_index'])
+        try:
+            if_name = if_data["interface_name"]
+        except KeyError:
+            if_name = None
+        return if_name
+
     @staticmethod
     def vpp_get_interface_mac(node, interface=None):
         """Get MAC address for the given interface from actual interface dump.
@@ -256,13 +279,19 @@ class InterfaceUtil(object):
          :type interface: str
          :type ip_version: str
          :returns: List of dictionaries, each containing IP address, subnet
-         prefix length and also the subnet mask for ipv4 addresses.
-         Note: A single interface may have multiple IP addresses assigned.
+            prefix length and also the subnet mask for ipv4 addresses.
+            Note: A single interface may have multiple IP addresses assigned.
          :rtype: list
         """
 
-        sw_if_index = Topology.convert_interface_reference(
-            node, interface, "sw_if_index")
+        try:
+            sw_if_index = Topology.convert_interface_reference(
+                node, interface, "sw_if_index")
+        except RuntimeError:
+            if isinstance(interface, basestring):
+                sw_if_index = InterfaceUtil.get_sw_if_index(node, interface)
+            else:
+                raise
 
         with VatTerminal(node) as vat:
             response = vat.vat_terminal_exec_cmd_from_template(
@@ -287,7 +316,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.
         """
@@ -326,7 +354,7 @@ class InterfaceUtil(object):
         :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.
+            information from the node.
 
         .. note::
             # lspci -vmmks 0000:00:05.0
@@ -342,21 +370,34 @@ class InterfaceUtil(object):
         ssh = SSH()
         ssh.connect(node)
 
-        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 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']))
 
-        for line in stdout.splitlines():
-            if len(line) == 0:
-                continue
-            (name, value) = line.split("\t", 1)
-            if name == 'Driver:':
-                return value
+            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']))
 
-        return None
+            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
+        raise RuntimeError('Get interface driver for: {0}'
+                           .format(pci_addr))
 
     @staticmethod
     def tg_set_interfaces_udev_rules(node):
@@ -430,12 +471,49 @@ class InterfaceUtil(object):
                                                         interface_dump_json)
 
     @staticmethod
-    def update_tg_interface_data_on_node(node):
-        """Update interface name for TG/linux node in DICT__nodes.
+    def update_nic_interface_names(node):
+        """Update interface names based on nic type and PCI address.
 
-        :param node: Node selected from DICT__nodes.
+        This method updates interface names in the same format as VPP does.
+
+        :param node: Node dictionary.
         :type node: dict
-        :raises RuntimeError: If getting of interface name and MAC fails.
+        """
+        for ifc in node['interfaces'].values():
+            if_pci = ifc['pci_address'].replace('.', ':').split(':')
+            bus = '{:x}'.format(int(if_pci[1], 16))
+            dev = '{:x}'.format(int(if_pci[2], 16))
+            fun = '{:x}'.format(int(if_pci[3], 16))
+            loc = '{bus}/{dev}/{fun}'.format(bus=bus, dev=dev, fun=fun)
+            if ifc['model'] == 'Intel-XL710':
+                ifc['name'] = 'FortyGigabitEthernet{loc}'.format(loc=loc)
+            elif ifc['model'] == 'Intel-X710':
+                ifc['name'] = 'TenGigabitEthernet{loc}'.format(loc=loc)
+            elif ifc['model'] == 'Intel-X520-DA2':
+                ifc['name'] = 'TenGigabitEthernet{loc}'.format(loc=loc)
+            elif ifc['model'] == 'Cisco-VIC-1385':
+                ifc['name'] = 'FortyGigabitEthernet{loc}'.format(loc=loc)
+            elif ifc['model'] == 'Cisco-VIC-1227':
+                ifc['name'] = 'TenGigabitEthernet{loc}'.format(loc=loc)
+            else:
+                ifc['name'] = 'UnknownEthernet{loc}'.format(loc=loc)
+
+    @staticmethod
+    def update_nic_interface_names_on_all_duts(nodes):
+        """Update interface names based on nic type and PCI address on all DUTs.
+
+        This method updates interface names in the same format as VPP does.
+
+        :param nodes: Topology nodes.
+        :type nodes: dict
+        """
+        for node in nodes.values():
+            if node['type'] == NodeType.DUT:
+                InterfaceUtil.update_nic_interface_names(node)
+
+    @staticmethod
+    def update_tg_interface_data_on_node(node):
+        """Update interface name for TG/linux node in DICT__nodes.
 
         .. note::
             # for dev in `ls /sys/class/net/`;
@@ -445,7 +523,11 @@ class InterfaceUtil(object):
             "52:54:00:e1:8a:0f": "eth2"
             "00:00:00:00:00:00": "lo"
 
-        .. todo:: parse lshw -json instead
+        .. note:: TODO: parse lshw -json instead
+
+        :param node: Node selected from DICT__nodes.
+        :type node: dict
+        :raises RuntimeError: If getting of interface name and MAC fails.
         """
         # First setup interface driver specified in yaml file
         InterfaceUtil.tg_set_interfaces_default_driver(node)
@@ -562,7 +644,7 @@ class InterfaceUtil(object):
         :returns: Name and index of created subinterface.
         :rtype: tuple
         :raises RuntimeError: if it is unable to create VLAN subinterface on the
-        node.
+            node.
         """
         iface_key = Topology.get_interface_by_name(node, interface)
         sw_if_index = Topology.get_interface_sw_index(node, iface_key)
@@ -571,10 +653,14 @@ class InterfaceUtil(object):
                                                sw_if_index=sw_if_index,
                                                vlan=vlan)
         if output[0]["retval"] == 0:
-            sw_subif_index = output[0]["sw_if_index"]
+            sw_subif_idx = output[0]["sw_if_index"]
             logger.trace('VLAN subinterface with sw_if_index {} and VLAN ID {} '
-                         'created on node {}'.format(sw_subif_index,
+                         'created on node {}'.format(sw_subif_idx,
                                                      vlan, node['host']))
+            if_key = Topology.add_new_port(node, "vlan_subif")
+            Topology.update_interface_sw_if_index(node, if_key, sw_subif_idx)
+            ifc_name = InterfaceUtil.vpp_get_interface_name(node, sw_subif_idx)
+            Topology.update_interface_name(node, if_key, ifc_name)
         else:
             raise RuntimeError('Unable to create VLAN subinterface on node {}'
                                .format(node['host']))
@@ -582,7 +668,7 @@ class InterfaceUtil(object):
         with VatTerminal(node, False) as vat:
             vat.vat_terminal_exec_cmd('exec show interfaces')
 
-        return '{}.{}'.format(interface, vlan), sw_subif_index
+        return '{}.{}'.format(interface, vlan), sw_subif_idx
 
     @staticmethod
     def create_vxlan_interface(node, vni, source_ip, destination_ip):
@@ -602,7 +688,7 @@ class InterfaceUtil(object):
         :returns: SW IF INDEX of created interface.
         :rtype: int
         :raises RuntimeError: if it is unable to create VxLAN interface on the
-        node.
+            node.
         """
         output = VatExecutor.cmd_from_template(node, "vxlan_create.vat",
                                                src=source_ip,
@@ -611,9 +697,14 @@ class InterfaceUtil(object):
         output = output[0]
 
         if output["retval"] == 0:
-            return output["sw_if_index"]
+            sw_if_idx = output["sw_if_index"]
+            if_key = Topology.add_new_port(node, "vxlan_tunnel")
+            Topology.update_interface_sw_if_index(node, if_key, sw_if_idx)
+            ifc_name = InterfaceUtil.vpp_get_interface_name(node, sw_if_idx)
+            Topology.update_interface_name(node, if_key, ifc_name)
+            return sw_if_idx
         else:
-            raise RuntimeError('Unable to create VXLAN interface on node {0}'
+            raise RuntimeError("Unable to create VXLAN interface on node {0}"
                                .format(node))
 
     @staticmethod
@@ -622,14 +713,14 @@ class InterfaceUtil(object):
 
         :param node: VPP node to get interface data from.
         :param interface: Numeric index or name string of a specific interface.
-        If None, information about all VxLAN interfaces is returned.
+            If None, information about all VxLAN interfaces is returned.
         :type node: dict
         :type interface: int or str
         :returns: Dictionary containing data for the given VxLAN interface or if
-        interface=None, the list of dictionaries with all VxLAN interfaces.
+            interface=None, the list of dictionaries with all VxLAN interfaces.
         :rtype: dict or list
         :raises TypeError: if the data type of interface is neither basestring
-        nor int.
+            nor int.
         """
         param = "sw_if_index"
         if interface is None:
@@ -678,7 +769,7 @@ class InterfaceUtil(object):
         :type node: dict
         :type name: str
         :returns: Dictionary of information about a specific TAP interface, or
-        a List of dictionaries containing all TAP data for the given node.
+            a List of dictionaries containing all TAP data for the given node.
         :rtype: dict or list
         """
         with VatTerminal(node) as vat:
@@ -704,7 +795,8 @@ class InterfaceUtil(object):
         :param outer_vlan_id: Optional outer VLAN ID.
         :param inner_vlan_id: Optional inner VLAN ID.
         :param type_subif: Optional type of sub-interface. Values supported by
-        VPP: [no_tags] [one_tag] [two_tags] [dot1ad] [exact_match] [default_sub]
+            VPP: [no_tags] [one_tag] [two_tags] [dot1ad] [exact_match]
+            [default_sub]
         :type node: dict
         :type interface: str or int
         :type sub_id: int
@@ -739,9 +831,13 @@ class InterfaceUtil(object):
                                                type_subif=type_subif)
 
         if output[0]["retval"] == 0:
-            sw_subif_index = output[0]["sw_if_index"]
+            sw_subif_idx = output[0]["sw_if_index"]
             logger.trace('Created subinterface with index {}'
-                         .format(sw_subif_index))
+                         .format(sw_subif_idx))
+            if_key = Topology.add_new_port(node, "subinterface")
+            Topology.update_interface_sw_if_index(node, if_key, sw_subif_idx)
+            ifc_name = InterfaceUtil.vpp_get_interface_name(node, sw_subif_idx)
+            Topology.update_interface_name(node, if_key, ifc_name)
         else:
             raise RuntimeError('Unable to create sub-interface on node {}'
                                .format(node['host']))
@@ -750,7 +846,7 @@ class InterfaceUtil(object):
             vat.vat_terminal_exec_cmd('exec show interfaces')
 
         name = '{}.{}'.format(interface, sub_id)
-        return name, sw_subif_index
+        return name, sw_subif_idx
 
     @staticmethod
     def create_gre_tunnel_interface(node, source_ip, destination_ip):
@@ -772,14 +868,19 @@ class InterfaceUtil(object):
         output = output[0]
 
         if output["retval"] == 0:
-            sw_if_index = output["sw_if_index"]
+            sw_if_idx = output["sw_if_index"]
 
             vat_executor = VatExecutor()
             vat_executor.execute_script_json_out("dump_interfaces.vat", node)
             interface_dump_json = vat_executor.get_script_stdout()
             name = VatJsonUtil.get_interface_name_from_json(
-                interface_dump_json, sw_if_index)
-            return name, sw_if_index
+                interface_dump_json, sw_if_idx)
+
+            if_key = Topology.add_new_port(node, "gre_tunnel")
+            Topology.update_interface_sw_if_index(node, if_key, sw_if_idx)
+            Topology.update_interface_name(node, if_key, name)
+
+            return name, sw_if_idx
         else:
             raise RuntimeError('Unable to create GRE tunnel on node {}.'
                                .format(node))
@@ -793,15 +894,134 @@ class InterfaceUtil(object):
         :returns: SW interface index.
         :rtype: int
         :raises RuntimeError: If it is not possible to create loopback on the
-        node.
+            node.
         """
         out = VatExecutor.cmd_from_template(node, "create_loopback.vat")
         if out[0].get('retval') == 0:
-            return out[0].get('sw_if_index')
+            sw_if_idx = out[0].get('sw_if_index')
+            if_key = Topology.add_new_port(node, "loopback")
+            Topology.update_interface_sw_if_index(node, if_key, sw_if_idx)
+            ifc_name = InterfaceUtil.vpp_get_interface_name(node, sw_if_idx)
+            Topology.update_interface_name(node, if_key, ifc_name)
+            return sw_if_idx
         else:
             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)
+        lb = '' if load_balance is None \
+            else 'lb {lb}'.format(lb=load_balance)
+
+        output = VatExecutor.cmd_from_template(
+            node, 'create_bond_interface.vat', mode=mode, lb=lb, mac=hw_addr)
+
+        if output[0].get('retval') == 0:
+            sw_if_idx = output[0].get('sw_if_index')
+            InterfaceUtil.add_bond_eth_interface(node, sw_if_idx=sw_if_idx)
+            if_key = Topology.get_interface_by_sw_index(node, sw_if_idx)
+            return if_key
+        else:
+            raise RuntimeError('Create bond interface failed on node "{n}"'
+                               .format(n=node['host']))
+
+    @staticmethod
+    def add_bond_eth_interface(node, ifc_name=None, sw_if_idx=None):
+        """Add BondEthernet interface to current topology.
+
+        :param node: DUT node from topology.
+        :param ifc_name: Name of the BondEthernet interface.
+        :param sw_if_idx: SW interface index.
+        :type node: dict
+        :type ifc_name: str
+        :type sw_if_idx: int
+        """
+        if_key = Topology.add_new_port(node, 'eth_bond')
+
+        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_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):
@@ -899,12 +1119,12 @@ class InterfaceUtil(object):
 
         :param node: VPP node to get interface data from.
         :param interface_name: Name of the specific interface. If None,
-        information about all VxLAN GPE interfaces is returned.
+            information about all VxLAN GPE interfaces is returned.
         :type node: dict
         :type interface_name: str
         :returns: Dictionary containing data for the given VxLAN GPE interface
-        or if interface=None, the list of dictionaries with all VxLAN GPE
-        interfaces.
+            or if interface=None, the list of dictionaries with all VxLAN GPE
+            interfaces.
         :rtype: dict or list
         """
 
@@ -983,7 +1203,6 @@ class InterfaceUtil(object):
             raise RuntimeError('Unable to assign interface to FIB node {}.'
                                .format(node))
 
-
     @staticmethod
     def set_linux_interface_mac(node, interface, mac, namespace=None):
         """Set MAC address for interface in linux.