aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xbootstrap.sh26
-rw-r--r--docs/tg_interface_driver14
-rw-r--r--resources/libraries/python/CrossConnectSetup.py45
-rw-r--r--resources/libraries/python/IPv4NodeAddress.py6
-rw-r--r--resources/libraries/python/IPv4Util.py92
-rw-r--r--resources/libraries/python/L2Util.py119
-rw-r--r--resources/libraries/python/NodePath.py192
-rw-r--r--resources/libraries/python/PacketVerifier.py8
-rw-r--r--resources/libraries/python/TrafficScriptExecutor.py4
-rw-r--r--resources/libraries/python/topology.py71
-rw-r--r--resources/libraries/robot/bridge_domain.robot43
-rw-r--r--resources/libraries/robot/ipv4.robot41
-rw-r--r--resources/libraries/robot/l2_xconnect.robot50
-rw-r--r--resources/libraries/robot/vat/interfaces.robot2
-rw-r--r--resources/templates/vat/add_ip_neighbor.vat1
-rw-r--r--resources/templates/vat/add_l2_fib_entry.vat1
-rw-r--r--resources/templates/vat/l2_bridge_domain.vat5
-rw-r--r--resources/templates/vat/l2_bridge_domain_gen.vat5
-rw-r--r--resources/templates/vat/l2_bridge_domain_static.vat6
-rw-r--r--resources/templates/vat/l2_xconnect.vat1
-rw-r--r--resources/templates/vat/l2xconnect.vat6
-rwxr-xr-xresources/traffic_scripts/arp_request.py118
-rwxr-xr-xresources/traffic_scripts/ipv4_ping_ttl_check.py6
-rwxr-xr-xresources/traffic_scripts/ipv4_sweep_ping.py112
-rwxr-xr-xresources/traffic_scripts/ipv6_sweep_ping.py2
-rw-r--r--tests/suites/bridge_domain/test.robot11
-rw-r--r--tests/suites/ipv4/ipv4_untagged.robot10
-rw-r--r--tests/suites/l2_xconnect/l2_xconnect_untagged.robot32
28 files changed, 945 insertions, 84 deletions
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.
@@ -264,8 +288,32 @@ 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]}"
+