From 8b1b3e5c7fb91f450b2abe4a8201bcfec14b2bb9 Mon Sep 17 00:00:00 2001 From: ahothan Date: Tue, 20 Nov 2018 09:52:00 -0800 Subject: NFVBENCH-111 Add support for VxLAN Change-Id: I7d9d7ccb6be7445e625ec520d22c5f045b56d5ff Signed-off-by: ahothan --- docs/testing/user/userguide/readme.rst | 4 +- nfvbench/cfg.default.yaml | 23 ++++++++ nfvbench/chain_runner.py | 30 ++++++++-- nfvbench/chain_workers.py | 3 + nfvbench/chaining.py | 86 +++++++++++++++++++++++++++- nfvbench/nfvbench.py | 21 ++++++- nfvbench/traffic_client.py | 100 ++++++++++++++++++++++++++++++--- nfvbench/traffic_gen/trex.py | 78 ++++++++++++++++++++----- nfvbench/traffic_server.py | 3 +- test/mock_trex.py | 5 ++ test/test_chains.py | 2 + test/test_nfvbench.py | 1 + 12 files changed, 321 insertions(+), 35 deletions(-) diff --git a/docs/testing/user/userguide/readme.rst b/docs/testing/user/userguide/readme.rst index 9915653..04b7fb1 100644 --- a/docs/testing/user/userguide/readme.rst +++ b/docs/testing/user/userguide/readme.rst @@ -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 diff --git a/nfvbench/cfg.default.yaml b/nfvbench/cfg.default.yaml index bc40af4..3138420 100755 --- a/nfvbench/cfg.default.yaml +++ b/nfvbench/cfg.default.yaml @@ -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. diff --git a/nfvbench/chain_runner.py b/nfvbench/chain_runner.py index 876fec2..e38cfcd 100644 --- a/nfvbench/chain_runner.py +++ b/nfvbench/chain_runner.py @@ -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()} diff --git a/nfvbench/chain_workers.py b/nfvbench/chain_workers.py index 7c669d1..0ed2648 100644 --- a/nfvbench/chain_workers.py +++ b/nfvbench/chain_workers.py @@ -29,6 +29,9 @@ class BasicWorker(object): def get_version(self): return {} + def config_interfaces(self): + return {} + def close(self): pass diff --git a/nfvbench/chaining.py b/nfvbench/chaining.py index 8d717aa..a02bb17 100644 --- a/nfvbench/chaining.py +++ b/nfvbench/chaining.py @@ -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. diff --git a/nfvbench/nfvbench.py b/nfvbench/nfvbench.py index 0d6da7f..f06c593 100644 --- a/nfvbench/nfvbench.py +++ b/nfvbench/nfvbench.py @@ -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: diff --git a/nfvbench/traffic_client.py b/nfvbench/traffic_client.py index 34d93cf..c9daf40 100755 --- a/nfvbench/traffic_client.py +++ b/nfvbench/traffic_client.py @@ -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') diff --git a/nfvbench/traffic_gen/trex.py b/nfvbench/traffic_gen/trex.py index 6bb0c34..1f460f6 100644 --- a/nfvbench/traffic_gen/trex.py +++ b/nfvbench/traffic_gen/trex.py @@ -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: diff --git a/nfvbench/traffic_server.py b/nfvbench/traffic_server.py index 2239ec3..b2d8367 100644 --- a/nfvbench/traffic_server.py +++ b/nfvbench/traffic_server.py @@ -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, diff --git a/test/mock_trex.py b/test/mock_trex.py index c128e9a..c4ce9d7 100644 --- a/test/mock_trex.py +++ b/test/mock_trex.py @@ -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 diff --git a/test/test_chains.py b/test/test_chains.py index 109b73b..a9c9ac7 100644 --- a/test/test_chains.py +++ b/test/test_chains.py @@ -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() diff --git a/test/test_nfvbench.py b/test/test_nfvbench.py index f532bba..04778e7 100644 --- a/test/test_nfvbench.py +++ b/test/test_nfvbench.py @@ -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' -- cgit 1.2.3-korg