X-Git-Url: https://gerrit.fd.io/r/gitweb?p=csit.git;a=blobdiff_plain;f=resources%2Flibraries%2Fpython%2FInterfaceUtil.py;h=7144e0adefa2179ce3632e33b5161dfbeb648372;hp=f5f1ce314e6a687032f63125f0ea772e1f145878;hb=82094363f6077e1b28845719db3a6191c0c93a99;hpb=1942363fa62b6a079ae5552d20c90eb55f0033d9 diff --git a/resources/libraries/python/InterfaceUtil.py b/resources/libraries/python/InterfaceUtil.py index f5f1ce314e..7144e0adef 100644 --- a/resources/libraries/python/InterfaceUtil.py +++ b/resources/libraries/python/InterfaceUtil.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016 Cisco and/or its affiliates. +# Copyright (c) 2019 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: @@ -11,19 +11,40 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Interface util library""" +"""Interface util library.""" -from time import time, sleep +from socket import AF_INET, AF_INET6, inet_ntop +from time import sleep +from enum import IntEnum +from ipaddress import ip_address 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.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.Constants import Constants +from resources.libraries.python.CpuUtils import CpuUtils +from resources.libraries.python.DUTSetup import DUTSetup +from resources.libraries.python.L2Util import L2Util +from resources.libraries.python.PapiExecutor import PapiExecutor from resources.libraries.python.parsers.JsonParser import JsonParser +from resources.libraries.python.ssh import SSH, exec_cmd_no_error +from resources.libraries.python.topology import NodeType, Topology +from resources.libraries.python.VPPUtil import VPPUtil + + +class LinkBondLoadBalance(IntEnum): + """Link bonding load balance.""" + L2 = 0 + L34 = 1 + L23 = 2 + + +class LinkBondMode(IntEnum): + """Link bonding load balance.""" + ROUND_ROBIN = 1 + ACTIVE_BACKUP = 2 + XOR = 3 + BROADCAST = 4 + LACP = 5 class InterfaceUtil(object): @@ -32,7 +53,47 @@ class InterfaceUtil(object): __UDEV_IF_RULES_FILE = '/etc/udev/rules.d/10-network.rules' @staticmethod - def set_interface_state(node, interface, state, if_type="key"): + def pci_to_int(pci_str): + """Convert PCI address from string format (0000:18:0a.0) to + integer representation (169345024). + + :param pci_str: PCI address in string representation. + :type pci_str: str + :returns: Integer representation of PCI address. + :rtype: int + """ + pci = list(pci_str.split(':')[0:2]) + pci.extend(pci_str.split(':')[2].split('.')) + + return (int(pci[0], 16) | int(pci[1], 16) << 16 | + int(pci[2], 16) << 24 | int(pci[3], 16) << 29) + + @staticmethod + def get_interface_index(node, interface): + """Get interface sw_if_index from topology file. + + :param node: Node where the interface is. + :param interface: Numeric index or name string of a specific interface. + :type node: dict + :type interface: str or int + :returns: SW interface index. + :rtype: int + """ + try: + sw_if_index = int(interface) + except ValueError: + sw_if_index = Topology.get_interface_sw_index(node, interface) + if sw_if_index is None: + sw_if_index = \ + Topology.get_interface_sw_index_by_name(node, interface) + except TypeError as err: + raise TypeError('Wrong interface format {ifc}: {err}'.format( + ifc=interface, err=err.message)) + + return sw_if_index + + @staticmethod + def set_interface_state(node, interface, state, if_type='key'): """Set interface state on a node. Function can be used for DUTs as well as for TGs. @@ -45,38 +106,48 @@ class InterfaceUtil(object): :type interface: str or int :type state: str :type if_type: str - :return: nothing + :returns: Nothing. + :raises ValueError: If the interface type is unknown. + :raises ValueError: If the state of interface is unexpected. + :raises ValueError: If the node has an unknown node type. """ - - if if_type == "key": + if if_type == 'key': if isinstance(interface, basestring): sw_if_index = Topology.get_interface_sw_index(node, interface) iface_name = Topology.get_interface_name(node, interface) else: sw_if_index = interface - elif if_type == "name": + elif if_type == 'name': iface_key = Topology.get_interface_by_name(node, interface) if iface_key is not None: sw_if_index = Topology.get_interface_sw_index(node, iface_key) iface_name = interface else: - raise ValueError("if_type unknown: {}".format(if_type)) + raise ValueError('Unknown if_type: {type}'.format(type=if_type)) if node['type'] == NodeType.DUT: if state == 'up': - state = 'admin-up' + admin_up_down = 1 elif state == 'down': - state = 'admin-down' + admin_up_down = 0 else: - raise ValueError('Unexpected interface state: {}'.format(state)) - VatExecutor.cmd_from_template(node, 'set_if_state.vat', - sw_if_index=sw_if_index, state=state) + raise ValueError('Unexpected interface state: {state}'.format( + state=state)) + cmd = 'sw_interface_set_flags' + err_msg = 'Failed to set interface state on host {host}'.format( + host=node['host']) + args = dict(sw_if_index=sw_if_index, + admin_up_down=admin_up_down) + with PapiExecutor(node) as papi_exec: + papi_exec.add(cmd, **args).get_replies(err_msg).\ + verify_reply(err_msg=err_msg) elif node['type'] == NodeType.TG or node['type'] == NodeType.VM: - cmd = 'ip link set {} {}'.format(iface_name, state) + cmd = 'ip link set {ifc} {state}'.format( + ifc=iface_name, state=state) exec_cmd_no_error(node, cmd, sudo=True) else: - raise Exception('Node {} has unknown NodeType: "{}"'. - format(node['host'], node['type'])) + raise ValueError('Node {} has unknown NodeType: "{}"' + .format(node['host'], node['type'])) @staticmethod def set_interface_ethernet_mtu(node, iface_key, mtu): @@ -85,23 +156,25 @@ class InterfaceUtil(object): Function can be used only for TGs. :param node: Node where the interface is. - :param interface: Interface key from topology file. + :param iface_key: Interface key from topology file. :param mtu: MTU to set. :type node: dict :type iface_key: str :type mtu: int - :return: nothing + :returns: Nothing. + :raises ValueError: If the node type is "DUT". + :raises ValueError: If the node has an unknown node type. """ if node['type'] == NodeType.DUT: - ValueError('Node {}: Setting Ethernet MTU for interface ' - 'on DUT nodes not supported', node['host']) + raise ValueError('Node {}: Setting Ethernet MTU for interface ' + 'on DUT nodes not supported', node['host']) elif node['type'] == NodeType.TG: iface_name = Topology.get_interface_name(node, iface_key) cmd = 'ip link set {} mtu {}'.format(iface_name, mtu) exec_cmd_no_error(node, cmd, sudo=True) else: - raise ValueError('Node {} has unknown NodeType: "{}"'. - format(node['host'], node['type'])) + raise ValueError('Node {} has unknown NodeType: "{}"' + .format(node['host'], node['type'])) @staticmethod def set_default_ethernet_mtu_on_all_interfaces_on_node(node): @@ -111,136 +184,230 @@ class InterfaceUtil(object): :param node: Node where to set default MTU. :type node: dict - :return: nothing + :returns: Nothing. """ for ifc in node['interfaces']: 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 + + cmd = 'hw_interface_set_mtu' + err_msg = 'Failed to set interface MTU on host {host}'.format( + host=node['host']) + args = dict(sw_if_index=sw_if_index, + mtu=int(mtu)) + try: + with PapiExecutor(node) as papi_exec: + papi_exec.add(cmd, **args).get_replies(err_msg).\ + verify_reply(err_msg=err_msg) + except AssertionError as err: + # TODO: Make failure tolerance optional. + logger.debug("Setting MTU failed. Expected?\n{err}".format( + err=err)) + + @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, retries=15): """Wait until all interfaces with admin-up are in link-up state. :param node: Node to wait on. - :param timeout: Waiting timeout in seconds (optional, default 10s). + :param retries: Number of retries to check interface status (optional, + default 15). :type node: dict - :type timeout: int - :raises: RuntimeError if the timeout period value has elapsed. + :type retries: int + :returns: Nothing. + :raises RuntimeError: If any interface is not in link-up state after + defined number of retries. """ - if_ready = False - not_ready = [] - start = time() - while not if_ready: + for _ in xrange(0, retries): + not_ready = list() out = InterfaceUtil.vpp_get_interface_data(node) - if time() - start > timeout: - for interface in out: - if interface.get('admin_up_down') == 1: - if interface.get('link_up_down') != 1: - logger.debug('{0} link-down'.format( - interface.get('interface_name'))) - raise RuntimeError('timeout, not up {0}'.format(not_ready)) - not_ready = [] for interface in out: if interface.get('admin_up_down') == 1: if interface.get('link_up_down') != 1: not_ready.append(interface.get('interface_name')) if not not_ready: - if_ready = True + break else: - logger.debug('Interfaces still in link-down state: {0}, ' - 'waiting...'.format(not_ready)) + logger.debug('Interfaces still in link-down state:\n{ifs} ' + '\nWaiting...'.format(ifs=not_ready)) sleep(1) + else: + err = 'Timeout, interfaces not up:\n{ifs}'.format(ifs=not_ready) \ + if 'not_ready' in locals() else 'No check executed!' + raise RuntimeError(err) @staticmethod - def vpp_nodes_interfaces_ready_wait(nodes, timeout=10): - """Wait until all interfaces with admin-up are in link-up state for - listed nodes. - - :param nodes: List of nodes to wait on. - :param timeout: Seconds to wait per node for all interfaces to come up. - :type nodes: list - :type timeout: int - :raises: RuntimeError if the timeout period value has elapsed. - """ - for node in nodes: - 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, retries=15): """Wait until all interfaces with admin-up are in link-up state for all nodes in the topology. :param nodes: Nodes in the topology. - :param timeout: Seconds to wait per node for all interfaces to come up. + :param retries: Number of retries to check interface status (optional, + default 15). :type nodes: dict - :type timeout: int - :raises: RuntimeError if the timeout period value has elapsed. + :type retries: int + :returns: Nothing. """ for node in nodes.values(): if node['type'] == NodeType.DUT: - InterfaceUtil.vpp_node_interfaces_ready_wait(node, timeout) + InterfaceUtil.vpp_node_interfaces_ready_wait(node, retries) @staticmethod def vpp_get_interface_data(node, interface=None): """Get all interface data from a VPP node. If a name or sw_interface_index is provided, return only data for the matching - interface. + interface(s). :param node: VPP node to get interface data from. :param interface: Numeric index or name string of a specific interface. :type node: dict :type interface: int or str - :return: List of dictionaries containing data for each interface, or a - single dictionary for the specified interface. + :returns: List of dictionaries containing data for each interface, or a + single dictionary for the specified interface. :rtype: list or dict + :raises TypeError: if the data type of interface is neither basestring + nor int. """ - with VatTerminal(node) as vat: - response = vat.vat_terminal_exec_cmd_from_template( - "interface_dump.vat") - - data = response[0] - if interface is not None: if isinstance(interface, basestring): - param = "interface_name" + param = 'interface_name' elif isinstance(interface, int): - param = "sw_if_index" + param = 'sw_if_index' else: - raise TypeError - for data_if in data: - if data_if[param] == interface: - return data_if - return dict() + raise TypeError('Wrong interface format {ifc}'.format( + ifc=interface)) + else: + param = '' + + cmd = 'sw_interface_dump' + cmd_reply = 'sw_interface_details' + args = dict(name_filter_valid=0, + name_filter='') + err_msg = 'Failed to get interface dump on host {host}'.format( + host=node['host']) + with PapiExecutor(node) as papi_exec: + papi_resp = papi_exec.add(cmd, **args).get_dump(err_msg) + + papi_if_dump = papi_resp.reply[0]['api_reply'] + + def process_if_dump(if_dump): + """Process interface dump. + + :param if_dump: Interface dump. + :type if_dump: dict + :returns: Processed interface dump. + :rtype: dict + """ + if_dump['interface_name'] = if_dump['interface_name'].rstrip('\x00') + if_dump['tag'] = if_dump['tag'].rstrip('\x00') + if_dump['l2_address'] = L2Util.bin_to_mac(if_dump['l2_address']) + if_dump['b_dmac'] = L2Util.bin_to_mac(if_dump['b_dmac']) + if_dump['b_smac'] = L2Util.bin_to_mac(if_dump['b_smac']) + return if_dump + + data = list() if interface is None else dict() + for item in papi_if_dump: + if interface is None: + data.append(process_if_dump(item[cmd_reply])) + elif str(item[cmd_reply].get(param)).rstrip('\x00') == \ + str(interface): + data = process_if_dump(item[cmd_reply]) + break + + logger.debug('Interface data:\n{if_data}'.format(if_data=data)) return data @staticmethod - def vpp_get_interface_ip_addresses(node, interface, ip_version): - """Get list of IP addresses from an interface on a VPP node. + 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 data from. - :param interface: Name of an interface on the VPP node. - :param ip_version: IP protocol version (ipv4 or ipv6). - :type node: dict - :type interface: str - :type ip_version: str - :return: 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. - :rtype: list + :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 """ - sw_if_index = InterfaceUtil.get_sw_if_index(node, interface) + 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']) - with VatTerminal(node) as vat: - response = vat.vat_terminal_exec_cmd_from_template( - "ip_address_dump.vat", ip_version=ip_version, - sw_if_index=sw_if_index) + return if_data.get('interface_name') - data = response[0] + @staticmethod + def vpp_get_interface_sw_index(node, interface_name): + """Get interface name for the given SW interface index from actual + interface dump. - if ip_version == "ipv4": - for item in data: - item["netmask"] = convert_ipv4_netmask_prefix( - item["prefix_length"]) - return data + :param node: VPP node to get interface data from. + :param interface_name: Interface name. + :type node: dict + :type interface_name: str + :returns: Name of the given interface. + :rtype: str + """ + if_data = InterfaceUtil.vpp_get_interface_data(node, interface_name) + + return if_data.get('sw_if_index') + + @staticmethod + def vpp_get_interface_mac(node, interface): + """Get MAC address for the given interface from actual interface dump. + + :param node: VPP node to get interface data from. + :param interface: Numeric index or name string of a specific interface. + :type node: dict + :type interface: int or str + :returns: MAC address. + :rtype: str + """ + if_data = InterfaceUtil.vpp_get_interface_data(node, interface) + 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']) + + return if_data.get('l2_address') @staticmethod def tg_set_interface_driver(node, pci_addr, driver): @@ -252,6 +419,8 @@ class InterfaceUtil(object): :type node: dict :type pci_addr: str :type driver: str + :raises RuntimeError: If unbinding from the current driver fails. + :raises RuntimeError: If binding to the new driver fails. """ old_driver = InterfaceUtil.tg_get_interface_driver(node, pci_addr) if old_driver == driver: @@ -262,19 +431,20 @@ class InterfaceUtil(object): # Unbind from current driver if old_driver is not None: - cmd = 'sh -c "echo {0} > /sys/bus/pci/drivers/{1}/unbind"'.format( - pci_addr, old_driver) + cmd = 'sh -c "echo {0} > /sys/bus/pci/drivers/{1}/unbind"'\ + .format(pci_addr, old_driver) (ret_code, _, _) = ssh.exec_command_sudo(cmd) if int(ret_code) != 0: - raise Exception("'{0}' failed on '{1}'".format(cmd, - node['host'])) + raise RuntimeError("'{0}' failed on '{1}'" + .format(cmd, node['host'])) # Bind to the new driver - cmd = 'sh -c "echo {0} > /sys/bus/pci/drivers/{1}/bind"'.format( - pci_addr, driver) + cmd = 'sh -c "echo {0} > /sys/bus/pci/drivers/{1}/bind"'\ + .format(pci_addr, driver) (ret_code, _, _) = ssh.exec_command_sudo(cmd) if int(ret_code) != 0: - raise Exception("'{0}' failed on '{1}'".format(cmd, node['host'])) + raise RuntimeError("'{0}' failed on '{1}'" + .format(cmd, node['host'])) @staticmethod def tg_get_interface_driver(node, pci_addr): @@ -284,37 +454,11 @@ class InterfaceUtil(object): :param pci_addr: PCI address of the interface. :type node: dict :type pci_addr: str - :return: Interface driver or None if not found. + :returns: Interface driver or None if not found. :rtype: str - - .. 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) - - cmd = 'lspci -vmmks {0}'.format(pci_addr) - - (ret_code, stdout, _) = ssh.exec_command(cmd) - if int(ret_code) != 0: - raise Exception("'{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 - - return None + return DUTSetup.get_pci_dev_driver(node, pci_addr) @staticmethod def tg_set_interfaces_udev_rules(node): @@ -331,6 +475,7 @@ class InterfaceUtil(object): :param node: Node to set udev rules on (must be TG node). :type node: dict + :raises RuntimeError: If setting of udev rules fails. """ ssh = SSH() ssh.connect(node) @@ -338,7 +483,8 @@ class InterfaceUtil(object): cmd = 'rm -f {0}'.format(InterfaceUtil.__UDEV_IF_RULES_FILE) (ret_code, _, _) = ssh.exec_command_sudo(cmd) if int(ret_code) != 0: - raise Exception("'{0}' failed on '{1}'".format(cmd, node['host'])) + raise RuntimeError("'{0}' failed on '{1}'" + .format(cmd, node['host'])) for interface in node['interfaces'].values(): rule = 'SUBSYSTEM==\\"net\\", ACTION==\\"add\\", ATTR{address}' + \ @@ -348,8 +494,8 @@ class InterfaceUtil(object): rule, InterfaceUtil.__UDEV_IF_RULES_FILE) (ret_code, _, _) = ssh.exec_command_sudo(cmd) if int(ret_code) != 0: - raise Exception("'{0}' failed on '{1}'".format(cmd, - node['host'])) + raise RuntimeError("'{0}' failed on '{1}'" + .format(cmd, node['host'])) cmd = '/etc/init.d/udev restart' ssh.exec_command_sudo(cmd) @@ -372,25 +518,75 @@ class InterfaceUtil(object): Updates interface names, software if index numbers and any other details generated specifically by vpp that are unknown before testcase run. - It does this by dumping interface list to JSON output from all - devices using vpp_api_test, and pairing known information from topology - (mac address/pci address of interface) to state from VPP. + It does this by dumping interface list from all devices using python + api, and pairing known information from topology (mac address) to state + from VPP. :param node: Node selected from DICT__nodes. :type node: dict """ - vat_executor = VatExecutor() - vat_executor.execute_script_json_out("dump_interfaces.vat", node) - interface_dump_json = vat_executor.get_script_stdout() - VatJsonUtil.update_vpp_interface_data_from_json(node, - interface_dump_json) + interface_list = InterfaceUtil.vpp_get_interface_data(node) + interface_dict = dict() + for ifc in interface_list: + interface_dict[ifc['l2_address']] = ifc + + for if_name, if_data in node['interfaces'].items(): + ifc_dict = interface_dict.get(if_data['mac_address']) + if ifc_dict is not None: + if_data['name'] = ifc_dict['interface_name'] + if_data['vpp_sw_index'] = ifc_dict['sw_if_index'] + if_data['mtu'] = ifc_dict['mtu'][0] + logger.trace('Interface {ifc} found by MAC {mac}'.format( + ifc=if_name, mac=if_data['mac_address'])) + else: + logger.trace('Interface {ifc} not found by MAC {mac}'.format( + ifc=if_name, mac=if_data['mac_address'])) + if_data['vpp_sw_index'] = None @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 + """ + 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, skip_tg_udev=False): + """Update interface name for TG/linux node in DICT__nodes. .. note:: # for dev in `ls /sys/class/net/`; @@ -400,7 +596,11 @@ class InterfaceUtil(object): "52:54:00:e1:8a:0f": "eth2" "00:00:00:00:00:00": "lo" - .. todo:: parse lshw -json instead + :param node: Node selected from DICT__nodes. + :param skip_tg_udev: Skip udev rename on TG node. + :type node: dict + :type skip_tg_udev: bool + :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) @@ -414,7 +614,7 @@ class InterfaceUtil(object): (ret_code, stdout, _) = ssh.exec_command(cmd) if int(ret_code) != 0: - raise Exception('Get interface name and MAC failed') + raise RuntimeError('Get interface name and MAC failed') tmp = "{" + stdout.rstrip().replace('\n', ',') + "}" interfaces = JsonParser().parse_data(tmp) for interface in node['interfaces'].values(): @@ -424,7 +624,8 @@ class InterfaceUtil(object): interface['name'] = name # Set udev rules for interfaces - InterfaceUtil.tg_set_interfaces_udev_rules(node) + if not skip_tg_udev: + InterfaceUtil.tg_set_interfaces_udev_rules(node) @staticmethod def iface_update_numa_node(node): @@ -433,19 +634,56 @@ class InterfaceUtil(object): :param node: Node from topology. :type node: dict - :return: nothing + :returns: Nothing. + :raises ValueError: If numa node ia less than 0. + :raises RuntimeError: If update of numa node failes. """ ssh = SSH() for if_key in Topology.get_node_interfaces(node): if_pci = Topology.get_interface_pci_addr(node, if_key) ssh.connect(node) cmd = "cat /sys/bus/pci/devices/{}/numa_node".format(if_pci) - (ret, out, _) = ssh.exec_command(cmd) - if ret == 0: - Topology.set_interface_numa_node(node, if_key, int(out)) + for _ in range(3): + (ret, out, _) = ssh.exec_command(cmd) + if ret == 0: + try: + numa_node = int(out) + if numa_node < 0: + 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)) + else: + Topology.set_interface_numa_node(node, if_key, + numa_node) + break + else: + raise RuntimeError('Update numa node failed for: {0}' + .format(if_pci)) + + @staticmethod + def update_all_numa_nodes(nodes, skip_tg=False): + """For all nodes and all their interfaces from topology file update numa + node information based on information from the node. + + :param nodes: Nodes in the topology. + :param skip_tg: Skip TG node + :type nodes: dict + :type skip_tg: bool + :returns: Nothing. + """ + for node in nodes.values(): + if node['type'] == NodeType.DUT: + InterfaceUtil.iface_update_numa_node(node) + elif node['type'] == NodeType.TG and not skip_tg: + InterfaceUtil.iface_update_numa_node(node) @staticmethod def update_all_interface_data_on_all_nodes(nodes, skip_tg=False, + skip_tg_udev=False, numa_node=False): """Update interface names on all nodes in DICT__nodes. @@ -453,17 +691,20 @@ class InterfaceUtil(object): of all nodes mentioned in the topology dictionary. :param nodes: Nodes in the topology. - :param skip_tg: Skip TG node + :param skip_tg: Skip TG node. + :param skip_tg_udev: Skip udev rename on TG node. :param numa_node: Retrieve numa_node location. :type nodes: dict :type skip_tg: bool + :type skip_tg_udev: bool :type numa_node: bool """ for node_data in nodes.values(): if node_data['type'] == NodeType.DUT: InterfaceUtil.update_vpp_interface_data_on_node(node_data) elif node_data['type'] == NodeType.TG and not skip_tg: - InterfaceUtil.update_tg_interface_data_on_node(node_data) + InterfaceUtil.update_tg_interface_data_on_node( + node_data, skip_tg_udev) if numa_node: if node_data['type'] == NodeType.DUT: @@ -473,7 +714,7 @@ class InterfaceUtil(object): @staticmethod def create_vlan_subinterface(node, interface, vlan): - """Create VLAN subinterface on node. + """Create VLAN sub-interface on node. :param node: Node to add VLAN subinterface on. :param interface: Interface name on which create VLAN subinterface. @@ -481,36 +722,35 @@ class InterfaceUtil(object): :type node: dict :type interface: str :type vlan: int - :return: Name and index of created subinterface. + :returns: Name and index of created subinterface. :rtype: tuple + :raises RuntimeError: if it is unable to create VLAN subinterface on the + node. """ iface_key = Topology.get_interface_by_name(node, interface) sw_if_index = Topology.get_interface_sw_index(node, iface_key) - output = VatExecutor.cmd_from_template(node, "create_vlan_subif.vat", - sw_if_index=sw_if_index, - vlan=vlan) - if output[0]["retval"] == 0: - sw_subif_index = output[0]["sw_if_index"] - logger.trace('VLAN subinterface with sw_if_index {} and VLAN ID {} ' - 'created on node {}'.format(sw_subif_index, - vlan, node['host'])) - else: - raise RuntimeError('Unable to create VLAN subinterface on node {}' - .format(node['host'])) + cmd = 'create_vlan_subif' + args = dict(sw_if_index=sw_if_index, + vlan_id=int(vlan)) + err_msg = 'Failed to create VLAN sub-interface on host {host}'.format( + host=node['host']) + with PapiExecutor(node) as papi_exec: + papi_resp = papi_exec.add(cmd, **args).get_replies(err_msg).\ + verify_reply(err_msg=err_msg) - with VatTerminal(node, False) as vat: - vat.vat_terminal_exec_cmd('exec show interfaces') + sw_if_idx = papi_resp['sw_if_index'] + if_key = Topology.add_new_port(node, 'vlan_subif') + 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 '{}.{}'.format(interface, vlan), sw_subif_index + return '{ifc}.{vlan}'.format(ifc=interface, vlan=vlan), sw_if_idx @staticmethod def create_vxlan_interface(node, vni, source_ip, destination_ip): """Create VXLAN interface and return sw if index of created interface. - Executes "vxlan_add_del_tunnel src {src} dst {dst} vni {vni}" VAT - command on the node. - :param node: Node where to create VXLAN interface. :param vni: VXLAN Network Identifier. :param source_ip: Source IP of a VXLAN Tunnel End Point. @@ -519,20 +759,37 @@ class InterfaceUtil(object): :type vni: int :type source_ip: str :type destination_ip: str - :return: SW IF INDEX of created interface. + :returns: SW IF INDEX of created interface. :rtype: int + :raises RuntimeError: if it is unable to create VxLAN interface on the + node. """ - output = VatExecutor.cmd_from_template(node, "vxlan_create.vat", - src=source_ip, - dst=destination_ip, - vni=vni) - output = output[0] - - if output["retval"] == 0: - return output["sw_if_index"] - else: - raise RuntimeError('Unable to create VXLAN interface on node {0}' - .format(node)) + src_address = ip_address(unicode(source_ip)) + dst_address = ip_address(unicode(destination_ip)) + + cmd = 'vxlan_add_del_tunnel' + args = dict(is_add=1, + is_ipv6=1 if src_address.version == 6 else 0, + instance=Constants.BITWISE_NON_ZERO, + src_address=src_address.packed, + dst_address=dst_address.packed, + mcast_sw_if_index=Constants.BITWISE_NON_ZERO, + encap_vrf_id=0, + decap_next_index=Constants.BITWISE_NON_ZERO, + vni=int(vni)) + err_msg = 'Failed to create VXLAN tunnel interface on host {host}'.\ + format(host=node['host']) + with PapiExecutor(node) as papi_exec: + papi_resp = papi_exec.add(cmd, **args).get_replies(err_msg).\ + verify_reply(err_msg=err_msg) + + sw_if_idx = papi_resp['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 @staticmethod def vxlan_dump(node, interface=None): @@ -540,73 +797,156 @@ 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 - :return: Dictionary containing data for the given VxLAN interface or if - interface=None, the list of dictionaries with all VxLAN interfaces. + :returns: Dictionary containing data for the given VxLAN interface or if + 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. """ - param = "sw_if_index" - if interface is None: - param = '' - sw_if_index = '' - elif isinstance(interface, basestring): - sw_if_index = Topology.get_interface_sw_index(node, interface) - elif isinstance(interface, int): - sw_if_index = interface + if interface is not None: + sw_if_index = InterfaceUtil.get_interface_index(node, interface) else: - raise Exception("Wrong interface format {0}".format(interface)) - - with VatTerminal(node) as vat: - response = vat.vat_terminal_exec_cmd_from_template( - "vxlan_dump.vat", param=param, sw_if_index=sw_if_index) - - if sw_if_index: - for vxlan in response[0]: - if vxlan["sw_if_index"] == sw_if_index: - return vxlan - return {} - return response[0] + sw_if_index = int(Constants.BITWISE_NON_ZERO) + + cmd = 'vxlan_tunnel_dump' + cmd_reply = 'vxlan_tunnel_details' + args = dict(sw_if_index=sw_if_index) + err_msg = 'Failed to get VXLAN dump on host {host}'.format( + host=node['host']) + with PapiExecutor(node) as papi_exec: + papi_resp = papi_exec.add(cmd, **args).get_dump(err_msg) + + papi_vxlan_dump = papi_resp.reply[0]['api_reply'] + + def process_vxlan_dump(vxlan_dump): + """Process vxlan dump. + + :param vxlan_dump: Vxlan interface dump. + :type vxlan_dump: dict + :returns: Processed vxlan interface dump. + :rtype: dict + """ + if vxlan_dump['is_ipv6']: + vxlan_dump['src_address'] = \ + inet_ntop(AF_INET6, vxlan_dump['src_address']) + vxlan_dump['dst_address'] = \ + inet_ntop(AF_INET6, vxlan_dump['dst_address']) + else: + vxlan_dump['src_address'] = \ + inet_ntop(AF_INET, vxlan_dump['src_address'][0:4]) + vxlan_dump['dst_address'] = \ + inet_ntop(AF_INET, vxlan_dump['dst_address'][0:4]) + return vxlan_dump + + data = list() if interface is None else dict() + for item in papi_vxlan_dump: + if interface is None: + data.append(process_vxlan_dump(item[cmd_reply])) + elif item[cmd_reply]['sw_if_index'] == sw_if_index: + data = process_vxlan_dump(item[cmd_reply]) + break + + logger.debug('VXLAN data:\n{vxlan_data}'.format(vxlan_data=data)) + return data @staticmethod def vhost_user_dump(node): """Get vhost-user data for the given node. + TODO: Move to VhostUser.py + :param node: VPP node to get interface data from. :type node: dict - :return: List of dictionaries with all vhost-user interfaces. + :returns: List of dictionaries with all vhost-user interfaces. :rtype: list """ - with VatTerminal(node) as vat: - response = vat.vat_terminal_exec_cmd_from_template( - "vhost_user_dump.vat") - - return response[0] + cmd = 'sw_interface_vhost_user_dump' + cmd_reply = 'sw_interface_vhost_user_details' + err_msg = 'Failed to get vhost-user dump on host {host}'.format( + host=node['host']) + with PapiExecutor(node) as papi_exec: + papi_resp = papi_exec.add(cmd).get_dump(err_msg) + + papi_vxlan_dump = papi_resp.reply[0]['api_reply'] + + def process_vhost_dump(vhost_dump): + """Process vhost dump. + + :param vhost_dump: Vhost interface dump. + :type vhost_dump: dict + :returns: Processed vhost interface dump. + :rtype: dict + """ + vhost_dump['interface_name'] = \ + vhost_dump['interface_name'].rstrip('\x00') + vhost_dump['sock_filename'] = \ + vhost_dump['sock_filename'].rstrip('\x00') + return vhost_dump + + data = list() + for item in papi_vxlan_dump: + data.append(process_vhost_dump(item[cmd_reply])) + + logger.debug('Vhost-user data:\n{vhost_data}'.format(vhost_data=data)) + return data @staticmethod def tap_dump(node, name=None): """Get all TAP interface data from the given node, or data about a specific TAP interface. + TODO: Move to Tap.py + :param node: VPP node to get data from. :param name: Optional name of a specific TAP interface. :type node: dict :type name: str - :return: Dictionary of information about a specific TAP interface, or - a List of dictionaries containing all TAP data for the given node. + :returns: Dictionary of information about a specific TAP interface, or + a List of dictionaries containing all TAP data for the given node. :rtype: dict or list """ - with VatTerminal(node) as vat: - response = vat.vat_terminal_exec_cmd_from_template( - "tap_dump.vat") - if name is None: - return response[0] - else: - for item in response[0]: - if name == item['dev_name']: - return item - return {} + cmd = 'sw_interface_tap_v2_dump' + cmd_reply = 'sw_interface_tap_v2_details' + err_msg = 'Failed to get TAP dump on host {host}'.format( + host=node['host']) + with PapiExecutor(node) as papi_exec: + papi_resp = papi_exec.add(cmd).get_dump(err_msg) + + papi_tap_dump = papi_resp.reply[0]['api_reply'] + + def process_tap_dump(tap_dump): + """Process tap dump. + + :param tap_dump: Tap interface dump. + :type tap_dump: dict + :returns: Processed tap interface dump. + :rtype: dict + """ + tap_dump['dev_name'] = tap_dump['dev_name'].rstrip('\x00') + tap_dump['host_if_name'] = tap_dump['host_if_name'].rstrip('\x00') + tap_dump['host_namespace'] = \ + tap_dump['host_namespace'].rstrip('\x00') + tap_dump['host_mac_addr'] = \ + L2Util.bin_to_mac(tap_dump['host_mac_addr']) + tap_dump['host_ip4_addr'] = \ + inet_ntop(AF_INET, tap_dump['host_ip4_addr']) + tap_dump['host_ip6_addr'] = \ + inet_ntop(AF_INET6, tap_dump['host_ip6_addr']) + return tap_dump + + data = list() if name is None else dict() + for item in papi_tap_dump: + if name is None: + data.append(process_tap_dump(item[cmd_reply])) + elif item[cmd_reply].get('dev_name').rstrip('\x00') == name: + data = process_tap_dump(item[cmd_reply]) + break + + logger.debug('TAP data:\n{tap_data}'.format(tap_data=data)) + return data @staticmethod def create_subinterface(node, interface, sub_id, outer_vlan_id=None, @@ -620,53 +960,47 @@ 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 :type outer_vlan_id: int :type inner_vlan_id: int :type type_subif: str - :return: Name and index of created sub-interface. + :returns: Name and index of created sub-interface. :rtype: tuple :raises RuntimeError: If it is not possible to create sub-interface. """ - - outer_vlan_id = 'outer_vlan_id {0}'.format(outer_vlan_id)\ - if outer_vlan_id else '' - - inner_vlan_id = 'inner_vlan_id {0}'.format(inner_vlan_id)\ - if inner_vlan_id else '' - - if type_subif is None: - type_subif = '' - - if isinstance(interface, basestring): - iface_key = Topology.get_interface_by_name(node, interface) - sw_if_index = Topology.get_interface_sw_index(node, iface_key) - else: - sw_if_index = interface - - output = VatExecutor.cmd_from_template(node, "create_sub_interface.vat", - sw_if_index=sw_if_index, - sub_id=sub_id, - outer_vlan_id=outer_vlan_id, - inner_vlan_id=inner_vlan_id, - type_subif=type_subif) - - if output[0]["retval"] == 0: - sw_subif_index = output[0]["sw_if_index"] - logger.trace('Created subinterface with index {}' - .format(sw_subif_index)) - else: - raise RuntimeError('Unable to create sub-interface on node {}' - .format(node['host'])) - - with VatTerminal(node, json_param=False) as vat: - vat.vat_terminal_exec_cmd('exec show interfaces') - - name = '{}.{}'.format(interface, sub_id) - return name, sw_subif_index + subif_types = type_subif.split() + + cmd = 'create_subif' + args = dict( + sw_if_index=InterfaceUtil.get_interface_index(node, interface), + sub_id=int(sub_id), + no_tags=1 if 'no_tags' in subif_types else 0, + one_tag=1 if 'one_tag' in subif_types else 0, + two_tags=1 if 'two_tags' in subif_types else 0, + dot1ad=1 if 'dot1ad' in subif_types else 0, + exact_match=1 if 'exact_match' in subif_types else 0, + default_sub=1 if 'default_sub' in subif_types else 0, + outer_vlan_id_any=1 if type_subif == 'default_sub' else 0, + inner_vlan_id_any=1 if type_subif == 'default_sub' else 0, + outer_vlan_id=int(outer_vlan_id) if outer_vlan_id else 0, + inner_vlan_id=int(inner_vlan_id) if inner_vlan_id else 0) + err_msg = 'Failed to create sub-interface on host {host}'.format( + host=node['host']) + with PapiExecutor(node) as papi_exec: + papi_resp = papi_exec.add(cmd, **args).get_replies(err_msg).\ + verify_reply(err_msg=err_msg) + + sw_subif_idx = papi_resp['sw_if_index'] + 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) + + return '{ifc}.{s_id}'.format(ifc=interface, s_id=sub_id), sw_subif_idx @staticmethod def create_gre_tunnel_interface(node, source_ip, destination_ip): @@ -678,27 +1012,32 @@ class InterfaceUtil(object): :type node: dict :type source_ip: str :type destination_ip: str - :return: Name and index of created GRE tunnel interface. + :returns: Name and index of created GRE tunnel interface. :rtype: tuple :raises RuntimeError: If unable to create GRE tunnel interface. """ - output = VatExecutor.cmd_from_template(node, "create_gre.vat", - src=source_ip, - dst=destination_ip) - output = output[0] - - if output["retval"] == 0: - sw_if_index = 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 - else: - raise RuntimeError('Unable to create GRE tunnel on node {}.' - .format(node)) + cmd = 'gre_tunnel_add_del' + tunnel = dict(type=0, + instance=Constants.BITWISE_NON_ZERO, + src=str(source_ip), + dst=str(destination_ip), + outer_fib_id=0, + session_id=0) + args = dict(is_add=1, + tunnel=tunnel) + err_msg = 'Failed to create GRE tunnel interface on host {host}'.format( + host=node['host']) + with PapiExecutor(node) as papi_exec: + papi_resp = papi_exec.add(cmd, **args).get_replies(err_msg).\ + verify_reply(err_msg=err_msg) + + sw_if_idx = papi_resp['sw_if_index'] + if_key = Topology.add_new_port(node, 'gre_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 ifc_name, sw_if_idx @staticmethod def vpp_create_loopback(node): @@ -706,15 +1045,254 @@ class InterfaceUtil(object): :param node: Node to create loopback interface on. :type node: dict - :return: SW interface index. + :returns: SW interface index. :rtype: int + :raises RuntimeError: If it is not possible to create loopback on the + node. """ - out = VatExecutor.cmd_from_template(node, "create_loopback.vat") - if out[0].get('retval') == 0: - return out[0].get('sw_if_index') - else: - raise RuntimeError('Create loopback failed on node "{}"' - .format(node['host'])) + cmd = 'create_loopback' + args = dict(mac_address=0) + err_msg = 'Failed to create loopback interface on host {host}'.format( + host=node['host']) + with PapiExecutor(node) as papi_exec: + papi_resp = papi_exec.add(cmd, **args).get_replies(err_msg).\ + verify_reply(err_msg=err_msg) + + sw_if_idx = papi_resp['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 + + @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. + """ + cmd = 'bond_create' + args = dict(id=int(Constants.BITWISE_NON_ZERO), + use_custom_mac=0 if mac is None else 1, + mac_address=0 if mac is None else L2Util.mac_to_bin(mac), + mode=getattr(LinkBondMode, '{md}'.format( + md=mode.replace('-', '_').upper())).value, + lb=0 if load_balance is None else getattr( + LinkBondLoadBalance, '{lb}'.format( + lb=load_balance.upper())).value) + err_msg = 'Failed to create bond interface on host {host}'.format( + host=node['host']) + with PapiExecutor(node) as papi_exec: + papi_resp = papi_exec.add(cmd, **args).get_replies(err_msg).\ + verify_reply(err_msg=err_msg) + + sw_if_idx = papi_resp['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 + + @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) + + if ifc_name and sw_if_idx is None: + sw_if_idx = InterfaceUtil.vpp_get_interface_sw_index(node, 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 = InterfaceUtil.vpp_get_interface_mac(node, sw_if_idx) + Topology.update_interface_mac_address(node, if_key, ifc_mac) + + @staticmethod + def vpp_create_avf_interface(node, vf_pci_addr, num_rx_queues=None): + """Create AVF interface on VPP node. + + :param node: DUT node from topology. + :param vf_pci_addr: Virtual Function PCI address. + :param num_rx_queues: Number of RX queues. + :type node: dict + :type vf_pci_addr: str + :type num_rx_queues: int + :returns: Interface key (name) in topology. + :rtype: str + :raises RuntimeError: If it is not possible to create AVF interface on + the node. + """ + cmd = 'avf_create' + args = dict(pci_addr=InterfaceUtil.pci_to_int(vf_pci_addr), + enable_elog=0, + rxq_num=int(num_rx_queues) if num_rx_queues else 0, + rxq_size=0, + txq_size=0) + err_msg = 'Failed to create AVF interface on host {host}'.format( + host=node['host']) + with PapiExecutor(node) as papi_exec: + papi_resp = papi_exec.add(cmd, **args).get_replies(err_msg).\ + verify_reply(err_msg=err_msg) + + sw_if_idx = papi_resp['sw_if_index'] + 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 + + @staticmethod + def vpp_enslave_physical_interface(node, interface, bond_if): + """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_if: Load balance + :type node: dict + :type interface: str + :type bond_if: str + :raises RuntimeError: If it is not possible to enslave physical + interface to bond interface on the node. + """ + cmd = 'bond_enslave' + args = dict( + sw_if_index=Topology.get_interface_sw_index(node, interface), + bond_sw_if_index=Topology.get_interface_sw_index(node, bond_if), + is_passive=0, + is_long_timeout=0) + err_msg = 'Failed to enslave physical interface {ifc} to bond ' \ + 'interface {bond} on host {host}'.format(ifc=interface, + bond=bond_if, + host=node['host']) + with PapiExecutor(node) as papi_exec: + papi_exec.add(cmd, **args).get_replies(err_msg).\ + verify_reply(err_msg=err_msg) + + @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 = 'sw_interface_bond_dump' + cmd_reply = 'sw_interface_bond_details' + err_msg = 'Failed to get bond interface dump on host {host}'.format( + host=node['host']) + + data = ('Bond data on node {host}:\n'.format(host=node['host'])) + with PapiExecutor(node) as papi_exec: + papi_resp = papi_exec.add(cmd).get_dump(err_msg) + + papi_dump = papi_resp.reply[0]['api_reply'] + for item in papi_dump: + data += ('{b}\n'.format(b=item[cmd_reply]['interface_name']. + rstrip('\x00'))) + data += (' mode: {m}\n'. + format(m=LinkBondMode(item[cmd_reply]['mode']).name. + lower())) + data += (' load balance: {lb}\n'. + format(lb=LinkBondLoadBalance(item[cmd_reply]['lb']).name. + lower())) + data += (' number of active slaves: {n}\n'. + format(n=item[cmd_reply]['active_slaves'])) + if details: + slave_data = InterfaceUtil.vpp_bond_slave_dump( + node, Topology.get_interface_by_sw_index( + node, item[cmd_reply]['sw_if_index'])) + for slave in slave_data: + if not slave['is_passive']: + data += (' {s}\n'.format(s=slave['interface_name'])) + data += (' number of slaves: {n}\n'. + format(n=item[cmd_reply]['slaves'])) + if details: + for slave in slave_data: + data += (' {s}\n'.format(s=slave['interface_name'])) + data += (' interface id: {i}\n'. + format(i=item[cmd_reply]['id'])) + data += (' sw_if_index: {i}\n'. + format(i=item[cmd_reply]['sw_if_index'])) + logger.info(data) + + @staticmethod + def vpp_bond_slave_dump(node, interface): + """Get bond interface slave(s) data on VPP node. + + :param node: DUT node from topology. + :param interface: Physical interface key from topology file. + :type node: dict + :type interface: str + :returns: Bond slave interface data. + :rtype: dict + """ + cmd = 'sw_interface_slave_dump' + cmd_reply = 'sw_interface_slave_details' + args = dict(sw_if_index=Topology.get_interface_sw_index( + node, interface)) + err_msg = 'Failed to get slave dump on host {host}'.format( + host=node['host']) + + with PapiExecutor(node) as papi_exec: + papi_resp = papi_exec.add(cmd, **args).get_dump(err_msg) + + papi_dump = papi_resp.reply[0]['api_reply'] + + def process_slave_dump(slave_dump): + """Process slave dump. + + :param slave_dump: Slave interface dump. + :type slave_dump: dict + :returns: Processed slave interface dump. + :rtype: dict + """ + slave_dump['interface_name'] = slave_dump['interface_name'].\ + rstrip('\x00') + return slave_dump + + data = list() + for item in papi_dump: + data.append(process_slave_dump(item[cmd_reply])) + + logger.debug('Slave data:\n{slave_data}'.format(slave_data=data)) + return data + + @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, @@ -730,26 +1308,33 @@ class InterfaceUtil(object): :type ip_version: str :type table_index: int """ - if isinstance(interface, basestring): - sw_if_index = Topology.get_interface_sw_index(node, interface) - else: - sw_if_index = interface - - with VatTerminal(node) as vat: - vat.vat_terminal_exec_cmd_from_template("input_acl_int.vat", - sw_if_index=sw_if_index, - ip_version=ip_version, - table_index=table_index) + cmd = 'input_acl_set_interface' + args = dict( + sw_if_index=InterfaceUtil.get_interface_index(node, interface), + ip4_table_index=table_index if ip_version == 'ip4' + else Constants.BITWISE_NON_ZERO, + ip6_table_index=table_index if ip_version == 'ip6' + else Constants.BITWISE_NON_ZERO, + l2_table_index=table_index if ip_version == 'l2' + else Constants.BITWISE_NON_ZERO, + is_add=1) + err_msg = 'Failed to enable input acl on interface {ifc}'.format( + ifc=interface) + with PapiExecutor(node) as papi_exec: + papi_exec.add(cmd, **args).get_replies(err_msg).\ + verify_reply(err_msg=err_msg) @staticmethod def get_interface_classify_table(node, interface): """Get name of classify table for the given interface. + TODO: Move to Classify.py. + :param node: VPP node to get data from. :param interface: Name or sw_if_index of a specific interface. :type node: dict :type interface: str or int - :return: Classify table name. + :returns: Classify table name. :rtype: str """ if isinstance(interface, basestring): @@ -757,12 +1342,15 @@ class InterfaceUtil(object): else: sw_if_index = interface - with VatTerminal(node) as vat: - data = vat.vat_terminal_exec_cmd_from_template( - "classify_interface_table.vat", - sw_if_index=sw_if_index - ) - return data[0] + cmd = 'classify_table_by_interface' + args = dict(sw_if_index=sw_if_index) + err_msg = 'Failed to get classify table name by interface {ifc}'.format( + ifc=interface) + with PapiExecutor(node) as papi_exec: + papi_resp = papi_exec.add(cmd, **args).get_replies(err_msg). \ + verify_reply(err_msg=err_msg) + + return papi_resp @staticmethod def get_sw_if_index(node, interface_name): @@ -772,18 +1360,12 @@ class InterfaceUtil(object): :param interface_name: Name of the specific interface. :type node: dict :type interface_name: str - :return: sw_if_index of the given interface. + :returns: sw_if_index of the given interface. :rtype: str """ - - with VatTerminal(node) as vat: - if_data = vat.vat_terminal_exec_cmd_from_template( - "interface_dump.vat") - for interface in if_data[0]: - if interface["interface_name"] == interface_name: - return interface["sw_if_index"] - - return None + interface_data = InterfaceUtil.vpp_get_interface_data( + node, interface=interface_name) + return interface_data.get('sw_if_index') @staticmethod def vxlan_gpe_dump(node, interface_name=None): @@ -791,60 +1373,61 @@ 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 - :return: Dictionary containing data for the given VxLAN GPE interface or - if interface=None, the list of dictionaries with all VxLAN GPE - interfaces. + :returns: Dictionary containing data for the given VxLAN GPE interface + or if interface=None, the list of dictionaries with all VxLAN GPE + interfaces. :rtype: dict or list """ - - with VatTerminal(node) as vat: - vxlan_gpe_data = vat.vat_terminal_exec_cmd_from_template( - "vxlan_gpe_dump.vat") - - if interface_name: - sw_if_index = InterfaceUtil.get_sw_if_index(node, interface_name) - if sw_if_index: - for vxlan_gpe in vxlan_gpe_data[0]: - if vxlan_gpe["sw_if_index"] == sw_if_index: - return vxlan_gpe - return {} - - return vxlan_gpe_data[0] - - @staticmethod - def vpp_proxy_arp_interface_enable(node, interface): - """Enable proxy ARP on interface. - - :param node: VPP node to enable proxy ARP on interface. - :param interface: Interface to enable proxy ARP. - :type node: dict - :type interface: str or int - """ - if isinstance(interface, basestring): - sw_if_index = InterfaceUtil.get_sw_if_index(node, interface) + if interface_name is not None: + sw_if_index = InterfaceUtil.get_interface_index( + node, interface_name) else: - sw_if_index = interface - - with VatTerminal(node) as vat: - vat.vat_terminal_exec_cmd_from_template( - "proxy_arp_intfc_enable.vat", - sw_if_index=sw_if_index) - - @staticmethod - def vpp_ip_source_check_setup(node, interface): - """Setup Reverse Path Forwarding source check on interface. - - :param node: Node to setup RPF source check. - :param interface: Interface name to setup RPF source check. - :type node: dict - :type interface: str - """ - with VatTerminal(node) as vat: - vat.vat_terminal_exec_cmd_from_template("ip_source_check.vat", - interface_name=interface) + sw_if_index = int(Constants.BITWISE_NON_ZERO) + + cmd = 'vxlan_gpe_tunnel_dump' + cmd_reply = 'vxlan_gpe_tunnel_details' + args = dict(sw_if_index=sw_if_index) + err_msg = 'Failed to get VXLAN-GPE dump on host {host}'.format( + host=node['host']) + with PapiExecutor(node) as papi_exec: + papi_resp = papi_exec.add(cmd, **args).get_dump(err_msg) + + papi_vxlan_dump = papi_resp.reply[0]['api_reply'] + + def process_vxlan_gpe_dump(vxlan_dump): + """Process vxlan_gpe dump. + + :param vxlan_dump: Vxlan_gpe nterface dump. + :type vxlan_dump: dict + :returns: Processed vxlan_gpe interface dump. + :rtype: dict + """ + if vxlan_dump['is_ipv6']: + vxlan_dump['local'] = \ + inet_ntop(AF_INET6, vxlan_dump['local']) + vxlan_dump['remote'] = \ + inet_ntop(AF_INET6, vxlan_dump['remote']) + else: + vxlan_dump['local'] = \ + inet_ntop(AF_INET, vxlan_dump['local'][0:4]) + vxlan_dump['remote'] = \ + inet_ntop(AF_INET, vxlan_dump['remote'][0:4]) + return vxlan_dump + + data = list() if interface_name is None else dict() + for item in papi_vxlan_dump: + if interface_name is None: + data.append(process_vxlan_gpe_dump(item[cmd_reply])) + elif item[cmd_reply]['sw_if_index'] == sw_if_index: + data = process_vxlan_gpe_dump(item[cmd_reply]) + break + + logger.debug('VXLAN-GPE data:\n{vxlan_gpe_data}'.format( + vxlan_gpe_data=data)) + return data @staticmethod def assign_interface_to_fib_table(node, interface, table_id, ipv6=False): @@ -859,35 +1442,240 @@ class InterfaceUtil(object): :type table_id: int :type ipv6: bool """ - if isinstance(interface, basestring): - sw_if_index = Topology.get_interface_sw_index(node, interface) - else: - sw_if_index = interface - - ipv6 = 'ipv6' if ipv6 else '' - - with VatTerminal(node) as vat: - vat.vat_terminal_exec_cmd_from_template("set_fib_to_interface.vat", - sw_index=sw_if_index, - vrf=table_id, - ipv6=ipv6) + cmd = 'sw_interface_set_table' + args = dict( + sw_if_index=InterfaceUtil.get_interface_index(node, interface), + is_ipv6=1 if ipv6 else 0, + vrf_id=int(table_id)) + err_msg = 'Failed to assign interface {ifc} to FIB table'.format( + ifc=interface) + with PapiExecutor(node) as papi_exec: + papi_exec.add(cmd, **args).get_replies(err_msg). \ + verify_reply(err_msg=err_msg) @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, osi_layer='L2'): + """Init PCI device by creating VFs and bind them to vfio-pci for AVF + driver testing on DUT. + + :param node: DUT node. + :param ifc_key: Interface key from topology file. + :param numvfs: Number of VFs to initialize, 0 - disable the VFs. + :param osi_layer: OSI Layer type to initialize TG with. + Default value "L2" sets linux interface spoof off. + :type node: dict + :type ifc_key: str + :type numvfs: int + :type osi_layer: str + :returns: Virtual Function topology interface keys. + :rtype: list + :raises RuntimeError: If a reason preventing initialization is found. + """ + # 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) + if kernel_driver != "i40e": + raise RuntimeError( + "AVF needs i40e driver, not {driver} at node {host} ifc {ifc}"\ + .format(driver=kernel_driver, host=node["host"], ifc=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 osi_layer == '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 + + @staticmethod + def vpp_sw_interface_rx_placement_dump(node): + """Dump VPP interface RX placement on node. + + :param node: Node to run command on. + :type node: dict + :returns: Thread mapping information as a list of dictionaries. + :rtype: list + """ + cmd = 'sw_interface_rx_placement_dump' + cmd_reply = 'sw_interface_rx_placement_details' + err_msg = "Failed to run '{cmd}' PAPI command on host {host}!".format( + cmd=cmd, host=node['host']) + with PapiExecutor(node) as papi_exec: + for ifc in node['interfaces'].values(): + if ifc['vpp_sw_index'] is not None: + papi_exec.add(cmd, sw_if_index=ifc['vpp_sw_index']) + papi_resp = papi_exec.get_dump(err_msg) + thr_mapping = [s[cmd_reply] for r in papi_resp.reply + for s in r['api_reply']] + return sorted(thr_mapping, key=lambda k: k['sw_if_index']) + + @staticmethod + def vpp_sw_interface_set_rx_placement(node, sw_if_index, queue_id, + worker_id): + """Set interface RX placement to worker on node. + + :param node: Node to run command on. + :param sw_if_index: VPP SW interface index. + :param queue_id: VPP interface queue ID. + :param worker_id: VPP worker ID (indexing from 0). + :type node: dict + :type sw_if_index: int + :type queue_id: int + :type worker_id: int + :raises RuntimeError: If failed to run command on host or if no API + reply received. + """ + cmd = 'sw_interface_set_rx_placement' + err_msg = "Failed to set interface RX placement to worker on host " \ + "{host}!".format(host=node['host']) + args = dict(sw_if_index=sw_if_index, queue_id=queue_id, + worker_id=worker_id) + with PapiExecutor(node) as papi_exec: + papi_exec.add(cmd, **args).get_replies(err_msg).\ + verify_reply(err_msg=err_msg) + + @staticmethod + def vpp_round_robin_rx_placement(node, prefix): + """Set Round Robin interface RX placement on all worker threads + on node. + + :param node: Topology nodes. + :param prefix: Interface name prefix. + :type node: dict + :type prefix: str + """ + worker_id = 0 + worker_cnt = len(VPPUtil.vpp_show_threads(node)) - 1 + if not worker_cnt: + return + for placement in InterfaceUtil.vpp_sw_interface_rx_placement_dump(node): + for interface in node['interfaces'].values(): + if placement['sw_if_index'] == interface['vpp_sw_index'] \ + and prefix in interface['name']: + InterfaceUtil.vpp_sw_interface_set_rx_placement( + node, placement['sw_if_index'], placement['queue_id'], + worker_id % worker_cnt) + worker_id += 1 + + @staticmethod + def vpp_round_robin_rx_placement_on_all_duts(nodes, prefix): + """Set Round Robin interface RX placement on all worker threads + on all DUTs. + + :param nodes: Topology nodes. + :param prefix: Interface name prefix. + :type nodes: dict + :type prefix: str + """ + for node in nodes.values(): + if node['type'] == NodeType.DUT: + InterfaceUtil.vpp_round_robin_rx_placement(node, prefix)