Kubernetes: Infrastructure For K8S Net testing. 94/71494/1
authoropensource-tnbt <sridhar.rao@spirent.com>
Wed, 25 Nov 2020 09:41:47 +0000 (15:11 +0530)
committeropensource-tnbt <sridhar.rao@spirent.com>
Wed, 25 Nov 2020 09:53:55 +0000 (15:23 +0530)
This patch adds necessary code to perform K8S Networking performance
benchmarking.

Signed-off-by: Sridhar K. N. Rao <sridhar.rao@spirent.com>
Change-Id: I059ddd2e9ad3ee7c05e4620c64401f81474be195

17 files changed:
conf/12_k8s.conf [new file with mode: 0644]
conf/kubernetes/01_testcases.conf [new file with mode: 0644]
core/component_factory.py
core/loader/loader.py
core/pod_controller.py [new file with mode: 0644]
core/vswitch_controller_p2p.py
pods/__init__.py [new file with mode: 0644]
pods/papi/__init__.py [new file with mode: 0644]
pods/papi/papi.py [new file with mode: 0644]
pods/pod/__init__.py [new file with mode: 0644]
pods/pod/pod.py [new file with mode: 0644]
requirements.txt
testcases/__init__.py
testcases/k8s_performance.py [new file with mode: 0644]
testcases/testcase.py
vsperf
vswitches/vpp_dpdk_vhost.py

diff --git a/conf/12_k8s.conf b/conf/12_k8s.conf
new file mode 100644 (file)
index 0000000..5cfac96
--- /dev/null
@@ -0,0 +1,41 @@
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Information about the Master Node.
+
+POD_DIR=os.path.join(ROOT_DIR, 'pods/')
+POD='Papi'
+
+MASTER_IP = '10.10.120.22'
+MASTER_LOGIN = 'opnfv'
+MASTER_PASSWD =  'opnfv'
+
+K8S_CONFIG_FILEPATH = '/home/opnfv/sridhar/k8sconfig'
+
+# Information about the Worker Node. Default is Localhost.
+WORKER_IP = '10.10.120.21'
+WORKER_LOGIN = 'opnfv'
+WORKER_PASSWD = 'opnfv'
+
+
+# Plugin to use.
+PLUGIN = 'ovsdpdk'
+
+# Paths. Default location: Master Node.
+NETWORK_ATTACHMENT_FILEPATH = ['/home/opnfv/sridhar/cnb/userspace/ovsdpdk/userspace-ovs-netAttach.yaml']
+POD_MANIFEST_FILEPATH = '/home/opnfv/sridhar/cnb/userspace/ovsdpdk/userspace-ovs-netapp-pod.yaml'
+
+
+# Application pod
+APP_NAME = 'l2fwd'
+
diff --git a/conf/kubernetes/01_testcases.conf b/conf/kubernetes/01_testcases.conf
new file mode 100644 (file)
index 0000000..c5b3135
--- /dev/null
@@ -0,0 +1,12 @@
+K8SPERFORMANCE_TESTS = [
+    {
+        "Name": "pcp_tput",
+        "Deployment": "p2p",
+        "Description": "LTD.Throughput.RFC2544.Throughput",
+        "Parameters" : {
+            "TRAFFIC" : {
+                "traffic_type" : "rfc2544_throughput",
+            },
+        },
+    },
+]
index 2c51a06..f13bfb5 100644 (file)
@@ -24,7 +24,7 @@ from core.vswitch_controller_op2p import VswitchControllerOP2P
 from core.vswitch_controller_ptunp import VswitchControllerPtunP
 from core.vnf_controller import VnfController
 from core.pktfwd_controller import PktFwdController
-
+from core.pod_controller import PodController
 
 def __init__():
     """Finds and loads all the modules required.
@@ -102,6 +102,19 @@ def create_vnf(deployment_scenario, vnf_class, extra_vnfs):
     """
     return VnfController(deployment_scenario, vnf_class, extra_vnfs)
 
