NFVBENCH-111 Add support for VxLAN 85/65285/2 3.0.0
authorahothan <ahothan@cisco.com>
Tue, 20 Nov 2018 17:52:00 +0000 (09:52 -0800)
committerahothan <ahothan@cisco.com>
Tue, 20 Nov 2018 18:09:21 +0000 (10:09 -0800)
Change-Id: I7d9d7ccb6be7445e625ec520d22c5f045b56d5ff
Signed-off-by: ahothan <ahothan@cisco.com>
12 files changed:
docs/testing/user/userguide/readme.rst
nfvbench/cfg.default.yaml
nfvbench/chain_runner.py
nfvbench/chain_workers.py
nfvbench/chaining.py
nfvbench/nfvbench.py
nfvbench/traffic_client.py
nfvbench/traffic_gen/trex.py
nfvbench/traffic_server.py
test/mock_trex.py
test/test_chains.py
test/test_nfvbench.py

index 9915653..04b7fb1 100644 (file)
@@ -18,6 +18,7 @@ NFVbench supports the following main measurement capabilities:
 - built-in loopback VNFs based on fast L2 or L3 forwarders running in VMs
 - configurable number of flows and service chains
 - configurable traffic direction (single or bi-directional)
+- can support optional VLAN tagging (dot1q) or VxLAN overlays
 
 
 NDR is the highest throughput achieved without dropping packets.
@@ -191,5 +192,4 @@ NFVbench is agnostic of the virtual switch implementation and has been tested wi
 
 Limitations
 ***********
-NFVbench only supports VLAN with OpenStack.
-VxLAN overlays is planned for a coming release.
+VxLAN: latency measurement and per chain stats is not available in the first VxLAN release
index bc40af4..3138420 100755 (executable)
@@ -152,6 +152,23 @@ traffic_generator:
     udp_src_port:
     udp_dst_port:
 
+    # VxLAN only: optionally specify what VLAN tag to use for the VxLAN overlay
+    # This is used if the vxlan tunnels are running on a specific VLAN.
+    # Leave empty if there is no VLAN tagging required, or specify the VLAN id to use
+    # for all VxLAN tunneled traffic
+    vtep_vlan:
+    # VxLAN only: VNI range for VXLAN encapsulation [start_vni, end_vni]   [5000, 6000]
+    # VNI can have a value from range 5000-16777216
+    # For PVP, VNIs are allocated consecutively - 2 per each chain
+    # Chain 1: 5000, 5001; Chain 2: 5002, 5003; Chain X: 5000+x, 5000+x+1
+    # For PVVP scenario VNIs allocated consecutively - 3 per each chain
+    # Chain 1: 5000, 5001, 5002; Chain 2: 5003, 5004, 5005; Chain X: 5000+x, 5000+x+1, 5000+x+1
+    vnis:
+    # VxLAN only: local/source vteps IP addresses for port 0 and 1 ['10.1.1.230', '10.1.1.231']
+    src_vteps:
+    # VxLAN only: remote IP address of the remote VTEPs that terminate all tunnels originating from local VTEPs
+    dst_vtep:
+
     # L2 ADDRESSING OF UDP PACKETS
     # Lists of dest MAC addresses to use on each traffic generator port (one dest MAC per chain)
     # Leave empty for PVP, PVVP, EXT with ARP
@@ -304,11 +321,17 @@ external_networks:
     left: 'ext-lnet'
     right: 'ext-rnet'
 
+# Use 'true' to enable VXLAN encapsulation support and sent by the traffic generator
+# When this option enabled internal networks 'network type' parameter value should be 'vxlan'
+vxlan: false
+
 # Use 'true' to enable VLAN tagging of packets generated and sent by the traffic generator
 # Leave empty or set to false if you do not want the traffic generator to insert the VLAN tag (this is
 # needed for example if VLAN tagging is enabled on switch (access mode) or if you want to hook
 # directly to a NIC).
 # By default is set to true (which is the nominal use case with TOR and trunk mode to Trex ports)
+# If VxLAN is enabled, this option should be set to false (vlan tagging for encapsulated packets
+# is not supported). Use the vtep_vlan option to enable vlan tagging for the VxLAN overlay network.
 vlan_tagging: true
 
 # Used only in the case of EXT chain and no openstack to specify the VLAN IDs to use.
