diff options
-rw-r--r-- | conf/__init__.py | 13 | ||||
-rw-r--r-- | conf/integration/01_testcases.conf | 121 | ||||
-rw-r--r-- | docs/testing/user/userguide/teststeps.rst | 4 | ||||
-rw-r--r-- | testcases/testcase.py | 3 | ||||
-rw-r--r-- | tools/tasks.py | 33 | ||||
-rw-r--r-- | tools/teststepstools.py | 17 |
6 files changed, 187 insertions, 4 deletions
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..692f1561 100644 --- a/conf/integration/01_testcases.conf +++ b/conf/integration/01_testcases.conf @@ -1000,6 +1000,127 @@ 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'], + ], + }, + # + # End of examples of functional testcases with traffic capture validation + # ] # Example of TC definition with exact vSwitch, VNF and TRAFFICGEN values. 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 cf71d596..991c2890 100644 --- a/testcases/testcase.py +++ b/testcases/testcase.py @@ -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/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 |