aboutsummaryrefslogtreecommitdiffstats
path: root/resources/libraries
diff options
context:
space:
mode:
authorStefan Kobza <skobza@cisco.com>2016-02-11 19:09:06 +0100
committerStefan Kobza <skobza@cisco.com>2016-02-11 19:09:06 +0100
commitb92a827b1c7f48da4214e992e5503ebe1c182416 (patch)
treecf272e0cd0354d0f13bbcb65c170545e9492c60c /resources/libraries
parent33499c81c94c2d3baef9d3e9f061cd76ef86fa74 (diff)
Update of latest tests.
Change-Id: Ifb04651ff4a3c1ba9aaa725eb9a309278ebc1140 Signed-off-by: Stefan Kobza <skobza@cisco.com>
Diffstat (limited to 'resources/libraries')
-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
12 files changed, 618 insertions, 55 deletions
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