trex: Add support for traffic capture 49/50349/2
authorMartin Klozik <martinx.klozik@intel.com>
Thu, 4 Jan 2018 10:32:48 +0000 (10:32 +0000)
committerMartin Klozik <martinx.klozik@intel.com>
Fri, 12 Jan 2018 09:43:57 +0000 (09:43 +0000)
A support of traffic capture was added into T-Rex.
It allows to write a functional tests, which will
verify proper vSwitch functionality by inspection
of packets received by T-Rex.
A testcase example was added into integration
testcases.

JIRA: VSPERF-556

Change-Id: I5ad28479ca2ec29760b68f24510af1a6d74866ae
Signed-off-by: Martin Klozik <martinx.klozik@intel.com>
Reviewed-by: Al Morton <acmorton@att.com>
Reviewed-by: Christian Trautman <ctrautma@redhat.com>
Reviewed-by: Sridhar Rao <sridhar.rao@spirent.com>
Reviewed-by: Trevor Cooper <trevor.cooper@intel.com>
Reviewed-by: Richard Elias <richardx.elias@intel.com>
conf/03_traffic.conf
conf/integration/01_testcases.conf
core/results/results_constants.py
docs/testing/developer/devguide/design/vswitchperf_design.rst
docs/testing/user/configguide/trafficgen.rst
tools/pkt_gen/trex/trex.py

index 3c7bd2f..6731889 100644 (file)
@@ -147,6 +147,30 @@ LOG_FILE_TRAFFIC_GEN = 'traffic-gen.log'
 #                      congestion (DEI header field).
 #                      Data type: int (NOTE: must fit to 1 bit)
 #                      Default value: 0
+#    'capture'       - A dictionary with traffic capture configuration.
+#                      NOTE: It is supported only by T-Rex traffic generator.
+#        'enabled'   - Specifies if traffic should be captured
+#                      Data type: bool
+#                      Default value: False
+#        'tx_ports'  - A list of ports, where frames transmitted towards DUT will
+#                      be captured. Ports have numbers 0 and 1. TX packet capture
+#                      is disabled if list of ports is empty.
+#                      Data type: list
+#                      Default value: [0]
+#        'rx_ports'  - A list of ports, where frames received from DUT will
+#                      be captured. Ports have numbers 0 and 1. RX packet capture
+#                      is disabled if list of ports is empty.
+#                      Data type: list
+#                      Default value: [1]
+#        'count'     - A number of frames to be captured. The same count value
+#                      is applied to both TX and RX captures.
+#                      Data type: int
+#                      Default value: 1
+#        'filter'    - An expression used to filter TX and RX packets. It uses the same
+#                      syntax as pcap library. See pcap-filter man page for additional
+#                      details.
+#                      Data type: str
+#                      Default value: ''
 TRAFFIC = {
     'traffic_type' : 'rfc2544_throughput',
     'frame_rate' : 100,
@@ -179,6 +203,13 @@ TRAFFIC = {
         'priority': 0,
         'cfi': 0,
     },
+    'capture': {
+        'enabled': False,
+        'tx_ports' : [0],
+        'rx_ports' : [1],
+        'count': 1,
+        'filter': '',
+    },
 }
 
 #path to traffic generators directory.
index 692f156..bb2809b 100644 (file)
@@ -1118,6 +1118,44 @@ INTEGRATION_TESTS += [
             ['tools', 'assert', '#STEP[-1][0] == 0'],
         ],
     },
+    # Capture Example 3 - Traffic capture by traffic generator.
+    # This TestCase uses OVS flow to add VLAN tag with given ID into every
+    # frame send by traffic generator. Correct frame modificaiton is verified by
+    # inspection of packet capture received by T-Rex.
+    {
+        "Name": "capture_p2p_add_vlan_ovs_trex",
+        "Deployment": "clean",
+        "Description": "OVS: Test VLAN tag modification and verify it by traffic capture",
+        "vSwitch" : "OvsDpdkVhost", # works also for Vanilla OVS
+        "Parameters" : {
+            "TRAFFICGEN" : "Trex",
+            "TRAFFICGEN_DURATION" : 5,
+            "TRAFFIC" : {
+                "traffic_type" : "rfc2544_continuous",
+                "frame_rate" : 100,
+                # enable capture of five RX frames
+                'capture': {
+                    'enabled': True,
+                    'tx_ports' : [],
+                    'rx_ports' : [1],
+                    'count' : 5,
+                },
+            },
+        },
+        "TestSteps" : STEP_VSWITCH_P2P_INIT + [
+            # replace standard L2 flows by flows, which will add VLAN tag with ID 3
+            ['!vswitch', 'add_flow', 'int_br0', {'in_port': '1', 'actions': ['mod_vlan_vid:3','output:2']}],
+            ['!vswitch', 'add_flow', 'int_br0', {'in_port': '2', 'actions': ['mod_vlan_vid:3','output:1']}],
+            ['vswitch', 'dump_flows', 'int_br0'],
+            ['trafficgen', 'send_traffic', {}],
+            ['trafficgen', 'get_results'],
+            # verify that captured frames have vlan tag with ID 3
+            ['tools', 'exec_shell', 'tcpdump -qer $RESULTS_PATH/#STEP[-1][0]["capture_rx"] vlan 3 '
+                                    '2>/dev/null | wc -l', '|^(\d+)$'],
+            # number of received frames with expected VLAN id must match the number of captured frames
+            ['tools', 'assert', '#STEP[-1][0] == 5'],
+        ] + STEP_VSWITCH_P2P_FINIT,
+    },
     #
     # End of examples of functional testcases with traffic capture validation
     #
