Kubernetes: Infrastructure For K8S Net testing.
[vswitchperf.git] / testcases / testcase.py
index 37cdefa..51d212b 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright 2015-2017 Intel Corporation.
+# Copyright 2015-2018 Intel Corporation, Tieto and others.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -26,7 +26,7 @@ import subprocess
 
 from datetime import datetime as dt
 from conf import settings as S
-from conf import get_test_param, merge_spec
+from conf import merge_spec
 import core.component_factory as component_factory
 from core.loader import Loader
 from core.results.results_constants import ResultsConstants
@@ -36,9 +36,19 @@ from tools import functions
 from tools import namespace
 from tools import veth
 from tools.teststepstools import TestStepsTools
+from tools.llc_management import rmd
 
+# Validation methods required for integration TCs will have following prefix before the name
+# of original method.
 CHECK_PREFIX = 'validate_'
 
+# Several parameters can be defined by both TC definition keywords and configuration parameters.
+# Following mapping table is used to correctly evaluate priority of testcase configuration, where
+# TC definition keywords (i.e. mapping table keys) have higher priority than appropriate TC
+# parameters (i.e. mapping table values). TC parameters can be defined within "Parameters"
+# section, via CLI parameters or within configuration files.
+MAPPING_TC_CFG2CONF = {'vSwitch':'VSWITCH', 'VNF':'VNF', 'Trafficgen':'TRAFFICGEN', 'Tunnel Type':'TUNNEL_TYPE'}
+
 # pylint: disable=too-many-instance-attributes
 class TestCase(object):
     """TestCase base class
@@ -63,6 +73,8 @@ class TestCase(object):
         self._hugepages_mounted = False
         self._traffic_ctl = None
         self._vnf_ctl = None
+        self._pod_ctl = None
+        self._pod_list = None
         self._vswitch_ctl = None
         self._collector = None
         self._loadgen = None
@@ -71,6 +83,7 @@ class TestCase(object):
         self._settings_paths_modified = False
         self._testcast_run_time = None
         self._versions = []
+        self._k8s = False
         # initialization of step driven specific members
         self._step_check = False    # by default don't check result for step driven testcases
         self._step_vnf_list = {}
@@ -78,14 +91,22 @@ class TestCase(object):
         self._step_result_mapping = {}
         self._step_status = None
         self._step_send_traffic = False # indication if send_traffic was called within test steps
+        self._vnf_list = []
         self._testcase_run_time = None
 
         S.setValue('VSWITCH', cfg.get('vSwitch', S.getValue('VSWITCH')))
         S.setValue('VNF', cfg.get('VNF', S.getValue('VNF')))
         S.setValue('TRAFFICGEN', cfg.get('Trafficgen', S.getValue('TRAFFICGEN')))
+        S.setValue('TUNNEL_TYPE', cfg.get('Tunnel Type', S.getValue('TUNNEL_TYPE')))
         test_params = copy.deepcopy(S.getValue('TEST_PARAMS'))
         tc_test_params = cfg.get('Parameters', S.getValue('TEST_PARAMS'))
         test_params = merge_spec(test_params, tc_test_params)
+
+        # ensure that parameters from TC definition have the highest priority, see MAPPING_TC_CFG2CONF
+        for (cfg_param, param) in MAPPING_TC_CFG2CONF.items():
+            if cfg_param in cfg and param in test_params:
+                del test_params[param]
+
         S.setValue('TEST_PARAMS', test_params)
         S.check_test_params()
 
@@ -123,16 +144,7 @@ class TestCase(object):
         self.deployment = cfg['Deployment']
         self._frame_mod = cfg.get('Frame Modification', None)
 
-        self._tunnel_type = None
-        self._tunnel_operation = None
-
-        if self.deployment == 'op2p':
-            self._tunnel_operation = cfg['Tunnel Operation']
-
-            if 'Tunnel Type' in cfg:
-                self._tunnel_type = cfg['Tunnel Type']
-                self._tunnel_type = get_test_param('TUNNEL_TYPE',
-                                                   self._tunnel_type)
+        self._tunnel_operation = cfg.get('Tunnel Operation', None)
 
         # check if test requires background load and which generator it uses
         self._load_cfg = cfg.get('Load', None)
@@ -143,16 +155,14 @@ class TestCase(object):
 
         # set traffic details, so they can be passed to vswitch and traffic ctls
         self._traffic = copy.deepcopy(S.getValue('TRAFFIC'))
-        self._traffic.update({'bidir': bidirectional,
-                              'tunnel_type': self._tunnel_type,})
-
-        self._traffic = functions.check_traffic(self._traffic)
+        self._traffic.update({'bidir': bidirectional})
 
         # Packet Forwarding mode
         self._vswitch_none = str(S.getValue('VSWITCH')).strip().lower() == 'none'
 
         # trafficgen configuration required for tests of tunneling protocols
-        if self.deployment == "op2p":
+        if self._tunnel_operation:
+            self._traffic.update({'tunnel_type': S.getValue('TUNNEL_TYPE')})
             self._traffic['l2'].update({'srcmac':
                                         S.getValue('TRAFFICGEN_PORT1_MAC'),
                                         'dstmac':
@@ -164,11 +174,11 @@ class TestCase(object):
                                         S.getValue('TRAFFICGEN_PORT2_IP')})
 
             if self._tunnel_operation == "decapsulation":
-                self._traffic['l2'] = S.getValue(self._tunnel_type.upper() + '_FRAME_L2')
-                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'].update(S.getValue(S.getValue('TUNNEL_TYPE').upper() + '_FRAME_L2'))
+                self._traffic['l3'].update(S.getValue(S.getValue('TUNNEL_TYPE').upper() + '_FRAME_L3'))
+                self._traffic['l4'].update(S.getValue(S.getValue('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']
@@ -178,12 +188,18 @@ class TestCase(object):
             else:
                 self._logger.debug("MAC addresses can not be read")
 
+        self._traffic = functions.check_traffic(self._traffic)
+
         # count how many VNFs are involved in TestSteps
         if self.test:
             for step in self.test:
                 if step[0].startswith('vnf'):
                     self._step_vnf_list[step[0]] = None
 
+        # if llc allocation is required, initialize it.
+        if S.getValue('LLC_ALLOCATION'):
+            self._rmd = rmd.CacheAllocator()
+
     def run_initialize(self):
         """ Prepare test execution environment
         """
@@ -201,6 +217,14 @@ class TestCase(object):
             loader.get_vnf_class(),
             len(self._step_vnf_list))
 
+        self._vnf_list = self._vnf_ctl.get_vnfs()
+
+        self._pod_ctl = component_factory.create_pod(
+            self.deployment,
+            loader.get_pod_class())
+
+        self._pod_list = self._pod_ctl.get_pods()
+
         # 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.')
@@ -249,11 +273,15 @@ class TestCase(object):
             loader.get_loadgen_class(),
             self._load_cfg)
 
-        self._output_file = os.path.join(self._results_dir, "result_" + self.name +
-                                         "_" + self.deployment + ".csv")
+        self._output_file = os.path.join(self._results_dir, "result_{}_{}_{}.csv".format(
+            str(S.getValue('_TEST_INDEX')), self.name, self.deployment))
 
         self._step_status = {'status' : True, 'details' : ''}
 
+        # Perform LLC-allocations
+        if S.getValue('LLC_ALLOCATION'):
+            self._rmd.setup_llc_allocation()
+
         self._logger.debug("Setup:")
 
     def run_finalize(self):
@@ -262,13 +290,24 @@ class TestCase(object):
         # Stop all VNFs started by TestSteps in case that something went wrong
         self.step_stop_vnfs()
 
+        if self._k8s:
+            self._pod_ctl.stop()
+
+
+        # Cleanup any LLC-allocations
+        if S.getValue('LLC_ALLOCATION'):
+            self._rmd.cleanup_llc_allocation()
+
+        # Stop all processes executed by testcase
+        tasks.terminate_all_tasks(self._logger)
+
         # umount hugepages if mounted
         self._umount_hugepages()
 
         # cleanup any namespaces created
         if os.path.isdir('/tmp/namespaces'):
             namespace_list = os.listdir('/tmp/namespaces')
-            if len(namespace_list):
+            if namespace_list:
                 self._logger.info('Cleaning up namespaces')
             for name in namespace_list:
                 namespace.delete_namespace(name)
@@ -276,7 +315,7 @@ class TestCase(object):
         # cleanup any veth ports created
         if os.path.isdir('/tmp/veth'):
             veth_list = os.listdir('/tmp/veth')
-            if len(veth_list):
+            if veth_list:
                 self._logger.info('Cleaning up veth ports')
             for eth in veth_list:
                 port1, port2 = eth.split('-')
@@ -303,8 +342,8 @@ class TestCase(object):
             if len(self._tc_results) < len(results):
                 if len(self._tc_results) > 1:
                     raise RuntimeError('Testcase results do not match:'
-                                       'results: {}\n'
-                                       'trafficgen results: {}\n',
+                                       'results: %s\n'
+                                       'trafficgen results: %s\n' %
                                        self._tc_results,
                                        results)
                 else:
@@ -324,15 +363,18 @@ class TestCase(object):
         """Run the test
 
         All setup and teardown through controllers is included.
