Update of latest tests. 97/297/1
authorStefan Kobza <skobza@cisco.com>
Thu, 11 Feb 2016 18:09:06 +0000 (19:09 +0100)
committerStefan Kobza <skobza@cisco.com>
Thu, 11 Feb 2016 18:09:06 +0000 (19:09 +0100)
Change-Id: Ifb04651ff4a3c1ba9aaa725eb9a309278ebc1140
Signed-off-by: Stefan Kobza <skobza@cisco.com>
28 files changed:
bootstrap.sh
docs/tg_interface_driver [new file with mode: 0644]
resources/libraries/python/CrossConnectSetup.py [new file with mode: 0644]
resources/libraries/python/IPv4NodeAddress.py
resources/libraries/python/IPv4Util.py
resources/libraries/python/L2Util.py [new file with mode: 0644]
resources/libraries/python/NodePath.py [new file with mode: 0644]
resources/libraries/python/PacketVerifier.py
resources/libraries/python/TrafficScriptExecutor.py
resources/libraries/python/topology.py
resources/libraries/robot/bridge_domain.robot
resources/libraries/robot/ipv4.robot
resources/libraries/robot/l2_xconnect.robot [new file with mode: 0644]
resources/libraries/robot/vat/interfaces.robot
resources/templates/vat/add_ip_neighbor.vat [new file with mode: 0644]
resources/templates/vat/add_l2_fib_entry.vat [new file with mode: 0644]
resources/templates/vat/l2_bridge_domain.vat
resources/templates/vat/l2_bridge_domain_gen.vat [deleted file]
resources/templates/vat/l2_bridge_domain_static.vat [new file with mode: 0644]
resources/templates/vat/l2_xconnect.vat [new file with mode: 0644]
resources/templates/vat/l2xconnect.vat [deleted file]
resources/traffic_scripts/arp_request.py [new file with mode: 0755]
resources/traffic_scripts/ipv4_ping_ttl_check.py
resources/traffic_scripts/ipv4_sweep_ping.py [new file with mode: 0755]
resources/traffic_scripts/ipv6_sweep_ping.py
tests/suites/bridge_domain/test.robot
tests/suites/ipv4/ipv4_untagged.robot
tests/suites/l2_xconnect/l2_xconnect_untagged.robot [new file with mode: 0644]

index 835759c..4a91121 100755 (executable)
@@ -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 (file)
index 0000000..a45999a
--- /dev/null
@@ -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 (file)
index 0000000..3f1f7d4
--- /dev/null
@@ -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)
index 0f2c1d9..8db9ffa 100644 (file)
@@ -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):
index 5480bfc..d3a016d 100644 (file)
@@ -45,6 +45,7 @@ class IPv4Node(object):
     @abstractmethod
     def set_ip(self, interface, address, prefix_length):
         """Configure IPv4 address on interface
+
         :param interface: interface name
         :param address:
         :param prefix_length:
@@ -58,6 +59,7 @@ class IPv4Node(object):
     @abstractmethod
     def set_interface_state(self, interface, state):
         """Set interface state
+
         :param interface: interface name string
         :param state: one of following values: "up" or "down"
         :return: nothing
@@ -67,6 +69,7 @@ class IPv4Node(object):
     @abstractmethod
     def set_route(self, network, prefix_length, gateway, interface):
         """Configure IPv4 route
+
         :param network: network IPv4 address
         :param prefix_length: mask length
         :param gateway: IPv4 address of the gateway
@@ -82,6 +85,7 @@ class IPv4Node(object):
     @abstractmethod
     def unset_route(self, network, prefix_length, gateway, interface):
         """Remove specified IPv4 route
+
         :param network: network IPv4 address
         :param prefix_length: mask length
         :param gateway: IPv4 address of the gateway
@@ -97,6 +101,7 @@ class IPv4Node(object):
     @abstractmethod
     def flush_ip_addresses(self, interface):
         """Flush all IPv4 addresses from specified interface
+
         :param interface: interface name
         :type interface: str
         :return: nothing
@@ -106,6 +111,7 @@ class IPv4Node(object):
     @abstractmethod
     def ping(self, destination_address, source_interface):
         """Send an ICMP request to destination node
