From ce8e18ddcfb84f887aa91f38133781da2f592309 Mon Sep 17 00:00:00 2001 From: Martin Klozik Date: Thu, 4 Jan 2018 10:32:48 +0000 Subject: trex: Add support for traffic capture 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 Reviewed-by: Al Morton Reviewed-by: Christian Trautman Reviewed-by: Sridhar Rao Reviewed-by: Trevor Cooper Reviewed-by: Richard Elias --- conf/03_traffic.conf | 31 ++++++++++++ conf/integration/01_testcases.conf | 38 ++++++++++++++ core/results/results_constants.py | 4 ++ .../devguide/design/vswitchperf_design.rst | 24 +++++++++ docs/testing/user/configguide/trafficgen.rst | 10 +++- tools/pkt_gen/trex/trex.py | 59 ++++++++++++++++++---- 6 files changed, 155 insertions(+), 11 deletions(-) diff --git a/conf/03_traffic.conf b/conf/03_traffic.conf index 3c7bd2f5..67318893 100644 --- a/conf/03_traffic.conf +++ b/conf/03_traffic.conf @@ -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. diff --git a/conf/integration/01_testcases.conf b/conf/integration/01_testcases.conf index 692f1561..bb2809b8 100644 --- a/conf/integration/01_testcases.conf +++ b/conf/integration/01_testcases.conf @@ -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 # diff --git a/core/results/results_constants.py b/core/results/results_constants.py index ef2df847..967adbf9 100644 --- a/core/results/results_constants.py +++ b/core/results/results_constants.py @@ -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. diff --git a/docs/testing/developer/devguide/design/vswitchperf_design.rst b/docs/testing/developer/devguide/design/vswitchperf_design.rst index 33051493..96ffcf62 100644 --- a/docs/testing/developer/devguide/design/vswitchperf_design.rst +++ b/docs/testing/developer/devguide/design/vswitchperf_design.rst @@ -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: diff --git a/docs/testing/user/configguide/trafficgen.rst b/docs/testing/user/configguide/trafficgen.rst index 91c4084e..029247f2 100644 --- a/docs/testing/user/configguide/trafficgen.rst +++ b/docs/testing/user/configguide/trafficgen.rst @@ -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 diff --git a/tools/pkt_gen/trex/trex.py b/tools/pkt_gen/trex/trex.py index 82118f6f..5ce87b1d 100644 --- a/tools/pkt_gen/trex/trex.py +++ b/tools/pkt_gen/trex/trex.py @@ -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) -- cgit 1.2.3-korg