+def create_pod(deployment_scenario, pod_class):
+    """Return a new PodController for the deployment_scenario.
+
+    The returned controller is configured with the given POD class.
+
+    Deployment scenarios: 'pvp', 'pvvp'
+
+    :param deployment_scenario: The deployment scenario name
+    :param pod_class: Reference to pod class to be used.
+    :return: PodController for the deployment_scenario
+    """
+    return PodController(deployment_scenario, pod_class)
+
 def create_collector(collector_class, result_dir, test_name):
     """Return a new Collector of the given class
 
index dcd77ce..45e0d5b 100755 (executable)
@@ -23,6 +23,7 @@ from tools.pkt_fwd.pkt_fwd import IPktFwd
 from tools.pkt_gen.trafficgen import ITrafficGenerator
 from vswitches.vswitch import IVSwitch
 from vnfs.vnf.vnf import IVnf
+from pods.pod.pod import IPod
 
 # pylint: disable=too-many-public-methods
 class Loader(object):
@@ -71,6 +72,11 @@ class Loader(object):
             settings.getValue('PKTFWD'),
             IPktFwd)
 
+        self._pod_loader = LoaderServant(
+            settings.getValue('POD_DIR'),
+            settings.getValue('POD'),
+            IPod)
+
     def get_trafficgen(self):
         """Returns a new instance configured traffic generator.
 
@@ -220,6 +226,37 @@ class Loader(object):
         """
         return self._vnf_loader.get_classes_printable()
 
+    def get_pod(self):
+        """Returns instance of currently configured pod implementation.
+
+        :return: IPod implementation if available, None otherwise.
+        """
+        return self._pod_loader.get_class()()
+
+    def get_pod_class(self):
+        """Returns type of currently configured pod implementation.
+
+        :return: Type of IPod implementation if available.
+            None otherwise.
+        """
+        return self._pod_loader.get_class()
+
+    def get_pods(self):
+        """Returns dictionary of all available pods.
+
+        :return: Dictionary of pods.
+            - key: name of the class which implements IPod,
+            - value: Type of vnf which implements IPod.
+        """
+        return self._pod_loader.get_classes()
+
+    def get_pods_printable(self):
+        """Returns all available pods in printable format.
+
+        :return: String containing printable list of pods.
+        """
+        return self._pod_loader.get_classes_printable()
+
     def get_pktfwd(self):
         """Returns instance of currently configured packet forwarder implementation.
 
diff --git a/core/pod_controller.py b/core/pod_controller.py
new file mode 100644 (file)
index 0000000..8bc91ec
--- /dev/null
@@ -0,0 +1,93 @@
+# Copyright 2020 Spirent Communications
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+""" pod Controller interface
+"""
+
+import logging
+import pexpect
+#from conf import settings
+from pods.pod.pod import IPod
+
+class PodController():
+    """POD controller class
+
+    Used to set-up and control PODs for specified scenario
+
+    Attributes:
+        _pod_class: A class object representing the POD.
+        _deployment: A string describing the scenario to set-up in the
+            constructor.
+        _pods: A list of pods controlled by the controller.
+    """
+
+    def __init__(self, deployment, pod_class):
+        """Sets up the POD infrastructure based on deployment scenario
+
+        :param pod_class: The POD class to be used.
+        """
+        # reset POD ID counter for each testcase
+        IPod.reset_pod_counter()
+        pod_number = 0
+        # setup controller with requested number of pods
+        self._logger = logging.getLogger(__name__)
+        self._pod_class = pod_class
+        self._deployment = deployment.lower()
+        self._pods = []
+        if self._deployment == 'p2p':
+            pod_number = 1
+
+        if pod_number:
+            self._pods = [pod_class() for _ in range(pod_number)]
+
+            self._logger.debug('Initializing the pod')
+
+    def get_pods(self):
+        """Returns a list of pods controlled by this controller.
+        """
+        self._logger.debug('get the pods')
+        return self._pods
+
+    def get_pods_number(self):
+        """Returns a number of pods controlled by this controller.
+        """
+        self._logger.debug('get_pods_number %s pod[s]', str(len(self._pods)))
+        return len(self._pods)
+
+    def start(self):
+        """Boots all pods set-up by __init__.
+
+        This is a blocking function.
+        """
+        self._logger.debug('start the pod')
+        try:
+            for pod in self._pods:
+                pod.create()
+        except pexpect.TIMEOUT:
+            self.stop()
+            raise
+
+    def stop(self):
+        """Stops all pods set-up by __init__.
+
+        This is a blocking function.
+        """
+        self._logger.debug('stopping the pod')
+        for pod in self._pods:
+            pod.terminate()
+
+    def __enter__(self):
+        self.start()
+
+    def __exit__(self, type_, value, traceback):
+        self.stop()
index d8f22e4..0037d48 100644 (file)
@@ -45,8 +45,9 @@ class VswitchControllerP2P(IVswitchController):
             (port1, _) = self._vswitch.add_phy_port(self._bridge)
             (port2, _) = self._vswitch.add_phy_port(self._bridge)
 
-            self._vswitch.add_connection(self._bridge, port1, port2, self._traffic)
-            self._vswitch.add_connection(self._bridge, port2, port1, self._traffic)
+            if not settings.getValue('K8S'):
+                self._vswitch.add_connection(self._bridge, port1, port2, self._traffic)
+                self._vswitch.add_connection(self._bridge, port2, port1, self._traffic)
 
         except:
             self._vswitch.stop()
diff --git a/pods/__init__.py b/pods/__init__.py
new file mode 100644 (file)
index 0000000..e3ce18d
--- /dev/null
@@ -0,0 +1,19 @@
+# Copyright 2020 Spirent Communications
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Package for POD wrappers for use with VSPERF.
+
+This package contains an interface the VSPERF core uses for controlling
+PODs and POD-specific implementation modules of this interface.
+"""
diff --git a/pods/papi/__init__.py b/pods/papi/__init__.py
new file mode 100644 (file)
index 0000000..16760b8
--- /dev/null
@@ -0,0 +1,19 @@
+# Copyright 2020 Spirent Communications
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Package for POD wrappers for use with VSPERF.
+
+This package contains an implementation of the interface the VSPERF core
+uses for controlling PODs using Kubernetes Python-API (PAPI)
+"""
diff --git a/pods/papi/papi.py b/pods/papi/papi.py
new file mode 100644 (file)
index 0000000..5a21f1d
--- /dev/null
@@ -0,0 +1,143 @@
+# Copyright 2020 University Of Delhi.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""
+Automation of Pod Deployment with Kubernetes Python API
+"""
+
+# import os
+import logging
+import json
+import time
+import yaml
+from kubernetes import client, config
+from kubernetes.client.rest import ApiException
+
+from conf import settings as S
+from pods.pod.pod import IPod
+
+class Papi(IPod):
+    """
+    Class for controlling the pod through PAPI
+    """
+
+    def __init__(self):
+        """
+        Initialisation function.
+        """
+        #super(Papi, self).__init__()
+        super().__init__()
+
+        self._logger = logging.getLogger(__name__)
+        self._sriov_config = None
+        self._sriov_config_ns = None
+        config.load_kube_config(S.getValue('K8S_CONFIG_FILEPATH'))
+
+    def create(self):
+        """
+        Creation Process
+        """
+        # create vswitchperf namespace
+        api = client.CoreV1Api()
+        namespace = 'default'
+        #namespace = 'vswitchperf'
+        # replace_namespace(api, namespace)
+
+        # sriov configmap
+        if S.getValue('PLUGIN') == 'sriov':
+            configmap = load_manifest(S.getValue('CONFIGMAP_FILEPATH'))
+            self._sriov_config = configmap['metadata']['name']
+            self._sriov_config_ns = configmap['metadata']['namespace']
+            api.create_namespaced_config_map(self._sriov_config_ns, configmap)
+
+
+        # create nad(network attachent definitions)
+        group = 'k8s.cni.cncf.io'
+        version = 'v1'
+        kind_plural = 'network-attachment-definitions'
+        api = client.CustomObjectsApi()
+
+        for nad_filepath in S.getValue('NETWORK_ATTACHMENT_FILEPATH'):
+            nad_manifest = load_manifest(nad_filepath)
+
+            try:
+                response = api.create_namespaced_custom_object(group, version, namespace,
+                                                               kind_plural, nad_manifest)
+                self._logger.info(str(response))
+                self._logger.info("Created Network Attachment Definition: %s", nad_filepath)
+            except ApiException as err:
+                raise Exception from err
+
+        #create pod workloads
+        pod_manifest = load_manifest(S.getValue('POD_MANIFEST_FILEPATH'))
+        api = client.CoreV1Api()
+
+        try:
+            response = api.create_namespaced_pod(namespace, pod_manifest)
+            self._logger.info(str(response))
+            self._logger.info("Created POD %d ...", self._number)
+        except ApiException as err:
+            raise Exception from err
+
+        time.sleep(12)
+
+    def terminate(self):
+        """
+        Cleanup Process
+        """
+        #self._logger.info(self._log_prefix + "Cleaning vswitchperf namespace")
+        self._logger.info("Terminating Pod")
+        api = client.CoreV1Api()
+        # api.delete_namespace(name="vswitchperf", body=client.V1DeleteOptions())
+
+        if S.getValue('PLUGIN') == 'sriov':
+            api.delete_namespaced_config_map(self._sriov_config, self._sriov_config_ns)
+
+
+def load_manifest(filepath):
+    """
+    Reads k8s manifest files and returns as string
+
+    :param str filepath: filename of k8s manifest file to read
+
+    :return: k8s resource definition as string
+    """
+    with open(filepath) as handle:
+        data = handle.read()
+
+    try:
+        manifest = json.loads(data)
+    except ValueError:
+        try:
+            manifest = yaml.safe_load(data)
+        except yaml.parser.ParserError as err:
+            raise Exception from err
+
+    return manifest
+
+def replace_namespace(api, namespace):
+    """
+    Creates namespace if does not exists
+    """
+    namespaces = api.list_namespace()
+    for nsi in namespaces.items:
+        if namespace == nsi.metadata.name:
+            api.delete_namespace(name=namespace,
+                                 body=client.V1DeleteOptions())
+            break
+
+        time.sleep(0.5)
+        api.create_namespace(client.V1Namespace(
+            metadata=client.V1ObjectMeta(name=namespace)))
diff --git a/pods/pod/__init__.py b/pods/pod/__init__.py
new file mode 100644 (file)
index 0000000..b91706e
--- /dev/null
@@ -0,0 +1,18 @@
+# Copyright 2020 Spirent Communications
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""POD interface and helpers.
+"""
+
+import pods
diff --git a/pods/pod/pod.py b/pods/pod/pod.py
new file mode 100644 (file)
index 0000000..c25744d
--- /dev/null
@@ -0,0 +1,63 @@
+# Copyright 2020 Spirent Communications, University Of Delhi.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Interface for POD
+"""
+
+#import time
+#import pexpect
+from tools import tasks
+
+class IPod(tasks.Process):
+    """
+    Interface for POD
+
+    Inheriting from Process helps in managing system process.
+    execute a command, wait, kill, etc.
+    """
+    _number_pods = 0
+
+    def __init__(self):
+        """
+        Initialization Method
+        """
+        self._number = IPod._number_pods
+        self._logger.debug('Initializing %s. Pod with index %s',
+                           self._number + 1, self._number)
+        IPod._number_pods = IPod._number_pods + 1
+        self._log_prefix = 'pod_%d_cmd : ' % self._number
+        # raise NotImplementedError()
+
+    def create(self):
+        """
+        Start the Pod
+        """
+        raise NotImplementedError()
+
+
+    def terminate(self):
+        """
+        Stop the Pod
+        """
+        raise NotImplementedError()
+
+    @staticmethod
+    def reset_pod_counter():
+        """
+        Reset internal POD counter
+
+        This method is static
+        """
+        IPod._number_pods = 0
index bd66060..9202101 100644 (file)
@@ -40,3 +40,4 @@ PyYAML>=3.10.0
 pyzmq>=16.0
 six>=1.9.0
 timeout-decorator>=0.4.0
+kubernetes
index 0b6b77e..736be88 100644 (file)
@@ -17,3 +17,4 @@
 from testcases.testcase import (TestCase)
 from testcases.performance import (PerformanceTestCase)
 from testcases.integration import (IntegrationTestCase)
+from testcases.k8s_performance import (K8sPerformanceTestCase)
diff --git a/testcases/k8s_performance.py b/testcases/k8s_performance.py
new file mode 100644 (file)
index 0000000..3c31430
--- /dev/null
@@ -0,0 +1,39 @@
+# Copyright 2015-2017 Intel Corporation.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""PerformanceTestCase class
+"""
+
+import logging
+
+from testcases.testcase import TestCase
+#from tools.report import report
+
+class K8sPerformanceTestCase(TestCase):
+    """K8sPerformanceTestCase class
+
+    In this basic form runs RFC2544 throughput test
+    """
+    def __init__(self, cfg):
+        """ Testcase initialization
+        """
+        self._type = 'k8s_performance'
+        super().__init__(cfg)
+        self._logger = logging.getLogger(__name__)
+        self._k8s = True
+
+    def run_report(self):
+        pass
+        #super().run_report()
+        #if self._tc_results:
+        #    report.generate(self)
index a30558f..51d212b 100644 (file)
@@ -73,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
@@ -81,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 = {}
@@ -216,6 +219,12 @@ class TestCase(object):
 
         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.')
