From: Filip Tehlar Date: Wed, 17 Feb 2016 14:59:08 +0000 (-0800) Subject: Refactor IPv4 utils X-Git-Url: https://gerrit.fd.io/r/gitweb?p=csit.git;a=commitdiff_plain;h=da15035461569ea175aabbac1df735cd5598b0b3;ds=sidebyside Refactor IPv4 utils Change-Id: Iae12444efba33e2d37b5d7beb1620e859abd84d7 Signed-off-by: Filip Tehlar --- diff --git a/resources/libraries/python/IPv4NodeAddress.py b/resources/libraries/python/IPv4NodeAddress.py index 8db9ffafe3..49ce41b25c 100644 --- a/resources/libraries/python/IPv4NodeAddress.py +++ b/resources/libraries/python/IPv4NodeAddress.py @@ -18,6 +18,8 @@ """ from ipaddress import IPv4Network +from resources.libraries.python.topology import Topology + # Default list of IPv4 subnets IPV4_NETWORKS = ['20.20.20.0/24', '10.10.10.0/24', @@ -34,14 +36,14 @@ class IPv4NetworkGenerator(object): self._networks = list() for network in networks: net = IPv4Network(unicode(network)) - subnet, _ = network.split('/') - self._networks.append((net, subnet)) + self._networks.append(net) if len(self._networks) == 0: raise Exception('No IPv4 networks') def next_network(self): """ :return: next network in form (IPv4Network, subnet) + :raises: StopIteration if there are no more elements. """ if len(self._networks): return self._networks.pop() @@ -49,56 +51,44 @@ class IPv4NetworkGenerator(object): raise StopIteration() -def get_variables(networks=IPV4_NETWORKS[:]): - """ - Create dictionary of IPv4 addresses generated from provided subnet list. - - Example of returned dictionary: - network = { - 'NET1': { - 'subnet': '192.168.1.0', - 'prefix': 24, - 'port1': { - 'addr': '192.168.1.1', - }, - 'port2': { - 'addr': '192.168.1.0', - }, - }, - 'NET2': { - 'subnet': '192.168.2.0', - 'prefix': 24, - 'port1': { - 'addr': '192.168.2.1', - }, - 'port2': { - 'addr': '192.168.2.2', - }, - }, - } - - This function is called by RobotFramework automatically. - - :param networks: list of subnets in form a.b.c.d/length - :return: Dictionary of IPv4 addresses +def get_variables(nodes, networks=IPV4_NETWORKS[:]): + """Special robot framework method that returns dictionary nodes_ipv4_addr, + mapping of node and interface name to IPv4 adddress. + + :param nodes: Nodes of the test topology. + :param networks: list of available IPv4 networks + :type nodes: dict + :type networks: list + + .. note:: + Robot framework calls it automatically. """ - net_object = IPv4NetworkGenerator(networks) - - network = {} - interface_count_per_node = 2 - - for subnet_num in range(len(networks)): - net, net_str = net_object.next_network() - key = 'NET{}'.format(subnet_num + 1) - network[key] = { - 'subnet': net_str, - 'prefix': net.prefixlen, - } - hosts = net.hosts() - for port_num in range(interface_count_per_node): - port = 'port{}'.format(port_num + 1) - network[key][port] = { - 'addr': str(next(hosts)), - } - - return {'DICT__nodes_ipv4_addr': network} + topo = Topology() + links = topo.get_links(nodes) + + if len(links) > len(networks): + raise Exception('Not enough available IPv4 networks for topology.') + + ip4_n = IPv4NetworkGenerator(networks) + + nets = {} + + for link in links: + ip4_net = ip4_n.next_network() + net_hosts = ip4_net.hosts() + port_idx = 0 + ports = {} + for node in nodes.values(): + if_name = topo.get_interface_by_link_name(node, link) + if if_name is not None: + port = {'addr': str(next(net_hosts)), + 'node': node['host'], + 'if': if_name} + port_idx += 1 + port_id = 'port{0}'.format(port_idx) + ports.update({port_id: port}) + nets.update({link: {'net_addr': str(ip4_net.network_address), + 'prefix': ip4_net.prefixlen, + 'ports': ports}}) + + return {'DICT__nodes_ipv4_addr': nets} diff --git a/resources/libraries/python/IPv4Setup.py b/resources/libraries/python/IPv4Setup.py new file mode 100644 index 0000000000..ed65518254 --- /dev/null +++ b/resources/libraries/python/IPv4Setup.py @@ -0,0 +1,309 @@ +# Copyright (c) 2016 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""IPv4 setup library""" + +from socket import inet_ntoa +from struct import pack +from abc import ABCMeta, abstractmethod +from robot.api.deco import keyword + +import resources.libraries.python.ssh as ssh +from resources.libraries.python.Routing import Routing +from resources.libraries.python.InterfaceUtil import InterfaceUtil +from resources.libraries.python.topology import NodeType, Topology +from resources.libraries.python.VatExecutor import VatExecutor + + +class IPv4Node(object): + """Abstract class of a node in a topology.""" + __metaclass__ = ABCMeta + + def __init__(self, node_info): + self.node_info = node_info + + @staticmethod + def _get_netmask(prefix_length): + bits = 0xffffffff ^ (1 << 32 - prefix_length) - 1 + return inet_ntoa(pack('>I', bits)) + + @abstractmethod + def set_ip(self, interface, address, prefix_length): + """Configure IPv4 address on interface + + :param interface: interface name + :param address: + :param prefix_length: + :type interface: str + :type address: str + :type prefix_length: int + :return: nothing + """ + pass + + @abstractmethod + def set_route(self, network, prefix_length, gateway, interface): + """Configure IPv4 route + + :param network: network IPv4 address + :param prefix_length: mask length + :param gateway: IPv4 address of the gateway + :param interface: interface name + :type network: str + :type prefix_length: int + :type gateway: str + :type interface: str + :return: nothing + """ + pass + + @abstractmethod + def unset_route(self, network, prefix_length, gateway, interface): + """Remove specified IPv4 route + + :param network: network IPv4 address + :param prefix_length: mask length + :param gateway: IPv4 address of the gateway + :param interface: interface name + :type network: str + :type prefix_length: int + :type gateway: str + :type interface: str + :return: nothing + """ + pass + + @abstractmethod + def flush_ip_addresses(self, interface): + """Flush all IPv4 addresses from specified interface + + :param interface: interface name + :type interface: str + :return: nothing + """ + pass + + @abstractmethod + def ping(self, destination_address, source_interface): + """Send an ICMP request to destination node + + :param destination_address: address to send the ICMP request + :param source_interface: + :type destination_address: str + :type source_interface: str + :return: nothing + """ + pass + + +class Tg(IPv4Node): + """Traffic generator node""" + def __init__(self, node_info): + super(Tg, self).__init__(node_info) + + def _execute(self, cmd): + return ssh.exec_cmd_no_error(self.node_info, cmd) + + def _sudo_execute(self, cmd): + return ssh.exec_cmd_no_error(self.node_info, cmd, sudo=True) + + def set_ip(self, interface, address, prefix_length): + cmd = 'ip -4 addr flush dev {}'.format(interface) + self._sudo_execute(cmd) + cmd = 'ip addr add {}/{} dev {}'.format(address, prefix_length, + interface) + self._sudo_execute(cmd) + + def set_route(self, network, prefix_length, gateway, interface): + netmask = self._get_netmask(prefix_length) + cmd = 'route add -net {} netmask {} gw {}'.\ + format(network, netmask, gateway) + self._sudo_execute(cmd) + + def unset_route(self, network, prefix_length, gateway, interface): + self._sudo_execute('ip route delete {}/{}'. + format(network, prefix_length)) + + def arp_ping(self, destination_address, source_interface): + self._sudo_execute('arping -c 1 -I {} {}'.format(source_interface, + destination_address)) + + def ping(self, destination_address, source_interface): + self._execute('ping -c 1 -w 5 -I {} {}'.format(source_interface, + destination_address)) + + def flush_ip_addresses(self, interface): + self._sudo_execute('ip addr flush dev {}'.format(interface)) + + +class Dut(IPv4Node): + """Device under test""" + def __init__(self, node_info): + super(Dut, self).__init__(node_info) + + def get_sw_if_index(self, interface): + """Get sw_if_index of specified interface from current node + + :param interface: interface name + :type interface: str + :return: sw_if_index of 'int' type + """ + return Topology().get_interface_sw_index(self.node_info, interface) + + def exec_vat(self, script, **args): + """Wrapper for VAT executor. + + :param script: script to execute + :param args: parameters to the script + :type script: str + :type args: dict + :return: nothing + """ + # TODO: check return value + VatExecutor.cmd_from_template(self.node_info, script, **args) + + def set_arp(self, interface, ip_address, mac_address): + """Set entry in ARP cache. + + :param interface: Interface name. + :param ip_address: IP address. + :param mac_address: MAC address. + :type interface: str + :type ip_address: str + :type mac_address: str + """ + self.exec_vat('add_ip_neighbor.vat', + sw_if_index=self.get_sw_if_index(interface), + ip_address=ip_address, mac_address=mac_address) + + def set_ip(self, interface, address, prefix_length): + self.exec_vat('add_ip_address.vat', + sw_if_index=self.get_sw_if_index(interface), + address=address, prefix_length=prefix_length) + + def set_route(self, network, prefix_length, gateway, interface): + Routing.vpp_route_add(self.node_info, + network=network, prefix_len=prefix_length, + gateway=gateway, interface=interface) + + def unset_route(self, network, prefix_length, gateway, interface): + self.exec_vat('del_route.vat', network=network, + prefix_length=prefix_length, gateway=gateway, + sw_if_index=self.get_sw_if_index(interface)) + + def arp_ping(self, destination_address, source_interface): + pass + + def flush_ip_addresses(self, interface): + self.exec_vat('flush_ip_addresses.vat', + sw_if_index=self.get_sw_if_index(interface)) + + def ping(self, destination_address, source_interface): + pass + + +def get_node(node_info): + """Creates a class instance derived from Node based on type. + + :param node_info: dictionary containing information on nodes in topology + :return: Class instance that is derived from Node + """ + if node_info['type'] == NodeType.TG: + return Tg(node_info) + elif node_info['type'] == NodeType.DUT: + return Dut(node_info) + else: + raise NotImplementedError('Node type "{}" unsupported!'. + format(node_info['type'])) + + +class IPv4Setup(object): + """IPv4 setup in topology.""" + + @staticmethod + def vpp_nodes_setup_ipv4_addresses(nodes, nodes_addr): + """Setup IPv4 addresses on all VPP nodes in topology. + + :param nodes: Nodes of the test topology. + :param nodes_addr: Available nodes IPv4 adresses. + :type nodes: dict + :type nodes_addr: dict + """ + for net in nodes_addr.values(): + for port in net['ports'].values(): + host = port.get('node') + if host is None: + continue + topo = Topology() + node = topo.get_node_by_hostname(nodes, host) + if node is None: + continue + if node['type'] != NodeType.DUT: + continue + get_node(node).set_ip(port['if'], port['addr'], net['prefix']) + InterfaceUtil.set_interface_state(node, port['if'], 'up') + + @staticmethod + @keyword('Get IPv4 address of node "${node}" interface "${port}" ' + 'from "${nodes_addr}"') + def get_ip_addr(node, interface, nodes_addr): + """Return IPv4 address of the node port. + :param node: Node in the topology. + :param interface: Interface name of the node. + :param nodes_addr: Nodes IPv4 adresses. + :type node: dict + :type interface: str + :type nodes_addr: dict + :return: IPv4 address string + """ + for net in nodes_addr.values(): + for port in net['ports'].values(): + host = port.get('node') + dev = port.get('if') + if host == node['host'] and dev == interface: + ip = port.get('addr') + if ip is not None: + return ip + else: + raise Exception( + 'Node {n} port {p} IPv4 address is not set'.format( + n=node['host'], p=interface)) + + raise Exception('Node {n} port {p} IPv4 address not found.'.format( + n=node['host'], p=interface)) + + @staticmethod + def setup_arp_on_all_duts(nodes_info, nodes_addr): + """For all DUT nodes extract MAC and IP addresses of adjacent + interfaces from topology and use them to setup ARP entries. + + :param nodes_info: Dictionary containing information on all nodes + in topology. + :param nodes_addr: Nodes IPv4 adresses. + :type nodes_info: dict + :type nodes_addr: dict + """ + for node in nodes_info.values(): + if node['type'] == NodeType.TG: + continue + for interface, interface_data in node['interfaces'].iteritems(): + if interface == 'mgmt': + continue + interface_name = interface_data['name'] + adj_node, adj_int = Topology.\ + get_adjacent_node_and_interface(nodes_info, node, + interface_name) + ip_address = IPv4Setup.get_ip_addr(adj_node, adj_int['name'], + nodes_addr) + mac_address = adj_int['mac_address'] + get_node(node).set_arp(interface_name, ip_address, mac_address) \ No newline at end of file diff --git a/resources/libraries/python/IPv4Util.py b/resources/libraries/python/IPv4Util.py index d3a016d767..30e185c9ff 100644 --- a/resources/libraries/python/IPv4Util.py +++ b/resources/libraries/python/IPv4Util.py @@ -13,430 +13,26 @@ """Implements IPv4 RobotFramework keywords""" -from socket import inet_ntoa -from struct import pack -from abc import ABCMeta, abstractmethod -import copy - from robot.api import logger as log from robot.api.deco import keyword -from robot.utils.asserts import assert_not_equal -import resources.libraries.python.ssh as ssh from resources.libraries.python.topology import Topology from resources.libraries.python.topology import NodeType -from resources.libraries.python.VatExecutor import VatExecutor from resources.libraries.python.TrafficScriptExecutor\ import TrafficScriptExecutor - - -class IPv4Node(object): - """Abstract class of a node in a topology.""" - __metaclass__ = ABCMeta - - def __init__(self, node_info): - self.node_info = node_info - - @staticmethod - def _get_netmask(prefix_length): - bits = 0xffffffff ^ (1 << 32 - prefix_length) - 1 - return inet_ntoa(pack('>I', bits)) - - @abstractmethod - def set_ip(self, interface, address, prefix_length): - """Configure IPv4 address on interface - - :param interface: interface name - :param address: - :param prefix_length: - :type interface: str - :type address: str - :type prefix_length: int - :return: nothing - """ - pass - - @abstractmethod - def set_interface_state(self, interface, state): - """Set interface state - - :param interface: interface name string - :param state: one of following values: "up" or "down" - :return: nothing - """ - pass - - @abstractmethod - def set_route(self, network, prefix_length, gateway, interface): - """Configure IPv4 route - - :param network: network IPv4 address - :param prefix_length: mask length - :param gateway: IPv4 address of the gateway - :param interface: interface name - :type network: str - :type prefix_length: int - :type gateway: str - :type interface: str - :return: nothing - """ - pass - - @abstractmethod - def unset_route(self, network, prefix_length, gateway, interface): - """Remove specified IPv4 route - - :param network: network IPv4 address - :param prefix_length: mask length - :param gateway: IPv4 address of the gateway - :param interface: interface name - :type network: str - :type prefix_length: int - :type gateway: str - :type interface: str - :return: nothing - """ - pass - - @abstractmethod - def flush_ip_addresses(self, interface): - """Flush all IPv4 addresses from specified interface - - :param interface: interface name - :type interface: str - :return: nothing - """ - pass - - @abstractmethod - def ping(self, destination_address, source_interface): - """Send an ICMP request to destination node - - :param destination_address: address to send the ICMP request - :param source_interface: - :type destination_address: str - :type source_interface: str - :return: nothing - """ - pass - - -class Tg(IPv4Node): - """Traffic generator node""" - def __init__(self, node_info): - super(Tg, self).__init__(node_info) - - def _execute(self, cmd): - return ssh.exec_cmd_no_error(self.node_info, cmd) - - def _sudo_execute(self, cmd): - return ssh.exec_cmd_no_error(self.node_info, cmd, sudo=True) - - def set_ip(self, interface, address, prefix_length): - cmd = 'ip -4 addr flush dev {}'.format(interface) - self._sudo_execute(cmd) - cmd = 'ip addr add {}/{} dev {}'.format(address, prefix_length, - interface) - self._sudo_execute(cmd) - - # TODO: not ipv4-specific, move to another class - def set_interface_state(self, interface, state): - cmd = 'ip link set {} {}'.format(interface, state) - self._sudo_execute(cmd) - - def set_route(self, network, prefix_length, gateway, interface): - netmask = self._get_netmask(prefix_length) - cmd = 'route add -net {} netmask {} gw {}'.\ - format(network, netmask, gateway) - self._sudo_execute(cmd) - - def unset_route(self, network, prefix_length, gateway, interface): - self._sudo_execute('ip route delete {}/{}'. - format(network, prefix_length)) - - def arp_ping(self, destination_address, source_interface): - self._sudo_execute('arping -c 1 -I {} {}'.format(source_interface, - destination_address)) - - def ping(self, destination_address, source_interface): - self._execute('ping -c 1 -w 5 -I {} {}'.format(source_interface, - destination_address)) - - def flush_ip_addresses(self, interface): - self._sudo_execute('ip addr flush dev {}'.format(interface)) - - -class Dut(IPv4Node): - """Device under test""" - def __init__(self, node_info): - super(Dut, self).__init__(node_info) - - def get_sw_if_index(self, interface): - """Get sw_if_index of specified interface from current node - - :param interface: interface name - :type interface: str - :return: sw_if_index of 'int' type - """ - return Topology().get_interface_sw_index(self.node_info, interface) - - def exec_vat(self, script, **args): - """Wrapper for VAT executor. - - :param script: script to execute - :param args: parameters to the script - :type script: str - :type args: dict - :return: nothing - """ - # TODO: check return value - VatExecutor.cmd_from_template(self.node_info, script, **args) - - def set_arp(self, interface, ip_address, mac_address): - """Set entry in ARP cache. - - :param interface: Interface name. - :param ip_address: IP address. - :param mac_address: MAC address. - :type interface: str - :type ip_address: str - :type mac_address: str - """ - self.exec_vat('add_ip_neighbor.vat', - sw_if_index=self.get_sw_if_index(interface), - ip_address=ip_address, mac_address=mac_address) - - def set_ip(self, interface, address, prefix_length): - self.exec_vat('add_ip_address.vat', - sw_if_index=self.get_sw_if_index(interface), - address=address, prefix_length=prefix_length) - - def set_interface_state(self, interface, state): - if state == 'up': - state = 'admin-up link-up' - elif state == 'down': - state = 'admin-down link-down' - else: - raise Exception('Unexpected interface state: {}'.format(state)) - - self.exec_vat('set_if_state.vat', - sw_if_index=self.get_sw_if_index(interface), state=state) - - def set_route(self, network, prefix_length, gateway, interface): - sw_if_index = self.get_sw_if_index(interface) - self.exec_vat('add_route.vat', - network=network, prefix_length=prefix_length, - gateway=gateway, sw_if_index=sw_if_index) - - def unset_route(self, network, prefix_length, gateway, interface): - self.exec_vat('del_route.vat', network=network, - prefix_length=prefix_length, gateway=gateway, - sw_if_index=self.get_sw_if_index(interface)) - - def arp_ping(self, destination_address, source_interface): - pass - - def flush_ip_addresses(self, interface): - self.exec_vat('flush_ip_addresses.vat', - sw_if_index=self.get_sw_if_index(interface)) - - def ping(self, destination_address, source_interface): - pass - - -def get_node(node_info): - """Creates a class instance derived from Node based on type. - - :param node_info: dictionary containing information on nodes in topology - :return: Class instance that is derived from Node - """ - if node_info['type'] == NodeType.TG: - return Tg(node_info) - elif node_info['type'] == NodeType.DUT: - return Dut(node_info) - else: - raise NotImplementedError('Node type "{}" unsupported!'. - format(node_info['type'])) - - -def get_node_hostname(node_info): - """Get string identifying specifed node. - - :param node_info: Node in the topology. - :type node_info: Dict - :return: String identifying node. - """ - return node_info['host'] +from resources.libraries.python.IPv4Setup import get_node class IPv4Util(object): """Implements keywords for IPv4 tests.""" - ADDRESSES = {} # holds configured IPv4 addresses - PREFIXES = {} # holds configured IPv4 addresses' prefixes - SUBNETS = {} # holds configured IPv4 addresses' subnets - - """ - Helper dictionary used when setting up ipv4 addresses in topology - - Example value: - 'link1': { b'port1': {b'addr': b'192.168.3.1'}, - b'port2': {b'addr': b'192.168.3.2'}, - b'prefix': 24, - b'subnet': b'192.168.3.0'} - """ - topology_helper = None - - @staticmethod - def setup_arp_on_all_duts(nodes_info): - """For all DUT nodes extract MAC and IP addresses of adjacent interfaces - from topology and use them to setup ARP entries. - - :param nodes_info: Dictionary containing information on all nodes - in topology. - :type nodes_info: dict - """ - for node in nodes_info.values(): - if node['type'] == NodeType.TG: - continue - for interface, interface_data in node['interfaces'].iteritems(): - if interface == 'mgmt': - continue - interface_name = interface_data['name'] - adj_node, adj_int = Topology.\ - get_adjacent_node_and_interface(nodes_info, node, - interface_name) - ip_address = IPv4Util.get_ip_addr(adj_node, adj_int['name']) - mac_address = adj_int['mac_address'] - get_node(node).set_arp(interface_name, ip_address, mac_address) - - @staticmethod - def next_address(subnet): - """Get next unused IPv4 address from a subnet - - :param subnet: holds available IPv4 addresses - :return: tuple (ipv4_address, prefix_length) - """ - for i in range(1, 4): - # build a key and try to get it from address dictionary - interface = 'port{}'.format(i) - if interface in subnet: - addr = subnet[interface]['addr'] - del subnet[interface] - return addr, subnet['prefix'] - raise Exception('Not enough ipv4 addresses in subnet') - - @staticmethod - def next_network(nodes_addr): - """Get next unused network from dictionary - - :param nodes_addr: dictionary of available networks - :return: dictionary describing an IPv4 subnet with addresses - """ - assert_not_equal(len(nodes_addr), 0, 'Not enough networks') - _, subnet = nodes_addr.popitem() - return subnet - - @staticmethod - def configure_ipv4_addr_on_node(node, nodes_addr): - """Configure IPv4 address for all non-management interfaces - on a node in topology. - - :param node: dictionary containing information about node - :param nodes_addr: dictionary containing IPv4 addresses - :return: - """ - for interface, interface_data in node['interfaces'].iteritems(): - if interface == 'mgmt': - continue - if interface_data['link'] not in IPv4Util.topology_helper: - IPv4Util.topology_helper[interface_data['link']] = \ - IPv4Util.next_network(nodes_addr) - - network = IPv4Util.topology_helper[interface_data['link']] - address, prefix = IPv4Util.next_address(network) - - if node['type'] != NodeType.TG: - get_node(node).set_ip(interface_data['name'], address, prefix) - get_node(node).set_interface_state(interface_data['name'], 'up') - - key = (get_node_hostname(node), interface_data['name']) - IPv4Util.ADDRESSES[key] = address - IPv4Util.PREFIXES[key] = prefix - IPv4Util.SUBNETS[key] = network['subnet'] - - @staticmethod - def dut_nodes_setup_ipv4_addresses(nodes_info, nodes_addr): - """Configure IPv4 addresses on all non-management interfaces for each - node in nodes_info if node type is not traffic generator - - :param nodes_info: dictionary containing information on all nodes - in topology - :param nodes_addr: dictionary containing IPv4 addresses - :return: nothing - """ - IPv4Util.topology_helper = {} - # make a deep copy of nodes_addr because of modifications - nodes_addr_copy = copy.deepcopy(nodes_addr) - for node in nodes_info.values(): - IPv4Util.configure_ipv4_addr_on_node(node, nodes_addr_copy) - - @staticmethod - def nodes_clear_ipv4_addresses(nodes): - """Clear all addresses from all nodes in topology - - :param nodes: dictionary containing information on all nodes - :return: nothing - """ - for node in nodes.values(): - for interface, interface_data in node['interfaces'].iteritems(): - if interface == 'mgmt': - continue - IPv4Util.flush_ip_addresses(interface_data['name'], node) - - # TODO: not ipv4-specific, move to another class - @staticmethod - @keyword('Node "${node}" interface "${interface}" is in "${state}" state') - def set_interface_state(node, interface, state): - """See IPv4Node.set_interface_state for more information. - - :param node: - :param interface: - :param state: - :return: - """ - log.debug('Node {} interface {} is in {} state'.format( - get_node_hostname(node), interface, state)) - get_node(node).set_interface_state(interface, state) - - @staticmethod - @keyword('Node "${node}" interface "${port}" has IPv4 address ' - '"${address}" with prefix length "${prefix_length}"') - def set_interface_address(node, interface, address, length): - """See IPv4Node.set_ip for more information. - - :param node: - :param interface: - :param address: - :param length: - :return: - """ - log.debug('Node {} interface {} has IPv4 address {} with prefix ' - 'length {}'.format(get_node_hostname(node), interface, - address, length)) - get_node(node).set_ip(interface, address, int(length)) - hostname = get_node_hostname(node) - IPv4Util.ADDRESSES[hostname, interface] = address - IPv4Util.PREFIXES[hostname, interface] = int(length) - # TODO: Calculate subnet from ip address and prefix length. - # IPv4Util.SUBNETS[hostname, interface] = - @staticmethod @keyword('From node "${node}" interface "${port}" ARP-ping ' 'IPv4 address "${ip_address}"') def arp_ping(node, interface, ip_address): log.debug('From node {} interface {} ARP-ping IPv4 address {}'. - format(get_node_hostname(node), interface, ip_address)) + format(Topology.get_node_hostname(node), + interface, ip_address)) get_node(node).arp_ping(ip_address, interface) @staticmethod @@ -454,40 +50,28 @@ class IPv4Util(object): :return: """ log.debug('Node {} routes to network {} with prefix length {} ' - 'via {} interface {}'.format(get_node_hostname(node), + 'via {} interface {}'.format(Topology.get_node_hostname(node), network, prefix_length, gateway, interface)) get_node(node).set_route(network, int(prefix_length), gateway, interface) - @staticmethod - @keyword('Remove IPv4 route from "${node}" to network "${network}" with ' - 'prefix length "${prefix_length}" interface "${interface}" via ' - '"${gateway}"') - def unset_route(node, network, prefix_length, interface, gateway): - """See IPv4Node.unset_route for more information. - - :param node: - :param network: - :param prefix_length: - :param interface: - :param gateway: - :return: - """ - get_node(node).unset_route(network, prefix_length, gateway, interface) - @staticmethod @keyword('After ping is sent in topology "${nodes_info}" from node ' - '"${src_node}" interface "${src_port}" with destination IPv4 ' - 'address of node "${dst_node}" interface "${dst_port}" a ping ' - 'response arrives and TTL is decreased by "${hops}"') - def send_ping(nodes_info, src_node, src_port, dst_node, dst_port, hops): + '"${src_node}" interface "${src_port}" "${src_ip}" with ' + 'destination IPv4 address "${dst_ip}" of node "${dst_node}" ' + 'interface "${dst_port}" a ping response arrives and TTL is ' + 'decreased by "${hops}"') + def send_ping(nodes_info, src_node, src_port, src_ip, dst_ip, dst_node, + dst_port, hops): """Send IPv4 ping and wait for response. :param nodes_info: Dictionary containing information on all nodes in topology. :param src_node: Source node. :param src_port: Source interface. + :param src_ip: Source ipv4 address. + :param dst_ip: Destination ipv4 address. :param dst_node: Destination node. :param dst_port: Destination interface. :param hops: Number of hops between src_node and dst_node. @@ -495,61 +79,58 @@ class IPv4Util(object): log.debug('After ping is sent from node "{}" interface "{}" ' 'with destination IPv4 address of node "{}" interface "{}" ' 'a ping response arrives and TTL is decreased by "${}"'. - format(get_node_hostname(src_node), src_port, - get_node_hostname(dst_node), dst_port, hops)) - node = src_node + format(Topology.get_node_hostname(src_node), src_port, + Topology.get_node_hostname(dst_node), dst_port, hops)) + dst_mac = None src_mac = Topology.get_interface_mac(src_node, src_port) if dst_node['type'] == NodeType.TG: dst_mac = Topology.get_interface_mac(src_node, src_port) _, adj_int = Topology.\ get_adjacent_node_and_interface(nodes_info, src_node, src_port) first_hop_mac = adj_int['mac_address'] - src_ip = IPv4Util.get_ip_addr(src_node, src_port) - dst_ip = IPv4Util.get_ip_addr(dst_node, dst_port) args = '--src_if "{}" --src_mac "{}" --first_hop_mac "{}" ' \ '--src_ip "{}" --dst_ip "{}" --hops "{}"'\ .format(src_port, src_mac, first_hop_mac, src_ip, dst_ip, hops) if dst_node['type'] == NodeType.TG: args += ' --dst_if "{}" --dst_mac "{}"'.format(dst_port, dst_mac) TrafficScriptExecutor.run_traffic_script_on_node( - "ipv4_ping_ttl_check.py", node, args) + "ipv4_ping_ttl_check.py", src_node, args) @staticmethod - @keyword('Get IPv4 address of node "${node}" interface "${port}"') - def get_ip_addr(node, port): - """Get IPv4 address configured on specified interface - - :param node: node dictionary - :param port: interface name - :return: IPv4 address of specified interface as a 'str' type - """ - log.debug('Get IPv4 address of node {} interface {}'. - format(get_node_hostname(node), port)) - return IPv4Util.ADDRESSES[(get_node_hostname(node), port)] - - @staticmethod - @keyword('Get IPv4 address prefix of node "${node}" interface "${port}"') - def get_ip_addr_prefix(node, port): + @keyword('Get IPv4 address prefix of node "${node}" interface "${port}" ' + 'from "${nodes_addr}"') + def get_ip_addr_prefix_length(node, port, nodes_addr): """ Get IPv4 address prefix for specified interface. :param node: Node dictionary. :param port: Interface name. + :return: IPv4 prefix length """ - log.debug('Get IPv4 address prefix of node {} interface {}'. - format(get_node_hostname(node), port)) - return IPv4Util.PREFIXES[(get_node_hostname(node), port)] + for net in nodes_addr.values(): + for p in net['ports'].values(): + if p['node'] == node['host'] and p['if'] == port: + return net['prefix'] + + raise Exception('Subnet not found for node {n} port {p}'. + format(n=node['host'], p=port)) @staticmethod - @keyword('Get IPv4 subnet of node "${node}" interface "${port}"') - def get_ip_addr_subnet(node, port): + @keyword('Get IPv4 subnet of node "${node}" interface "${port}" from ' + '"${nodes_addr}"') + def get_ip_addr_subnet(node, port, nodes_addr): """ Get IPv4 subnet of specified interface. :param node: Node dictionary. :param port: Interface name. + :return: IPv4 subnet of 'str' type """ - log.debug('Get IPv4 subnet of node {} interface {}'. - format(get_node_hostname(node), port)) - return IPv4Util.SUBNETS[(get_node_hostname(node), port)] + for net in nodes_addr.values(): + for p in net['ports'].values(): + if p['node'] == node['host'] and p['if'] == port: + return net['net_addr'] + + raise Exception('Subnet not found for node {n} port {p}'. + format(n=node['host'], p=port)) @staticmethod @keyword('Flush IPv4 addresses "${port}" "${node}"') @@ -560,8 +141,4 @@ class IPv4Util(object): :param node: :return: """ - key = (get_node_hostname(node), port) - del IPv4Util.ADDRESSES[key] - del IPv4Util.PREFIXES[key] - del IPv4Util.SUBNETS[key] get_node(node).flush_ip_addresses(port) diff --git a/resources/libraries/python/InterfaceUtil.py b/resources/libraries/python/InterfaceUtil.py new file mode 100644 index 0000000000..bc8bf94a9b --- /dev/null +++ b/resources/libraries/python/InterfaceUtil.py @@ -0,0 +1,54 @@ +# Copyright (c) 2016 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Interface util library""" + +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 + + +class InterfaceUtil(object): + """General utilities for managing interfaces""" + + @staticmethod + def set_interface_state(node, interface, state): + """Set interface state on a node. + + Function can be used for DUTs as well as for TGs. + + :param node: node where the interface is + :param interface: interface name + :param state: one of 'up' or 'down' + :type node: dict + :type interface: str + :type state: str + :return: nothing + """ + if node['type'] == NodeType.DUT: + if state == 'up': + state = 'admin-up link-up' + elif state == 'down': + state = 'admin-down link-down' + else: + raise Exception('Unexpected interface state: {}'.format(state)) + + sw_if_index = Topology.get_interface_sw_index(node, interface) + VatExecutor.cmd_from_template(node, 'set_if_state.vat', + sw_if_index=sw_if_index, state=state) + + elif node['type'] == NodeType.TG: + cmd = 'ip link set {} {}'.format(interface, state) + exec_cmd_no_error(node, cmd, sudo=True) + else: + raise Exception('Unknown NodeType: "{}"'.format(node['type'])) diff --git a/resources/libraries/python/topology.py b/resources/libraries/python/topology.py index 6a7ea798cc..8b6905d7e1 100644 --- a/resources/libraries/python/topology.py +++ b/resources/libraries/python/topology.py @@ -647,3 +647,20 @@ class Topology(object): 'DUT1_BD_LINKS': dut1_bd_links, 'DUT2_BD_LINKS': dut2_bd_links} return topology_links + + @staticmethod + def is_tg_node(node): + """Find out whether the node is TG + + :param node: node to examine + :return: True if node is type of TG; False otherwise + """ + return node['type'] == NodeType.TG + + @staticmethod + def get_node_hostname(node): + """ + :param node: node dictionary + :return: host name as 'str' type + """ + return node['host'] \ No newline at end of file diff --git a/resources/libraries/robot/ipv4.robot b/resources/libraries/robot/ipv4.robot index b5f313df89..a5745a8ce0 100644 --- a/resources/libraries/robot/ipv4.robot +++ b/resources/libraries/robot/ipv4.robot @@ -13,31 +13,26 @@ *** Settings *** | Resource | resources/libraries/robot/default.robot | Resource | resources/libraries/robot/counters.robot -| Library | resources.libraries.python.IPv4Util +| Library | resources.libraries.python.IPv4Util.IPv4Util +| Library | resources.libraries.python.IPv4Setup.IPv4Setup | Library | resources.libraries.python.TrafficScriptExecutor -| Variables | resources/libraries/python/IPv4NodeAddress.py +| Variables | resources/libraries/python/IPv4NodeAddress.py | ${nodes} *** Keywords *** | Setup IPv4 adresses on all DUT nodes in topology | | [Documentation] | Setup IPv4 address on all DUTs in topology | | [Arguments] | ${nodes} | ${nodes_addr} -| | DUT Nodes setup IPv4 addresses | ${nodes} | ${nodes_addr} - -| Interfaces needed for IPv4 testing are in "${state}" state -| | Node "${nodes['DUT1']}" interface "${nodes['DUT1']['interfaces']['port1']['name']}" is in "${state}" state -| | Node "${nodes['DUT1']}" interface "${nodes['DUT1']['interfaces']['port3']['name']}" is in "${state}" state -| | Node "${nodes['DUT2']}" interface "${nodes['DUT2']['interfaces']['port1']['name']}" is in "${state}" state -| | Node "${nodes['DUT2']}" interface "${nodes['DUT2']['interfaces']['port3']['name']}" is in "${state}" state +| | VPP nodes setup ipv4 addresses | ${nodes} | ${nodes_addr} | Routes are set up for IPv4 testing -| | ${gateway} = | Get IPv4 address of node "${nodes['DUT2']}" interface "${nodes['DUT2']['interfaces']['port3']['name']}" -| | ${subnet} = | Get IPv4 subnet of node "${nodes['DUT2']}" interface "${nodes['DUT2']['interfaces']['port1']['name']}" -| | ${prefix_length} = | Get IPv4 address prefix of node "${nodes['DUT2']}" interface "${nodes['DUT2']['interfaces']['port1']['name']}" +| | ${gateway}= | Get IPv4 address of node "${nodes['DUT2']}" interface "${nodes['DUT2']['interfaces']['port3']['name']}" from "${nodes_ipv4_addr}" +| | ${subnet} = | Get IPv4 subnet of node "${nodes['DUT2']}" interface "${nodes['DUT2']['interfaces']['port1']['name']}" from "${nodes_ipv4_addr}" +| | ${prefix_length} = | Get IPv4 address prefix of node "${nodes['DUT2']}" interface "${nodes['DUT2']['interfaces']['port1']['name']}" from "${nodes_ipv4_addr}" | | Node "${nodes['DUT1']}" routes to IPv4 network "${subnet}" with prefix length "${prefix_length}" using interface "${nodes['DUT1']['interfaces']['port3']['name']}" via "${gateway}" -| | ${gateway} = | Get IPv4 address of node "${nodes['DUT1']}" interface "${nodes['DUT1']['interfaces']['port3']['name']}" -| | ${subnet} = | Get IPv4 subnet of node "${nodes['DUT1']}" interface "${nodes['DUT1']['interfaces']['port1']['name']}" -| | ${prefix_length} = | Get IPv4 address prefix of node "${nodes['DUT1']}" interface "${nodes['DUT1']['interfaces']['port1']['name']}" +| | ${gateway} = | Get IPv4 address of node "${nodes['DUT1']}" interface "${nodes['DUT1']['interfaces']['port3']['name']}" from "${nodes_ipv4_addr}" +| | ${subnet} = | Get IPv4 subnet of node "${nodes['DUT1']}" interface "${nodes['DUT1']['interfaces']['port1']['name']}" from "${nodes_ipv4_addr}" +| | ${prefix_length} = | Get IPv4 address prefix of node "${nodes['DUT1']}" interface "${nodes['DUT1']['interfaces']['port1']['name']}" from "${nodes_ipv4_addr}" | | Node "${nodes['DUT2']}" routes to IPv4 network "${subnet}" with prefix length "${prefix_length}" using interface "${nodes['DUT2']['interfaces']['port3']['name']}" via "${gateway}" | Setup DUT nodes for IPv4 testing @@ -47,22 +42,31 @@ | | Routes are set up for IPv4 testing | Setup nodes for IPv4 testing -| | Interfaces needed for IPv4 testing are in "up" state | | Setup IPv4 adresses on all DUT nodes in topology | ${nodes} | ${nodes_ipv4_addr} -| | Setup ARP on all DUTs | ${nodes} +| | Setup ARP on all DUTs | ${nodes} | ${nodes_ipv4_addr} | | Routes are set up for IPv4 testing | TG interface "${tg_port}" can route to node "${node}" interface "${port}" "${hops}" hops away using IPv4 | | Node "${nodes['TG']}" interface "${tg_port}" can route to node "${node}" interface "${port}" "${hops}" hops away using IPv4 | Node "${from_node}" interface "${from_port}" can route to node "${to_node}" interface "${to_port}" "${hops}" hops away using IPv4 -| | After ping is sent in topology "${nodes}" from node "${from_node}" interface "${from_port}" with destination IPv4 address of node "${to_node}" interface "${to_port}" a ping response arrives and TTL is decreased by "${hops}" +| | ${src_ip}= | Get IPv4 address of node "${from_node}" interface "${from_port}" from "${nodes_ipv4_addr}" +| | ${dst_ip}= | Get IPv4 address of node "${to_node}" interface "${to_port}" from "${nodes_ipv4_addr}" +| | ${src_mac}= | Get interface mac | ${from_node} | ${from_port} +| | ${dst_mac}= | Get interface mac | ${to_node} | ${to_port} +| | ${is_dst_tg}= | Is TG node | ${to_node} +| | ${adj_node} | ${adj_int}= | Get adjacent node and interface | ${nodes} | ${from_node} | ${from_port} +| | ${args}= | Traffic Script Gen Arg | ${to_port} | ${from_port} | ${src_mac} +| | | ... | ${dst_mac} | ${src_ip} | ${dst_ip} +| | ${args}= | Catenate | ${args} | --hops ${hops} | --first_hop_mac ${adj_int['mac_address']} +| | | ... | --is_dst_defined ${is_dst_tg} +| | Run Traffic Script On Node | ipv4_ping_ttl_check.py | ${from_node} | ${args} | Ipv4 icmp echo sweep | | [Documentation] | Type of the src_node must be TG and dst_node must be DUT | | [Arguments] | ${src_node} | ${dst_node} | ${src_port} | ${dst_port} -| | ${src_ip}= | Get IPv4 address of node "${src_node}" interface "${src_port}" -| | ${dst_ip}= | Get IPv4 address of node "${dst_node}" interface "${dst_port}" +| | ${src_ip}= | Get IPv4 address of node "${src_node}" interface "${src_port}" from "${nodes_ipv4_addr}" +| | ${dst_ip}= | Get IPv4 address of node "${dst_node}" interface "${dst_port}" from "${nodes_ipv4_addr}" | | ${src_mac}= | Get Interface Mac | ${src_node} | ${src_port} | | ${dst_mac}= | Get Interface Mac | ${dst_node} | ${dst_port} | | ${args}= | Traffic Script Gen Arg | ${src_port} | ${src_port} | ${src_mac} @@ -78,8 +82,8 @@ | | ${link_name}= | Get first active connecting link between node "${tg_node}" and "${vpp_node}" | | ${src_if}= | Get interface by link name | ${tg_node} | ${link_name} | | ${dst_if}= | Get interface by link name | ${vpp_node} | ${link_name} -| | ${src_ip}= | Get IPv4 address of node "${tg_node}" interface "${src_if}" -| | ${dst_ip}= | Get IPv4 address of node "${vpp_node}" interface "${dst_if}" +| | ${src_ip}= | Get IPv4 address of node "${tg_node}" interface "${src_if}" from "${nodes_ipv4_addr}" +| | ${dst_ip}= | Get IPv4 address of node "${vpp_node}" interface "${dst_if}" from "${nodes_ipv4_addr}" | | ${src_mac}= | Get node link mac | ${tg_node} | ${link_name} | | ${dst_mac}= | Get node link mac | ${vpp_node} | ${link_name} | | ${args}= | Traffic Script Gen Arg | ${src_if} | ${src_if} | ${src_mac} diff --git a/resources/libraries/robot/l2_xconnect.robot b/resources/libraries/robot/l2_xconnect.robot index 2603863c91..f42c748970 100644 --- a/resources/libraries/robot/l2_xconnect.robot +++ b/resources/libraries/robot/l2_xconnect.robot @@ -17,7 +17,7 @@ | Library | resources.libraries.python.CrossConnectSetup | Library | resources.libraries.python.topology.Topology | Library | resources.libraries.python.TrafficScriptExecutor -| Library | resources.libraries.python.IPv4Util +| Library | resources.libraries.python.InterfaceUtil.InterfaceUtil | Variables | resources/libraries/python/constants.py *** Keywords *** @@ -52,8 +52,7 @@ | Interfaces on all DUTs are in "${state}" state -| | Node "${nodes['DUT1']}" interface "${nodes['DUT1']['interfaces']['port1']['name']}" is in "${state}" state -| | Node "${nodes['DUT1']}" interface "${nodes['DUT1']['interfaces']['port3']['name']}" is in "${state}" state -| | Node "${nodes['DUT2']}" interface "${nodes['DUT2']['interfaces']['port1']['name']}" is in "${state}" state -| | Node "${nodes['DUT2']}" interface "${nodes['DUT2']['interfaces']['port3']['name']}" is in "${state}" state - +| | Set interface state | ${nodes['DUT1']} | ${nodes['DUT1']['interfaces']['port1']['name']} | ${state} +| | Set interface state | ${nodes['DUT1']} | ${nodes['DUT1']['interfaces']['port3']['name']} | ${state} +| | Set interface state | ${nodes['DUT2']} | ${nodes['DUT2']['interfaces']['port1']['name']} | ${state} +| | Set interface state | ${nodes['DUT2']} | ${nodes['DUT2']['interfaces']['port3']['name']} | ${state} diff --git a/resources/traffic_scripts/ipv4_ping_ttl_check.py b/resources/traffic_scripts/ipv4_ping_ttl_check.py index 1286b46876..2fd9d552ea 100755 --- a/resources/traffic_scripts/ipv4_ping_ttl_check.py +++ b/resources/traffic_scripts/ipv4_ping_ttl_check.py @@ -16,7 +16,7 @@ from scapy.all import Ether, IP, ICMP from resources.libraries.python.PacketVerifier \ import Interface, create_gratuitous_arp_request, auto_pad -from optparse import OptionParser +from resources.libraries.python.TrafficScriptArg import TrafficScriptArg def check_ttl(ttl_begin, ttl_end, ttl_diff): @@ -45,28 +45,19 @@ def ckeck_packets_equal(pkt_send, pkt_recv): raise Exception("Sent packet doesn't match received packet") -parser = OptionParser() -parser.add_option("--src_if", dest="src_if") -parser.add_option("--dst_if", dest="dst_if") # optional -parser.add_option("--src_mac", dest="src_mac") -parser.add_option("--first_hop_mac", dest="first_hop_mac") -parser.add_option("--dst_mac", dest="dst_mac") # optional -parser.add_option("--src_ip", dest="src_ip") -parser.add_option("--dst_ip", dest="dst_ip") -parser.add_option("--hops", dest="hops") # optional -# If one of 'dst_if', 'dst_mac' and 'hops' is specified all must be specified. -(opts, args) = parser.parse_args() -src_if_name = opts.src_if -dst_if_name = opts.dst_if -dst_if_defined = True -if dst_if_name is None: - dst_if_defined = False -src_mac = opts.src_mac -first_hop_mac = opts.first_hop_mac -dst_mac = opts.dst_mac -src_ip = opts.src_ip -dst_ip = opts.dst_ip -hops = int(opts.hops) +args = TrafficScriptArg(['src_mac', 'dst_mac', 'src_ip', 'dst_ip', + 'hops', 'first_hop_mac', 'is_dst_defined']) + +src_if_name = args.get_arg('tx_if') +dst_if_name = args.get_arg('rx_if') +dst_if_defined = True if args.get_arg('is_dst_defined') == 'True' else False + +src_mac = args.get_arg('src_mac') +first_hop_mac = args.get_arg('first_hop_mac') +dst_mac = args.get_arg('dst_mac') +src_ip = args.get_arg('src_ip') +dst_ip = args.get_arg('dst_ip') +hops = int(args.get_arg('hops')) if dst_if_defined and (src_if_name == dst_if_name): raise Exception("Source interface name equals destination interface name")