NFVBENCH-94 End to end traffic test triggers too early on chatty network
[nfvbench.git] / nfvbench / traffic_gen / trex.py
index 498ff50..cabf1cb 100644 (file)
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import os
+import random
+import time
+import traceback
+
 from collections import defaultdict
 from itertools import count
 from nfvbench.log import LOG
@@ -20,14 +25,11 @@ from nfvbench.traffic_server import TRexTrafficServer
 from nfvbench.utils import cast_integer
 from nfvbench.utils import timeout
 from nfvbench.utils import TimeoutError
-import os
-import random
-import time
-import traceback
 from traffic_base import AbstractTrafficGenerator
 from traffic_base import TrafficGeneratorException
 import traffic_utils as utils
 
+# pylint: disable=import-error
 from trex_stl_lib.api import CTRexVmInsFixHwCs
 from trex_stl_lib.api import Dot1Q
 from trex_stl_lib.api import Ether
@@ -48,6 +50,9 @@ from trex_stl_lib.api import UDP
 from trex_stl_lib.services.trex_stl_service_arp import STLServiceARP
 
 
+# pylint: enable=import-error
+
+
 class TRex(AbstractTrafficGenerator):
     LATENCY_PPS = 1000
 
@@ -61,31 +66,42 @@ class TRex(AbstractTrafficGenerator):
         self.streamblock = defaultdict(list)
         self.rates = []
         self.arps = {}
+        self.capture_id = None
+        self.packet_list = []
 
     def get_version(self):
         return self.client.get_server_version()
 
     def extract_stats(self, in_stats):
+        """Extract stats from dict returned by Trex API.
+
+        :param in_stats: dict as returned by TRex api
+        """
         utils.nan_replace(in_stats)
         LOG.debug(in_stats)
 
         result = {}
+        # port_handles should have only 2 elements: [0, 1]
+        # so (1 - ph) will be the index for the far end port
         for ph in self.port_handle:
-            stats = self.__combine_stats(in_stats, ph)
+            stats = in_stats[ph]
+            far_end_stats = in_stats[1 - ph]
             result[ph] = {
                 'tx': {
-                    'total_pkts': cast_integer(stats['tx_pkts']['total']),
-                    'total_pkt_bytes': cast_integer(stats['tx_bytes']['total']),
-                    'pkt_rate': cast_integer(stats['tx_pps']['total']),
-                    'pkt_bit_rate': cast_integer(stats['tx_bps']['total'])
+                    'total_pkts': cast_integer(stats['opackets']),
+                    'total_pkt_bytes': cast_integer(stats['obytes']),
+                    'pkt_rate': cast_integer(stats['tx_pps']),
+                    'pkt_bit_rate': cast_integer(stats['tx_bps'])
                 },
                 'rx': {
-                    'total_pkts': cast_integer(stats['rx_pkts']['total']),
-                    'total_pkt_bytes': cast_integer(stats['rx_bytes']['total']),
-                    'pkt_rate': cast_integer(stats['rx_pps']['total']),
-                    'pkt_bit_rate': cast_integer(stats['rx_bps']['total']),
+                    'total_pkts': cast_integer(stats['ipackets']),
+                    'total_pkt_bytes': cast_integer(stats['ibytes']),
+                    'pkt_rate': cast_integer(stats['rx_pps']),
+                    'pkt_bit_rate': cast_integer(stats['rx_bps']),
+                    # how many pkts were dropped in RX direction
+                    # need to take the tx counter on the far end port
                     'dropped_pkts': cast_integer(
-                        stats['tx_pkts']['total'] - stats['rx_pkts']['total'])
+                        far_end_stats['opackets'] - stats['ipackets'])
                 }
             }
 
@@ -95,29 +111,14 @@ class TRex(AbstractTrafficGenerator):
             result[ph]['rx']['min_delay_usec'] = cast_integer(
                 lat['total_min']) if 'total_min' in lat else float('nan')
             result[ph]['rx']['avg_delay_usec'] = cast_integer(
-                lat['average']) if 'average' in lat else float(
-                'nan')
+                lat['average']) if 'average' in lat else float('nan')
         total_tx_pkts = result[0]['tx']['total_pkts'] + result[1]['tx']['total_pkts']
         result["total_tx_rate"] = cast_integer(total_tx_pkts / self.config.duration_sec)
         return result
 