index 876fec2..e38cfcd 100644 (file)
@@ -79,6 +79,24 @@ class ChainRunner(object):
             gen_config.set_dest_macs(0, self.chain_manager.get_dest_macs(0))
             gen_config.set_dest_macs(1, self.chain_manager.get_dest_macs(1))
 
+        if config.vxlan:
+            # VXLAN is discovered from the networks
+            vtep_vlan = gen_config.gen_config.vtep_vlan
+            src_vteps = gen_config.gen_config.src_vteps
+            dst_vtep = gen_config.gen_config.dst_vtep
+            int_nets = self.config.internal_networks
+            network_type = set(
+                [int_nets[net].get('network_type') for net in int_nets])
+            if 'vxlan' in network_type:
+                gen_config.set_vxlans(0, self.chain_manager.get_chain_vxlans(0))
+                gen_config.set_vxlans(1, self.chain_manager.get_chain_vxlans(1))
+            gen_config.set_vtep_vlan(0, vtep_vlan)
+            gen_config.set_vtep_vlan(1, vtep_vlan)
+            # Configuring source an remote VTEPs on TREx interfaces
+            gen_config.set_vxlan_endpoints(0, src_vteps[0], dst_vtep)
+            gen_config.set_vxlan_endpoints(1, src_vteps[1], dst_vtep)
+            self.config['vxlan_gen_config'] = gen_config
+
         # get an instance of the stats manager
         self.stats_manager = StatsManager(self)
         LOG.info('ChainRunner initialized')
@@ -86,7 +104,9 @@ class ChainRunner(object):
     def __setup_traffic(self):
         self.traffic_client.setup()
         if not self.config.no_traffic:
-            if self.config.service_chain == ChainType.EXT and not self.config.no_arp:
+            # ARP is needed for EXT chain or VxLAN overlay unless disabled explicitly
+            if (self.config.service_chain == ChainType.EXT or self.config.vxlan) and \
+               not self.config.no_arp:
                 self.traffic_client.ensure_arp_successful()
             self.traffic_client.ensure_end_to_end()
 
@@ -146,10 +166,12 @@ class ChainRunner(object):
             return results
 
         LOG.info('Starting %dx%s benchmark...', self.config.service_chain_count, self.chain_name)
-        self.__setup_traffic()
-        # now that the dest MAC for all VNFs is known in all cases, it is time to create
-        # workers as they might be needed to extract stats prior to sending traffic
         self.stats_manager.create_worker()
+        if self.config.vxlan:
+            # Configure vxlan tunnels
+            self.stats_manager.worker.config_interfaces()
+
+        self.__setup_traffic()
 
         results[self.chain_name] = {'result': self.__get_chain_result()}
 
index 7c669d1..0ed2648 100644 (file)
@@ -29,6 +29,9 @@ class BasicWorker(object):
     def get_version(self):
         return {}
 
+    def config_interfaces(self):
+        return {}
+
     def close(self):
         pass
 
index 8d717aa..a02bb17 100644 (file)
@@ -288,6 +288,16 @@ class ChainNetwork(object):
             raise ChainException('Trying to retrieve VLAN id for non VLAN network')
         return self.network['provider:segmentation_id']
 
+    def get_vxlan(self):
+        """
+        Extract VNI for this network.
+
+        :return: VNI ID for this network
+        """
+        if self.network['provider:network_type'] != 'vxlan':
+            raise ChainException('Trying to retrieve VNI for non VXLAN network')
+        return self.network['provider:segmentation_id']
+
     def delete(self):
         """Delete this network."""
         if not self.reuse and self.network:
@@ -631,6 +641,20 @@ class Chain(object):
             port_index = -1
         return self.networks[port_index].get_vlan()
 
