aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--conf/03_traffic.conf31
-rw-r--r--conf/__init__.py13
-rw-r--r--conf/integration/01_testcases.conf159
-rw-r--r--core/results/results_constants.py4
-rw-r--r--core/traffic_controller.py7
-rw-r--r--docs/testing/developer/devguide/design/vswitchperf_design.rst24
-rw-r--r--docs/testing/user/configguide/installation.rst23
-rw-r--r--docs/testing/user/configguide/trafficgen.rst14
-rw-r--r--docs/testing/user/userguide/teststeps.rst4
-rw-r--r--testcases/testcase.py5
-rwxr-xr-xtools/pkt_gen/ixia/ixia.py8
-rwxr-xr-xtools/pkt_gen/ixnet/ixnet.py8
-rw-r--r--tools/pkt_gen/trex/trex.py70
-rw-r--r--tools/tasks.py33
-rw-r--r--tools/teststepstools.py17
-rw-r--r--vswitches/ovs_dpdk_vhost.py5
-rw-r--r--vswitches/ovs_vanilla.py6
-rw-r--r--vswitches/vpp_dpdk_vhost.py3
18 files changed, 400 insertions, 34 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/__init__.py b/conf/__init__.py
index a7c0ee5d..d5d26757 100644
--- a/conf/__init__.py
+++ b/conf/__init__.py
@@ -124,6 +124,13 @@ class Settings(object):
if name is not None and value is not None:
super(Settings, self).__setattr__(name, value)
+ def resetValue(self, attr):
+ """If parameter was overridden by TEST_PARAMS, then it will
+ be set to its original value.
+ """
+ if attr in self.__dict__['TEST_PARAMS']:
+ self.__dict__['TEST_PARAMS'].pop(attr)
+
def load_from_file(self, path):
"""Update ``settings`` with values found in module at ``path``.
"""
@@ -324,6 +331,12 @@ class Settings(object):
assert value == self.__dict__[name]
return True
+ def validate_resetValue(self, dummy_result, attr):
+ """Verifies, that value was correctly reset
+ """
+ return 'TEST_PARAMS' not in self.__dict__ or \
+ attr not in self.__dict__['TEST_PARAMS']
+
settings = Settings()
def get_test_param(key, default=None):
diff --git a/conf/integration/01_testcases.conf b/conf/integration/01_testcases.conf
index dfc8a4c2..bb2809b8 100644
--- a/conf/integration/01_testcases.conf
+++ b/conf/integration/01_testcases.conf
@@ -1000,6 +1000,165 @@ INTEGRATION_TESTS = [
# END of VPP tests used by VERIFY and MERGE jobs by OPNFV Jenkins
#
+ #
+ # Examples of functional testcases with traffic capture validation
+ #
+ # Capture Example 1 - Traffic capture inside VM (PVP scenario)
+ # This TestCase will modify VLAN ID set by the traffic generator to the new value.
+ # Correct VLAN ID settings is verified by inspection of captured frames.
+ {
+ "Name": "capture_pvp_modify_vid",
+ "Deployment": "pvp",
+ "Description": "Test and verify VLAN ID modification by Open vSwitch",
+ "Parameters" : {
+ "VSWITCH" : "OvsDpdkVhost", # works also for Vanilla OVS
+ "TRAFFICGEN_DURATION" : 5,
+ "TRAFFIC" : {
+ "traffic_type" : "rfc2544_continuous",
+ "frame_rate" : 100,
+ 'vlan': {
+ 'enabled': True,
+ 'id': 8,
+ 'priority': 1,
+ 'cfi': 0,
+ },
+ },
+ "GUEST_LOOPBACK" : ['linux_bridge'],
+ },
+ "TestSteps": [
+ # replace original flows with vlan ID modification
+ ['!vswitch', 'add_flow', 'br0', {'in_port': '1', 'actions': ['mod_vlan_vid:4','output:3']}],
+ ['!vswitch', 'add_flow', 'br0', {'in_port': '2', 'actions': ['mod_vlan_vid:4','output:4']}],
+ ['vswitch', 'dump_flows', 'br0'],
+ # verify that received frames have modified vlan ID
+ ['VNF0', 'execute_and_wait', 'tcpdump -i eth0 -c 5 -w dump.pcap vlan 4 &'],
+ ['trafficgen', 'send_traffic',{}],
+ ['!VNF0', 'execute_and_wait', 'tcpdump -qer dump.pcap vlan 4 2>/dev/null | wc -l','|^(\d+)$'],
+ ['tools', 'assert', '#STEP[-1][0] == 5'],
+ ],
+ },
+]
+# Capture Example 2 - Setup with 2 NICs, where traffic is captured after it is
+# processed by NIC under the test (2nd NIC). See documentation for further details.
+# This TestCase will strip VLAN headers from traffic sent by the traffic generator.
+# The removal of VLAN headers is verified by inspection of captured frames.
+#
+# NOTE: This setup expects a DUT with two NICs with two ports each. First NIC is
+# connected to the traffic generator (standard VSPERF setup). Ports of a second NIC
+# are interconnected by a patch cable. PCI addresses of all four ports have to be
+# properly configured in the WHITELIST_NICS parameter.
+_CAPTURE_P2P2P_OVS_ACTION = ''
+_CAPTURE_P2P2P_SETUP = [
+ # restore original NICS configuration, so we can refer to NICS elements
+ ['settings', 'resetValue', 'WHITELIST_NICS'],
+ ['settings', 'resetValue', 'NICS'],
+ # create and configure two bridges to forward traffic through NIC under
+ # the test and back to the traffic generator
+ # 1st bridge:
+ ['vswitch', 'add_switch', 'br0'],
+ ['tools', 'exec_shell', 'sudo ip addr flush dev $NICS[0]["device"]'],
+ ['tools', 'exec_shell', 'sudo ip link set dev $NICS[0]["device"] up'],
+ ['tools', 'exec_shell', '$TOOLS["ovs-vsctl"] add-port br0 $NICS[0]["device"]'],
+ ['tools', 'exec_shell', 'sudo $TOOLS["bind-tool"] --bind igb_uio $NICS[3]["pci"]'],
+ ['tools', 'exec_shell', '$TOOLS["ovs-vsctl"] add-port br0 dpdk0 -- '
+ 'set Interface dpdk0 type=dpdk options:dpdk-devargs=$NICS[3]["pci"]'],
+ ['tools', 'exec_shell', '$TOOLS["ovs-ofctl"] add-flow br0 in_port=1,action='
+ '$_CAPTURE_P2P2P_OVS_ACTION,output:2'],
+ # 2nd bridge:
+ ['vswitch', 'add_switch', 'br1'],
+ ['tools', 'exec_shell', 'sudo ip addr flush dev $NICS[2]["device"]'],
+ ['tools', 'exec_shell', 'sudo ip link set dev $NICS[2]["device"] up'],
+ ['tools', 'exec_shell', '$TOOLS["ovs-vsctl"] add-port br1 $NICS[2]["device"]'],
+ ['tools', 'exec_shell', 'sudo ip addr flush dev $NICS[1]["device"]'],
+ ['tools', 'exec_shell', 'sudo ip link set dev $NICS[1]["device"] up'],
+ ['tools', 'exec_shell', '$TOOLS["ovs-vsctl"] add-port br1 $NICS[1]["device"]'],
+ ['vswitch', 'add_flow', 'br1', {'in_port': '1', 'actions': ['output:2']}],
+ # log flow details
+ ['vswitch', 'dump_flows', 'br0'],
+ ['vswitch', 'dump_flows', 'br1'],
+]
+INTEGRATION_TESTS += [
+ {
+ "Name": "capture_p2p2p_strip_vlan_ovs",
+ "Deployment": "clean",
+ "Description": "P2P Continuous Stream",
+ "Parameters" : {
+ "_CAPTURE_P2P2P_OVS_ACTION" : 'strip_vlan',
+ "TRAFFIC" : {
+ "bidir" : "False",
+ "traffic_type" : "rfc2544_continuous",
+ "frame_rate" : 100,
+ 'l2': {
+ 'srcmac': "ca:fe:00:00:00:00",
+ 'dstmac': "00:00:00:00:00:01"
+ },
+ 'vlan': {
+ 'enabled': True,
+ 'id': 8,
+ 'priority': 1,
+ 'cfi': 0,
+ },
+ },
+ # suppress DPDK configuration, so physical interfaces are not bound to DPDK driver
+ 'WHITELIST_NICS' : [],
+ 'NICS' : [],
+ },
+ "TestSteps": _CAPTURE_P2P2P_SETUP + [
+ # capture traffic after processing by NIC under the test (after possible egress HW offloading)
+ ['tools', 'exec_shell_background', 'tcpdump -i $NICS[2]["device"] -c 5 -w capture.pcap '
+ 'ether src $TRAFFIC["l2"]["srcmac"]'],
+ ['trafficgen', 'send_traffic', {}],
+ ['vswitch', 'dump_flows', 'br0'],
+ ['vswitch', 'dump_flows', 'br1'],
+ # there must be 5 captured frames...
+ ['tools', 'exec_shell', 'tcpdump -r capture.pcap | wc -l', '|^(\d+)$'],
+ ['tools', 'assert', '#STEP[-1][0] == 5'],
+ # ...but no vlan headers
+ ['tools', 'exec_shell', 'tcpdump -r capture.pcap vlan | wc -l', '|^(\d+)$'],
+ ['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
+ #
]
# Example of TC definition with exact vSwitch, VNF and TRAFFICGEN values.
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/core/traffic_controller.py b/core/traffic_controller.py
index d6e7629c..de82dddf 100644
--- a/core/traffic_controller.py
+++ b/core/traffic_controller.py
@@ -43,6 +43,7 @@ class TrafficController(object):
self._duration = None
self._lossrate = None
self._packet_sizes = None
+ self._connected = False
self._mode = str(settings.getValue('mode')).lower()
self._results = []
@@ -51,6 +52,10 @@ class TrafficController(object):
"""Set configuration values just before test execution so they
can be changed during runtime by test steps.
"""
+ if not self._connected:
+ self._traffic_gen_class.connect()
+ self._connected = True
+
self._duration = int(settings.getValue('TRAFFICGEN_DURATION'))
self._lossrate = float(settings.getValue('TRAFFICGEN_LOSSRATE'))
self._packet_sizes = settings.getValue('TRAFFICGEN_PKT_SIZES')
@@ -62,7 +67,7 @@ class TrafficController(object):
def __enter__(self):
"""Call initialisation function.
"""
- self._traffic_gen_class.connect()
+ pass
def __exit__(self, type_, value, traceback):
"""Stop traffic, clean up.
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/installation.rst b/docs/testing/user/configguide/installation.rst
index e98976bf..7f4d640b 100644
--- a/docs/testing/user/configguide/installation.rst
+++ b/docs/testing/user/configguide/installation.rst
@@ -136,13 +136,19 @@ The test suite requires Python 3.3 or newer and relies on a number of other
system and python packages. These need to be installed for the test suite
to function.
+Updated kernel and certain development packages are required by DPDK,
+OVS (especially Vanilla OVS) and QEMU. It is necessary to check if the
+versions of these packages are not being **held-back** and if the
+DNF/APT/YUM configuration does not prevent their modification, by
+enforcing settings such as **"exclude-kernel"**.
+
Installation of required packages, preparation of Python 3 virtual
environment and compilation of OVS, DPDK and QEMU is performed by
-script **systems/build_base_machine.sh**. It should be executed under
+script **systems/build_base_machine.sh**. It should be executed under the
user account, which will be used for vsperf execution.
**NOTE:** Password-less sudo access must be configured for given
-user account before script is executed.
+user account before the script is executed.
.. code:: bash
@@ -156,13 +162,14 @@ automatically.
Script **build_base_machine.sh** will install all the vsperf dependencies
in terms of system packages, Python 3.x and required Python modules.
In case of CentOS 7 or RHEL it will install Python 3.3 from an additional
-repository provided by Software Collections (`a link`_). Installation script
+repository provided by Software Collections (`a link`_). The installation script
will also use `virtualenv`_ to create a vsperf virtual environment, which is
-isolated from the default Python environment. This environment will reside in a
-directory called **vsperfenv** in $HOME. It will ensure, that system wide Python
-installation is not modified or broken by VSPERF installation. The complete list
-of Python packages installed inside virtualenv can be found at file
-``requirements.txt``, which is located at vswitchperf repository.
+isolated from the default Python environment, using the Python3 package located
+in **/usr/bin/python3**. This environment will reside in a directory called
+**vsperfenv** in $HOME. It will ensure, that system wide Python installation
+ is not modified or broken by VSPERF installation. The complete list of Python
+packages installed inside virtualenv can be found in the file
+``requirements.txt``, which is located at the vswitchperf repository.
**NOTE:** For RHEL 7.3 Enterprise and CentOS 7.3 OVS Vanilla is not
built from upstream source due to kernel incompatibilities. Please see the
diff --git a/docs/testing/user/configguide/trafficgen.rst b/docs/testing/user/configguide/trafficgen.rst
index 91c4084e..33824486 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
@@ -783,6 +791,10 @@ It is neccesary for proper connection between Trex server and VSPERF.
cd trex-core/scripts/
./t-rex-64 -i
+**NOTE:** Please check your firewall settings at both DUT and T-Rex server.
+Firewall must allow a connection from DUT (VSPERF) to the T-Rex server running
+at TCP port 4501.
+
For additional information about Trex stateless mode see Trex stateless documentation:
https://trex-tgn.cisco.com/trex/doc/trex_stateless.html
diff --git a/docs/testing/user/userguide/teststeps.rst b/docs/testing/user/userguide/teststeps.rst
index 40cc732c..08c95311 100644
--- a/docs/testing/user/userguide/teststeps.rst
+++ b/docs/testing/user/userguide/teststeps.rst
@@ -271,7 +271,9 @@ of supported objects and their most common functions follows:
in case that condition is not ``True``
* ``Eval expression`` - evaluates given expression as a python code and returns
its result
- * ``Exec_Shell command`` - executes a shell command
+ * ``Exec_Shell command`` - executes a shell command and wait until it finishes
+ * ``Exec_Shell_Background command`` - executes a shell command at background;
+ Command will be automatically terminated at the end of testcase execution.
* ``Exec_Python code`` - executes a python code
diff --git a/testcases/testcase.py b/testcases/testcase.py
index b3300b89..991c2890 100644
--- a/testcases/testcase.py
+++ b/testcases/testcase.py
@@ -169,7 +169,7 @@ class TestCase(object):
self._traffic['l3'] = S.getValue(self._tunnel_type.upper() + '_FRAME_L3')
self._traffic['l4'] = S.getValue(self._tunnel_type.upper() + '_FRAME_L4')
self._traffic['l2']['dstmac'] = S.getValue('NICS')[1]['mac']
- elif len(S.getValue('NICS')) and \
+ elif len(S.getValue('NICS')) >= 2 and \
(S.getValue('NICS')[0]['type'] == 'vf' or
S.getValue('NICS')[1]['type'] == 'vf'):
mac1 = S.getValue('NICS')[0]['mac']
@@ -265,6 +265,9 @@ class TestCase(object):
# Stop all VNFs started by TestSteps in case that something went wrong
self.step_stop_vnfs()
+ # Stop all processes executed by testcase
+ tasks.terminate_all_tasks(self._logger)
+
# umount hugepages if mounted
self._umount_hugepages()
diff --git a/tools/pkt_gen/ixia/ixia.py b/tools/pkt_gen/ixia/ixia.py
index e768be06..d4ca56f2 100755
--- a/tools/pkt_gen/ixia/ixia.py
+++ b/tools/pkt_gen/ixia/ixia.py
@@ -111,6 +111,11 @@ def _build_set_cmds(values, prefix='dict set'):
yield subkey
continue
+ if isinstance(value, list):
+ value = '{{{}}}'.format(' '.join(str(x) for x in value))
+ yield ' '.join([prefix, 'set', key, value]).strip()
+ continue
+
# tcl doesn't recognise the strings "True" or "False", only "1"
# or "0". Special case to convert them
if isinstance(value, bool):
@@ -118,6 +123,9 @@ def _build_set_cmds(values, prefix='dict set'):
else:
value = str(value)
+ if isinstance(value, str) and not value:
+ value = '{}'
+
if prefix:
yield ' '.join([prefix, key, value]).strip()
else:
diff --git a/tools/pkt_gen/ixnet/ixnet.py b/tools/pkt_gen/ixnet/ixnet.py
index b8fb1879..d1ba9096 100755
--- a/tools/pkt_gen/ixnet/ixnet.py
+++ b/tools/pkt_gen/ixnet/ixnet.py
@@ -127,6 +127,11 @@ def _build_set_cmds(values, prefix='dict set'):
yield subkey
continue
+ if isinstance(value, list):
+ value = '{{{}}}'.format(' '.join(str(x) for x in value))
+ yield ' '.join([prefix, 'set', key, value]).strip()
+ continue
+
# tcl doesn't recognise the strings "True" or "False", only "1"
# or "0". Special case to convert them
if isinstance(value, bool):
@@ -134,6 +139,9 @@ def _build_set_cmds(values, prefix='dict set'):
else:
value = str(value)
+ if isinstance(value, str) and not value:
+ value = '{}'
+
if prefix:
yield ' '.join([prefix, key, value]).strip()
else:
diff --git a/tools/pkt_gen/trex/trex.py b/tools/pkt_gen/trex/trex.py
index 82118f6f..cfe54b78 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,18 @@ 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...")
+ try:
+ self._stlclient = STLClient(username=self._trex_user, server=self._trex_host_ip_addr,
+ verbose_level=0)
+ self._stlclient.connect()
+ except STLError:
+ raise RuntimeError('T-Rex: Cannot connect to T-Rex server. Please check if it is '
+ 'running and that firewall allows connection to TCP port 4501.')
+
+ self._logger.info("T-Rex: Trex host successfully found...")
def disconnect(self):
"""Disconnect from the traffic generator.
@@ -140,7 +146,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 +251,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 +275,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 +361,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 +377,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 +446,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)
diff --git a/tools/tasks.py b/tools/tasks.py
index 4179291f..18f4d712 100644
--- a/tools/tasks.py
+++ b/tools/tasks.py
@@ -114,6 +114,16 @@ def run_task(cmd, logger, msg=None, check_error=False):
return ('\n'.join(sout.decode(my_encoding).strip() for sout in stdout),
('\n'.join(sout.decode(my_encoding).strip() for sout in stderr)))
+def update_pids(pid):
+ """update list of running pids, so they can be terminated at the end
+ """
+ try:
+ pids = settings.getValue('_EXECUTED_PIDS')
+ pids.append(pid)
+ except AttributeError:
+ pids = [pid]
+ settings.setValue('_EXECUTED_PIDS', pids)
+
def run_background_task(cmd, logger, msg):
"""Run task in background and log when started.
@@ -132,6 +142,8 @@ def run_background_task(cmd, logger, msg):
proc = subprocess.Popen(map(os.path.expanduser, cmd), stdout=_get_stdout(), bufsize=0)
+ update_pids(proc.pid)
+
return proc.pid
@@ -174,14 +186,13 @@ def terminate_task_subtree(pid, signal='-15', sleep=10, logger=None):
:param logger: Logger to write details to
"""
try:
- output = subprocess.check_output("pgrep -P " + str(pid), shell=True).decode().rstrip('\n')
+ children = subprocess.check_output("pgrep -P " + str(pid), shell=True).decode().rstrip('\n').split()
except subprocess.CalledProcessError:
- output = ""
+ children = []
terminate_task(pid, signal, sleep, logger)
# just for case children were kept alive
- children = output.split('\n')
for child in children:
terminate_task(child, signal, sleep, logger)
@@ -208,6 +219,22 @@ def terminate_task(pid, signal='-15', sleep=10, logger=None):
if signal.lstrip('-').upper() not in ('9', 'KILL', 'SIGKILL') and systeminfo.pid_isalive(pid):
terminate_task(pid, '-9', sleep, logger)
+ pids = settings.getValue('_EXECUTED_PIDS')
+ if pid in pids:
+ pids.remove(pid)
+ settings.setValue('_EXECUTED_PIDS', pids)
+
+def terminate_all_tasks(logger):
+ """Terminate all processes executed by vsperf, just for case they were not
+ terminated by standard means.
+ """
+ pids = settings.getValue('_EXECUTED_PIDS')
+ if pids:
+ logger.debug('Following processes will be terminated: %s', pids)
+ for pid in pids:
+ terminate_task_subtree(pid, logger=logger)
+ settings.setValue('_EXECUTED_PIDS', [])
+
class Process(object):
"""Control an instance of a long-running process.
diff --git a/tools/teststepstools.py b/tools/teststepstools.py
index 639e3437..33db8f79 100644
--- a/tools/teststepstools.py
+++ b/tools/teststepstools.py
@@ -19,6 +19,7 @@ import logging
import subprocess
import locale
from tools.functions import filter_output
+from tools.tasks import run_background_task
_LOGGER = logging.getLogger(__name__)
@@ -102,3 +103,19 @@ class TestStepsTools(object):
""" validate result of shell `command' execution
"""
return result is not None
+
+ @staticmethod
+ def Exec_Shell_Background(command):
+ """ Execute a shell `command' at the background and return its PID id
+ """
+ try:
+ pid = run_background_task(command.split(), _LOGGER, "Background task: {}".format(command))
+ return pid
+ except OSError:
+ return None
+
+ @staticmethod
+ def validate_Exec_Shell_Background(result, dummy_command, dummy_regex=None):
+ """ validate result of shell `command' execution on the background
+ """
+ return result is not None
diff --git a/vswitches/ovs_dpdk_vhost.py b/vswitches/ovs_dpdk_vhost.py
index 11b32c88..6deb0c25 100644
--- a/vswitches/ovs_dpdk_vhost.py
+++ b/vswitches/ovs_dpdk_vhost.py
@@ -114,12 +114,15 @@ class OvsDpdkVhost(IVSwitchOvs):
Creates a port of type dpdk.
The new port is named dpdk<n> where n is an integer starting from 0.
"""
+ _nics = S.getValue('NICS')
bridge = self._bridges[switch_name]
dpdk_count = self._get_port_count('type=dpdk')
+ if dpdk_count == len(_nics):
+ raise RuntimeError("Can't add phy port! There are only {} ports defined "
+ "by WHITELIST_NICS parameter!".format(len(_nics)))
port_name = 'dpdk' + str(dpdk_count)
# PCI info. Please note there must be no blank space, eg must be
# like 'options:dpdk-devargs=0000:06:00.0'
- _nics = S.getValue('NICS')
nic_pci = 'options:dpdk-devargs=' + _nics[dpdk_count]['pci']
params = ['--', 'set', 'Interface', port_name, 'type=dpdk', nic_pci]
# multi-queue enable
diff --git a/vswitches/ovs_vanilla.py b/vswitches/ovs_vanilla.py
index 942ddd41..83c52050 100644
--- a/vswitches/ovs_vanilla.py
+++ b/vswitches/ovs_vanilla.py
@@ -75,10 +75,8 @@ class OvsVanilla(IVSwitchOvs):
See IVswitch for general description
"""
if self._current_id == len(self._ports):
- self._logger.error("Can't add port! There are only " +
- len(self._ports) + " ports " +
- "defined in config!")
- raise RuntimeError('Failed to add phy port')
+ raise RuntimeError("Can't add phy port! There are only {} ports defined "
+ "by WHITELIST_NICS parameter!".format(len(self._ports)))
if not self._ports[self._current_id]:
self._logger.error("Can't detect device name for NIC %s", self._current_id)
raise ValueError("Invalid device name for %s" % self._current_id)
diff --git a/vswitches/vpp_dpdk_vhost.py b/vswitches/vpp_dpdk_vhost.py
index c62e28d4..58d6bf51 100644
--- a/vswitches/vpp_dpdk_vhost.py
+++ b/vswitches/vpp_dpdk_vhost.py
@@ -225,7 +225,8 @@ class VppDpdkVhost(IVSwitch, tasks.Process):
vpp_nics = self._get_nic_info(key='Pci')
# check if there are any NICs left
if len(self._phy_ports) >= len(S.getValue('NICS')):
- raise RuntimeError('All available NICs are already configured!')
+ raise RuntimeError("Can't add phy port! There are only {} ports defined "
+ "by WHITELIST_NICS parameter!".format(len(S.getValue('NICS'))))
nic = S.getValue('NICS')[len(self._phy_ports)]
if not nic['pci'] in vpp_nics: