From: Matej Klotton Date: Mon, 22 Feb 2016 17:12:15 +0000 (+0100) Subject: Add VXLAN test X-Git-Url: https://gerrit.fd.io/r/gitweb?p=csit.git;a=commitdiff_plain;h=1254d80a0b64985de2816eff5ef79e3e22cde972 Add VXLAN test Change-Id: Id1d37fda2697fbfb7aa7a79318f8316b80e96963 Signed-off-by: Matej Klotton --- diff --git a/bootstrap.sh b/bootstrap.sh index fa3f00635c..f28524a6d9 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -151,4 +151,5 @@ PYTHONPATH=`pwd` pybot -L TRACE \ --include 3_NODE_SINGLE_LINK_TOPO \ --exclude 3_node_double_link_topoNOT3_node_single_link_topo \ --exclude PERFTEST \ + --noncritical EXPECTED_FAILING \ tests/ diff --git a/resources/libraries/python/IPv4Util.py b/resources/libraries/python/IPv4Util.py index 3dec3d8e60..3b500ab1d9 100644 --- a/resources/libraries/python/IPv4Util.py +++ b/resources/libraries/python/IPv4Util.py @@ -35,6 +35,26 @@ class IPv4Util(object): interface, ip_address)) get_node(node).arp_ping(ip_address, interface) + @staticmethod + @keyword('Node "${node}" interface "${port}" has IPv4 address "${address}"' + ' with prefix length "${prefix_length}"') + def set_interface_address(node, interface, address, length): + """See IPv4Node.set_ip for more information. + + :param node: Node where IP address should be set to. + :param interface: Interface name + :param address: IP address + :param length: prefix length + :type node: dict + :type interface: str + :type address: str + :type length: int + """ + log.debug('Node {} interface {} has IPv4 address {} with prefix ' + 'length {}'.format(Topology.get_node_hostname(node), + interface, address, length)) + get_node(node).set_ip(interface, address, int(length)) + @staticmethod @keyword('Node "${node}" routes to IPv4 network "${network}" with prefix ' 'length "${prefix_length}" using interface "${interface}" via ' diff --git a/resources/libraries/python/InterfaceSetup.py b/resources/libraries/python/InterfaceSetup.py index 9b6043545a..f981d2323a 100644 --- a/resources/libraries/python/InterfaceSetup.py +++ b/resources/libraries/python/InterfaceSetup.py @@ -14,6 +14,8 @@ """Interface setup library.""" from ssh import SSH +from robot.api.deco import keyword +from resources.libraries.python.VatExecutor import VatExecutor class InterfaceSetup(object): @@ -21,18 +23,15 @@ class InterfaceSetup(object): __UDEV_IF_RULES_FILE = '/etc/udev/rules.d/10-network.rules' - def __init__(self): - pass - @staticmethod def tg_set_interface_driver(node, pci_addr, driver): """Set interface driver on the TG node. :param node: Node to set interface driver on (must be TG node). - :param interface: Interface name. + :param pci_addr: PCI address of the interface. :param driver: Driver name. :type node: dict - :type interface: str + :type pci_addr: str :type driver: str """ old_driver = InterfaceSetup.tg_get_interface_driver(node, pci_addr) @@ -63,9 +62,9 @@ class InterfaceSetup(object): """Get interface driver from the TG node. :param node: Node to get interface driver on (must be TG node). - :param interface: Interface name. + :param pci_addr: PCI address of the interface. :type node: dict - :type interface: str + :type pci_addr: str :return: Interface driver or None if not found. :rtype: str @@ -150,3 +149,36 @@ class InterfaceSetup(object): continue InterfaceSetup.tg_set_interface_driver(node, if_v['pci_address'], if_v['driver']) + + @staticmethod + @keyword('Create VXLAN interface on "${DUT}" with VNI "${VNI}"' + ' from "${SRC_IP}" to "${DST_IP}"') + def create_vxlan_interface(node, vni, source_ip, destination_ip): + """Create VXLAN interface and return index of created interface + + Executes "vxlan_add_del_tunnel src {src} dst {dst} vni {vni}" VAT + command on the node. + + :param node: Node where to create VXLAN interface + :param vni: VXLAN Network Identifier + :param source_ip: Source IP of a VXLAN Tunnel End Point + :param destination_ip: Destination IP of a VXLAN Tunnel End Point + :type node: dict + :type vni: int + :type source_ip: str + :type destination_ip: str + :return: SW IF INDEX of created interface + :rtype: int + """ + + output = VatExecutor.cmd_from_template(node, "vxlan_create.vat", + src=source_ip, + dst=destination_ip, + vni=vni) + output = output[0] + + if output["retval"] == 0: + return output["sw_if_index"] + else: + raise RuntimeError('Unable to create VXLAN interface on node {}'.\ + format(node)) diff --git a/resources/libraries/python/L2Util.py b/resources/libraries/python/L2Util.py index 065c97cb97..b39515dcee 100644 --- a/resources/libraries/python/L2Util.py +++ b/resources/libraries/python/L2Util.py @@ -11,7 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""L2 bridge domain utilities Library.""" +"""L2 Utilities Library.""" from robot.api.deco import keyword from resources.libraries.python.topology import Topology @@ -19,14 +19,11 @@ from resources.libraries.python.VatExecutor import VatExecutor class L2Util(object): - """Utilities for l2 bridge domain configuration""" - - def __init__(self): - pass + """Utilities for l2 configuration""" @staticmethod def vpp_add_l2fib_entry(node, mac, interface, bd_id): - """ Creates a static L2FIB entry on a vpp node. + """ Create a static L2FIB entry on a vpp node. :param node: Node to add L2FIB entry on. :param mac: Destination mac address. @@ -43,58 +40,78 @@ class L2Util(object): 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 + def create_l2_bd(node, bd_id, flood=1, uu_flood=1, forward=1, learn=1, + arp_term=0): + """Create a l2 bridge domain on the chosen VPP node + + Execute "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 bd_id: bridge domain index number :param flood: enable flooding + :param uu_flood: enable uu_flood :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 node: dict + :type bd_id: int :type flood: bool + :type uu_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: + VatExecutor.cmd_from_template(node, "l2_bd_create.vat", + bd_id=bd_id, flood=flood, + uu_flood=uu_flood, forward=forward, + learn=learn, arp_term=arp_term) + + @staticmethod + def add_interface_to_l2_bd(node, interface, bd_id, shg=0): + """Add a interface to the l2 bridge domain. + + Get SW IF ID and add it to the bridge domain. + + :param node: node where we want to execute the command that does this + :param interface: interface name + :param bd_id: bridge domain index number to add Interface name to + :param shg: split horizon group :type node: dict :type interface: str - :type bd_id: str - :type shg: str + :type bd_id: int + :type shg: int + """ + sw_if_index = Topology.get_interface_sw_index(node, interface) + L2Util.add_sw_if_index_to_l2_bd(node, sw_if_index, bd_id, shg) + + @staticmethod + def add_sw_if_index_to_l2_bd(node, sw_if_index, bd_id, shg=0): + """Add interface with sw_if_index to l2 bridge domain. + + Execute the "sw_interface_set_l2_bridge sw_if_index {sw_if_index} + bd_id {bd_id} shg {shg} enable" VAT command on the given node. + + :param node: node where we want to execute the command that does this + :param sw_if_index: interface index + :param bd_id: bridge domain index number to add SW IF ID to + :param shg: split horizon group + :type node: dict + :type sw_if_index: int + :type bd_id: int + :type shg: int :return: """ - pass + VatExecutor.cmd_from_template(node, "l2_bd_add_sw_if_index.vat", + bd_id=bd_id, sw_if_index=sw_if_index, + shg=shg) @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. + """Create 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 @@ -107,7 +124,7 @@ class L2Util(object): The resulting dictionary looks like this: 'interface1': interface name of first interface 'interface2': interface name of second interface - 'bd_id': bridge domian index + 'bd_id': bridge domain index """ bd_dict = Topology().get_interfaces_by_link_names(node, link_names) bd_dict['bd_id'] = bd_id @@ -120,12 +137,12 @@ class L2Util(object): :param node: Node to add L2BD on. :param bd_id: Bridge domain ID. :param port_1: First interface name added to L2BD. - :param port_2: Second interface name addded to L2BD. + :param port_2: Second interface name added to L2BD. :param learn: Enable/disable MAC learn. :type node: dict :type bd_id: int - :type interface1: str - :type interface2: str + :type port_1: str + :type port_2: str :type learn: bool """ sw_if_index1 = Topology.get_interface_sw_index(node, port_1) diff --git a/resources/libraries/python/topology.py b/resources/libraries/python/topology.py index 103416078a..e2c069fc98 100644 --- a/resources/libraries/python/topology.py +++ b/resources/libraries/python/topology.py @@ -26,7 +26,7 @@ __all__ = ["DICT__nodes", 'Topology'] def load_topo_from_yaml(): - """Loads topology from file defined in "${TOPOLOGY_PATH}" variable + """Load topology from file defined in "${TOPOLOGY_PATH}" variable :return: nodes from loaded topology """ @@ -43,12 +43,13 @@ class NodeType(object): # Traffic Generator (this node has traffic generator on it) TG = 'TG' + class NodeSubTypeTG(object): #T-Rex traffic generator TREX = 'TREX' # Moongen MOONGEN = 'MOONGEN' - #IxNetwork + # IxNetwork IXNET = 'IXNET' DICT__nodes = load_topo_from_yaml() @@ -61,9 +62,6 @@ class Topology(object): the used topology. """ - def __init__(self): - pass - @staticmethod def get_node_by_hostname(nodes, hostname): """Get node from nodes of the topology by hostname. @@ -102,7 +100,7 @@ class Topology(object): @staticmethod def _get_interface_by_key_value(node, key, value): - """ Return node interface name according to key and value + """Return node interface name according to key and value :param node: :param node: the node dictionary :param key: key by which to select the interface. @@ -184,7 +182,7 @@ class Topology(object): return list_mac def _extract_vpp_interface_by_mac(self, interfaces_list, mac_address): - """Returns interface dictionary from interface_list by mac address. + """Return interface dictionary from interface_list by mac address. Extracts interface dictionary from all of the interfaces in interfaces list parsed from json according to mac_address of the interface @@ -238,7 +236,7 @@ class Topology(object): return interface_name def _update_node_interface_data_from_json(self, node, interface_dump_json): - """ Update node vpp data in node__DICT from json interface dump. + """Update node vpp data in node__DICT from json interface dump. This method updates vpp interface names and sw indexexs according to interface mac addresses found in interface_dump_json @@ -318,7 +316,7 @@ class Topology(object): InterfaceSetup.tg_set_interfaces_udev_rules(node) def update_all_interface_data_on_all_nodes(self, nodes): - """ Update interface names on all nodes in DICT__nodes + """Update interface names on all nodes in DICT__nodes :param nodes: Nodes in the topology. :type nodes: dict @@ -407,7 +405,7 @@ class Topology(object): :rtype: (dict, dict) """ link_name = None - # get link name where the interface belongs to + # get link name where the interface belongs to for port_name, port_data in node['interfaces'].iteritems(): if port_name == 'mgmt': continue @@ -516,7 +514,7 @@ class Topology(object): @staticmethod def _get_node_active_link_names(node): - """Returns list of link names that are other than mgmt links + """Return list of link names that are other than mgmt links :param node: node topology dictionary :return: list of strings that represent link names occupied by the node @@ -532,7 +530,7 @@ class Topology(object): @keyword('Get active links connecting "${node1}" and "${node2}"') def get_active_connecting_links(self, node1, node2): - """Returns list of link names that connect together node1 and node2 + """Return list of link names that connect together node1 and node2 :param node1: node topology dictionary :param node2: node topology dictionary @@ -574,7 +572,7 @@ class Topology(object): :param node2: Second node. :type node1: dict :type node2: dict - :return: Engress interfaces. + :return: Egress interfaces. :rtype: list """ interfaces = [] @@ -602,25 +600,25 @@ class Topology(object): :param node2: Second node. :type node1: dict :type node2: dict - :return: Engress interface. + :return: Egress interface. :rtype: str """ interfaces = self.get_egress_interfaces_for_nodes(node1, node2) if not interfaces: - raise RuntimeError('No engress interface for nodes') + raise RuntimeError('No egress 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. + """Return 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 tgen: traffic generator node data :param dut1: DUT1 node data :param dut2: DUT2 node data - :type tg: dict + :type tgen: dict :type dut1: dict :type dut2: dict :return: dictionary of possible link combinations @@ -662,8 +660,10 @@ class Topology(object): @staticmethod def get_node_hostname(node): + """Return host (hostname/ip address) of the node. + + :param node: Node created from topology. + :type node: dict + :return: host as 'str' type """ - :param node: node dictionary - :return: host name as 'str' type - """ - return node['host'] \ No newline at end of file + return node['host'] diff --git a/resources/libraries/robot/vxlan.robot b/resources/libraries/robot/vxlan.robot new file mode 100644 index 0000000000..e38c678d2b --- /dev/null +++ b/resources/libraries/robot/vxlan.robot @@ -0,0 +1,68 @@ +# 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 | Collections +| Resource | resources/libraries/robot/default.robot +| Resource | resources/libraries/robot/bridge_domain.robot +| Library | resources.libraries.python.L2Util +| Library | resources.libraries.python.IPUtil +| Library | resources.libraries.python.IPv4Util +| Library | resources.libraries.python.IPv4Setup +| Library | resources.libraries.python.InterfaceSetup +| Library | resources.libraries.python.InterfaceUtil +| Library | resources.libraries.python.topology.Topology +| Library | resources.libraries.python.NodePath + + +*** Keywords *** +| Setup VXLAN tunnel on nodes +| | [Arguments] | ${TG} | ${DUT1} | ${DUT2} | ${VNI} +| | Append Nodes | ${TG} | ${DUT1} | ${DUT2} | ${TG} +| | Compute Path +| | ${tgs_to_dut1} | ${tg}= | Next Interface +| | ${dut1s_to_tg} | ${dut1}= | Next Interface +| | ${dut1s_to_dut2} | ${dut1}= | Next Interface +| | ${dut2s_to_dut1} | ${dut2}= | Next Interface +| | ${dut2s_to_tg} | ${dut2}= | Next Interface +| | ${tgs_to_dut2} | ${tg}= | Next Interface +| | Set Suite Variable | ${tgs_to_dut1} +| | Set Suite Variable | ${dut1s_to_tg} +| | Set Suite Variable | ${tgs_to_dut2} +| | Set Suite Variable | ${dut2s_to_tg} +| | Set Suite Variable | ${dut1s_to_dut2} +| | Set Suite Variable | ${dut2s_to_dut1} +# TODO: replace with address generator +| | Set Suite Variable | ${dut1s_ip_address} | 172.16.0.1 +| | Set Suite Variable | ${dut2s_ip_address} | 172.16.0.2 +| | Set Suite Variable | ${duts_ip_address_prefix} | 24 +| | Set Interface State | ${TG} | ${tgs_to_dut1} | up +| | Set Interface State | ${TG} | ${tgs_to_dut2} | up +| | Setup DUT for VXLAN | ${DUT1} | ${VNI} | ${dut1s_ip_address} | ${dut2s_ip_address} +| | ... | ${dut1s_to_tg} | ${dut1s_to_dut2} | ${dut1s_ip_address} | ${duts_ip_address_prefix} +| | Setup DUT for VXLAN | ${DUT2} | ${VNI} | ${dut2s_ip_address} | ${dut1s_ip_address} +| | ... | ${dut2s_to_tg} | ${dut2s_to_dut1} | ${dut2s_ip_address} | ${duts_ip_address_prefix} +| | @{test_nodes}= | Create list | ${DUT1} | ${DUT2} +| | Vpp Nodes Interfaces Ready Wait | ${test_nodes} +# ip arp table must be filled on both nodes with neighbors address +| | VPP IP Probe | ${DUT1} | ${dut1s_to_dut2} | ${dut2s_ip_address} + +| Setup DUT for VXLAN +| | [Arguments] | ${DUT} | ${VNI} | ${SRC_IP} | ${DST_IP} | ${INGRESS} | ${EGRESS} | ${IP} | ${PREFIX} +| | Set Interface State | ${DUT} | ${EGRESS} | up +| | Set Interface State | ${DUT} | ${INGRESS} | up +| | Node "${DUT}" interface "${EGRESS}" has IPv4 address "${IP}" with prefix length "${PREFIX}" +| | ${vxlan_if_index}= | Create VXLAN interface on "${DUT}" with VNI "${VNI}" from "${SRC_IP}" to "${DST_IP}" +| | Create L2 BD | ${DUT} | ${VNI} +| | Add sw if index To L2 BD | ${DUT} | ${vxlan_if_index} | ${VNI} +| | Add Interface To L2 BD | ${DUT} | ${INGRESS} | ${VNI} diff --git a/resources/templates/vat/l2_bd_add_sw_if_index.vat b/resources/templates/vat/l2_bd_add_sw_if_index.vat new file mode 100644 index 0000000000..a0384c9170 --- /dev/null +++ b/resources/templates/vat/l2_bd_add_sw_if_index.vat @@ -0,0 +1 @@ +sw_interface_set_l2_bridge sw_if_index {sw_if_index} bd_id {bd_id} shg {shg} enable \ No newline at end of file diff --git a/resources/templates/vat/l2_bd_create.vat b/resources/templates/vat/l2_bd_create.vat new file mode 100644 index 0000000000..fa1316b932 --- /dev/null +++ b/resources/templates/vat/l2_bd_create.vat @@ -0,0 +1 @@ +bridge_domain_add_del bd_id {bd_id} flood {flood} uu-flood {uu_flood} forward {forward} learn {learn} arp-term {arp_term} \ No newline at end of file diff --git a/resources/templates/vat/vxlan_create.vat b/resources/templates/vat/vxlan_create.vat new file mode 100644 index 0000000000..15125f9be3 --- /dev/null +++ b/resources/templates/vat/vxlan_create.vat @@ -0,0 +1 @@ +vxlan_add_del_tunnel src {src} dst {dst} vni {vni} \ No newline at end of file diff --git a/tests/suites/vxlan/vxlan_untagged.robot b/tests/suites/vxlan/vxlan_untagged.robot new file mode 100644 index 0000000000..fe4428210d --- /dev/null +++ b/tests/suites/vxlan/vxlan_untagged.robot @@ -0,0 +1,27 @@ +# 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 *** +| Documentation | VXLAN tunnel untagged traffic tests +| Resource | resources/libraries/robot/default.robot +| Resource | resources/libraries/robot/bridge_domain.robot +| Resource | resources/libraries/robot/vxlan.robot +| Force Tags | 3_NODE_SINGLE_LINK_TOPO | EXPECTED_FAILING +| Suite Setup | Run Keywords | Setup all DUTs before test +| ... | AND | Setup all TGs before traffic script +| ... | AND | Setup VXLAN tunnel on nodes | ${nodes['TG']} +| | ... | ${nodes['DUT1']} | ${nodes['DUT2']} | ${23} + +*** Test Cases *** +| VPP can encapsulate L2 in VXLAN over V4 +| | Send and receive traffic | ${nodes['TG']} | ${tgs_to_dut1} | ${tgs_to_dut2}