index ef2df84..967adbf 100644 (file)
@@ -69,6 +69,10 @@ class ResultsConstants(object):
     TEST_START_TIME = "start_time"
     TEST_STOP_TIME = "stop_time"
 
+    # files with traffic capture
+    CAPTURE_TX = "capture_tx"
+    CAPTURE_RX = "capture_rx"
+
     @staticmethod
     def get_traffic_constants():
         """Method returns all Constants used to store results.
index 3305149..96ffcf6 100644 (file)
@@ -415,6 +415,30 @@ Detailed description of ``TRAFFIC`` dictionary items follows:
                       congestion (DEI header field).
                       Data type: int (NOTE: must fit to 1 bit)
                       Default value: 0
+    'capture'       - A dictionary with traffic capture configuration.
+                      NOTE: It is supported only by T-Rex traffic generator.
+        'enabled'   - Specifies if traffic should be captured
+                      Data type: bool
+                      Default value: False
+        'tx_ports'  - A list of ports, where frames transmitted towards DUT will
+                      be captured. Ports have numbers 0 and 1. TX packet capture
+                      is disabled if list of ports is empty.
+                      Data type: list
+                      Default value: [0]
+        'rx_ports'  - A list of ports, where frames received from DUT will
+                      be captured. Ports have numbers 0 and 1. RX packet capture
+                      is disabled if list of ports is empty.
+                      Data type: list
+                      Default value: [1]
+        'count'     - A number of frames to be captured. The same count value
+                      is applied to both TX and RX captures.
+                      Data type: int
+                      Default value: 1
+        'filter'    - An expression used to filter TX and RX packets. It uses the same
+                      syntax as pcap library. See pcap-filter man page for additional
+                      details.
+                      Data type: str
+                      Default value: ''
 
 .. _configuration-of-guest-options:
 
index 91c4084..029247f 100644 (file)
@@ -44,7 +44,8 @@ and is configured as follows:
         'stream_type' : 'L4',
         'pre_installed_flows' : 'No',           # used by vswitch implementation
         'flow_type' : 'port',                   # used by vswitch implementation
-
+        'flow_control' : False,                 # supported only by IxNet
+        'learning_frames' : True,               # supported only by IxNet
         'l2': {
             'framesize': 64,
             'srcmac': '00:00:00:00:00:00',
@@ -67,6 +68,13 @@ and is configured as follows:
             'priority': 0,
             'cfi': 0,
         },
+        'capture': {
+            'enabled': False,
+            'tx_ports' : [0],
+            'rx_ports' : [1],
+            'count': 1,
+            'filter': '',
+        },
     }
 
 The framesize parameter can be overridden from the configuration
index 82118f6..5ce87b1 100644 (file)
@@ -20,6 +20,7 @@ import logging
 import subprocess
 import sys
 import time
+import os
 from collections import OrderedDict
 # pylint: disable=unused-import
 import netaddr
@@ -91,11 +92,11 @@ class Trex(ITrafficGenerator):
         the configuration file
         '''
         self._stlclient = STLClient()
-        self._logger.info("TREX:  In Trex connect method...")
+        self._logger.info("T-Rex:  In Trex connect method...")
         if self._trex_host_ip_addr:
             cmd_ping = "ping -c1 " + self._trex_host_ip_addr
         else:
-            raise RuntimeError('TREX: Trex host not defined')
+            raise RuntimeError('T-Rex: Trex host not defined')
 
         ping = subprocess.Popen(cmd_ping, shell=True, stderr=subprocess.PIPE)
         output, error = ping.communicate()
@@ -103,7 +104,7 @@ class Trex(ITrafficGenerator):
         if ping.returncode:
             self._logger.error(error)
             self._logger.error(output)
-            raise RuntimeError('TREX: Cannot ping Trex host at ' + \
+            raise RuntimeError('T-Rex: Cannot ping Trex host at ' + \
                                self._trex_host_ip_addr)
 
         connect_trex = "ssh " + self._trex_user + \
@@ -122,13 +123,13 @@ class Trex(ITrafficGenerator):
             self._logger.error(error)
             self._logger.error(output)
             raise RuntimeError(
-                'TREX: Cannot locate Trex program at %s within %s' \
+                'T-Rex: Cannot locate Trex program at %s within %s' \
                 % (self._trex_host_ip_addr, self._trex_base_dir))
 
         self._stlclient = STLClient(username=self._trex_user, server=self._trex_host_ip_addr,
                                     verbose_level=0)
         self._stlclient.connect()
-        self._logger.info("TREX: Trex host successfully found...")
+        self._logger.info("T-Rex: Trex host successfully found...")
 
     def disconnect(self):
         """Disconnect from the traffic generator.