+
         """
         # prepare test execution environment
         self.run_initialize()
 
         try:
-            with self._vswitch_ctl, self._loadgen:
-                with self._vnf_ctl, self._collector:
-                    if not self._vswitch_none:
+            with self._vswitch_ctl:
+                with self._vnf_ctl, self._pod_ctl, self._collector, self._loadgen:
+                    if not self._vswitch_none and not self._k8s:
                         self._add_flows()
+                    if self._k8s:
+                        self._add_connections()
 
                     self._versions += self._vswitch_ctl.get_vswitch().get_version()
 
@@ -347,7 +389,7 @@ class TestCase(object):
 
                         # dump vswitch flows before they are affected by VNF termination
                         if not self._vswitch_none:
-                            self._vswitch_ctl.dump_vswitch_flows()
+                            self._vswitch_ctl.dump_vswitch_connections()
 
                     # garbage collection for case that TestSteps modify existing deployment
                     self.step_stop_vnfs()
@@ -360,7 +402,7 @@ class TestCase(object):
         self._testcase_run_time = time.strftime("%H:%M:%S",
                                                 time.gmtime(self._testcase_stop_time -
                                                             self._testcase_start_time))
-        logging.info("Testcase execution time: " + self._testcase_run_time)
+        logging.info("Testcase execution time: %s", self._testcase_run_time)
         # report test results
         self.run_report()
 
@@ -390,8 +432,8 @@ class TestCase(object):
                 item[ResultsConstants.SCAL_PRE_INSTALLED_FLOWS] = self._traffic['pre_installed_flows']
             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
+            if self._tunnel_operation:
+                item[ResultsConstants.TUNNEL_TYPE] = S.getValue('TUNNEL_TYPE')
         return results
 
     def _copy_fwd_tools_for_all_guests(self, vm_count):
@@ -543,7 +585,7 @@ class TestCase(object):
         """
         with open(output, 'a') as csvfile:
 
