X-Git-Url: https://gerrit.fd.io/r/gitweb?a=blobdiff_plain;f=resources%2Flibraries%2Fpython%2FInterfaceUtil.py;h=7fc7f857233820092131f5cdc3fd77adaaf2812a;hb=9db603df1076b5ee56ef6326fd0e396b166ed5b1;hp=c0a895793585fa64ecad9d3fea6a5caf9121bd9c;hpb=4c6fe5602edcbd9857a846e5b13a21d5c671a2c8;p=csit.git diff --git a/resources/libraries/python/InterfaceUtil.py b/resources/libraries/python/InterfaceUtil.py index c0a8957935..7fc7f85723 100644 --- a/resources/libraries/python/InterfaceUtil.py +++ b/resources/libraries/python/InterfaceUtil.py @@ -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