From: Stefan Kobza Date: Thu, 11 Feb 2016 18:09:06 +0000 (+0100) Subject: Update of latest tests. X-Git-Url: https://gerrit.fd.io/r/gitweb?p=csit.git;a=commitdiff_plain;h=b92a827b1c7f48da4214e992e5503ebe1c182416 Update of latest tests. Change-Id: Ifb04651ff4a3c1ba9aaa725eb9a309278ebc1140 Signed-off-by: Stefan Kobza --- diff --git a/bootstrap.sh b/bootstrap.sh index 835759c6ac..4a91121f50 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -9,18 +9,20 @@ set -euf -o pipefail # #ls -la -set -x - -ping 10.30.51.17 -w 3 || true -ping 10.30.51.18 -w 3 || true -ping 10.30.51.16 -w 3 || true -ping 10.30.51.21 -w 3 || true -ping 10.30.51.22 -w 3 || true -ping 10.30.51.20 -w 3 || true -ping 10.30.51.25 -w 3 || true -ping 10.30.51.26 -w 3 || true -ping 10.30.51.24 -w 3 || true - +#set -x +# +#ping 10.30.51.17 -w 3 || true +#ping 10.30.51.18 -w 3 || true +#ping 10.30.51.16 -w 3 || true +#ping 10.30.51.21 -w 3 || true +#ping 10.30.51.22 -w 3 || true +#ping 10.30.51.20 -w 3 || true +#ping 10.30.51.25 -w 3 || true +#ping 10.30.51.26 -w 3 || true +#ping 10.30.51.24 -w 3 || true + + +exit 0 #IFS=',' read -ra ADDR <<< "${JCLOUDS_IPS}" # diff --git a/docs/tg_interface_driver b/docs/tg_interface_driver new file mode 100644 index 0000000000..a45999a819 --- /dev/null +++ b/docs/tg_interface_driver @@ -0,0 +1,14 @@ +If using traffic scripts in test add "| Suite Setup | Setup all TGs before +traffic script" to test suite robot file, this bind TG interfaces to the kernel +driver specified in topology. Also add kernel driver name for TG interfaces to +topology YAML file. You can find driver name with following command where you +specify interface PCI address: +# lspci -vmmks 0000:00:07.0 +Slot: 00:07.0 +Class: Ethernet controller +Vendor: Red Hat, Inc +Device: Virtio network device +SVendor: Red Hat, Inc +SDevice: Device 0001 +PhySlot: 7 +Driver: virtio-pci diff --git a/resources/libraries/python/CrossConnectSetup.py b/resources/libraries/python/CrossConnectSetup.py new file mode 100644 index 0000000000..3f1f7d42b6 --- /dev/null +++ b/resources/libraries/python/CrossConnectSetup.py @@ -0,0 +1,45 @@ +# 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. + +"""Library to set up cross-connect in topology.""" + +from resources.libraries.python.VatExecutor import VatExecutor +from resources.libraries.python.topology import Topology + +__all__ = ['CrossConnectSetup'] + +class CrossConnectSetup(object): + """Crossconnect setup in topology.""" + + def __init__(self): + pass + + @staticmethod + def vpp_setup_bidirectional_cross_connect(node, interface1, interface2): + """Create crossconnect between 2 interfaces on vpp node. + + :param node: Node to add bidirectional crossconnect + :param interface1: first interface + :param interface2: second interface + :type node: dict + :type interface1: str + :type interface2: str + """ + sw_iface1 = Topology().get_interface_sw_index(node, interface1) + sw_iface2 = Topology().get_interface_sw_index(node, interface2) + VatExecutor.cmd_from_template(node, "l2_xconnect.vat", + interface1=sw_iface1, + interface2=sw_iface2) + VatExecutor.cmd_from_template(node, "l2_xconnect.vat", + interface1=sw_iface2, + interface2=sw_iface1) diff --git a/resources/libraries/python/IPv4NodeAddress.py b/resources/libraries/python/IPv4NodeAddress.py index 0f2c1d9cd3..8db9ffafe3 100644 --- a/resources/libraries/python/IPv4NodeAddress.py +++ b/resources/libraries/python/IPv4NodeAddress.py @@ -19,9 +19,9 @@ from ipaddress import IPv4Network # Default list of IPv4 subnets -IPV4_NETWORKS = ['192.168.1.0/24', - '192.168.2.0/24', - '192.168.3.0/24'] +IPV4_NETWORKS = ['20.20.20.0/24', + '10.10.10.0/24', + '1.1.1.0/30'] class IPv4NetworkGenerator(object): diff --git a/resources/libraries/python/IPv4Util.py b/resources/libraries/python/IPv4Util.py index 5480bfcea3..d3a016d767 100644 --- a/resources/libraries/python/IPv4Util.py +++ b/resources/libraries/python/IPv4Util.py @@ -45,6 +45,7 @@ class IPv4Node(object): @abstractmethod def set_ip(self, interface, address, prefix_length): """Configure IPv4 address on interface + :param interface: interface name :param address: :param prefix_length: @@ -58,6 +59,7 @@ class IPv4Node(object): @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 @@ -67,6 +69,7 @@ class IPv4Node(object): @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 @@ -82,6 +85,7 @@ class IPv4Node(object): @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 @@ -97,6 +101,7 @@ class IPv4Node(object): @abstractmethod def flush_ip_addresses(self, interface): """Flush all IPv4 addresses from specified interface + :param interface: interface name :type interface: str :return: nothing @@ -106,6 +111,7 @@ class IPv4Node(object): @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 @@ -167,6 +173,7 @@ class Dut(IPv4Node): 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 @@ -175,6 +182,7 @@ class Dut(IPv4Node): def exec_vat(self, script, **args): """Wrapper for VAT executor. + :param script: script to execute :param args: parameters to the script :type script: str @@ -184,6 +192,20 @@ class Dut(IPv4Node): # 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), @@ -224,6 +246,7 @@ class Dut(IPv4Node): 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 """ @@ -238,6 +261,7 @@ def get_node(node_info): 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. @@ -263,9 +287,33 @@ class IPv4Util(object): """ 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) """ @@ -281,6 +329,7 @@ class IPv4Util(object): @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 """ @@ -290,7 +339,9 @@ class IPv4Util(object): @staticmethod def configure_ipv4_addr_on_node(node, nodes_addr): - """Configure IPv4 address for all interfaces on a node in topology + """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: @@ -305,16 +356,20 @@ class IPv4Util(object): network = IPv4Util.topology_helper[interface_data['link']] address, prefix = IPv4Util.next_address(network) - get_node(node).set_ip(interface_data['name'], address, prefix) + 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 nodes_setup_ipv4_addresses(nodes_info, nodes_addr): + def dut_nodes_setup_ipv4_addresses(nodes_info, nodes_addr): """Configure IPv4 addresses on all non-management interfaces for each - node in nodes_info + 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 @@ -323,16 +378,17 @@ class IPv4Util(object): 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.iteritems(): + 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.iteritems(): + for node in nodes.values(): for interface, interface_data in node['interfaces'].iteritems(): if interface == 'mgmt': continue @@ -343,6 +399,7 @@ class IPv4Util(object): @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: @@ -357,6 +414,7 @@ class IPv4Util(object): '"${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: @@ -387,6 +445,7 @@ class IPv4Util(object): '"${gateway}"') def set_route(node, network, prefix_length, interface, gateway): """See IPv4Node.set_route for more information. + :param node: :param network: :param prefix_length: @@ -407,6 +466,7 @@ class IPv4Util(object): '"${gateway}"') def unset_route(node, network, prefix_length, interface, gateway): """See IPv4Node.unset_route for more information. + :param node: :param network: :param prefix_length: @@ -417,12 +477,15 @@ class IPv4Util(object): get_node(node).unset_route(network, prefix_length, gateway, interface) @staticmethod - @keyword('After ping is sent 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 "${ttl_dec}"') - def send_ping(src_node, src_port, dst_node, dst_port, hops): + @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): """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 dst_node: Destination node. @@ -438,7 +501,8 @@ class IPv4Util(object): 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_interface(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) @@ -454,6 +518,7 @@ class IPv4Util(object): @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 @@ -466,6 +531,7 @@ class IPv4Util(object): @keyword('Get IPv4 address prefix of node "${node}" interface "${port}"') def get_ip_addr_prefix(node, port): """ Get IPv4 address prefix for specified interface. + :param node: Node dictionary. :param port: Interface name. """ @@ -477,6 +543,7 @@ class IPv4Util(object): @keyword('Get IPv4 subnet of node "${node}" interface "${port}"') def get_ip_addr_subnet(node, port): """ Get IPv4 subnet of specified interface. + :param node: Node dictionary. :param port: Interface name. """ @@ -488,6 +555,7 @@ class IPv4Util(object): @keyword('Flush IPv4 addresses "${port}" "${node}"') def flush_ip_addresses(port, node): """See IPv4Node.flush_ip_addresses for more information. + :param port: :param node: :return: diff --git a/resources/libraries/python/L2Util.py b/resources/libraries/python/L2Util.py new file mode 100644 index 0000000000..8581b1e879 --- /dev/null +++ b/resources/libraries/python/L2Util.py @@ -0,0 +1,119 @@ +# 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. + +"""L2 bridge domain utilities Library.""" + +from robot.api.deco import keyword +from resources.libraries.python.topology import Topology +from resources.libraries.python.VatExecutor import VatExecutor + + +class L2Util(object): + """Utilities for l2 bridge domain configuration""" + + def __init__(self): + pass + + @staticmethod + @keyword('Setup static L2FIB entry on node "${node}" for MAC "${dst_mac}"' + ' link "${link}" pair on bd_index "${bd_id}"') + def static_l2_fib_entry_via_links(node, dst_mac, link, bd_id): + """ Creates a static fib entry on a vpp node + + :param node: node where we wish to add the static fib entry + :param dst_mac: destination mac address in the entry + :param link: link name of the node destination interface + :param bd_id: l2 bridge domain id + :type node: dict + :type dst_mac: str + :type link: str + :type bd_id: str + """ + topology = Topology() + interface_name = topology.get_interface_by_link_name(node, link) + sw_if_index = topology.get_interface_sw_index(node, interface_name) + VatExecutor.cmd_from_template(node, "add_l2_fib_entry.vat", + mac=dst_mac, bd=bd_id, + interface=sw_if_index) + + @staticmethod + @keyword('Setup l2 bridge domain with id "${bd_id}" flooding "${flood}" ' + 'forwarding "${forward}" learning "${learn}" and arp termination ' + '"${arp_term}" on vpp node "${node}"') + def setup_vpp_l2_bridge_domain(node, bd_id, flood, forward, learn, + arp_term): + """Create a l2 bridge domain on the chosen vpp node + + Executes "bridge_domain_add_del bd_id {bd_id} flood {flood} uu-flood 1 + forward {forward} learn {learn} arp-term {arp_term}" VAT command on + the node. + For the moment acts as a placeholder + :param node: node where we wish to crate the l2 bridge domain + :param bd_id: bridge domain id + :param flood: enable flooding + :param forward: enable forwarding + :param learn: enable mac address learning to fib + :param arp_term: enable arp_termination + :type node: str + :type bd_id: str + :type flood: bool + :type forward: bool + :type learn: bool + :type arp_term:bool + :return: + """ + pass + + @keyword('Add interface "${interface}" to l2 bridge domain with index ' + '"${bd_id}" and shg "${shg}" on vpp node "${node}"') + def add_interface_to_l2_bd(self, node, interface, bd_id, shg): + """Adds interface to l2 bridge domain. + + Executes the "sw_interface_set_l2_bridge {interface1} bd_id {bd_id} + shg {shg} enable" VAT command on the given node. + For the moment acts as a placeholder + :param node: node where we want to execute the command that does this. + :param interface: + :param bd_id: + :param shg: + :type node: dict + :type interface: str + :type bd_id: str + :type shg: str + :return: + """ + pass + + @staticmethod + @keyword('Create dict used in bridge domain template file for node ' + '"${node}" with links "${link_names}" and bd_id "${bd_id}"') + def create_bridge_domain_vat_dict(node, link_names, bd_id): + """Creates dictionary that can be used in l2 bridge domain template. + + :param node: node data dictionary + :param link_names: list of names of links the bridge domain should be + connecting + :param bd_id: bridge domain index number + :type node: dict + :type link_names: list + :return: dictionary used to generate l2 bridge domain VAT configuration + from template file + The resulting dictionary looks like this: + 'interface1': interface name of first interface + 'interface2': interface name of second interface + 'bd_id': bridge domian index + """ + bd_dict = Topology().get_interfaces_by_link_names(node, link_names) + bd_dict['bd_id'] = bd_id + return bd_dict + diff --git a/resources/libraries/python/NodePath.py b/resources/libraries/python/NodePath.py new file mode 100644 index 0000000000..d1aa1f76d4 --- /dev/null +++ b/resources/libraries/python/NodePath.py @@ -0,0 +1,192 @@ +# 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. + +"""Path utilities library for nodes in the topology.""" + +from topology import Topology + + +class NodePath(object): + """Path utilities for nodes in the topology. + + :Example: + + node1--link1-->node2--link2-->node3--link3-->node2--link4-->node1 + RobotFramework: + | Library | resources/libraries/python/NodePath.py + + | Path test + | | [Arguments] | ${node1} | ${node2} | ${node3} + | | Append Node | ${nodes1} + | | Append Node | ${nodes2} + | | Append Nodes | ${nodes3} | ${nodes2} + | | Append Node | ${nodes1} + | | Compute Path | ${FALSE} + | | ${first_int} | ${node}= | First Interface + | | ${last_int} | ${node}= | Last Interface + | | ${first_ingress} | ${node}= | First Ingress Interface + | | ${last_egress} | ${node}= | Last Egress Interface + | | ${next} | ${node}= | Next Interface + + Python: + >>> from NodePath import NodePath + >>> path = NodePath() + >>> path.append_node(node1) + >>> path.append_node(node2) + >>> path.append_nodes(node3, node2) + >>> path.append_node(node1) + >>> path.compute_path() + >>> (interface, node) = path.first_interface() + >>> (interface, node) = path.last_interface() + >>> (interface, node) = path.first_ingress_interface() + >>> (interface, node) = path.last_egress_interface() + >>> (interface, node) = path.next_interface() + """ + + def __init__(self): + self._nodes = [] + self._links = [] + self._path = [] + self._path_iter = [] + + def append_node(self, node): + """Append node to the path. + + :param node: Node to append to the path. + :type node: dict + """ + self._nodes.append(node) + + def append_nodes(self, *nodes): + """Append nodes to the path. + + :param nodes: Nodes to append to the path. + :type nodes: dict + + .. note:: Node order does matter. + """ + for node in nodes: + self.append_node(node) + + def clear_path(self): + """Clear path.""" + self._nodes = [] + self._links = [] + self._path = [] + self._path_iter = [] + + def compute_path(self, always_same_link=True): + """Compute path for added nodes. + + :param always_same_link: If True use always same link between two nodes + in path. If False use different link (if available) between two + nodes if one link was used before. + :type always_same_link: bool + + .. note:: First add at least two nodes to the topology. + """ + nodes = self._nodes + if len(nodes) < 2: + raise RuntimeError('Not enough nodes to compute path') + + for idx in range(0, len(nodes) - 1): + topo = Topology() + node1 = nodes[idx] + node2 = nodes[idx + 1] + links = topo.get_active_connecting_links(node1, node2) + if not links: + raise RuntimeError('No link between {0} and {1}'.format( + node1['host'], node2['host'])) + + link = None + l_set = set() + + if always_same_link: + l_set = set(links).intersection(self._links) + else: + l_set = set(links).difference(self._links) + + if not l_set: + link = links.pop() + else: + link = l_set.pop() + + self._links.append(link) + interface1 = topo.get_interface_by_link_name(node1, link) + interface2 = topo.get_interface_by_link_name(node2, link) + self._path.append((interface1, node1)) + self._path.append((interface2, node2)) + + self._path_iter.extend(self._path) + self._path_iter.reverse() + + def next_interface(self): + """Path interface iterator. + + :return: Interface and node or None if not next interface. + :rtype: tuple (str, dict) + + .. note:: Call compute_path before. + """ + if not self._path_iter: + return (None, None) + else: + return self._path_iter.pop() + + def first_interface(self): + """Return first interface on the path. + + :return: Interface and node. + :rtype: tuple (str, dict) + + .. note:: Call compute_path before. + """ + if not self._path: + raise RuntimeError('No path for topology') + return self._path[0] + + def last_interface(self): + """Return last interface on the path. + + :return: Interface and node. + :rtype: tuple (str, dict) + + .. note:: Call compute_path before. + """ + if not self._path: + raise RuntimeError('No path for topology') + return self._path[-1] + + def first_ingress_interface(self): + """Return first ingress interface on the path. + + :return: Interface and node. + :rtype: tuple (str, dict) + + .. note:: Call compute_path before. + """ + if not self._path: + raise RuntimeError('No path for topology') + return self._path[1] + + def last_egress_interface(self): + """Return last egress interface on the path. + + :return: Interface and node. + :rtype: tuple (str, dict) + + .. note:: Call compute_path before. + """ + if not self._path: + raise RuntimeError('No path for topology') + return self._path[-2] diff --git a/resources/libraries/python/PacketVerifier.py b/resources/libraries/python/PacketVerifier.py index 81798e1f68..54f719fa9a 100644 --- a/resources/libraries/python/PacketVerifier.py +++ b/resources/libraries/python/PacketVerifier.py @@ -188,7 +188,7 @@ def packet_reader(interface_name, queue): buf = "" while True: - recvd = sock.recv(1500) + recvd = sock.recv(1514) buf = buf + recvd pkt = extract_one_packet(buf) @@ -285,11 +285,7 @@ class Interface(object): self.txq.send(pkt) def recv_pkt(self, timeout=3): - while True: - pkt = self.rxq.recv(timeout, self.sent_packets) - # TODO: FIX FOLLOWING: DO NOT SKIP RARP IN ALL TESTS!!! - if pkt.type != 32821: # Skip RARP packets - return pkt + return self.rxq.recv(timeout, self.sent_packets) def close(self): self.rxq._proc.terminate() diff --git a/resources/libraries/python/TrafficScriptExecutor.py b/resources/libraries/python/TrafficScriptExecutor.py index 2e65a520d0..ee29695812 100644 --- a/resources/libraries/python/TrafficScriptExecutor.py +++ b/resources/libraries/python/TrafficScriptExecutor.py @@ -70,8 +70,8 @@ class TrafficScriptExecutor(object): def traffic_script_gen_arg(rx_if, tx_if, src_mac, dst_mac, src_ip, dst_ip): """Generate traffic script basic arguments string. - :param rx_if: Interface that sends traffic. - :param tx_if: Interface that receives traffic. + :param rx_if: Interface that receives traffic. + :param tx_if: Interface that sends traffic. :param src_mac: Source MAC address. :param dst_mac: Destination MAC address. :param src_ip: Source IP address. diff --git a/resources/libraries/python/topology.py b/resources/libraries/python/topology.py index 522de37d13..2b202e5b4a 100644 --- a/resources/libraries/python/topology.py +++ b/resources/libraries/python/topology.py @@ -365,19 +365,25 @@ class Topology(object): return None @staticmethod - def get_adjacent_interface(node, interface_name): - """Get interface adjacent to specified interface on local network. - - :param node: Node that contains specified interface. - :param interface_name: Interface name. - :type node: dict - :type interface_name: str - :return: Return interface or None if not found. - :rtype: dict + def get_adjacent_node_and_interface(nodes_info, node, interface_name): + """Get node and interface adjacent to specified interface + on local network. + + :param nodes_info: Dictionary containing information on all nodes + in topology. + :param node: Node that contains specified interface. + :param interface_name: Interface name. + :type nodes_info: dict + :type node: dict + :type interface_name: str + :return: Return (node, interface info) tuple or None if not found. + :rtype: (dict, dict) """ link_name = None # get link name where the interface belongs to - for _, port_data in node['interfaces'].iteritems(): + for port_name, port_data in node['interfaces'].iteritems(): + if port_name == 'mgmt': + continue if port_data['name'] == interface_name: link_name = port_data['link'] break @@ -386,7 +392,7 @@ class Topology(object): return None # find link - for _, node_data in DICT__nodes.iteritems(): + for node_data in nodes_info.values(): # skip self if node_data['host'] == node['host']: continue @@ -395,7 +401,7 @@ class Topology(object): if 'link' not in interface_data: continue if interface_data['link'] == link_name: - return node_data['interfaces'][interface] + return node_data, node_data['interfaces'][interface] @staticmethod def get_interface_pci_addr(node, interface): @@ -537,3 +543,44 @@ class Topology(object): if not interfaces: raise RuntimeError('No engress interface for nodes') return interfaces[0] + + @keyword('Get link data useful in circular topology test from tg "${tgen}"' + ' dut1 "${dut1}" dut2 "${dut2}"') + def get_links_dict_from_nodes(self, tgen, dut1, dut2): + """Returns link combinations used in tests in circular topology. + + For the time being it returns links from the Node path: + TG->DUT1->DUT2->TG + :param tg: traffic generator node data + :param dut1: DUT1 node data + :param dut2: DUT2 node data + :type tg: dict + :type dut1: dict + :type dut2: dict + :return: dictionary of possible link combinations + the naming convention until changed to something more general is + implemented is this: + DUT1_DUT2_LINK: link name between DUT! and DUT2 + DUT1_TG_LINK: link name between DUT1 and TG + DUT2_TG_LINK: link name between DUT2 and TG + TG_TRAFFIC_LINKS: list of link names that generated traffic is sent + to and from + DUT1_BD_LINKS: list of link names that will be connected by the bridge + domain on DUT1 + DUT2_BD_LINKS: list of link names that will be connected by the bridge + domain on DUT2 + """ + # TODO: replace with generic function. + dut1_dut2_link = self.get_first_active_connecting_link(dut1, dut2) + dut1_tg_link = self.get_first_active_connecting_link(dut1, tgen) + dut2_tg_link = self.get_first_active_connecting_link(dut2, tgen) + tg_traffic_links = [dut1_tg_link, dut2_tg_link] + dut1_bd_links = [dut1_dut2_link, dut1_tg_link] + dut2_bd_links = [dut1_dut2_link, dut2_tg_link] + topology_links = {'DUT1_DUT2_LINK': dut1_dut2_link, + 'DUT1_TG_LINK': dut1_tg_link, + 'DUT2_TG_LINK': dut2_tg_link, + 'TG_TRAFFIC_LINKS': tg_traffic_links, + 'DUT1_BD_LINKS': dut1_bd_links, + 'DUT2_BD_LINKS': dut2_bd_links} + return topology_links diff --git a/resources/libraries/robot/bridge_domain.robot b/resources/libraries/robot/bridge_domain.robot index fc3705700e..da669c42ce 100644 --- a/resources/libraries/robot/bridge_domain.robot +++ b/resources/libraries/robot/bridge_domain.robot @@ -12,16 +12,18 @@ # limitations under the License. *** Settings *** -| Library | resources/libraries/python/VatExecutor.py -| Library | resources/libraries/python/VatConfigGenerator.py +| Library | resources.libraries.python.VatExecutor +| Library | resources.libraries.python.VatConfigGenerator | Library | resources.libraries.python.topology.Topology -| Library | resources/libraries/python/TrafficScriptExecutor.py +| Library | resources.libraries.python.TrafficScriptExecutor +| Library | resources.libraries.python.L2Util | Variables | resources/libraries/python/constants.py *** Variables *** | ${VAT_BD_TEMPLATE} | ${Constants.RESOURCES_TPL_VAT}/l2_bridge_domain.vat +| ${VAT_BD_STATIC_TPL} | ${Constants.RESOURCES_TPL_VAT}/l2_bridge_domain_static.vat | ${VAT_BD_GEN_FILE} | ${Constants.RESOURCES_TPL_VAT}/l2_bridge_domain_gen.vat -| ${VAT_BD_REMOTE_PATH} | ${Constants.REMOTE_FW_DIR}/l2_bridge_domain_gen.vat +| ${VAT_BD_REMOTE_PATH} | ${Constants.REMOTE_FW_DIR}/${Constants.RESOURCES_TPL_VAT}/l2_bridge_domain_gen.vat *** Keywords *** | Setup l2 bridge on node "${node}" via links "${link_names}" @@ -32,6 +34,14 @@ | | Execute Script | l2_bridge_domain_gen.vat | ${node} | json_out=False | | Script Should Have Passed +| Setup l2 bridge with static fib on node "${node}" via links "${link_names}" on bd with index "${bd_id}" +| | ${vat_template_dict}= | Create dict used in bridge domain template file for node "${node}" with links "${link_names}" and bd_id "${bd_id}" +| | ${commands}= | Generate Vat Config File | ${VAT_BD_STATIC_TPL} | ${vat_template_dict} | ${VAT_BD_GEN_FILE} +| | Copy Config To Remote | ${node} | ${VAT_BD_GEN_FILE} | ${VAT_BD_REMOTE_PATH} +# TODO: will be removed once v4 is merged to master. +| | Execute Script | l2_bridge_domain_gen.vat | ${node} | json_out=False +| | Script Should Have Passed + | Send traffic on node "${node}" from link "${link1}" to link "${link2}" | | ${src_port}= | Get Interface By Link Name | ${node} | ${link1} | | ${dst_port}= | Get Interface By Link Name | ${node} | ${link2} @@ -39,16 +49,21 @@ | | ${dst_ip}= | Set Variable | 192.168.100.2 | | ${src_mac}= | Get Node Link Mac | ${node} | ${link1} | | ${dst_mac}= | Get Node Link Mac | ${node} | ${link2} -| | ${args}= | Traffic Script Gen Arg | ${src_port} | ${src_port} | ${src_mac} | ${dst_mac} | ${src_ip} | ${dst_ip} +| | ${args}= | Traffic Script Gen Arg | ${dst_port} | ${src_port} | ${src_mac} | ${dst_mac} | ${src_ip} | ${dst_ip} | | Run Traffic Script On Node | send_ip_icmp.py | ${node} | ${args} | Setup TG "${tg}" DUT1 "${dut1}" and DUT2 "${dut2}" for 3 node l2 bridge domain test -| | ${DUT1_DUT2_link}= | Get first active connecting link between node "${dut1}" and "${dut2}" -| | ${DUT1_TG_link}= | Get first active connecting link between node "${dut1}" and "${tg}" -| | ${DUT2_TG_link}= | Get first active connecting link between node "${dut2}" and "${tg}" -| | ${tg_traffic_links}= | Create List | ${DUT1_TG_link} | ${DUT2_TG_link} -| | ${DUT1_BD_links}= | Create_list | ${DUT1_DUT2_link} | ${DUT1_TG_link} -| | ${DUT2_BD_links}= | Create_list | ${DUT1_DUT2_link} | ${DUT2_TG_link} -| | Setup l2 bridge on node "${dut1}" via links "${DUT1_BD_links}" -| | Setup l2 bridge on node "${dut2}" via links "${DUT2_BD_links}" -| | [Return] | ${tg_traffic_links} \ No newline at end of file +| | ${topology_links}= | Get link data useful in circular topology test from tg "${tg}" dut1 "${dut1}" dut2 "${dut2}" +| | Setup l2 bridge on node "${dut1}" via links "${topology_links['DUT1_BD_LINKS']}" +| | Setup l2 bridge on node "${dut2}" via links "${topology_links['DUT2_BD_LINKS']}" +| | [Return] | ${topology_links['TG_TRAFFIC_LINKS']} + +| Setup TG "${tg}" DUT1 "${dut1}" and DUT2 "${dut2}" for 3 node static l2fib test +| | ${topology_links}= | Get link data useful in circular topology test from tg "${tg}" dut1 "${dut1}" dut2 "${dut2}" +| | ${dst_mac}= | Get Node Link Mac | ${tg} | ${topology_links["DUT2_TG_LINK"]} +| | ${bd_index}= | Set Variable | 1 +| | Setup l2 bridge with static fib on node "${dut1}" via links "${topology_links['DUT1_BD_LINKS']}" on bd with index "${bd_index}" +| | Setup static L2FIB entry on node "${dut1}" for MAC "${dst_mac}" link "${topology_links['DUT1_DUT2_LINK']}" pair on bd_index "${bd_index}" +| | Setup l2 bridge with static fib on node "${dut2}" via links "${topology_links['DUT2_BD_LINKS']}" on bd with index "${bd_index}" +| | Setup static L2FIB entry on node "${dut2}" for MAC "${dst_mac}" link "${topology_links['DUT2_TG_LINK']}" pair on bd_index "${bd_index}" +| | [Return] | ${topology_links["TG_TRAFFIC_LINKS"]} diff --git a/resources/libraries/robot/ipv4.robot b/resources/libraries/robot/ipv4.robot index a4e1086d38..60d729fa7f 100644 --- a/resources/libraries/robot/ipv4.robot +++ b/resources/libraries/robot/ipv4.robot @@ -13,15 +13,16 @@ *** Settings *** | Resource | resources/libraries/robot/default.robot | Resource | resources/libraries/robot/counters.robot -| Library | resources/libraries/python/IPv4Util.py +| Library | resources.libraries.python.IPv4Util +| Library | resources.libraries.python.TrafficScriptExecutor | Variables | resources/libraries/python/IPv4NodeAddress.py *** Keywords *** -| Setup IPv4 adresses on all nodes in topology +| Setup IPv4 adresses on all DUT nodes in topology | | [Documentation] | Setup IPv4 address on all DUTs and TG in topology | | [Arguments] | ${nodes} | ${nodes_addr} -| | Nodes setup IPv4 addresses | ${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 @@ -41,11 +42,41 @@ | Setup nodes for IPv4 testing | | Interfaces needed for IPv4 testing are in "up" state -| | Setup IPv4 adresses on all nodes in topology | ${nodes} | ${nodes_ipv4_addr} +| | Setup IPv4 adresses on all DUT nodes in topology | ${nodes} | ${nodes_ipv4_addr} +| | Setup ARP on all DUTs | ${nodes} | | 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 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}" +| | 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}" + +| 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_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} +| | | ... | ${dst_mac} | ${src_ip} | ${dst_ip} +| # TODO: end_size is currently minimum MTU size for Ethernet minus IPv4 and +| # ICMP echo header size (1500 - 20 - 8), +| # MTU info is not in VAT sw_interface_dump output +| | ${args}= | Set Variable | ${args} --start_size 0 --end_size 1472 --step 1 +| | Run Traffic Script On Node | ipv4_sweep_ping.py | ${src_node} | ${args} + +| Send ARP request and validate response +| | [Arguments] | ${tg_node} | ${vpp_node} +| | ${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_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} +| | | ... | ${dst_mac} | ${src_ip} | ${dst_ip} +| | Run Traffic Script On Node | arp_request.py | ${tg_node} | ${args} + diff --git a/resources/libraries/robot/l2_xconnect.robot b/resources/libraries/robot/l2_xconnect.robot new file mode 100644 index 0000000000..001062c616 --- /dev/null +++ b/resources/libraries/robot/l2_xconnect.robot @@ -0,0 +1,50 @@ +# 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. + +*** Settings *** + +| Library | resources.libraries.python.VatExecutor +| Library | resources.libraries.python.CrossConnectSetup +| Library | resources.libraries.python.topology.Topology +| Library | resources.libraries.python.TrafficScriptExecutor +| Variables | resources/libraries/python/constants.py + +*** Keywords *** + +| L2 setup xconnect on DUTs +| | [Documentation] | Setup Bidirectional Cross Connect on DUTs +# TODO: rewrite with dynamic path selection +| | Vpp Setup Bidirectional Cross Connect | ${nodes['DUT1']} +| | ... | ${nodes['DUT1']['interfaces']['port1']['name']} +| | ... | ${nodes['DUT1']['interfaces']['port3']['name']} +| | Vpp Setup Bidirectional Cross Connect | ${nodes['DUT2']} +| | ... | ${nodes['DUT2']['interfaces']['port1']['name']} +| | ... | ${nodes['DUT2']['interfaces']['port3']['name']} + + +| Get traffic links between TG "${tg}" and DUT1 "${dut1}" and DUT2 "${dut2}" +| | ${DUT1_TG_link}= | Get first active connecting link between node "${dut1}" and "${tg}" +| | ${DUT2_TG_link}= | Get first active connecting link between node "${dut2}" and "${tg}" +| | ${tg_traffic_links}= | Create List | ${DUT1_TG_link} | ${DUT2_TG_link} +| | [Return] | ${tg_traffic_links} + + +| Send traffic on node "${node}" from link "${link1}" to link "${link2}" +| | ${src_port}= | Get Interface By Link Name | ${node} | ${link1} +| | ${dst_port}= | Get Interface By Link Name | ${node} | ${link2} +| | ${src_ip}= | Set Variable | 192.168.100.1 +| | ${dst_ip}= | Set Variable | 192.168.100.2 +| | ${src_mac}= | Get Node Link Mac | ${node} | ${link1} +| | ${dst_mac}= | Get Node Link Mac | ${node} | ${link2} +| | ${args}= | Traffic Script Gen Arg | ${src_port} | ${src_port} | ${src_mac} | ${dst_mac} | ${src_ip} | ${dst_ip} +| | Run Traffic Script On Node | send_ip_icmp.py | ${node} | ${args} diff --git a/resources/libraries/robot/vat/interfaces.robot b/resources/libraries/robot/vat/interfaces.robot index 1342f6326b..3a6c4be205 100644 --- a/resources/libraries/robot/vat/interfaces.robot +++ b/resources/libraries/robot/vat/interfaces.robot @@ -11,7 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. *** Settings *** -| Library | resources/libraries/python/VatExecutor.py +| Library | resources.libraries.python.VatExecutor *** Variables *** | ${VAT_DUMP_INTERFACES} | dump_interfaces.vat diff --git a/resources/templates/vat/add_ip_neighbor.vat b/resources/templates/vat/add_ip_neighbor.vat new file mode 100644 index 0000000000..e8587b28ca --- /dev/null +++ b/resources/templates/vat/add_ip_neighbor.vat @@ -0,0 +1 @@ +ip_neighbor_add_del sw_if_index {sw_if_index} dst {ip_address} mac {mac_address} \ No newline at end of file diff --git a/resources/templates/vat/add_l2_fib_entry.vat b/resources/templates/vat/add_l2_fib_entry.vat new file mode 100644 index 0000000000..2920c4b123 --- /dev/null +++ b/resources/templates/vat/add_l2_fib_entry.vat @@ -0,0 +1 @@ +l2fib_add_del mac {mac} bd_id {bd} sw_if_index {interface} diff --git a/resources/templates/vat/l2_bridge_domain.vat b/resources/templates/vat/l2_bridge_domain.vat index 84bf409944..9c81dbc8e8 100644 --- a/resources/templates/vat/l2_bridge_domain.vat +++ b/resources/templates/vat/l2_bridge_domain.vat @@ -1,5 +1,6 @@ +sw_interface_set_flags {interface1} admin-up link-up +sw_interface_set_flags {interface2} admin-up link-up bridge_domain_add_del bd_id 1 flood 1 uu-flood 1 forward 1 learn 1 arp-term 0 sw_interface_set_l2_bridge {interface1} bd_id 1 shg 0 enable sw_interface_set_l2_bridge {interface2} bd_id 1 shg 0 enable -sw_interface_set_flags {interface1} admin-up link-up -sw_interface_set_flags {interface2} admin-up link-up \ No newline at end of file +exec trace add dpdk-input 100 diff --git a/resources/templates/vat/l2_bridge_domain_gen.vat b/resources/templates/vat/l2_bridge_domain_gen.vat deleted file mode 100644 index 4e635e29c4..0000000000 --- a/resources/templates/vat/l2_bridge_domain_gen.vat +++ /dev/null @@ -1,5 +0,0 @@ -bridge_domain_add_del bd_id 1 flood 1 uu-flood 1 forward 1 learn 1 arp-term 0 -sw_interface_set_l2_bridge TenGigabitEthernet84/0/1 bd_id 1 shg 0 enable -sw_interface_set_l2_bridge TenGigabitEthernet84/0/0 bd_id 1 shg 0 enable -sw_interface_set_flags TenGigabitEthernet84/0/1 admin-up link-up -sw_interface_set_flags TenGigabitEthernet84/0/0 admin-up link-up \ No newline at end of file diff --git a/resources/templates/vat/l2_bridge_domain_static.vat b/resources/templates/vat/l2_bridge_domain_static.vat new file mode 100644 index 0000000000..bb99b2daba --- /dev/null +++ b/resources/templates/vat/l2_bridge_domain_static.vat @@ -0,0 +1,6 @@ +sw_interface_set_flags {interface1} admin-up link-up +sw_interface_set_flags {interface2} admin-up link-up +bridge_domain_add_del bd_id {bd_id} flood 1 uu-flood 1 forward 1 learn 0 arp-term 0 +sw_interface_set_l2_bridge {interface1} bd_id {bd_id} shg 0 enable +sw_interface_set_l2_bridge {interface2} bd_id {bd_id} shg 0 enable +exec trace add dpdk-input 100 diff --git a/resources/templates/vat/l2_xconnect.vat b/resources/templates/vat/l2_xconnect.vat new file mode 100644 index 0000000000..3812e9ad1f --- /dev/null +++ b/resources/templates/vat/l2_xconnect.vat @@ -0,0 +1 @@ +sw_interface_set_l2_xconnect rx_sw_if_index {interface1} tx_sw_if_index {interface2} \ No newline at end of file diff --git a/resources/templates/vat/l2xconnect.vat b/resources/templates/vat/l2xconnect.vat deleted file mode 100644 index 8059007f17..0000000000 --- a/resources/templates/vat/l2xconnect.vat +++ /dev/null @@ -1,6 +0,0 @@ -exec set interface state TenGigabitEthernet84/0/0 up -exec set interface state TenGigabitEthernet84/0/1 up -exec set interface l2 xconnect TenGigabitEthernet84/0/0 TenGigabitEthernet84/0/1 -exec set interface l2 xconnect TenGigabitEthernet84/0/1 TenGigabitEthernet84/0/0 -quit - diff --git a/resources/traffic_scripts/arp_request.py b/resources/traffic_scripts/arp_request.py new file mode 100755 index 0000000000..86a4c015cd --- /dev/null +++ b/resources/traffic_scripts/arp_request.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python + +# 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. + +"""Send an ARP request and verify the reply""" + +import sys + +from scapy.all import Ether, ARP + +from resources.libraries.python.PacketVerifier import Interface +from resources.libraries.python.TrafficScriptArg import TrafficScriptArg + + +def parse_arguments(): + """Parse arguments of the script passed through command line + + :return: tuple of parsed arguments + """ + args = TrafficScriptArg(['src_if', 'src_mac', 'dst_mac', + 'src_ip', 'dst_ip']) + + # check for mandatory parameters + params = (args.get_arg('tx_if'), + args.get_arg('src_mac'), + args.get_arg('dst_mac'), + args.get_arg('src_ip'), + args.get_arg('dst_ip')) + if None in params: + raise Exception('Missing mandatory parameter(s)!') + + return params + + +def arp_request_test(): + """Send ARP request, expect a reply and verify its fields. + + returns: test status + """ + test_passed = False + (src_if, src_mac, dst_mac, src_ip, dst_ip) = parse_arguments() + + interface = Interface(src_if) + + # build an ARP request + arp_request = (Ether(src=src_mac, dst='ff:ff:ff:ff:ff:ff') / + ARP(psrc=src_ip, hwsrc=src_mac, pdst=dst_ip, + hwdst='ff:ff:ff:ff:ff:ff')) + + # send the request + interface.send_pkt(arp_request) + + try: + # wait for APR reply + ether = interface.recv_pkt() + + # verify received packet + + if not ether.haslayer(ARP): + raise RuntimeError('Unexpected packet: does not contain ARP ' + + 'header "{}"'.format(ether.__repr__())) + + arp = ether['ARP'] + arp_reply = 2 + + if arp.op != arp_reply: + raise RuntimeError('expected op={}, received {}'.format(arp_reply, + arp.op)) + if arp.ptype != 0x800: + raise RuntimeError('expected ptype=0x800, received {}'. + format(arp.ptype)) + if arp.hwlen != 6: + raise RuntimeError('expected hwlen=6, received {}'. + format(arp.hwlen)) + if arp.plen != 4: + raise RuntimeError('expected plen=4, received {}'.format(arp.plen)) + if arp.hwsrc != dst_mac: + raise RuntimeError('expected hwsrc={}, received {}'. + format(dst_mac, arp.hwsrc)) + if arp.psrc != dst_ip: + raise RuntimeError('expected psrc={}, received {}'. + format(dst_ip, arp.psrc)) + if arp.hwdst != src_mac: + raise RuntimeError('expected hwdst={}, received {}'. + format(src_mac, arp.hwdst)) + if arp.pdst != src_ip: + raise RuntimeError('expected pdst={}, received {}'. + format(src_ip, arp.pdst)) + test_passed = True + + except RuntimeError as ex: + print 'Error occurred: {}'.format(ex) + finally: + interface.close() + + return test_passed + + +def main(): + """Run the test and collect result""" + if arp_request_test(): + sys.exit(0) + else: + sys.exit(1) + +if __name__ == '__main__': + main() diff --git a/resources/traffic_scripts/ipv4_ping_ttl_check.py b/resources/traffic_scripts/ipv4_ping_ttl_check.py index 050a1d7b29..1286b46876 100755 --- a/resources/traffic_scripts/ipv4_ping_ttl_check.py +++ b/resources/traffic_scripts/ipv4_ping_ttl_check.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from scapy.all import * +from scapy.all import Ether, IP, ICMP from resources.libraries.python.PacketVerifier \ import Interface, create_gratuitous_arp_request, auto_pad from optparse import OptionParser @@ -25,8 +25,8 @@ def check_ttl(ttl_begin, ttl_end, ttl_diff): if dst_if_defined: dst_if.close() raise Exception( - "TTL changed from {} to {} but decrease by {} expected")\ - .format(ttl_begin, ttl_end, hops) + "TTL changed from {} to {} but decrease by {} expected" + .format(ttl_begin, ttl_end, hops)) def ckeck_packets_equal(pkt_send, pkt_recv): diff --git a/resources/traffic_scripts/ipv4_sweep_ping.py b/resources/traffic_scripts/ipv4_sweep_ping.py new file mode 100755 index 0000000000..4b82a9b03e --- /dev/null +++ b/resources/traffic_scripts/ipv4_sweep_ping.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python + +# 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. + +"""Traffic script for IPv4 sweep ping.""" + +import sys +import logging +import os +logging.getLogger("scapy.runtime").setLevel(logging.ERROR) +from resources.libraries.python.PacketVerifier import RxQueue, TxQueue,\ + auto_pad, create_gratuitous_arp_request +from resources.libraries.python.TrafficScriptArg import TrafficScriptArg +from scapy.layers.inet import IP, ICMP +from scapy.all import Ether, Raw + + +def main(): + # start_size - start size of the ICMPv4 echo data + # end_size - end size of the ICMPv4 echo data + # step - increment step + args = TrafficScriptArg(['src_mac', 'dst_mac', 'src_ip', 'dst_ip', + 'start_size', 'end_size', 'step']) + + rxq = RxQueue(args.get_arg('rx_if')) + txq = TxQueue(args.get_arg('tx_if')) + + src_mac = args.get_arg('src_mac') + dst_mac = args.get_arg('dst_mac') + src_ip = args.get_arg('src_ip') + dst_ip = args.get_arg('dst_ip') + start_size = int(args.get_arg('start_size')) + end_size = int(args.get_arg('end_size')) + step = int(args.get_arg('step')) + echo_id = 0xa + # generate some random data buffer + data = bytearray(os.urandom(end_size)) + + sent_packets = [] + pkt_send = create_gratuitous_arp_request(src_mac, src_ip) + sent_packets.append(pkt_send) + txq.send(pkt_send) + + # send ICMP echo request with incremented data length and receive ICMP + # echo reply + for echo_seq in range(start_size, end_size+1, step): + pkt_send = auto_pad(Ether(src=src_mac, dst=dst_mac) / + IP(src=src_ip, dst=dst_ip) / + ICMP(id=echo_id, seq=echo_seq) / + Raw(load=data[0:echo_seq])) + sent_packets.append(pkt_send) + txq.send(pkt_send) + + ether = rxq.recv(ignore=sent_packets) + if ether is None: + rxq._proc.terminate() + raise RuntimeError( + 'ICMP echo reply seq {0} Rx timeout'.format(echo_seq)) + + if not ether.haslayer(IP): + rxq._proc.terminate() + raise RuntimeError( + 'Unexpected packet with no IPv4 received {0}'.format( + ether.__repr__())) + + ipv4 = ether['IP'] + + if not ipv4.haslayer(ICMP): + rxq._proc.terminate() + raise RuntimeError( + 'Unexpected packet with no ICMP received {0}'.format( + ipv4.__repr__())) + + icmpv4 = ipv4['ICMP'] + + if icmpv4.id != echo_id or icmpv4.seq != echo_seq: + rxq._proc.terminate() + raise RuntimeError( + 'Invalid ICMP echo reply received ID {0} seq {1} should be ' + + 'ID {2} seq {3}, {0}'.format(icmpv4.id, icmpv4.seq, echo_id, + echo_seq)) + + chksum = icmpv4.chksum + del icmpv4.chksum + tmp = ICMP(str(icmpv4)) + if tmp.chksum != chksum: + rxq._proc.terminate() + raise RuntimeError( + 'Invalid checksum {0} should be {1}'.format(chksum, tmp.chksum)) + recv_payload_len = ipv4.len - 20 - 8 + load = tmp['Raw'].load[0:recv_payload_len] + if load != data[0:echo_seq]: + rxq._proc.terminate() + raise RuntimeError( + 'Received ICMP payload does not match sent payload') + + rxq._proc.terminate() + sys.exit(0) + +if __name__ == "__main__": + main() diff --git a/resources/traffic_scripts/ipv6_sweep_ping.py b/resources/traffic_scripts/ipv6_sweep_ping.py index 2282f40f78..c79b74d760 100755 --- a/resources/traffic_scripts/ipv6_sweep_ping.py +++ b/resources/traffic_scripts/ipv6_sweep_ping.py @@ -58,7 +58,7 @@ def main(): # send ICMPv6 echo request with incremented data length and receive ICMPv6 # echo reply - for echo_seq in range(start_size, end_size, step): + for echo_seq in range(start_size, end_size+1, step): pkt_send = (Ether(src=src_mac, dst=dst_mac) / IPv6(src=src_ip, dst=dst_ip) / ICMPv6EchoRequest(id=echo_id, seq=echo_seq, diff --git a/tests/suites/bridge_domain/test.robot b/tests/suites/bridge_domain/test.robot index a36e5928b6..108e2aa4dd 100644 --- a/tests/suites/bridge_domain/test.robot +++ b/tests/suites/bridge_domain/test.robot @@ -18,6 +18,7 @@ | Library | resources.libraries.python.topology.Topology | Variables | resources/libraries/python/topology.py | Force Tags | 3_NODE_DOUBLE_LINK_TOPO +| Suite Setup | Setup all TGs before traffic script *** Test Cases *** @@ -36,4 +37,14 @@ | | ${dut1}= | Set Variable | ${nodes['DUT1']} | | ${dut2}= | Set Variable | ${nodes['DUT2']} | | ${tg_links}= | Setup TG "${tg}" DUT1 "${dut1}" and DUT2 "${dut2}" for 3 node l2 bridge domain test +| | Sleep | 5 | Workaround for interface still in down state after vpp restart +| | Send traffic on node "${nodes['TG']}" from link "${tg_links[0]}" to link "${tg_links[1]}" + +| Vpp forwards packets via L2 bridge domain in circular topology with static L2FIB entries +| | [Tags] | 3_NODE_DOUBLE_LINK_TOPO +| | ${tg}= | Set Variable | ${nodes['TG']} +| | ${dut1}= | Set Variable | ${nodes['DUT1']} +| | ${dut2}= | Set Variable | ${nodes['DUT2']} +| | ${tg_links}= | Setup TG "${tg}" DUT1 "${dut1}" and DUT2 "${dut2}" for 3 node static l2fib test +| | Sleep | 5 | Workaround for interface still in down state after vpp restart | | Send traffic on node "${nodes['TG']}" from link "${tg_links[0]}" to link "${tg_links[1]}" diff --git a/tests/suites/ipv4/ipv4_untagged.robot b/tests/suites/ipv4/ipv4_untagged.robot index dde2e5bb67..ed9574057d 100644 --- a/tests/suites/ipv4/ipv4_untagged.robot +++ b/tests/suites/ipv4/ipv4_untagged.robot @@ -12,10 +12,12 @@ # limitations under the License. *** Settings *** +| Documentation | TODO: rewrite with generic path keywords | Library | resources.libraries.python.topology.Topology | Resource | resources/libraries/robot/default.robot | Resource | resources/libraries/robot/ipv4.robot | Suite Setup | Run Keywords | Setup all DUTs before test +| ... | AND | Setup all TGs before traffic script | ... | AND | Update All Interface Data On All Nodes | ${nodes} | ... | AND | Setup nodes for IPv4 testing | Test Setup | Clear interface counters on all vpp nodes in topology | ${nodes} @@ -56,3 +58,11 @@ | | Vpp dump stats table | ${nodes['DUT2']} | | Check ipv4 interface counter | ${nodes['DUT2']} | ${nodes['DUT2']['interfaces']['port3']['name']} | ${1} | | Check ipv4 interface counter | ${nodes['DUT2']} | ${nodes['DUT2']['interfaces']['port1']['name']} | ${1} + +| VPP can process ICMP echo request from min to max packet size with 1B increment +| | Ipv4 icmp echo sweep | ${nodes['TG']} | ${nodes['DUT1']} +| | ... | ${nodes['TG']['interfaces']['port3']['name']} +| | ... | ${nodes['DUT1']['interfaces']['port1']['name']} + +| VPP responds to ARP request +| | Send ARP request and validate response | ${nodes['TG']} | ${nodes['DUT1']} diff --git a/tests/suites/l2_xconnect/l2_xconnect_untagged.robot b/tests/suites/l2_xconnect/l2_xconnect_untagged.robot new file mode 100644 index 0000000000..4aaf15075f --- /dev/null +++ b/tests/suites/l2_xconnect/l2_xconnect_untagged.robot @@ -0,0 +1,32 @@ +# 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. + +*** Settings *** + +| Resource | resources/libraries/robot/default.robot +| Resource | resources/libraries/robot/l2_xconnect.robot +| Force Tags | 3_NODE_SINGLE_LINK_TOPO +| Test Setup | Setup all DUTs before test +| Suite Setup | Setup all TGs before traffic script + + +*** Test Cases *** + +| VPP forwards packets through xconnect in circular topology +| | Given L2 setup xconnect on DUTs +| | ${tg}= | Set Variable | ${nodes['TG']} +| | ${dut1}= | Set Variable | ${nodes['DUT1']} +| | ${dut2}= | Set Variable | ${nodes['DUT2']} +| | ${tg_links}= | Get traffic links between TG "${tg}" and DUT1 "${dut1}" and DUT2 "${dut2}" +| | Send traffic on node "${nodes['TG']}" from link "${tg_links[0]}" to link "${tg_links[1]}" +