+    def get_vxlan(self, port_index):
+        """Get the VXLAN id on a given port.
+
+        port_index: left port is 0, right port is 1
+        return: the vxlan_id or None if there is no vxlan
+        """
+        # for port 1 we need to return the VLAN of the last network in the chain
+        # The networks array contains 2 networks for PVP [left, right]
+        # and 3 networks in the case of PVVP [left.middle,right]
+        if port_index:
+            # this will pick the last item in array
+            port_index = -1
+        return self.networks[port_index].get_vxlan()
+
     def get_dest_mac(self, port_index):
         """Get the dest MAC on a given port.
 
@@ -834,6 +858,12 @@ class ChainManager(object):
                 re_vlan = "[0-9]*$"
                 self.vlans = [self._check_list('vlans[0]', config.vlans[0], re_vlan),
                               self._check_list('vlans[1]', config.vlans[1], re_vlan)]
+            if config.vxlan:
+                # make sure there are 2 entries
+                if len(config.vnis) != 2:
+                    raise ChainException('The config vnis property must be a list with 2 VNIs')
+                self.vnis = [self._check_list('vnis[0]', config.vnis[0], re_vlan),
+                             self._check_list('vnis[1]', config.vnis[1], re_vlan)]
 
     def _get_dest_macs_from_config(self):
         re_mac = "[0-9a-fA-F]{2}([-:])[0-9a-fA-F]{2}(\\1[0-9a-fA-F]{2}){4}$"
@@ -930,6 +960,39 @@ class ChainManager(object):
         if initial_instance_count:
             LOG.info('All instances are active')
 
+    def _get_vxlan_net_cfg(self, chain_id):
+        int_nets = self.config.internal_networks
+        net_left = int_nets.left
+        net_right = int_nets.right
+        vnis = self.generator_config.vnis
+        chain_id += 1
+        seg_id_left = vnis[0]
+        if self.config.service_chain == ChainType.PVP:
+            if chain_id > 1:
+                seg_id_left = ((chain_id - 1) * 2) + seg_id_left
+            seg_id_right = seg_id_left + 1
+            if (seg_id_left and seg_id_right) > vnis[1]:
+                raise Exception('Segmentation ID is more than allowed '
+                                'value: {}'.format(vnis[1]))
+            net_left['segmentation_id'] = seg_id_left
+            net_right['segmentation_id'] = seg_id_right
+            net_cfg = [net_left, net_right]
+        else:
+            # PVVP
+            net_middle = int_nets.middle
+            if chain_id > 1:
+                seg_id_left = ((chain_id - 1) * 3) + seg_id_left
+            seg_id_middle = seg_id_left + 1
+            seg_id_right = seg_id_left + 2
+            if (seg_id_left and seg_id_right and seg_id_middle) > vnis[1]:
+                raise Exception('Segmentation ID is more than allowed '
+                                'value: {}'.format(vnis[1]))
+            net_left['segmentation_id'] = seg_id_left
+            net_middle['segmentation_id'] = seg_id_middle
+            net_right['segmentation_id'] = seg_id_right
+            net_cfg = [net_left, net_middle, net_right]
+        return net_cfg
+
     def get_networks(self, chain_id=None):
         """Get the networks for given EXT, PVP or PVVP chain.
 
@@ -952,10 +1015,15 @@ class ChainManager(object):
         else:
             lookup_only = False
             int_nets = self.config.internal_networks
-            if self.config.service_chain == ChainType.PVP:
-                net_cfg = [int_nets.left, int_nets.right]
+            network_type = set([int_nets[net].get('network_type') for net in int_nets])
+            if self.config.vxlan and 'vxlan' in network_type:
+                net_cfg = self._get_vxlan_net_cfg()
             else:
-                net_cfg = [int_nets.left, int_nets.middle, int_nets.right]
+                # VLAN
+                if self.config.service_chain == ChainType.PVP:
+                    net_cfg = [int_nets.left, int_nets.right]
+                else:
+                    net_cfg = [int_nets.left, int_nets.middle, int_nets.right]
         networks = []
         try:
             for cfg in net_cfg:
@@ -1048,6 +1116,18 @@ class ChainManager(object):
         # no openstack
         return self.vlans[port_index]
 