+
         :param destination_address: address to send the ICMP request
         :param source_interface:
         :type destination_address: str
@@ -167,6 +173,7 @@ class Dut(IPv4Node):
 
     def get_sw_if_index(self, interface):
         """Get sw_if_index of specified interface from current node
+
         :param interface: interface name
         :type interface: str
         :return: sw_if_index of 'int' type
@@ -175,6 +182,7 @@ class Dut(IPv4Node):
 
     def exec_vat(self, script, **args):
         """Wrapper for VAT executor.
+
         :param script: script to execute
         :param args: parameters to the script
         :type script: str
@@ -184,6 +192,20 @@ class Dut(IPv4Node):
         # TODO: check return value
         VatExecutor.cmd_from_template(self.node_info, script, **args)
 
+    def set_arp(self, interface, ip_address, mac_address):
+        """Set entry in ARP cache.
+
+        :param interface: Interface name.
+        :param ip_address: IP address.
+        :param mac_address: MAC address.
+        :type interface: str
+        :type ip_address: str
+        :type mac_address: str
+        """
+        self.exec_vat('add_ip_neighbor.vat',
+                      sw_if_index=self.get_sw_if_index(interface),
+                      ip_address=ip_address, mac_address=mac_address)
+
     def set_ip(self, interface, address, prefix_length):
         self.exec_vat('add_ip_address.vat',
                       sw_if_index=self.get_sw_if_index(interface),
@@ -224,6 +246,7 @@ class Dut(IPv4Node):
 
 def get_node(node_info):
     """Creates a class instance derived from Node based on type.
+
     :param node_info: dictionary containing information on nodes in topology
     :return: Class instance that is derived from Node
     """
@@ -238,6 +261,7 @@ def get_node(node_info):
 
 def get_node_hostname(node_info):
     """Get string identifying specifed node.
+
     :param node_info: Node in the topology.
     :type node_info: Dict
     :return: String identifying node.
@@ -263,9 +287,33 @@ class IPv4Util(object):
     """
     topology_helper = None
 
+    @staticmethod
+    def setup_arp_on_all_duts(nodes_info):
+        """For all DUT nodes extract MAC and IP addresses of adjacent interfaces
+        from topology and use them to setup ARP entries.
+
+        :param nodes_info: Dictionary containing information on all nodes
+        in topology.
+        :type nodes_info: dict
+        """
+        for node in nodes_info.values():
+            if node['type'] == NodeType.TG:
+                continue
+            for interface, interface_data in node['interfaces'].iteritems():
+                if interface == 'mgmt':
+                    continue
+                interface_name = interface_data['name']
+                adj_node, adj_int = Topology.\
+                    get_adjacent_node_and_interface(nodes_info, node,
+                                                    interface_name)
+                ip_address = IPv4Util.get_ip_addr(adj_node, adj_int['name'])
+                mac_address = adj_int['mac_address']
+                get_node(node).set_arp(interface_name, ip_address, mac_address)
+
     @staticmethod
     def next_address(subnet):
         """Get next unused IPv4 address from a subnet
+
         :param subnet: holds available IPv4 addresses
         :return: tuple (ipv4_address, prefix_length)
         """
@@ -281,6 +329,7 @@ class IPv4Util(object):
     @staticmethod
     def next_network(nodes_addr):
         """Get next unused network from dictionary
+
         :param nodes_addr: dictionary of available networks
         :return: dictionary describing an IPv4 subnet with addresses
         """
@@ -290,7 +339,9 @@ class IPv4Util(object):
 
     @staticmethod
     def configure_ipv4_addr_on_node(node, nodes_addr):
-        """Configure IPv4 address for all interfaces on a node in topology
+        """Configure IPv4 address for all non-management interfaces
+        on a node in topology.
+
         :param node: dictionary containing information about node
         :param nodes_addr: dictionary containing IPv4 addresses
         :return:
@@ -305,16 +356,20 @@ class IPv4Util(object):
             network = IPv4Util.topology_helper[interface_data['link']]
             address, prefix = IPv4Util.next_address(network)
 
-            get_node(node).set_ip(interface_data['name'], address, prefix)
+            if node['type'] != NodeType.TG:
+                get_node(node).set_ip(interface_data['name'], address, prefix)
+                get_node(node).set_interface_state(interface_data['name'], 'up')
+
             key = (get_node_hostname(node), interface_data['name'])
             IPv4Util.ADDRESSES[key] = address
             IPv4Util.PREFIXES[key] = prefix
             IPv4Util.SUBNETS[key] = network['subnet']
 
     @staticmethod
-    def nodes_setup_ipv4_addresses(nodes_info, nodes_addr):
+    def dut_nodes_setup_ipv4_addresses(nodes_info, nodes_addr):
         """Configure IPv4 addresses on all non-management interfaces for each
-        node in nodes_info
+        node in nodes_info if node type is not traffic generator
+
         :param nodes_info: dictionary containing information on all nodes
         in topology
         :param nodes_addr: dictionary containing IPv4 addresses
@@ -323,16 +378,17 @@ class IPv4Util(object):
         IPv4Util.topology_helper = {}
         # make a deep copy of nodes_addr because of modifications
         nodes_addr_copy = copy.deepcopy(nodes_addr)
-        for _, node in nodes_info.iteritems():
+        for node in nodes_info.values():
             IPv4Util.configure_ipv4_addr_on_node(node, nodes_addr_copy)
 
     @staticmethod
     def nodes_clear_ipv4_addresses(nodes):
         """Clear all addresses from all nodes in topology
+
         :param nodes: dictionary containing information on all nodes
         :return: nothing
         """
-        for _, node in nodes.iteritems():
+        for node in nodes.values():
             for interface, interface_data in node['interfaces'].iteritems():
                 if interface == 'mgmt':
                     continue
@@ -343,6 +399,7 @@ class IPv4Util(object):
     @keyword('Node "${node}" interface "${interface}" is in "${state}" state')
     def set_interface_state(node, interface, state):
         """See IPv4Node.set_interface_state for more information.
+
         :param node:
         :param interface:
         :param state:
@@ -357,6 +414,7 @@ class IPv4Util(object):
              '"${address}" with prefix length "${prefix_length}"')
     def set_interface_address(node, interface, address, length):
         """See IPv4Node.set_ip for more information.
+
         :param node:
         :param interface:
         :param address:
@@ -387,6 +445,7 @@ class IPv4Util(object):
              '"${gateway}"')
     def set_route(node, network, prefix_length, interface, gateway):
         """See IPv4Node.set_route for more information.
+
         :param node:
         :param network:
         :param prefix_length:
@@ -407,6 +466,7 @@ class IPv4Util(object):
              '"${gateway}"')
     def unset_route(node, network, prefix_length, interface, gateway):
         """See IPv4Node.unset_route for more information.
+
         :param node:
         :param network:
         :param prefix_length:
@@ -417,12 +477,15 @@ class IPv4Util(object):
         get_node(node).unset_route(network, prefix_length, gateway, interface)
 
     @staticmethod
-    @keyword('After ping is sent from node "${src_node}" interface '
-             '"${src_port}" with destination IPv4 address of node '
-             '"${dst_node}" interface "${dst_port}" a ping response arrives '
-             'and TTL is decreased by "${ttl_dec}"')
-    def send_ping(src_node, src_port, dst_node, dst_port, hops):
+    @keyword('After ping is sent in topology "${nodes_info}" from node '
+             '"${src_node}" interface "${src_port}" with destination IPv4 '
+             'address of node "${dst_node}" interface "${dst_port}" a ping '
+             'response arrives and TTL is decreased by "${hops}"')
+    def send_ping(nodes_info, src_node, src_port, dst_node, dst_port, hops):
         """Send IPv4 ping and wait for response.
+
+        :param nodes_info: Dictionary containing information on all nodes
+        in topology.
         :param src_node: Source node.
         :param src_port: Source interface.
         :param dst_node: Destination node.
