MPLS support + loop_vm_arp test fix 15/68615/18 4.1.0
authormklyus <mklyus@cisco.com>
Fri, 11 Oct 2019 05:35:51 +0000 (08:35 +0300)
committermklyus <mklyus@cisco.com>
Fri, 31 Jan 2020 08:20:59 +0000 (11:20 +0300)
Change-Id: I17b1b2a97f0bc185d3906250d5f91b4c8fcb9686
Signed-off-by: Max Klyus <mklyus@cisco.com>
14 files changed:
docs/testing/user/userguide/index.rst
docs/testing/user/userguide/mpls.rst [new file with mode: 0644]
nfvbench/cfg.default.yaml
nfvbench/chain_runner.py
nfvbench/chaining.py
nfvbench/nfvbench.py
nfvbench/specs.py
nfvbench/traffic_client.py
nfvbench/traffic_gen/trex_gen.py
nfvbench/utils.py
pylint.rc
test-requirements.txt
test/test_chains.py
test/test_nfvbench.py

index 84c79b0..a61fa46 100644 (file)
@@ -25,6 +25,7 @@ Table of Content
    examples
    advanced
    pvpl3
+   mpls
    extchains
    fluentd
    sriov
diff --git a/docs/testing/user/userguide/mpls.rst b/docs/testing/user/userguide/mpls.rst
new file mode 100644 (file)
index 0000000..eaa9541
--- /dev/null
@@ -0,0 +1,93 @@
+==========================
+MPLS encapsulation feature
+==========================
+
+This feature allows to generate packets with standard MPLS L2VPN double stack MPLS labels, where the outer label is transport and the inner label is VPN.
+The top layer of a packets encapsulated inside MPLS L2VPN seems to be an Ethernet layer with the rest of the IP stack inside.
+Please refer to RFC-3031 for more details.
+The whole MPLS packet structure looks like the following:
+
+###[ Ethernet ]###
+  dst       = ['00:8a:96:bb:14:28']
+  src       = 3c:fd:fe:a3:48:7c
+  type      = 0x8847
+###[ MPLS ]### <-------------- Outer Label
+     label     = 16303
+     cos       = 1
+     s         = 0
+     ttl       = 255
+###[ MPLS ]### <-------------- Inner Label
+        label     = 5010
+        cos       = 1
+        s         = 1
+        ttl       = 255
+###[ Ethernet ]###
+           dst       = fa:16:3e:bd:02:b5
+           src       = 3c:fd:fe:a3:48:7c
+           type      = 0x800
+###[ IP ]###
+              version   = 4
+              ihl       = None
+              tos       = 0x0
+              len       = None
+              id        = 1
+              flags     =
+              frag      = 0
+              ttl       = 64
+              proto     = udp
+              chksum    = None
+              src       = 16.0.0.1
+              dst       = 48.0.0.1
+              \options   \
+###[ UDP ]###
+                 sport     = 53
+                 dport     = 53
+                 len       = None
+                 chksum    = None
+
+Example: nfvbench generates mpls traffic port A ----> port B. This example assumes openstack is at the other end of the mpls tunnels.
+Packets generated and sent to port B are delivered to the MPLS domain infrastructure which will transport that packet to the other end
+of the MPLS transport tunnel using the outer label. At that point, the outer label is decapsulated and the inner label is used to
+select the destination openstack network. After decapsulation of the inner label, the resulting L2 frame is then forwarded to the
+destination VM corresponding to the destination MAC. When the VM receives the packet, it is sent back to far end port of the traffic
+generator (port B) using either L2 forwarding or L3 routing though the peer virtual interface. The return packet is then encapsulated
+with the inner label first then outer label to reach nfvbench on port B.
+
+Only 2 MPLS labels stack is supported. If more than two labels stack is required then these operations should be handled by MPLS transport
+domain where nfvbench is attached next-hop mpls router and rest of the mpls domain should be configured accordingly to be able
+pop/swap/push labels and deliver packet to the proper destination based on an initial transport label injected by nfvbench, VPN label
+should stay unchanged until its delivered to PE (compute node).
+Set nfvbench 'mpls' parameter to 'true' to enable MPLS encapsulation.
+When this option is enabled internal networks 'network type' parameter value should be 'mpls'
+MPLS and VxLAN encapsulations are mutual exclusive features if 'mpls' is 'true' then 'vxlan' should be set to 'false' and vise versa.
+no_flow_stats, no_latency_stats, no_latency_streams parameters should be set to 'true' because these features are not supported at the moment.
+In future when these features will be supported they will require special NIC hardware.
+
+Example of 1-chain MPLS configuration:
+ internal_networks:
+    left:
+        network_type: mpls
+        segmentation_id: 5010
+        mpls_transport_labels: 16303
+        physical_network: phys_sriov0
+    right:
+        network_type: mpls
+        segmentation_id: 5011
+        mpls_transport_labels: 16303
+        physical_network: phys_sriov1
+
+Example of 2-chain MPLS configuration:
+ internal_networks:
+    left:
+        network_type: mpls
+        segmentation_id: [5010, 5020]
+        mpls_transport_labels: [16303, 16304]
+        physical_network: phys_sriov0
+    right:
+        network_type: mpls
+        segmentation_id: [5011, 5021]
+        mpls_transport_labels: [16303, 16304]
+        physical_network: phys_sriov1
+
+Example of how to run:
+nfvbench --rate 50000pps --duration 30 --mpls
index 2abc8dc..97f98cd 100755 (executable)
@@ -187,11 +187,14 @@ traffic_generator:
     # 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: local/source vteps IP addresses for port 0 and 1 ['10.1.1.230', '10.1.1.231']
+    # VxLAN and MPLS 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:
-
+    # The encapsulated L3/MPLS packet needs to traverse L3 or MPLS fabric to reach to its final dst_vtep.
+    # This parameter is required to resolve first next-hop MAC address if it next-hop is not its final dst_vtep.
+    # This parameter is mandatory for MPLS only
+    vtep_gateway_ips:
     # 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
@@ -369,7 +372,7 @@ loop_vm_name: 'nfvbench-loop-vm'
 #        segmentation_id: 2001
 #        physical_network: phys_sriov1
 #
-# For multi-chaining and non shared network mode (VLAN, SRIOV, VxLAN):
+# For multi-chaining and non shared network mode (VLAN, SRIOV, VxLAN, MPLS):
 # - the segmentation_id field if provided must be a list of values (as many as chains)
 # - segmentation_id auto-indexing:
 #   the segmentation_id field can also be a single value that represents the base value from which
@@ -379,23 +382,42 @@ loop_vm_name: 'nfvbench-loop-vm'
 # - the physical_network can be a single name (all VFs to be allocated on same physnet)
 #   of a list of physnet names to use different PFs
 #
-# Example of 2-chain configuration:
-# internal_networks:
-#    left:
-#        segmentation_id: [2000, 2001]
-#        physical_network: phys_sriov0
-#    right:
-#        segmentation_id: [2010, 2011]
-#        physical_network: phys_sriov1
+#   Example of 2-chain VLAN configuration:
+#   internal_networks:
+#      left:
+#          segmentation_id: [2000, 2001]
+#          physical_network: phys_sriov0
+#      right:
+#          segmentation_id: [2010, 2011]
+#          physical_network: phys_sriov1
+#   Equivalent to (using auto-indexing):
+#   internal_networks:
+#      left:
+#          segmentation_id: 2000
+#          physical_network: phys_sriov0
+#      right:
+#          segmentation_id: 2010
+#          physical_network: phys_sriov1
 #
-# Equivalent to (using auto-indexing):
-# internal_networks:
-#    left:
-#        segmentation_id: 2000
-#        physical_network: phys_sriov0
-#    right:
-#        segmentation_id: 2010
-#        physical_network: phys_sriov1
+# - mpls_transport_labels is used only when MPLS encapsulation is enabled (mpls: true)
+#   this parameter doesn't support auto-indexing because this is not a typical scenario
+#   expected the list of values in a range 256-1048575, one value per chain is expected
+#
+#   In the bellow configuration example 'segmentation_id; contains the inner MPLS label for each chain
+#   and 'mpls_transport_labels' contains the outer transport MPLS label for each chain
+#   Example of 2-chain MPLS configuration:
+#   internal_networks:
+#      left:
+#          network_type: mpls
+#          segmentation_id: [2000, 2001]
+#          mpls_transport_labels: [10000, 10000]
+#          physical_network: phys_sriov0
+#      right:
+#          network_type: mpls
+#          segmentation_id: [2010, 2011]
+#          mpls_transport_labels: [11000, 11000]
+#          physical_network: phys_sriov1
+
 
 internal_networks:
     left:
@@ -405,6 +427,7 @@ internal_networks:
         network_type: 'vlan'
         segmentation_id:
         physical_network:
+        mpls_transport_labels:
     right:
         name: 'nfvbench-rnet'
         subnet: 'nfvbench-rsubnet'
@@ -412,6 +435,7 @@ internal_networks:
         network_type: 'vlan'
         segmentation_id:
         physical_network:
+        mpls_transport_labels:
     middle:
         name: 'nfvbench-mnet'
         subnet: 'nfvbench-msubnet'
@@ -419,6 +443,7 @@ internal_networks:
         network_type: 'vlan'
         segmentation_id:
         physical_network:
+        mpls_transport_labels:
 
 # IDLE INTERFACES: PVP, PVVP and non shared net only.
 # By default each test VM will have 2 virtual interfaces for looping traffic.
@@ -436,7 +461,7 @@ idle_networks:
     # Prefix for all idle networks, the final name will append the chain ID and idle index
     # e.g. "nfvbench-idle-net.0.4" chain 0 idle index 4
     name: 'nfvbench-idle-net'
-    # Subnet name to use for all idle subnetworks 
+    # Subnet name to use for all idle subnetworks
     subnet: 'nfvbench-idle-subnet'
     # CIDR to use for all idle networks (value should not matter)
     cidr: '192.169.1.0/24'
@@ -537,7 +562,7 @@ use_sriov_middle_net: false
 # external_networks:
 #   left:  ['ext-lnet', 'ext-lnet2']
 #   right: ['ext-rnet', 'ext-rnet2']
-# 
+#
 external_networks:
     left:
     right:
@@ -578,14 +603,23 @@ edge_networks:
 
 # 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 and MPLS encapsulations are mutual exclusive if 'vxlan' is true then 'mpls' should be false
+# and vise versa
 vxlan: false
-
+# Use 'true' to enable MPLS encapsulation support and sent by the traffic generator
+# When this option enabled internal networks 'network type' parameter value should be 'mpls'
+# MPLS and VxLAN encapsulations are mutual exclusive if 'mpls' is 'true' then 'vxlan' should be set to 'false'
+# and vise versa. no_flow_stats, no_latency_stats, no_latency_streams should be set to 'true' because these
+# features are not supported at the moment. In future when these features will be supported they will require
+# special NIC hardware. Only 2 label stack supported at the moment where one label is transport and another
+# is VPN for more details please refer to 'mpls_transport_labels' and 'segmentation_id' in networks configuration
+mpls: 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
+# If VxLAN or MPLS are 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
 
index bb35426..62a3751 100644 (file)
@@ -96,6 +96,22 @@ class ChainRunner(object):
             gen_config.set_vxlan_endpoints(1, src_vteps[1], dst_vtep)
             self.config['vxlan_gen_config'] = gen_config
 
+        if config.mpls:
+            # MPLS VPN is discovered from the networks
+            src_vteps = gen_config.gen_config.src_vteps
+            vtep_gateway_ips = gen_config.gen_config.vtep_gateway_ips
+            gen_config.set_mpls_inner_labels(0, self.chain_manager.get_chain_mpls_inner_labels(0))
+            gen_config.set_mpls_inner_labels(1, self.chain_manager.get_chain_mpls_inner_labels(1))
+            outer_mpls_labels_left = self.config.internal_networks.left.mpls_transport_labels
+            outer_mpls_labels_right = self.config.internal_networks.right.mpls_transport_labels
+            if outer_mpls_labels_left or outer_mpls_labels_right:
+                gen_config.set_mpls_outer_labels(0, outer_mpls_labels_left)
+                gen_config.set_mpls_outer_labels(1, outer_mpls_labels_right)
+            # Configuring source an remote VTEPs on TREx interfaces
+            gen_config.set_mpls_peers(0, src_vteps[0], vtep_gateway_ips[0])
+            gen_config.set_mpls_peers(1, src_vteps[1], vtep_gateway_ips[1])
+            self.config['mpls_gen_config'] = gen_config
+
         # get an instance of the stats manager
         self.stats_manager = StatsManager(self)
         LOG.info('ChainRunner initialized')
@@ -103,8 +119,8 @@ class ChainRunner(object):
     def __setup_traffic(self):
         self.traffic_client.setup()
         if not self.config.no_traffic:
-            # ARP is needed for EXT chain or VxLAN overlay unless disabled explicitly
-            if (self.config.service_chain == ChainType.EXT or
+            # ARP is needed for EXT chain or VxLAN overlay or MPLS unless disabled explicitly
+            if (self.config.service_chain == ChainType.EXT or self.config.mpls or
                     self.config.vxlan or self.config.l3_router or self.config.loop_vm_arp)\
                     and not self.config.no_arp:
                 self.traffic_client.ensure_arp_successful()
@@ -167,10 +183,9 @@ class ChainRunner(object):
 
         LOG.info('Starting %dx%s benchmark...', self.config.service_chain_count, self.chain_name)
         self.stats_manager.create_worker()
-        if self.config.vxlan:
-            # Configure vxlan tunnels
+        if self.config.vxlan or self.config.mpls:
+            # Configure vxlan or mpls tunnels
             self.stats_manager.worker.config_interfaces()
-
         self.__setup_traffic()
 
         results[self.chain_name] = {'result': self.__get_chain_result()}
index 71693be..b9ed48b 100644 (file)
@@ -370,6 +370,15 @@ class ChainNetwork(object):
 
         return self.network['provider:segmentation_id']
 
+    def get_mpls_inner_label(self):
+        """
+        Extract MPLS VPN Label for this network.
+
+        :return: MPLS VPN Label for this network
+        """
+
+        return self.network['provider:segmentation_id']
+
     def delete(self):
         """Delete this network."""
         if not self.reuse and self.network:
@@ -931,6 +940,20 @@ class Chain(object):
             port_index = -1
         return self.networks[port_index].get_vxlan()
 
+    def get_mpls_inner_label(self, port_index):
+        """Get the MPLS VPN Label on a given port.
+
+        port_index: left port is 0, right port is 1
+        return: the mpls_label_id or None if there is no mpls
+        """
+        # for port 1 we need to return the MPLS Label 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_mpls_inner_label()
+
     def get_dest_mac(self, port_index):
         """Get the dest MAC on a given port.
 
@@ -1248,7 +1271,6 @@ class ChainManager(object):
         for chain in self.chains:
             instances.extend(chain.get_instances())
         initial_instance_count = len(instances)
-        # Give additional 10 seconds per VM
         max_retries = (self.config.check_traffic_time_sec + (initial_instance_count - 1) * 10 +
                        self.config.generic_poll_sec - 1) / self.config.generic_poll_sec
         retry = 0
@@ -1426,6 +1448,18 @@ class ChainManager(object):
         # no openstack
         raise ChainException('VxLAN is only supported with OpenStack and with admin user')
 
+    def get_chain_mpls_inner_labels(self, port_index):
+        """Get the list of per chain MPLS VPN Labels on a given port.
+
+        port_index: left port is 0, right port is 1
+        return: a MPLSs ID list indexed by the chain index or None if no mpls
+        """
+        if self.chains and self.is_admin:
+            return [self.chains[chain_index].get_mpls_inner_label(port_index)
+                    for chain_index in range(self.chain_count)]
+        # no openstack
+        raise ChainException('MPLS is only supported with OpenStack and with admin user')
+
     def get_dest_macs(self, port_index):
         """Get the list of per chain dest MACs on a given port.
 
index 168c545..50b96b6 100644 (file)
@@ -233,11 +233,14 @@ class NFVBench(object):
             raise Exception('vif_multiqueue_size (%d) must be in [1..8]' %
                             config.vif_multiqueue_size)
 
-        # VxLAN sanity checks
-        if config.vxlan:
+        # VxLAN and MPLS sanity checks
+        if config.vxlan or config.mpls:
             if config.vlan_tagging:
                 config.vlan_tagging = False
-                LOG.info('VxLAN: vlan_tagging forced to False '
+                config.no_latency_streams = True
+                config.no_latency_stats = True
+                config.no_flow_stats = True
+                LOG.info('VxLAN or MPLS: vlan_tagging forced to False '
                          '(inner VLAN tagging must be disabled)')
 
         self.config_plugin.validate_config(config, self.specs.openstack)
@@ -359,6 +362,11 @@ def _parse_opts_from_cli():
                         action='store_true',
                         help='Enable VxLan encapsulation')
 
+    parser.add_argument('--mpls', dest='mpls',
+                        default=None,
+                        action='store_true',
+                        help='Enable MPLS encapsulation')
+
     parser.add_argument('--no-cleanup', dest='no_cleanup',
                         default=None,
                         action='store_true',
@@ -602,6 +610,8 @@ def main():
             config.compute_nodes = opts.hypervisor
         if opts.vxlan:
             config.vxlan = True
+        if opts.mpls:
+            config.mpls = True
         if opts.restart:
             config.restart = True
         if opts.service_mode:
index 75fe703..ec5e24e 100644 (file)
 class Encaps(object):
     VLAN = "VLAN"
     VxLAN = "VxLAN"
+    MPLS = "MPLS"
     NO_ENCAPS = "NONE"
 
     encaps_mapping = {
         'VLAN': VLAN,
         'VXLAN': VxLAN,
+        'MPLS': MPLS,
         'NONE': NO_ENCAPS
     }
 
index a8573b0..062d414 100755 (executable)
@@ -26,6 +26,9 @@ from netaddr import IPNetwork
 from trex.stl.api import Ether
 from trex.stl.api import STLError
 from trex.stl.api import UDP
+# pylint: disable=wrong-import-order
+from scapy.contrib.mpls import MPLS  # flake8: noqa
+# pylint: enable=wrong-import-order
 # pylint: enable=import-error
 
 from .log import LOG
@@ -36,7 +39,6 @@ from .stats_collector import IterationCollector
 from .traffic_gen import traffic_utils as utils
 from .utils import cast_integer
 
-
 class TrafficClientException(Exception):
     """Generic traffic client exception."""
 
@@ -158,6 +160,9 @@ class Device(object):
         self.vtep_vlan = None
         self.vtep_src_mac = None
         self.vxlan = False
+        self.mpls = False
+        self.inner_labels = None
+        self.outer_labels = None
         self.pci = generator_config.interfaces[port].pci
         self.mac = None
         self.dest_macs = None
@@ -241,10 +246,25 @@ class Device(object):
         LOG.info("Port %d: src_vtep %s, dst_vtep %s", self.port,
                  self.vtep_src_ip, self.vtep_dst_ip)
 
+    def set_mpls_peers(self, src_ip, dst_ip):
+        self.mpls = True
+        self.vtep_dst_ip = dst_ip
+        self.vtep_src_ip = src_ip
+        LOG.info("Port %d: src_mpls_vtep %s, mpls_peer_ip %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 set_mpls_inner_labels(self, labels):
+        self.inner_labels = labels
+        LOG.info("Port %d: MPLS Inner Labels %s", self.port, self.inner_labels)
+
+    def set_mpls_outer_labels(self, labels):
+        self.outer_labels = labels
+        LOG.info("Port %d: MPLS Outer Labels %s", self.port, self.outer_labels)
+
     def set_gw_ip(self, gateway_ip):
         self.gw_ip_block = IpBlock(gateway_ip,
                                    self.generator_config.gateway_ip_addrs_step,
@@ -296,11 +316,15 @@ class Device(object):
                 '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_src_mac': self.mac if (self.vxlan or self.mpls) else None,
+                'vtep_dst_mac': self.vtep_dst_mac if (self.vxlan or self.mpls) 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
+                'net_vni': self.vnis[chain_idx] if self.vxlan is True else None,
+                'mpls': self.mpls,
+                'mpls_outer_label': self.outer_labels[chain_idx] if self.mpls is True else None,
+                'mpls_inner_label': self.inner_labels[chain_idx] if self.mpls is True else None
+
             })
             # after first chain, fall back to the flow count for all other chains
             cur_chain_flow_count = flows_per_chain
@@ -445,6 +469,28 @@ class GeneratorConfig(object):
                                          (vxlans, self.config.service_chain_count))
         self.devices[port_index].set_vxlans(vxlans)
 
+    def set_mpls_inner_labels(self, port_index, labels):
+        """Set the list of MPLS Labels to use indexed by the chain id on given port.
+
+        port_index: the port for which Labels must be set
+        Labels: a list of Labels lists indexed by chain id
+        """
+        if len(labels) != self.config.service_chain_count:
+            raise TrafficClientException('Inner MPLS list %s must have %d entries' %
+                                         (labels, self.config.service_chain_count))
+        self.devices[port_index].set_mpls_inner_labels(labels)
+
+    def set_mpls_outer_labels(self, port_index, labels):
+        """Set the list of MPLS Labels to use indexed by the chain id on given port.
+
+        port_index: the port for which Labels must be set
+        Labels: a list of Labels lists indexed by chain id
+        """
+        if len(labels) != self.config.service_chain_count:
+            raise TrafficClientException('Outer MPLS list %s must have %d entries' %
+                                         (labels, self.config.service_chain_count))
+        self.devices[port_index].set_mpls_outer_labels(labels)
+
     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
@@ -454,6 +500,9 @@ class GeneratorConfig(object):
     def set_vxlan_endpoints(self, port_index, src_ip, dst_ip):
         self.devices[port_index].set_vxlan_endpoints(src_ip, dst_ip)
 
+    def set_mpls_peers(self, port_index, src_ip, dst_ip):
+        self.devices[port_index].set_mpls_peers(src_ip, dst_ip)
+
     @staticmethod
     def __match_generator_profile(traffic_generator, generator_profile):
         gen_config = AttrDict(traffic_generator)
@@ -607,6 +656,9 @@ class TrafficClient(object):
             get_mac_id = lambda packet: packet['binary'][60:66]
         elif self.config.vxlan:
             get_mac_id = lambda packet: packet['binary'][56:62]
+        elif self.config.mpls:
+            get_mac_id = lambda packet: packet['binary'][24:30]
+            # mpls_transport_label = lambda packet: packet['binary'][14:18]
         else:
             get_mac_id = lambda packet: packet['binary'][6:12]
         for it in range(retry_count):
@@ -624,11 +676,18 @@ class TrafficClient(object):
             for packet in self.gen.packet_list:
                 mac_id = get_mac_id(packet).decode('latin-1')
                 src_mac = ':'.join(["%02x" % ord(x) for x in mac_id])
-                if src_mac in mac_map and self.is_udp(packet):
-                    port, chain = mac_map[src_mac]
-                    LOG.info('Received packet from mac: %s (chain=%d, port=%d)',
-                             src_mac, chain, port)
-                    mac_map.pop(src_mac, None)
+                if self.config.mpls:
+                    if src_mac in mac_map and self.is_mpls(packet):
+                        port, chain = mac_map[src_mac]
+                        LOG.info('Received mpls packet from mac: %s (chain=%d, port=%d)',
+                                 src_mac, chain, port)
+                        mac_map.pop(src_mac, None)
+                else:
+                    if src_mac in mac_map and self.is_udp(packet):
+                        port, chain = mac_map[src_mac]
+                        LOG.info('Received udp packet from mac: %s (chain=%d, port=%d)',
+                                 src_mac, chain, port)
+                        mac_map.pop(src_mac, None)
 
                 if not mac_map:
                     LOG.info('End-to-end connectivity established')
@@ -645,12 +704,16 @@ class TrafficClient(object):
         pkt = Ether(packet['binary'])
         return UDP in pkt
 
+    def is_mpls(self, packet):
+        pkt = Ether(packet['binary'])
+        return MPLS in pkt
+
     def ensure_arp_successful(self):
         """Resolve all IP using ARP and throw an exception in case of failure."""
         dest_macs = self.gen.resolve_arp()
         if dest_macs:
             # all dest macs are discovered, saved them into the generator config
-            if self.config.vxlan:
+            if self.config.vxlan or self.config.mpls:
                 self.generator_config.set_vtep_dest_macs(0, dest_macs[0])
                 self.generator_config.set_vtep_dest_macs(1, dest_macs[1])
             else:
@@ -676,6 +739,7 @@ class TrafficClient(object):
                 self.run_config['rates'][idx] = {'rate_pps': self.__convert_rates(rate)['rate_pps']}
 
         self.gen.clear_streamblock()
+
         if self.config.no_latency_streams:
             LOG.info("Latency streams are disabled")
         self.gen.create_traffic(frame_size, self.run_config['rates'], bidirectional,
index c2e0854..af70cde 100644 (file)
@@ -20,6 +20,9 @@ import time
 import traceback
 
 from itertools import count
+# pylint: disable=import-error
+from scapy.contrib.mpls import MPLS  # flake8: noqa
+# pylint: enable=import-error
 from nfvbench.log import LOG
 from nfvbench.traffic_server import TRexTrafficServer
 from nfvbench.utils import cast_integer
@@ -363,6 +366,17 @@ class TRex(AbstractTrafficGenerator):
                 op="random")
             vm_param = [vxlan_udp_src_fv,
                         STLVmWrFlowVar(fv_name="vxlan_udp_src", pkt_offset="UDP.sport")]
+        elif stream_cfg['mpls'] is True:
+            encap_level = '0'
+            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'])
+            if stream_cfg['mpls_outer_label'] is not None:
+                pkt_base /= MPLS(label=stream_cfg['mpls_outer_label'], cos=1, s=0, ttl=255)
+            if stream_cfg['mpls_inner_label'] is not None:
+                pkt_base /= MPLS(label=stream_cfg['mpls_inner_label'], cos=1, s=1, ttl=255)
+            #  Flow stats and MPLS labels randomization TBD
+            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'])
@@ -443,7 +457,7 @@ class TRex(AbstractTrafficGenerator):
         if l2frame == 'IMIX':
             for ratio, l2_frame_size in zip(IMIX_RATIOS, IMIX_L2_SIZES):
                 pkt = self._create_pkt(stream_cfg, l2_frame_size)
-                if e2e:
+                if e2e or stream_cfg['mpls']:
                     streams.append(STLStream(packet=pkt,
                                              mode=STLTXCont(pps=ratio)))
                 else:
@@ -466,8 +480,10 @@ class TRex(AbstractTrafficGenerator):
         else:
             l2frame_size = int(l2frame)
             pkt = self._create_pkt(stream_cfg, l2frame_size)
-            if e2e:
+            if e2e or stream_cfg['mpls']:
                 streams.append(STLStream(packet=pkt,
+                                         # Flow stats is disabled for MPLS now
+                                         # flow_stats=STLFlowStats(pg_id=pg_id),
                                          mode=STLTXCont()))
             else:
                 if stream_cfg['vxlan'] is True:
@@ -648,7 +664,7 @@ class TRex(AbstractTrafficGenerator):
             dst_macs = [None] * chain_count
             dst_macs_count = 0
             # the index in the list is the chain id
-            if self.config.vxlan:
+            if self.config.vxlan or self.config.mpls:
                 arps = [
                     ServiceARP(ctx,
                                src_ip=device.vtep_src_ip,
index 3974fd7..c8c485f 100644 (file)
@@ -113,8 +113,8 @@ def get_intel_pci(nic_slot=None, nic_ports=None):
 
     if nic_slot and nic_ports:
         dmidecode = subprocess.check_output(['dmidecode', '-t', 'slot'])
-        regex = r"(?<=SlotID:%s).*?(....:..:..\..)" % nic_slot
-        match = re.search(regex, dmidecode, flags=re.DOTALL)
+        regex = r"(?<=SlotID:{}).*?(....:..:..\..)".format(nic_slot)
+        match = re.search(regex, dmidecode.decode('utf-8'), flags=re.DOTALL)
         if not match:
             return None
 
index 09324be..c54981f 100644 (file)
--- a/pylint.rc
+++ b/pylint.rc
@@ -197,7 +197,7 @@ indent-string='    '
 max-line-length=100
 
 # Maximum number of lines in a module
-max-module-lines=1500
+max-module-lines=1600
 
 # List of optional constructs for which whitespace checking is disabled. `dict-
 # separator` is used to allow tabulation in dicts, etc.: {1  : 1,\n222: 2}.
@@ -361,7 +361,7 @@ max-args=12
 max-attributes=32
 
 # Maximum number of boolean expressions in a if statement
-max-bool-expr=5
+max-bool-expr=6
 
 # Maximum number of branch for function / method body
 max-branches=30
index 18f4952..a7e52ce 100644 (file)
@@ -7,6 +7,7 @@ hacking<0.11,>=0.10.0
 coverage>=3.6
 discover
 python-subunit>=0.0.18
+scapy>=2.3.1
 sphinx>=1.4.0
 sphinx_rtd_theme>=0.1.9
 oslosphinx>=2.5.0  # Apache-2.0
index 3cf75cb..a9df54f 100644 (file)
@@ -72,6 +72,7 @@ def _get_chain_config(sc=ChainType.PVP, scc=1, shared_net=True, rate='1Mpps'):
     config.no_flow_stats = False
     config.no_latency_stats = False
     config.no_latency_streams = False
+    config.loop_vm_arp = True
     return config
 
 def test_chain_runner_ext_no_openstack():
index 4a8a574..fa0e098 100644 (file)
@@ -341,6 +341,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['mpls'] = False
     config['ndr_run'] = True
     config['pdr_run'] = True
     config['generator_profile'] = 'dummy'