+    def get_chain_vxlans(self, port_index):
+        """Get the list of per chain VNIs id on a given port.
+
+        port_index: left port is 0, right port is 1
+        return: a VNIs ID list indexed by the chain index or None if no vlan tagging
+        """
+        if self.chains:
+            return [self.chains[chain_index].get_vxlan(port_index)
+                    for chain_index in range(self.chain_count)]
+        # no openstack
+        return self.vnis[port_index]
+
     def get_dest_macs(self, port_index):
         """Get the list of per chain dest MACs on a given port.
 
index 0d6da7f..f06c593 100644 (file)
@@ -66,6 +66,16 @@ class NFVBench(object):
         self.specs.set_openstack_spec(openstack_spec)
         self.vni_ports = []
         sys.stdout.flush()
+        self.check_options()
+
+    def check_options(self):
+        if self.base_config.vxlan:
+            if self.base_config.vlan_tagging:
+                raise Exception(
+                    'Inner VLAN tagging is not currently supported for VXLAN')
+            vtep_vlan = self.base_config.traffic_generator.get('vtep_vlan')
+            if vtep_vlan is None:
+                LOG.warning('Warning: VXLAN mode enabled, but VTEP vlan is not defined')
 
     def set_notifier(self, notifier):
         self.notifier = notifier
@@ -216,7 +226,7 @@ class NFVBench(object):
         self.config_plugin.validate_config(config, self.specs.openstack)
 
 
-def parse_opts_from_cli():
+def _parse_opts_from_cli():
     parser = argparse.ArgumentParser()
 
     parser.add_argument('--status', dest='status',
@@ -280,7 +290,7 @@ def parse_opts_from_cli():
     parser.add_argument('--inter-node', dest='inter_node',
                         default=None,
                         action='store_true',
-                        help='run VMs in different compute nodes (PVVP only)')
+                        help='(deprecated)')
 
     parser.add_argument('--sriov', dest='sriov',
                         default=None,
@@ -318,6 +328,11 @@ def parse_opts_from_cli():
                         action='store_true',
                         help='Skip vswitch configuration and retrieving of stats')
 
+    parser.add_argument('--vxlan', dest='vxlan',
+                        default=None,
+                        action='store_true',
+                        help='Enable VxLan encapsulation')
+
     parser.add_argument('--no-cleanup', dest='no_cleanup',
                         default=None,
                         action='store_true',
@@ -469,7 +484,7 @@ def main():
         config_plugin = factory.get_config_plugin_class()(config)
         config = config_plugin.get_config()
 
-        opts, unknown_opts = parse_opts_from_cli()
+        opts, unknown_opts = _parse_opts_from_cli()
         log.set_level(debug=opts.debug)
 
         if opts.version:
index 34d93cf..c9daf40 100755 (executable)
@@ -144,17 +144,26 @@ class Device(object):
     identified as port 0 or port 1.
     """
 
-    def __init__(self, port, generator_config, vtep_vlan=None):
+    def __init__(self, port, generator_config):
         """Create a new device for a given port."""
         self.generator_config = generator_config
         self.chain_count = generator_config.service_chain_count
         self.flow_count = generator_config.flow_count / 2
         self.port = port
         self.switch_port = generator_config.interfaces[port].get('switch_port', None)
-        self.vtep_vlan = vtep_vlan
+        self.vtep_vlan = None
+        self.vtep_src_mac = None
+        self.vxlan = False
         self.pci = generator_config.interfaces[port].pci
         self.mac = None
         self.dest_macs = None
+        self.vtep_dst_mac = None
+        self.vtep_dst_ip = None
+        if generator_config.vteps is None:
+            self.vtep_src_ip = None
+        else:
+            self.vtep_src_ip = generator_config.vteps[port]
+        self.vnis = None
         self.vlans = None
         self.ip_addrs = generator_config.ip_addrs[port]
         subnet = IPNetwork(self.ip_addrs)
@@ -181,6 +190,15 @@ class Device(object):
         """Get the peer device (device 0 -> device 1, or device 1 -> device 0)."""
         return self.generator_config.devices[1 - self.port]
 
