IETF Draft: Comments from IETF-96
[vswitchperf.git] / testcases / testcase.py
index d88840d..7f22c18 100644 (file)
@@ -57,27 +57,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._testcast_run_time = None
 
+        # store all GUEST_ specific settings to keep original values before their expansion
+        for key in S.__dict__:
+            if key.startswith('GUEST_'):
+                self._settings_original[key] = S.getValue(key)
+
         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
+        functions.settings_update_paths()
         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()
+            # we can put just one item, it'll be expanded automatically for all VMs
+            self._update_settings('GUEST_LOOPBACK', [guest_loopback])
 
         # set test parameters; CLI options take precedence to testcase settings
         self._logger = logging.getLogger(__name__)
@@ -112,12 +111,6 @@ 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
-        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
         multistream = cfg.get('MultiStream', TRAFFIC_DEFAULTS['multistream'])
@@ -153,13 +146,6 @@ class TestCase(object):
         # 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('VANILLA_TGEN_PORT1_MAC'),
-                                        'dstmac': S.getValue('VANILLA_TGEN_PORT2_MAC')})
-            self._traffic['l3'].update({'srcip': S.getValue('VANILLA_TGEN_PORT1_IP'),
-                                        'dstip': S.getValue('VANILLA_TGEN_PORT2_IP')})
-
         # trafficgen configuration required for tests of tunneling protocols
         if self.deployment == "op2p":
             self._traffic['l2'].update({'srcmac':
@@ -192,13 +178,6 @@ class TestCase(object):
         # mount hugepages if needed
         self._mount_hugepages()
 
-        # verify enough hugepages are free to run the testcase
-        if not self._check_for_enough_hugepages():
-            raise RuntimeError('Not enough hugepages free to run test.')
-
-        # 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()
         self._traffic_ctl = component_factory.create_traffic(
@@ -209,6 +188,32 @@ class TestCase(object):
             self.deployment,
             loader.get_vnf_class())
 
+        # verify enough hugepages are free to run the testcase
+        if not self._check_for_enough_hugepages():
+            raise RuntimeError('Not enough hugepages free to run test.')
+
+        # perform guest related handling
+        if self._vnf_ctl.get_vnfs_number():
+            # copy sources of l2 forwarding tools into VM shared dir if needed
+            self._copy_fwd_tools_for_all_guests(self._vnf_ctl.get_vnfs_number())
+
+            # in case of multi VM in parallel, set the number of streams to the number of VMs
+            if self.deployment.startswith('pvpv'):
+                # for each VM NIC pair we need an unique stream
+                streams = 0
+                for vm_nic in S.getValue('GUEST_NICS_NR')[:self._vnf_ctl.get_vnfs_number()]:
+                    streams += int(vm_nic / 2) if vm_nic > 1 else 1
+                self._logger.debug("VMs with parallel connection were detected. "
+                                   "Thus Number of streams was set to %s", streams)
+                self._traffic.update({'multistream': streams})
+
+            # OVS Vanilla requires guest VM MAC address and IPs to work
+            if 'linux_bridge' in S.getValue('GUEST_LOOPBACK'):
+                self._traffic['l2'].update({'srcmac': S.getValue('VANILLA_TGEN_PORT1_MAC'),
+                                            'dstmac': S.getValue('VANILLA_TGEN_PORT2_MAC')})
+                self._traffic['l3'].update({'srcip': S.getValue('VANILLA_TGEN_PORT1_IP'),
+                                            'dstip': S.getValue('VANILLA_TGEN_PORT2_IP')})
+
         if self._vswitch_none:
             self._vswitch_ctl = component_factory.create_pktfwd(
                 self.deployment,
@@ -350,28 +355,24 @@ 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 self.deployment in ['pvp', 'pvvp'] and len(self.guest_loopback):
-                item[ResultsConstants.GUEST_LOOPBACK] = ' '.join(self.guest_loopback)
+            if self._vnf_ctl.get_vnfs_number():
+                item[ResultsConstants.GUEST_LOOPBACK] = ' '.join(S.getValue('GUEST_LOOPBACK'))
             if self._tunnel_type:
                 item[ResultsConstants.TUNNEL_TYPE] = self._tunnel_type
         return results
 
-    def _copy_fwd_tools_for_all_guests(self):
+    def _copy_fwd_tools_for_all_guests(self, vm_count):
         """Copy dpdk and l2fwd code to GUEST_SHARE_DIR[s] based on selected deployment.
         """
-        # 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
+        # consider only VNFs involved in the test
+        for guest_dir in set(S.getValue('GUEST_SHARE_DIR')[:vm_count]):
+            self._copy_fwd_tools_for_guest(guest_dir)
 
-    def _copy_fwd_tools_for_guest(self, index):
+    def _copy_fwd_tools_for_guest(self, guest_dir):
         """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,
@@ -381,14 +382,32 @@ class TestCase(object):
         os.makedirs(guest_dir)
 
         # copy sources into shared dir only if neccessary
-        if 'testpmd' in self.guest_loopback or 'l2fwd' in self.guest_loopback:
+        guest_loopback = set(S.getValue('GUEST_LOOPBACK'))
+        if 'testpmd' in guest_loopback:
             try:
-                tasks.run_task(['rsync', '-a', '-r', '-l', r'--exclude="\.git"',
-                                os.path.join(S.getValue('RTE_SDK_USER'), ''),
+                # exclude whole .git/ subdirectory and all o-files;
+                # It is assumed, that the same RTE_TARGET is used in both host
+                # and VMs; This simplification significantly speeds up testpmd
+                # build. If we will need a different RTE_TARGET in VM,
+                # then we have to build whole DPDK from the scratch in VM.
+                # In that case we can copy just DPDK sources (e.g. by excluding
+                # all items obtained by git status -unormal --porcelain).
+                # NOTE: Excluding RTE_TARGET directory won't help on systems,
+                # where DPDK is built for multiple targets (e.g. for gcc & icc)
+                exclude = []
+                exclude.append(r'--exclude=.git/')
+                exclude.append(r'--exclude=*.o')
+                tasks.run_task(['rsync', '-a', '-r', '-l'] + exclude +
+                               [os.path.join(S.getValue('TOOLS')['dpdk_src'], ''),
                                 os.path.join(guest_dir, 'DPDK')],
                                self._logger,
                                'Copying DPDK to shared directory...',
                                True)
+            except subprocess.CalledProcessError:
+                self._logger.error('Unable to copy DPDK to shared directory')
+                raise
+        if 'l2fwd' in guest_loopback:
+            try:
                 tasks.run_task(['rsync', '-a', '-r', '-l',
                                 os.path.join(S.getValue('ROOT_DIR'), 'src/l2fwd/'),
                                 os.path.join(guest_dir, 'l2fwd')],
@@ -396,7 +415,8 @@ class TestCase(object):
                                'Copying l2fwd to shared directory...',
                                True)
             except subprocess.CalledProcessError:
-                self._logger.error('Unable to copy DPDK and l2fwd to shared directory')
+                self._logger.error('Unable to copy l2fwd to shared directory')
+                raise
 
     def _mount_hugepages(self):
         """Mount hugepages if usage of DPDK or Qemu is detected
@@ -423,8 +443,8 @@ class TestCase(object):
         """
         hugepages_needed = 0
         hugepage_size = hugepages.get_hugepage_size()
-        # get hugepage amounts per guest
-        for guest in range(self.deployment.count('v')):
+        # get hugepage amounts per guest involved in the test
+        for guest in range(self._vnf_ctl.get_vnfs_number()):
             hugepages_needed += math.ceil((int(S.getValue(
                 'GUEST_MEMORY')[guest]) * 1000) / hugepage_size)
 
@@ -436,11 +456,11 @@ class TestCase(object):
             from vswitches import ovs_dpdk_vhost
             if ovs_dpdk_vhost.OvsDpdkVhost.old_dpdk_config():
                 match = re.search(
-                    '-socket-mem\s+(\d+),(\d+)',
+                    r'-socket-mem\s+(\d+),(\d+)',
                     ''.join(S.getValue('VSWITCHD_DPDK_ARGS')))
                 if match:
-                    sock0_mem, sock1_mem = (int(match.group(1)) / 1024,
-                                            int(match.group(2)) / 1024)
+                    sock0_mem, sock1_mem = (int(match.group(1)) * 1024 / hugepage_size,
+                                            int(match.group(2)) * 1024 / hugepage_size)
                 else:
                     logging.info(
                         'Could not parse socket memory config in dpdk params.')
@@ -448,8 +468,8 @@ class TestCase(object):
                 sock0_mem, sock1_mem = (
                     S.getValue(
                         'VSWITCHD_DPDK_CONFIG')['dpdk-socket-mem'].split(','))
-                sock0_mem, sock1_mem = (int(sock0_mem) / 1024,
-                                        int(sock1_mem) / 1024)
+                sock0_mem, sock1_mem = (int(sock0_mem) * 1024 / hugepage_size,
+                                        int(sock1_mem) * 1024 / hugepage_size)
 
         # If hugepages needed, verify the amounts are free
         if any([hugepages_needed, sock0_mem, sock1_mem]):