From 3a535d0252be0a6fc014e654b61e06620cc615a0 Mon Sep 17 00:00:00 2001 From: Martin Klozik Date: Tue, 12 Apr 2016 12:56:27 +0100 Subject: integration: Support of PVP and PVVP integration TCs Integration TC support has been enhanced to support PVP and PVVP scenarios. Definition of integration testcases have been modified to use a sort of macros for repetitive parts. Additional improvements were introduced: * instances of testcases are created only for testcases selected for execution * new TC definition options allow to define test specific vswitch, VNF, traffic generator and test options * tests filter applied on pattern specified by --tests allows to define negative filter only; In that case list of all tests is used as base for negative filter. * traffic values defined within teststep passed to send_traffic is merged with default values; This is essential for execution of TCs with linux_bridge or SRIOV support. It also simplifies integration TC definition * typos removed Change-Id: Icb734a7afd7e5154f27a8ff25615a39e01f58c27 JIRA: VSPERF-213 JIRA: VSPERF-216 Signed-off-by: Martin Klozik Reviewed-by: Maryam Tahhan Reviewed-by: Al Morton Reviewed-by: Christian Trautman Reviewed-by: Brian Castelli --- testcases/integration.py | 39 +++++++++--- testcases/testcase.py | 159 +++++++++++++++++++++++++++++------------------ 2 files changed, 129 insertions(+), 69 deletions(-) (limited to 'testcases') diff --git a/testcases/integration.py b/testcases/integration.py index 53ba17f4..9733c263 100644 --- a/testcases/integration.py +++ b/testcases/integration.py @@ -17,10 +17,12 @@ import os import time import logging +import copy from testcases import TestCase from conf import settings as S from collections import OrderedDict +from core.loader import Loader CHECK_PREFIX = 'validate_' @@ -39,7 +41,7 @@ class IntegrationTestCase(TestCase): def report_status(self, label, status): """ Log status of test step """ - self._logger.debug("%s ... %s", label, 'OK' if status else 'FAILED') + self._logger.info("%s ... %s", label, 'OK' if status else 'FAILED') def run_initialize(self): """ Prepare test execution environment @@ -104,6 +106,8 @@ class IntegrationTestCase(TestCase): if not self.test: self._traffic_ctl.send_traffic(self._traffic) else: + vnf_list = {} + loader = Loader() # execute test based on TestSteps definition if self.test: step_result = [None] * len(self.test) @@ -113,6 +117,18 @@ class IntegrationTestCase(TestCase): test_object = self._vswitch_ctl.get_vswitch() elif step[0] == 'trafficgen': test_object = self._traffic_ctl + # in case of send_traffic method, ensure that specified + # traffic values are merged with existing self._traffic + if step[1] == 'send_traffic': + tmp_traffic = copy.deepcopy(self._traffic) + tmp_traffic.update(step[2]) + step[2] = tmp_traffic + elif step[0].startswith('vnf'): + if not step[0] in vnf_list: + # initialize new VM and copy data to its shared dir + vnf_list[step[0]] = loader.get_vnf_class()() + self._copy_fwd_tools_for_guest(len(vnf_list)) + test_object = vnf_list[step[0]] else: self._logger.error("Unsupported test object %s", step[0]) self._inttest = {'status' : False, 'details' : ' '.join(step)} @@ -130,23 +146,32 @@ class IntegrationTestCase(TestCase): step_params = eval_step_params(step[2:], step_result) step_log = '{} {}'.format(' '.join(step[:2]), step_params) step_result[i] = test_method(*step_params) - self._logger.debug("Step {} '{}' results '{}'".format( - i, step_log, step_result[i])) - time.sleep(2) + self._logger.debug("Step %s '%s' results '%s'", i, + step_log, step_result[i]) + time.sleep(5) step_ok = test_method_check(step_result[i], *step_params) except AssertionError: self._inttest = {'status' : False, 'details' : step_log} - self._logger.error("Step {} raised assertion error".format(i)) + self._logger.error("Step %s raised assertion error", i) + # stop vnfs in case of error + for vnf in vnf_list: + vnf_list[vnf].stop() break except IndexError: self._inttest = {'status' : False, 'details' : step_log} - self._logger.error("Step {} result index error {}".format( - i, ' '.join(step[2:]))) + self._logger.error("Step %s result index error %s", i, + ' '.join(step[2:])) + # stop vnfs in case of error + for vnf in vnf_list: + vnf_list[vnf].stop() break self.report_status("Step {} - '{}'".format(i, step_log), step_ok) if not step_ok: self._inttest = {'status' : False, 'details' : step_log} + # stop vnfs in case of error + for vnf in vnf_list: + vnf_list[vnf].stop() break # dump vswitch flows before they are affected by VNF termination diff --git a/testcases/testcase.py b/testcases/testcase.py index 7c935792..f7908af9 100644 --- a/testcases/testcase.py +++ b/testcases/testcase.py @@ -27,6 +27,7 @@ from core.loader import Loader from core.results.results_constants import ResultsConstants from tools import tasks from tools import hugepages +from tools import functions from tools.pkt_gen.trafficgen.trafficgenhelper import TRAFFIC_DEFAULTS from conf import settings as S from conf import get_test_param @@ -52,6 +53,26 @@ class TestCase(object): self._loadgen = None self._output_file = None self._tc_results = None + self.guest_loopback = [] + self._settings_original = {} + self._settings_paths_modified = False + + self._update_settings('VSWITCH', cfg.get('vSwitch', S.getValue('VSWITCH'))) + self._update_settings('VNF', cfg.get('VNF', S.getValue('VNF'))) + self._update_settings('TRAFFICGEN', cfg.get('Trafficgen', S.getValue('TRAFFICGEN'))) + self._update_settings('TEST_PARAMS', cfg.get('Parameters', S.getValue('TEST_PARAMS'))) + + # update global settings + guest_loopback = get_test_param('guest_loopback', None) + if guest_loopback: + self._update_settings('GUEST_LOOPBACK', [guest_loopback for dummy in S.getValue('GUEST_LOOPBACK')]) + + if 'VSWITCH' in self._settings_original or 'VNF' in self._settings_original: + self._settings_original.update({ + 'RTE_SDK' : S.getValue('RTE_SDK'), + 'OVS_DIR' : S.getValue('OVS_DIR'), + }) + functions.settings_update_paths() # set test parameters; CLI options take precedence to testcase settings self._logger = logging.getLogger(__name__) @@ -82,18 +103,11 @@ class TestCase(object): self._tunnel_type = get_test_param('tunnel_type', self._tunnel_type) - # identify guest loopback method, so it can be added into reports - self.guest_loopback = [] - if self.deployment in ['pvp', 'pvvp']: - guest_loopback = get_test_param('guest_loopback', None) - if guest_loopback: - self.guest_loopback.append(guest_loopback) - else: - if self.deployment == 'pvp': - self.guest_loopback.append(S.getValue('GUEST_LOOPBACK')[0]) - else: - self.guest_loopback = S.getValue('GUEST_LOOPBACK').copy() + if self.deployment == 'pvp': + self.guest_loopback.append(S.getValue('GUEST_LOOPBACK')[0]) + else: + self.guest_loopback = S.getValue('GUEST_LOOPBACK').copy() # read configuration of streams; CLI parameter takes precedence to # testcase definition @@ -127,6 +141,9 @@ class TestCase(object): 'pre_installed_flows' : pre_installed_flows, 'frame_rate': int(framerate)}) + # Packet Forwarding mode + self._vswitch_none = 'none' == S.getValue('VSWITCH').strip().lower() + # OVS Vanilla requires guest VM MAC address and IPs to work if 'linux_bridge' in self.guest_loopback: self._traffic['l2'].update({'srcmac': S.getValue('GUEST_NET2_MAC')[0], @@ -134,20 +151,7 @@ class TestCase(object): self._traffic['l3'].update({'srcip': S.getValue('VANILLA_TGEN_PORT1_IP'), 'dstip': S.getValue('VANILLA_TGEN_PORT2_IP')}) - # Packet Forwarding mode - self._vswitch_none = 'none' == S.getValue('VSWITCH').strip().lower() - - def run_initialize(self): - """ Prepare test execution environment - """ - self._logger.debug(self.name) - - # mount hugepages if needed - self._mount_hugepages() - - # copy sources of l2 forwarding tools into VM shared dir if needed - self._copy_fwd_tools_for_guest() - + # trafficgen configuration required for tests of tunneling protocols if self.deployment == "op2p": self._traffic['l2'].update({'srcmac': S.getValue('TRAFFICGEN_PORT1_MAC'), @@ -171,7 +175,16 @@ class TestCase(object): else: self._logger.debug("MAC addresses can not be read") + def run_initialize(self): + """ Prepare test execution environment + """ + self._logger.debug(self.name) + + # mount hugepages if needed + self._mount_hugepages() + # copy sources of l2 forwarding tools into VM shared dir if needed + self._copy_fwd_tools_for_all_guests() self._logger.debug("Controllers:") loader = Loader() @@ -212,6 +225,9 @@ class TestCase(object): # umount hugepages if mounted self._umount_hugepages() + # restore original settings + S.load_from_dict(self._settings_original) + def run_report(self): """ Report test results """ @@ -267,6 +283,19 @@ class TestCase(object): # report test results self.run_report() + def _update_settings(self, param, value): + """ Check value of given configuration parameter + In case that new value is different, then testcase + specific settings is updated and original value stored + + :param param: Name of parameter inside settings + :param value: Disired parameter value + """ + orig_value = S.getValue(param) + if orig_value != value: + self._settings_original[param] = orig_value + S.setValue(param, value) + def _append_results(self, results): """ Method appends mandatory Test Case results to list of dictionaries. @@ -284,50 +313,55 @@ class TestCase(object): item[ResultsConstants.SCAL_STREAM_COUNT] = self._traffic['multistream'] item[ResultsConstants.SCAL_STREAM_TYPE] = self._traffic['stream_type'] item[ResultsConstants.SCAL_PRE_INSTALLED_FLOWS] = self._traffic['pre_installed_flows'] - if len(self.guest_loopback): + if self.deployment in ['pvp', 'pvvp'] and len(self.guest_loopback): item[ResultsConstants.GUEST_LOOPBACK] = ' '.join(self.guest_loopback) if self._tunnel_type: item[ResultsConstants.TUNNEL_TYPE] = self._tunnel_type return results - def _copy_fwd_tools_for_guest(self): - """Copy dpdk and l2fwd code to GUEST_SHARE_DIR[s] for use by guests. + def _copy_fwd_tools_for_all_guests(self): + """Copy dpdk and l2fwd code to GUEST_SHARE_DIR[s] based on selected deployment. """ - counter = 0 - # method is executed only for pvp and pvvp, so let's count number of 'v' - while counter < self.deployment.count('v'): - guest_dir = S.getValue('GUEST_SHARE_DIR')[counter] - - # remove shared dir if it exists to avoid issues with file consistency - if os.path.exists(guest_dir): - tasks.run_task(['rm', '-f', '-r', guest_dir], self._logger, - 'Removing content of shared directory...', True) - - # directory to share files between host and guest - os.makedirs(guest_dir) - - # copy sources into shared dir only if neccessary - if 'testpmd' in self.guest_loopback or 'l2fwd' in self.guest_loopback: - try: - # always use DPDK vhost user version inside VM, so results are not - # affected by different testpmd behavior inside VM - tasks.run_task(['rsync', '-a', '-r', '-l', r'--exclude="\.git"', - os.path.join(S.getValue('RTE_SDK_USER'), ''), - os.path.join(guest_dir, 'DPDK')], - self._logger, - 'Copying DPDK to shared directory...', - True) - tasks.run_task(['rsync', '-a', '-r', '-l', - os.path.join(S.getValue('ROOT_DIR'), 'src/l2fwd/'), - os.path.join(guest_dir, 'l2fwd')], - self._logger, - 'Copying l2fwd to shared directory...', - True) - except subprocess.CalledProcessError: - self._logger.error('Unable to copy DPDK and l2fwd to shared directory') - + # data are copied only for pvp and pvvp, so let's count number of 'v' + counter = 1 + while counter <= self.deployment.count('v'): + self._copy_fwd_tools_for_guest(counter) counter += 1 + def _copy_fwd_tools_for_guest(self, index): + """Copy dpdk and l2fwd code to GUEST_SHARE_DIR of VM + + :param index: Index of VM starting from 1 (i.e. 1st VM has index 1) + """ + guest_dir = S.getValue('GUEST_SHARE_DIR')[index-1] + + # remove shared dir if it exists to avoid issues with file consistency + if os.path.exists(guest_dir): + tasks.run_task(['rm', '-f', '-r', guest_dir], self._logger, + 'Removing content of shared directory...', True) + + # directory to share files between host and guest + os.makedirs(guest_dir) + + # copy sources into shared dir only if neccessary + if 'testpmd' in self.guest_loopback or 'l2fwd' in self.guest_loopback: + try: + tasks.run_task(['rsync', '-a', '-r', '-l', r'--exclude="\.git"', + os.path.join(S.getValue('RTE_SDK'), ''), + os.path.join(guest_dir, 'DPDK')], + self._logger, + 'Copying DPDK to shared directory...', + True) + tasks.run_task(['rsync', '-a', '-r', '-l', + os.path.join(S.getValue('ROOT_DIR'), 'src/l2fwd/'), + os.path.join(guest_dir, 'l2fwd')], + self._logger, + 'Copying l2fwd to shared directory...', + True) + except subprocess.CalledProcessError: + self._logger.error('Unable to copy DPDK and l2fwd to shared directory') + + def _mount_hugepages(self): """Mount hugepages if usage of DPDK or Qemu is detected """ @@ -335,7 +369,8 @@ class TestCase(object): if not self._hugepages_mounted and \ (self.deployment.count('v') or \ S.getValue('VSWITCH').lower().count('dpdk') or \ - self._vswitch_none): + self._vswitch_none or \ + self.test and 'vnf' in [step[0][0:3] for step in self.test]): hugepages.mount_hugepages() self._hugepages_mounted = True -- cgit 1.2.3-korg