+    def set_vtep_dst_mac(self, dest_macs):
+        """Set the list of dest MACs indexed by the chain id.
+
+        This is only called in 2 cases:
+        - VM macs discovered using openstack API
+        - dest MACs provisioned in config file
+        """
+        self.vtep_dst_mac = map(str, dest_macs)
+
     def set_dest_macs(self, dest_macs):
         """Set the list of dest MACs indexed by the chain id.
 
@@ -206,6 +224,23 @@ class Device(object):
         self.vlans = vlans
         LOG.info("Port %d: VLANs %s", self.port, self.vlans)
 
+    def set_vtep_vlan(self, vlan):
+        """Set the vtep vlan to use indexed by specific port."""
+        self.vtep_vlan = vlan
+        self.vxlan = True
+        self.vlan_tagging = None
+        LOG.info("Port %d: VTEP VLANs %s", self.port, self.vtep_vlan)
+
+    def set_vxlan_endpoints(self, src_ip, dst_ip):
+        self.vtep_dst_ip = dst_ip
+        self.vtep_src_ip = src_ip
+        LOG.info("Port %d: src_vtep %s, dst_vtep %s", self.port,
+                 self.vtep_src_ip, self.vtep_dst_ip)
+
+    def set_vxlans(self, vnis):
+        self.vnis = vnis
+        LOG.info("Port %d: VNIs %s", self.port, self.vnis)
+
     def get_gw_ip(self, chain_index):
         """Retrieve the IP address assigned for the gateway of a given chain."""
         return self.gw_ip_block.get_ip(chain_index)
@@ -249,7 +284,14 @@ class Device(object):
                 'mac_discovery_gw': self.get_gw_ip(chain_idx),
                 'ip_src_tg_gw': self.tg_gw_ip_block.get_ip(chain_idx),
                 'ip_dst_tg_gw': peer.tg_gw_ip_block.get_ip(chain_idx),
-                'vlan_tag': self.vlans[chain_idx] if self.vlans else None
+                'vlan_tag': self.vlans[chain_idx] if self.vlans else None,
+                'vxlan': self.vxlan,
+                'vtep_vlan': self.vtep_vlan if self.vtep_vlan else None,
+                'vtep_src_mac': self.mac if self.vxlan is True else None,
+                'vtep_dst_mac': self.vtep_dst_mac if self.vxlan is True else None,
+                'vtep_dst_ip': self.vtep_dst_ip if self.vxlan is True else None,
+                'vtep_src_ip': self.vtep_src_ip if self.vxlan is True else None,
+                'net_vni': self.vnis[chain_idx] if self.vxlan is True else None
             })
             # after first chain, fall back to the flow count for all other chains
             cur_chain_flow_count = flows_per_chain
@@ -312,6 +354,8 @@ class GeneratorConfig(object):
         self.gateway_ips = gen_config.gateway_ip_addrs
         self.udp_src_port = gen_config.udp_src_port
         self.udp_dst_port = gen_config.udp_dst_port
+        self.vteps = gen_config.get('vteps')
+        self.vnis = gen_config.get('vnis')
         self.devices = [Device(port, self) for port in [0, 1]]
         # This should normally always be [0, 1]
         self.ports = [device.port for device in self.devices]
@@ -344,6 +388,18 @@ class GeneratorConfig(object):
         self.devices[port_index].set_dest_macs(dest_macs)
         LOG.info('Port %d: dst MAC %s', port_index, [str(mac) for mac in dest_macs])
 
+    def set_vtep_dest_macs(self, port_index, dest_macs):
+        """Set the list of dest MACs indexed by the chain id on given port.
+
+        port_index: the port for which dest macs must be set
+        dest_macs: a list of dest MACs indexed by chain id
+        """
+        if len(dest_macs) != self.config.service_chain_count:
+            raise TrafficClientException('Dest MAC list %s must have %d entries' %
+                                         (dest_macs, self.config.service_chain_count))
+        self.devices[port_index].set_vtep_dst_mac(dest_macs)
+        LOG.info('Port %d: vtep dst MAC %s', port_index, set([str(mac) for mac in dest_macs]))
+
     def get_dest_macs(self):
         """Return the list of dest macs indexed by port."""
         return [dev.get_dest_macs() for dev in self.devices]
@@ -359,6 +415,26 @@ class GeneratorConfig(object):
                                          (vlans, self.config.service_chain_count))
         self.devices[port_index].set_vlans(vlans)
 
+    def set_vxlans(self, port_index, vxlans):
+        """Set the list of vxlans (VNIs) to use indexed by the chain id on given port.
+
+        port_index: the port for which VXLANs must be set
+        VXLANs: a  list of VNIs lists indexed by chain id
+        """
+        if len(vxlans) != self.config.service_chain_count:
+            raise TrafficClientException('VXLAN list %s must have %d entries' %
+                                         (vxlans, self.config.service_chain_count))
+        self.devices[port_index].set_vxlans(vxlans)
+
+    def set_vtep_vlan(self, port_index, vlan):
+        """Set the vtep vlan to use indexed by the chain id on given port.
+        port_index: the port for which VLAN must be set
+        """
+        self.devices[port_index].set_vtep_vlan(vlan)
+
+    def set_vxlan_endpoints(self, port_index, src_ip, dst_ip):
+        self.devices[port_index].set_vxlan_endpoints(src_ip, dst_ip)
+
     @staticmethod
     def __match_generator_profile(traffic_generator, generator_profile):
         gen_config = AttrDict(traffic_generator)
@@ -507,6 +583,12 @@ class TrafficClient(object):
             for chain, mac in enumerate(dest_macs):
                 mac_map[mac] = (port, chain)
         unique_src_mac_count = len(mac_map)
+        if self.config.vxlan and self.config.traffic_generator.vtep_vlan:
+            get_mac_id = lambda packet: packet['binary'][60:66]
+        elif self.config.vxlan:
+            get_mac_id = lambda packet: packet['binary'][56:62]
+        else:
+            get_mac_id = lambda packet: packet['binary'][6:12]
         for it in xrange(retry_count):
             self.gen.clear_stats()
             self.gen.start_traffic()
@@ -521,8 +603,8 @@ class TrafficClient(object):
             self.gen.stop_capture()
 
             for packet in self.gen.packet_list:
-                src_mac = packet['binary'][6:12]
-                src_mac = ':'.join(["%02x" % ord(x) for x in src_mac])
+                mac_id = get_mac_id(packet)
+                src_mac = ':'.join(["%02x" % ord(x) for x in mac_id])
                 if src_mac in mac_map:
                     port, chain = mac_map[src_mac]
                     LOG.info('Received packet from mac: %s (chain=%d, port=%d)',
@@ -540,8 +622,12 @@ class TrafficClient(object):
         dest_macs = self.gen.resolve_arp()
         if dest_macs:
             # all dest macs are discovered, saved them into the generator config
-            self.generator_config.set_dest_macs(0, dest_macs[0])
-            self.generator_config.set_dest_macs(1, dest_macs[1])
+            if self.config.vxlan:
+                self.generator_config.set_vtep_dest_macs(0, dest_macs[0])
+                self.generator_config.set_vtep_dest_macs(1, dest_macs[1])
+            else:
+                self.generator_config.set_dest_macs(0, dest_macs[0])
+                self.generator_config.set_dest_macs(1, dest_macs[1])
         else:
             raise TrafficClientException('ARP cannot be resolved')
 
index 6bb0c34..1f460f6 100644 (file)
@@ -13,6 +13,7 @@
 #    under the License.
 """Driver module for TRex traffic generator."""
 
+import math
 import os
 import random
 import time
@@ -32,10 +33,13 @@ from traffic_utils import IMIX_L2_SIZES
 from traffic_utils import IMIX_RATIOS
 
 # pylint: disable=import-error
+from trex_stl_lib.api import bind_layers
 from trex_stl_lib.api import CTRexVmInsFixHwCs
 from trex_stl_lib.api import Dot1Q
 from trex_stl_lib.api import Ether
+from trex_stl_lib.api import FlagsField
 from trex_stl_lib.api import IP
+from trex_stl_lib.api import Packet
 from trex_stl_lib.api import STLClient
 from trex_stl_lib.api import STLError
 from trex_stl_lib.api import STLFlowLatencyStats
@@ -48,12 +52,26 @@ from trex_stl_lib.api import STLVmFixChecksumHw
 from trex_stl_lib.api import STLVmFlowVar
 from trex_stl_lib.api import STLVmFlowVarRepetableRandom
 from trex_stl_lib.api import STLVmWrFlowVar
+from trex_stl_lib.api import ThreeBytesField
 from trex_stl_lib.api import UDP
+from trex_stl_lib.api import XByteField
 from trex_stl_lib.services.trex_stl_service_arp import STLServiceARP
 
 
 # pylint: enable=import-error
 
+class VXLAN(Packet):
+    """VxLAN class."""
+
+    _VXLAN_FLAGS = ['R' * 27] + ['I'] + ['R' * 5]
+    name = "VXLAN"
+    fields_desc = [FlagsField("flags", 0x08000000, 32, _VXLAN_FLAGS),
+                   ThreeBytesField("vni", 0),
+                   XByteField("reserved", 0x00)]
+
+    def mysummary(self):
+        """Summary."""
+        return self.sprintf("VXLAN (vni=%VXLAN.vni%)")
 
 class TRex(AbstractTrafficGenerator):
     """TRex traffic generator driver."""
@@ -221,8 +239,12 @@ class TRex(AbstractTrafficGenerator):
                 lat = trex_stats['latency'][lat_pg_id]['latency']
                 # dropped_pkts += lat['err_cntrs']['dropped']
                 latencies[port].max_usec = get_latency(lat['total_max'])
-                latencies[port].min_usec = get_latency(lat['total_min'])
-                latencies[port].avg_usec = get_latency(lat['average'])
+                if math.isnan(lat['total_min']):
+                    latencies[port].min_usec = 0
+                    latencies[port].avg_usec = 0
+                else:
+                    latencies[port].min_usec = get_latency(lat['total_min'])
+                    latencies[port].avg_usec = get_latency(lat['average'])
             except KeyError:
                 pass
 
@@ -247,6 +269,10 @@ class TRex(AbstractTrafficGenerator):
         results['max_delay_usec'] = total_max
         results['avg_delay_usec'] = int(average / self.chain_count)
 
+    def _bind_vxlan(self):
+        bind_layers(UDP, VXLAN, dport=4789)
+        bind_layers(VXLAN, Ether)
+
     def _create_pkt(self, stream_cfg, l2frame_size):
         """Create a packet of given size.
 
@@ -255,7 +281,20 @@ class TRex(AbstractTrafficGenerator):
         # Trex will add the FCS field, so we need to remove 4 bytes from the l2 frame size
         frame_size = int(l2frame_size) - 4
 
-        pkt_base = Ether(src=stream_cfg['mac_src'], dst=stream_cfg['mac_dst'])
+        if stream_cfg['vxlan'] is True:
+            self._bind_vxlan()
+            encap_level = '1'
+            pkt_base = Ether(src=stream_cfg['vtep_src_mac'], dst=stream_cfg['vtep_dst_mac'])
+            if stream_cfg['vtep_vlan'] is not None:
+                pkt_base /= Dot1Q(vlan=stream_cfg['vtep_vlan'])
+            pkt_base /= IP(src=stream_cfg['vtep_src_ip'], dst=stream_cfg['vtep_dst_ip'])
+            pkt_base /= UDP(sport=random.randint(1337, 32767), dport=4789)
+            pkt_base /= VXLAN(vni=stream_cfg['net_vni'])
+            pkt_base /= Ether(src=stream_cfg['mac_src'], dst=stream_cfg['mac_dst'])
+        else:
+            encap_level = '0'
+            pkt_base = Ether(src=stream_cfg['mac_src'], dst=stream_cfg['mac_dst'])
+
         if stream_cfg['vlan_tag'] is not None:
             pkt_base /= Dot1Q(vlan=stream_cfg['vlan_tag'])
 