@@ -140,7 +141,7 @@ class Trex(ITrafficGenerator):
 
         :returns: None
         """
-        self._logger.info("TREX: In trex disconnect method")
+        self._logger.info("T-Rex: In trex disconnect method")
         self._stlclient.disconnect(stop_traffic=True, release_ports=True)
 
     @staticmethod
@@ -245,11 +246,16 @@ class Trex(ITrafficGenerator):
 
         return (stream_1, stream_2, stream_1_lat, stream_2_lat)
 
-    def generate_traffic(self, traffic, duration):
+    def generate_traffic(self, traffic, duration, disable_capture=False):
         """The method that generate a stream
         """
         my_ports = [0, 1]
+
+        # initialize ports
         self._stlclient.reset(my_ports)
+        self._stlclient.remove_all_captures()
+        self._stlclient.set_service_mode(ports=my_ports, enabled=False)
+
         ports_info = self._stlclient.get_port_info(my_ports)
         # for SR-IOV
         if settings.getValue('TRAFFICGEN_TREX_PROMISCUOUS'):
@@ -264,10 +270,35 @@ class Trex(ITrafficGenerator):
             self._stlclient.add_streams(stream_1_lat, ports=[0])
             self._stlclient.add_streams(stream_2_lat, ports=[1])
 
+        # enable traffic capture if requested
+        pcap_id = {}
+        if traffic['capture']['enabled'] and not disable_capture:
+            for ports in ['tx_ports', 'rx_ports']:
+                if traffic['capture'][ports]:
+                    pcap_dir = ports[:2]
+                    self._logger.info("T-Rex starting %s traffic capture", pcap_dir.upper())
+                    capture = {ports : traffic['capture'][ports],
+                               'limit' : traffic['capture']['count'],
+                               'bpf_filter' : traffic['capture']['filter']}
+                    self._stlclient.set_service_mode(ports=traffic['capture'][ports], enabled=True)
+                    pcap_id[pcap_dir] = self._stlclient.start_capture(**capture)
+
         self._stlclient.clear_stats()
-        self._stlclient.start(ports=[0, 1], force=True, duration=duration)
-        self._stlclient.wait_on_traffic(ports=[0, 1])
+        self._stlclient.start(ports=my_ports, force=True, duration=duration)
+        self._stlclient.wait_on_traffic(ports=my_ports)
         stats = self._stlclient.get_stats(sync_now=True)
+
+        # export captured data into pcap file if possible
+        if pcap_id:
+            for pcap_dir in pcap_id:
+                pcap_file = 'capture_{}.pcap'.format(pcap_dir)
+                self._stlclient.stop_capture(pcap_id[pcap_dir]['id'],
+                                             os.path.join(settings.getValue('RESULTS_PATH'), pcap_file))
+                stats['capture_{}'.format(pcap_dir)] = pcap_file
+                self._logger.info("T-Rex writing %s traffic capture into %s", pcap_dir.upper(), pcap_file)
+            # disable service mode for all ports used by Trex
+            self._stlclient.set_service_mode(ports=my_ports, enabled=False)
+
         return stats
 
     @staticmethod
@@ -325,6 +356,11 @@ class Trex(ITrafficGenerator):
             result[ResultsConstants.MIN_LATENCY_NS] = 'Unknown'
             result[ResultsConstants.MAX_LATENCY_NS] = 'Unknown'
             result[ResultsConstants.AVG_LATENCY_NS] = 'Unknown'
+
+        if 'capture_tx' in stats:
+            result[ResultsConstants.CAPTURE_TX] = stats['capture_tx']
+        if 'capture_rx' in stats:
+            result[ResultsConstants.CAPTURE_RX] = stats['capture_rx']
         return result
 
     def learning_packets(self, traffic):
@@ -336,7 +372,9 @@ class Trex(ITrafficGenerator):
         self._logger.info("T-Rex sending learning packets")
         learning_thresh_traffic = copy.deepcopy(traffic)
         learning_thresh_traffic["frame_rate"] = 1
-        self.generate_traffic(learning_thresh_traffic, settings.getValue("TRAFFICGEN_TREX_LEARNING_DURATION"))
+        self.generate_traffic(learning_thresh_traffic,
+                              settings.getValue("TRAFFICGEN_TREX_LEARNING_DURATION"),
+                              disable_capture=True)
         self._logger.info("T-Rex finished learning packets")
         time.sleep(3)  # allow packets to complete before starting test traffic
 
@@ -403,6 +441,7 @@ class Trex(ITrafficGenerator):
 
         if settings.getValue('TRAFFICGEN_TREX_LEARNING_MODE'):
             self.learning_packets(traffic)
+        self._logger.info("T-Rex sending traffic")
         stats = self.generate_traffic(traffic, duration)
 
         return self.calculate_results(stats)