@@ -438,7 +501,8 @@ class IPv4Util(object):
         src_mac = Topology.get_interface_mac(src_node, src_port)
         if dst_node['type'] == NodeType.TG:
             dst_mac = Topology.get_interface_mac(src_node, src_port)
-        adj_int = Topology.get_adjacent_interface(src_node, src_port)
+        _, adj_int = Topology.\
+            get_adjacent_node_and_interface(nodes_info, src_node, src_port)
         first_hop_mac = adj_int['mac_address']
         src_ip = IPv4Util.get_ip_addr(src_node, src_port)
         dst_ip = IPv4Util.get_ip_addr(dst_node, dst_port)
@@ -454,6 +518,7 @@ class IPv4Util(object):
     @keyword('Get IPv4 address of node "${node}" interface "${port}"')
     def get_ip_addr(node, port):
         """Get IPv4 address configured on specified interface
+
         :param node: node dictionary
         :param port: interface name
         :return: IPv4 address of specified interface as a 'str' type
@@ -466,6 +531,7 @@ class IPv4Util(object):
     @keyword('Get IPv4 address prefix of node "${node}" interface "${port}"')
     def get_ip_addr_prefix(node, port):
         """ Get IPv4 address prefix for specified interface.
+
         :param node: Node dictionary.
         :param port: Interface name.
         """
@@ -477,6 +543,7 @@ class IPv4Util(object):
     @keyword('Get IPv4 subnet of node "${node}" interface "${port}"')
     def get_ip_addr_subnet(node, port):
         """ Get IPv4 subnet of specified interface.
+
         :param node: Node dictionary.
         :param port: Interface name.
         """
@@ -488,6 +555,7 @@ class IPv4Util(object):
     @keyword('Flush IPv4 addresses "${port}" "${node}"')
     def flush_ip_addresses(port, node):
         """See IPv4Node.flush_ip_addresses for more information.
+
         :param port:
         :param node:
         :return:
diff --git a/resources/libraries/python/L2Util.py b/resources/libraries/python/L2Util.py
new file mode 100644 (file)
index 0000000..8581b1e
--- /dev/null
@@ -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 (file)
index 0000000..d1aa1f7
--- /dev/null
@@ -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]
index 81798e1..54f719f 100644 (file)
@@ -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()
index 2e65a52..ee29695 100644 (file)
@@ -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.
index 522de37..2b202e5 100644 (file)
@@ -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
index fc37057..da669c4 100644 (file)
 # 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}"
 | | 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}
 | | ${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"]}
index a4e1086..60d729f 100644 (file)
 *** 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
 
 | 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 (file)
index 0000000..001062c
--- /dev/null
@@ -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}
index 1342f63..3a6c4be 100644 (file)
@@ -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 (file)
index 0000000..e8587b2
--- /dev/null
@@ -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 (file)
index 0000000..2920c4b
--- /dev/null
@@ -0,0 +1 @@
+l2fib_add_del mac {mac} bd_id {bd} sw_if_index {interface}
index 84bf409..9c81dbc 100644 (file)
@@ -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 (file)
index 4e635e2..0000000
+++ /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 (file)
index 0000000..bb99b2d
--- /dev/null
@@ -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 (file)
index 0000000..3812e9a
--- /dev/null
@@ -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 (file)
index 8059007..0000000
+++ /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 (executable)
index 0000000..86a4c01
--- /dev/null
@@ -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()
index 050a1d7..1286b46 100755 (executable)
@@ -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 (executable)
index 0000000..4b82a9b
--- /dev/null
@@ -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()
index 2282f40..c79b74d 100755 (executable)
@@ -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,
index a36e592..108e2aa 100644 (file)
@@ -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 ***
 
 | | ${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]}"
index dde2e5b..ed95740 100644 (file)
 # 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}
 | | 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 (file)
index 0000000..4aaf150
--- /dev/null
@@ -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]}"
+