-            logging.info("Write results to file: " + output)
+            logging.info("Write results to file: %s", output)
             fieldnames = TestCase._get_unique_keys(results)
 
             writer = csv.DictWriter(csvfile, fieldnames)
@@ -569,6 +611,43 @@ class TestCase(object):
 
         return list(result.keys())
 
+    def _add_connections(self):
+        """
+        Add connections for Kubernetes Usecases
+        """
+        logging.info("Kubernetes: Adding Connections")
+        vswitch = self._vswitch_ctl.get_vswitch()
+        bridge = S.getValue('VSWITCH_BRIDGE_NAME')
+        if S.getValue('K8S') and 'sriov' not in S.getValue('PLUGIN'):
+            if 'Ovs' in S.getValue('VSWITCH'):
+                # Add OVS Flows
+                logging.info("Kubernetes: Adding OVS Connections")
+                flow = {'table':'0', 'in_port':'1',
+                        'idle_timeout':'0', 'actions': ['output:3']}
+                vswitch.add_flow(bridge, flow)
+                flow = {'table':'0', 'in_port':'3',
+                        'idle_timeout':'0', 'actions': ['output:1']}
+                vswitch.add_flow(bridge, flow)
+                flow = {'table':'0', 'in_port':'2',
+                        'idle_timeout':'0', 'actions': ['output:4']}
+                vswitch.add_flow(bridge, flow)
+                flow = {'table':'0', 'in_port':'4',
+                        'idle_timeout':'0', 'actions': ['output:2']}
+                vswitch.add_flow(bridge, flow)
+            elif 'vpp' in S.getValue('VSWITCH'):
+                phy_ports = vswitch.get_ports()
+                virt_port0 = 'memif1/0'
+                virt_port1 = 'memif2/0'
+                vswitch.add_connection(bridge, phy_ports[0],
+                                       virt_port0, None)
+                vswitch.add_connection(bridge, virt_port0,
+                                       phy_ports[0], None)
+                vswitch.add_connection(bridge, phy_ports[1],
+                                       virt_port1, None)
+                vswitch.add_connection(bridge, virt_port1,
+                                       phy_ports[1], None)
+
+
     def _add_flows(self):
         """Add flows to the vswitch
         """
@@ -701,7 +780,7 @@ class TestCase(object):
                         self._logger.debug("Skipping %s as it isn't a configuration "
                                            "parameter.", '${}'.format(macro[0]))
             return param
-        elif isinstance(param, list) or isinstance(param, tuple):
+        elif isinstance(param, (list, tuple)):
             tmp_list = []
             for item in param:
                 tmp_list.append(self.step_eval_param(item, step_result))
@@ -739,9 +818,6 @@ class TestCase(object):
         # initialize list with results
         self._step_result = [None] * len(self.test)
 
-        # We have to suppress pylint report, because test_object has to be set according
-        # to the test step definition
-        # pylint: disable=redefined-variable-type
         # run test step by step...
         for i, step in enumerate(self.test):
             step_ok = not self._step_check
@@ -792,10 +868,21 @@ class TestCase(object):
                     # so it is not sent again after the execution of teststeps
                     self._step_send_traffic = True
             elif step[0].startswith('vnf'):
+                # use vnf started within TestSteps
                 if not self._step_vnf_list[step[0]]:
                     # initialize new VM
                     self._step_vnf_list[step[0]] = loader.get_vnf_class()()
                 test_object = self._step_vnf_list[step[0]]
+            elif step[0].startswith('VNF'):
+                if step[1] in ('start', 'stop'):
+                    raise RuntimeError("Cannot execute start() or stop() method of "
+                                       "VNF deployed automatically by scenario.")
+                # use vnf started by scenario deployment (e.g. pvp)
+                vnf_index = int(step[0][3:])
+                try:
+                    test_object = self._vnf_list[vnf_index]
+                except IndexError:
+                    raise RuntimeError("VNF with index {} is not running.".format(vnf_index))
             elif step[0] == 'wait':
                 input(os.linesep + "Step {}: Press Enter to continue with "
                       "the next step...".format(i) + os.linesep + os.linesep)