@@ -299,11 +338,11 @@ class TRex(AbstractTrafficGenerator):
 
         vm_param = [
             src_fv,
-            STLVmWrFlowVar(fv_name="ip_src", pkt_offset="IP.src"),
+            STLVmWrFlowVar(fv_name="ip_src", pkt_offset="IP:{}.src".format(encap_level)),
             dst_fv,
-            STLVmWrFlowVar(fv_name="ip_dst", pkt_offset="IP.dst"),
-            STLVmFixChecksumHw(l3_offset="IP",
-                               l4_offset="UDP",
+            STLVmWrFlowVar(fv_name="ip_dst", pkt_offset="IP:{}.dst".format(encap_level)),
+            STLVmFixChecksumHw(l3_offset="IP:{}".format(encap_level),
+                               l4_offset="UDP:{}".format(encap_level),
                                l4_type=CTRexVmInsFixHwCs.L4_TYPE_UDP)
         ]
         pad = max(0, frame_size - len(pkt_base)) * 'x'
@@ -458,14 +497,23 @@ class TRex(AbstractTrafficGenerator):
             dst_macs = [None] * chain_count
             dst_macs_count = 0
             # the index in the list is the chain id
-            arps = [
-                STLServiceARP(ctx,
-                              src_ip=cfg['ip_src_tg_gw'],
-                              dst_ip=cfg['mac_discovery_gw'],
-                              # will be None if no vlan tagging
-                              vlan=cfg['vlan_tag'])
-                for cfg in stream_configs
-            ]
+            if self.config.vxlan:
+                arps = [
+                    STLServiceARP(ctx,
+                                  src_ip=device.vtep_src_ip,
+                                  dst_ip=device.vtep_dst_ip,
+                                  vlan=device.vtep_vlan)
+                    for cfg in stream_configs
+                ]
+            else:
+                arps = [
+                    STLServiceARP(ctx,
+                                  src_ip=cfg['ip_src_tg_gw'],
+                                  dst_ip=cfg['mac_discovery_gw'],
+                                  # will be None if no vlan tagging
+                                  vlan=cfg['vlan_tag'])
+                    for cfg in stream_configs
+                ]
 
             for attempt in range(self.config.generic_retry_count):
                 try:
index 2239ec3..b2d8367 100644 (file)
@@ -43,8 +43,9 @@ class TRexTrafficServer(TrafficServer):
         """
         cfg = self.__save_config(generator_config, filename)
         cores = generator_config.cores
+        vtep_vlan = generator_config.gen_config.get('vtep_vlan')
         sw_mode = "--software" if generator_config.software_mode else ""
-        vlan_opt = "--vlan" if generator_config.vlan_tagging else ""
+        vlan_opt = "--vlan" if (generator_config.vlan_tagging or vtep_vlan) else ""
         subprocess.Popen(['nohup', '/bin/bash', '-c',
                           './t-rex-64 -i -c {} --iom 0 --no-scapy-server --close-at-end {} '
                           '{} --cfg {} &> /tmp/trex.log & disown'.format(cores, sw_mode,
index c128e9a..c4ce9d7 100644 (file)
@@ -55,6 +55,11 @@ except ImportError:
     api_mod.STLVmFlowVarRepetableRandom = STLDummy
     api_mod.STLVmWrFlowVar = STLDummy
     api_mod.UDP = STLDummy
+    api_mod.bind_layers = STLDummy
+    api_mod.FlagsField = STLDummy
+    api_mod.Packet = STLDummy
+    api_mod.ThreeBytesField = STLDummy
+    api_mod.XByteField = STLDummy
 
     services_mod = ModuleType('trex_stl_lib.services')
     stl_lib_mod.services = services_mod
index 109b73b..a9c9ac7 100644 (file)
@@ -76,6 +76,7 @@ def test_chain_runner_ext_no_openstack():
     config = _get_chain_config(sc=ChainType.EXT)
     specs = Specs()
     config.vlans = [100, 200]
+    config.vnis = [5000, 6000]
     config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00']
     config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00']
 
@@ -172,6 +173,7 @@ def _check_nfvbench_openstack(sc=ChainType.PVP, l2_loopback=False):
         if l2_loopback:
             config.l2_loopback = True
             config.vlans = [[100], [200]]
+            config.vnis = [[5000], [6000]]
         factory = BasicFactory()
         config_plugin = factory.get_config_plugin_class()(config)
         config = config_plugin.get_config()
index f532bba..04778e7 100644 (file)
@@ -319,6 +319,7 @@ def _get_dummy_tg_config(chain_type, rate, scc=1, fc=10, step_ip='0.0.0.1',
 
 def _get_traffic_client():
     config = _get_dummy_tg_config('PVP', 'ndr_pdr')
+    config['vxlan'] = False
     config['ndr_run'] = True
     config['pdr_run'] = True
     config['generator_profile'] = 'dummy'