-    def __combine_stats(self, in_stats, port_handle):
-        """Traverses TRex result dictionary and combines stream stats. Used for combining latency
-        and regular streams together.
-        """
-        result = defaultdict(lambda: defaultdict(float))
-
-        for pg_id in [self.stream_ids[port_handle]] + self.latencies[port_handle]:
-            record = in_stats['flow_stats'][pg_id]
-            for stat_type, stat_type_values in record.iteritems():
-                for ph, value in stat_type_values.iteritems():
-                    result[stat_type][ph] += value
-
-        return result
-
     def __combine_latencies(self, in_stats, port_handle):
         """Traverses TRex result dictionary and combines chosen latency stats."""
-        if not len(self.latencies[port_handle]):
+        if not self.latencies[port_handle]:
             return {}
 
         result = defaultdict(float)
@@ -134,14 +135,16 @@ class TRex(AbstractTrafficGenerator):
         return result
 
     def create_pkt(self, stream_cfg, l2frame_size):
-        # 46 = 14 (Ethernet II) + 4 (CRC Checksum) + 20 (IPv4) + 8 (UDP)
-        payload = 'x' * (max(64, int(l2frame_size)) - 46)
 
         pkt_base = Ether(src=stream_cfg['mac_src'], dst=stream_cfg['mac_dst'])
-
         if stream_cfg['vlan_tag'] is not None:
+            # 50 = 14 (Ethernet II) + 4 (Vlan tag) + 4 (CRC Checksum) + 20 (IPv4) + 8 (UDP)
             pkt_base /= Dot1Q(vlan=stream_cfg['vlan_tag'])
-
+            l2payload_size = int(l2frame_size) - 50
+        else:
+            # 46 = 14 (Ethernet II) + 4 (CRC Checksum) + 20 (IPv4) + 8 (UDP)
+            l2payload_size = int(l2frame_size) - 46
+        payload = 'x' * l2payload_size
         udp_args = {}
         if stream_cfg['udp_src_port']:
             udp_args['sport'] = int(stream_cfg['udp_src_port'])
@@ -196,6 +199,8 @@ class TRex(AbstractTrafficGenerator):
         idx_lat = None
         streams = []
         if l2frame == 'IMIX':