@@ -281,6 +290,10 @@ 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()
@@ -350,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:
-                with self._vnf_ctl, self._collector, self._loadgen:
-                    if not self._vswitch_none:
+                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()
 
@@ -595,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
         """
diff --git a/vsperf b/vsperf
index 95f2a74..51612da 100755 (executable)
--- a/vsperf
+++ b/vsperf
@@ -40,6 +40,7 @@ import core.component_factory as component_factory
 from core.loader import Loader
 from testcases import PerformanceTestCase
 from testcases import IntegrationTestCase
+from testcases import K8sPerformanceTestCase
 from tools import tasks
 from tools import networkcard
 from tools import functions
@@ -179,6 +180,8 @@ def parse_arguments():
                         help='list all system vnfs and exit')
     parser.add_argument('--list-loadgens', action='store_true',
                         help='list all background load generators')
+    parser.add_argument('--list-pods', action='store_true',
+                        help='list all system pods')
     parser.add_argument('--list-settings', action='store_true',
                         help='list effective settings configuration and exit')
     parser.add_argument('exact_test_name', nargs='*', help='Exact names of\
@@ -202,6 +205,7 @@ def parse_arguments():
     group.add_argument('--verbosity', choices=list_logging_levels(),
                        help='debug level')
     group.add_argument('--integration', action='store_true', help='execute integration tests')
+    group.add_argument('--k8s', action='store_true', help='execute Kubernetes tests')
     group.add_argument('--openstack', action='store_true', help='Run VSPERF with openstack')
     group.add_argument('--trafficgen', help='traffic generator to use')
     group.add_argument('--vswitch', help='vswitch implementation to use')
@@ -579,6 +583,10 @@ def handle_list_options(args):
         print(Loader().get_loadgens_printable())
         sys.exit(0)
 
+    if args['list_pods']:
+        print(Loader().get_pods_printable())
+        sys.exit(0)
+
     if args['list_settings']:
         print(str(settings))
         sys.exit(0)
@@ -596,6 +604,8 @@ def list_testcases(args):
     # configure tests
     if args['integration']:
         testcases = settings.getValue('INTEGRATION_TESTS')
+    elif args['k8s']:
+        testcases = settings.getValue('K8SPERFORMANCE_TESTS')
     else:
         testcases = settings.getValue('PERFORMANCE_TESTS')
 
@@ -692,6 +702,8 @@ def main():
     # load non performance/integration tests
     if args['integration']:
         settings.load_from_dir(os.path.join(_CURR_DIR, 'conf/integration'))
+    if args['k8s']:
+        settings.load_from_dir(os.path.join(_CURR_DIR, 'conf/kubernetes'))
 
     # load command line parameters first in case there are settings files
     # to be used
@@ -709,6 +721,11 @@ def main():
 
     settings.setValue('mode', args['mode'])
 
+    if args['k8s']:
+        settings.setValue('K8S', True)
+    else:
+        settings.setValue('K8S', False)
+
     if args['openstack']:
         result = osdt.deploy_testvnf()
         if result:
@@ -833,6 +850,8 @@ def main():
         # configure tests
         if args['integration']:
             testcases = settings.getValue('INTEGRATION_TESTS')
+        elif args['k8s']:
+            testcases = settings.getValue('K8SPERFORMANCE_TESTS')
         else:
             testcases = settings.getValue('PERFORMANCE_TESTS')
 
@@ -875,6 +894,8 @@ def main():
 
                 if args['integration']:
                     test = IntegrationTestCase(cfg)
+                elif args['k8s']:
+                    test = K8sPerformanceTestCase(cfg)
                 else:
                     test = PerformanceTestCase(cfg)
 
index af5aca5..f88ed95 100644 (file)
@@ -462,4 +462,4 @@ class VppDpdkVhost(IVSwitch, tasks.Process):
     def get_ports(self, switch_name):
         """See IVswitch for general description
         """
-        raise NotImplementedError()
+        return self._phy_ports