+            min_size = 64 if stream_cfg['vlan_tag'] is None else 68
+            self.adjust_imix_min_size(min_size)
             for t, (ratio, l2_frame_size) in enumerate(zip(self.imix_ratios, self.imix_l2_sizes)):
                 pkt = self.create_pkt(stream_cfg, l2_frame_size)
                 streams.append(STLStream(packet=pkt,
@@ -206,6 +211,7 @@ class TRex(AbstractTrafficGenerator):
 
             if latency:
                 idx_lat = self.id.next()
+                pkt = self.create_pkt(stream_cfg, self.imix_avg_l2_size)
                 sl = STLStream(packet=pkt,
                                isg=isg,
                                flow_stats=STLFlowLatencyStats(pg_id=idx_lat),
@@ -245,7 +251,7 @@ class TRex(AbstractTrafficGenerator):
                 break
             except Exception as ex:
                 if it == (self.config.generic_retry_count - 1):
-                    raise ex
+                    raise
                 LOG.info("Retrying connection to TRex (%s)...", ex.message)
 
     def connect(self):
@@ -268,7 +274,7 @@ class TRex(AbstractTrafficGenerator):
                     if os.path.isfile(logpath):
                         # Wait for TRex to finish writing error message
                         last_size = 0
-                        for it in xrange(self.config.generic_retry_count):
+                        for _ in xrange(self.config.generic_retry_count):
                             size = os.path.getsize(logpath)
                             if size == last_size:
                                 # probably not writing anymore
@@ -316,7 +322,7 @@ class TRex(AbstractTrafficGenerator):
 
     def __start_server(self):
         server = TRexTrafficServer()
-        server.run_server(self.config.generator_config)
+        server.run_server(self.config.generator_config, self.config.vlan_tagging)
 
     def resolve_arp(self):
         self.client.set_service_mode(ports=self.port_handle)
@@ -347,13 +353,13 @@ class TRex(AbstractTrafficGenerator):
 
                 if len(self.arps[port]) == self.config.service_chain_count:
                     resolved += 1
-                    LOG.info('ARP resolved successfully for port {}'.format(port))
+                    LOG.info('ARP resolved successfully for port %s', port)
                     break
                 else:
                     failed = [arp.get_record().dst_ip for arp in arps
                               if arp.get_record().dst_mac is None]
-                    LOG.info('Retrying ARP for: {} ({} / {})'.format(
-                        failed, attempt, self.config.generic_retry_count))
+                    LOG.info('Retrying ARP for: %s (%d / %d)',
+                             failed, attempt, self.config.generic_retry_count)
                     time.sleep(self.config.generic_poll_sec)
 
         self.client.set_service_mode(ports=self.port_handle, enabled=False)
@@ -396,7 +402,7 @@ class TRex(AbstractTrafficGenerator):
 
         stream_cfgs = [d.get_stream_configs(self.config.generator_config.service_chain)
                        for d in self.config.generator_config.devices]
-        self.rates = map(lambda rate: utils.to_rate_str(rate), rates)
+        self.rates = [utils.to_rate_str(rate) for rate in rates]
 
         for ph in self.port_handle:
             # generate one pg_id for each direction
@@ -420,13 +426,7 @@ class TRex(AbstractTrafficGenerator):
 
         for ph in self.port_handle:
             self.client.add_streams(self.streamblock[ph], ports=ph)
-            LOG.info('Created traffic stream for port %s.' % ph)
-
-    def modify_rate(self, rate, reverse):
-        port_index = int(reverse)
-        port = self.port_handle[port_index]
-        self.rates[port_index] = utils.to_rate_str(rate)
-        LOG.info('Modified traffic stream for %s, new rate=%s.' % (port, utils.to_rate_str(rate)))
+            LOG.info('Created traffic stream for port %s.', ph)
 
     def clear_streamblock(self):
         self.streamblock = defaultdict(list)
@@ -437,7 +437,7 @@ class TRex(AbstractTrafficGenerator):
         LOG.info('Cleared all existing streams.')
 
     def get_stats(self):
-        stats = self.client.get_pgid_stats()
+        stats = self.client.get_stats()
         return self.extract_stats(stats)
 
     def get_macs(self):
@@ -454,6 +454,31 @@ class TRex(AbstractTrafficGenerator):
     def stop_traffic(self):
         self.client.stop(ports=self.port_handle)
 
+    def start_capture(self):
+        """Capture all packets on both ports that are unicast to us."""
+        if self.capture_id:
+            self.stop_capture()
+        # Need to filter out unwanted packets so we do not end up counting
+        # src MACs of frames that are not unicast to us
+        src_mac_list = self.get_macs()
+        bpf_filter = "ether dst %s or ether dst %s" % (src_mac_list[0], src_mac_list[1])
+        # ports must be set in service in order to enable capture
+        self.client.set_service_mode(ports=self.port_handle)
+        self.capture_id = self.client.start_capture(rx_ports=self.port_handle,
+                                                    bpf_filter=bpf_filter)
+
+    def fetch_capture_packets(self):
+        if self.capture_id:
+            self.packet_list = []
+            self.client.fetch_capture_packets(capture_id=self.capture_id['id'],
+                                              output=self.packet_list)
+
+    def stop_capture(self):
+        if self.capture_id:
+            self.client.stop_capture(capture_id=self.capture_id['id'])
+            self.capture_id = None
+            self.client.set_service_mode(ports=self.port_handle, enabled=False)
+
     def cleanup(self):
         if self.client:
             try: