Add vCMTS VNF for running vCMTSd containers 81/65481/14
authortreyad <treyad@viosoft.com>
Thu, 29 Nov 2018 04:29:25 +0000 (20:29 -0800)
committertreyad <treyad@viosoft.com>
Fri, 22 Mar 2019 13:51:34 +0000 (06:51 -0700)
Add a SampleVNF derived class to start vcmtsd

JIRA: YARDSTICK-1544
Change-Id: Ifafa43216a6bdd076ef1cbe5b722f4a39a97980b
Signed-off-by: treyad <treyad@viosoft.com>
yardstick/network_services/vnf_generic/vnf/vcmts_vnf.py [new file with mode: 0755]
yardstick/tests/unit/network_services/vnf_generic/vnf/test_vcmts_vnf.py [new file with mode: 0755]

diff --git a/yardstick/network_services/vnf_generic/vnf/vcmts_vnf.py b/yardstick/network_services/vnf_generic/vnf/vcmts_vnf.py
new file mode 100755 (executable)
index 0000000..0b48ef4
--- /dev/null
@@ -0,0 +1,273 @@
+# Copyright (c) 2019 Viosoft 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.
+
+import logging
+import os
+import yaml
+
+from influxdb import InfluxDBClient
+
+from yardstick.network_services.vnf_generic.vnf.sample_vnf import SetupEnvHelper
+from yardstick.common import constants
+from yardstick.common import exceptions
+from yardstick.network_services.vnf_generic.vnf.base import GenericVNF
+from yardstick.network_services.vnf_generic.vnf.sample_vnf import ScenarioHelper
+from yardstick.network_services.vnf_generic.vnf.vnf_ssh_helper import VnfSshHelper
+from yardstick.network_services.utils import get_nsb_option
+
+
+LOG = logging.getLogger(__name__)
+
+
+class InfluxDBHelper(object):
+
+    INITIAL_VALUE = 'now() - 1m'
+
+    def __init__(self, vcmts_influxdb_ip, vcmts_influxdb_port):
+        self._vcmts_influxdb_ip = vcmts_influxdb_ip
+        self._vcmts_influxdb_port = vcmts_influxdb_port
+        self._last_upstream_rx = self.INITIAL_VALUE
+        self._last_values_time = dict()
+
+    def start(self):
+        self._read_client = InfluxDBClient(host=self._vcmts_influxdb_ip,
+                                           port=self._vcmts_influxdb_port,
+                                           database='collectd')
+        self._write_client = InfluxDBClient(host=constants.INFLUXDB_IP,
+                                            port=constants.INFLUXDB_PORT,
+                                            database='collectd')
+
+    def _get_last_value_time(self, measurement):
+        if measurement in self._last_values_time:
+            return self._last_values_time[measurement]
+        return self.INITIAL_VALUE
+
+    def _set_last_value_time(self, measurement, time):
+        self._last_values_time[measurement] = "'" + time + "'"
+
+    def _query_measurement(self, measurement):
+        # There is a delay before influxdb flushes the data
+        query = "SELECT * FROM " + measurement + " WHERE time > " \
+                + self._get_last_value_time(measurement) \
+                + " ORDER BY time ASC;"
+        query_result = self._read_client.query(query)
+        if len(query_result.keys()) == 0:
+            return None
+        return query_result.get_points(measurement)
+
+    def _rw_measurment(self, measurement, columns):
+        query_result = self._query_measurement(measurement)
+        if query_result == None:
+            return
+
+        points_to_write = list()
+        for entry in query_result:
+            point = {
+                "measurement": measurement,
+                "tags": {
+                    "type": entry['type'],
+                    "host": entry['host']
+                },
+                "time": entry['time'],
+                "fields": {}
+            }
+
+            for column in columns:
+                if column == 'value':
+                    point["fields"][column] = float(entry[column])
+                else:
+                    point["fields"][column] = entry[column]
+
+            points_to_write.append(point)
+            self._set_last_value_time(measurement, entry['time'])
+
+        # Write the points to yardstick database
+        if self._write_client.write_points(points_to_write):
+            LOG.debug("%d new points written to '%s' measurement",
+                      len(points_to_write), measurement)
+
+    def copy_kpi(self):
+        self._rw_measurment("cpu_value", ["instance", "type_instance", "value"])
+        self._rw_measurment("cpufreq_value", ["type_instance", "value"])
+        self._rw_measurment("downstream_rx", ["value"])
+        self._rw_measurment("downstream_tx", ["value"])
+        self._rw_measurment("downstream_value", ["value"])
+        self._rw_measurment("ds_per_cm_value", ["instance", "value"])
+        self._rw_measurment("intel_rdt_value", ["instance", "type_instance", "value"])
+        self._rw_measurment("turbostat_value", ["instance", "type_instance", "value"])
+        self._rw_measurment("upstream_rx", ["value"])
+        self._rw_measurment("upstream_tx", ["value"])
+        self._rw_measurment("upstream_value", ["value"])
+
+
+class VcmtsdSetupEnvHelper(SetupEnvHelper):
+
+    BASE_PARAMETERS = "export LD_LIBRARY_PATH=/opt/collectd/lib:;"\
+                    + "export CMK_PROC_FS=/host/proc;"
+
+    def build_us_parameters(self, pod_cfg):
+        return self.BASE_PARAMETERS + " " \
+             + " /opt/bin/cmk isolate --conf-dir=/etc/cmk" \
+             + " --socket-id=" + pod_cfg['cpu_socket_id'] \
+             + " --pool=shared" \
+             + " /vcmts-config/run_upstream.sh " + pod_cfg['sg_id'] \
+             + " " + pod_cfg['ds_core_type'] \
+             + " " + pod_cfg['num_ofdm'] + "ofdm" \
+             + " " + pod_cfg['num_subs'] + "cm" \
+             + " " + pod_cfg['cm_crypto'] \
+             + " " + pod_cfg['qat'] \
+             + " " + pod_cfg['net_us'] \
+             + " " + pod_cfg['power_mgmt']
+
+    def build_ds_parameters(self, pod_cfg):
+        return self.BASE_PARAMETERS + " " \
+             + " /opt/bin/cmk isolate --conf-dir=/etc/cmk" \
+             + " --socket-id=" + pod_cfg['cpu_socket_id'] \
+             + " --pool=" + pod_cfg['ds_core_type'] \
+             + " /vcmts-config/run_downstream.sh " + pod_cfg['sg_id'] \
+             + " " + pod_cfg['ds_core_type'] \
+             + " " + pod_cfg['ds_core_pool_index'] \
+             + " " + pod_cfg['num_ofdm'] + "ofdm" \
+             + " " + pod_cfg['num_subs'] + "cm" \
+             + " " + pod_cfg['cm_crypto'] \
+             + " " + pod_cfg['qat'] \
+             + " " + pod_cfg['net_ds'] \
+             + " " + pod_cfg['power_mgmt']
+
+    def build_cmd(self, stream_dir, pod_cfg):
+        if stream_dir == 'ds':
+            return self.build_ds_parameters(pod_cfg)
+        else:
+            return self.build_us_parameters(pod_cfg)
+
+    def run_vcmtsd(self, stream_dir, pod_cfg):
+        cmd = self.build_cmd(stream_dir, pod_cfg)
+        LOG.debug("Executing %s", cmd)
+        self.ssh_helper.send_command(cmd)
+
+    def setup_vnf_environment(self):
+        pass
+
+
+class VcmtsVNF(GenericVNF):
+
+    RUN_WAIT = 4
+
+    def __init__(self, name, vnfd):
+        super(VcmtsVNF, self).__init__(name, vnfd)
+        self.name = name
+        self.bin_path = get_nsb_option('bin_path', '')
+        self.scenario_helper = ScenarioHelper(self.name)
+        self.ssh_helper = VnfSshHelper(self.vnfd_helper.mgmt_interface, self.bin_path)
+
+        self.setup_helper = VcmtsdSetupEnvHelper(self.vnfd_helper,
+                                                 self.ssh_helper,
+                                                 self.scenario_helper)
+
+    def extract_pod_cfg(self, vcmts_pods_cfg, sg_id):
+        for pod_cfg in vcmts_pods_cfg:
+            if pod_cfg['sg_id'] == sg_id:
+                return pod_cfg
+
+    def instantiate(self, scenario_cfg, context_cfg):
+        self._update_collectd_options(scenario_cfg, context_cfg)
+        self.scenario_helper.scenario_cfg = scenario_cfg
+        self.context_cfg = context_cfg
+
+        options = scenario_cfg.get('options', {})
+
+        try:
+            self.vcmts_influxdb_ip = options['vcmts_influxdb_ip']
+            self.vcmts_influxdb_port = options['vcmts_influxdb_port']
+        except KeyError:
+            raise KeyError("Missing destination InfluxDB details in scenario" \
+                           " section of the task definition file")
+
+        try:
+            vcmtsd_values_filepath = options['vcmtsd_values']
+        except KeyError:
+            raise KeyError("Missing vcmtsd_values key in scenario options" \
+                           "section of the task definition file")
+
+        if not os.path.isfile(vcmtsd_values_filepath):
+            raise RuntimeError("The vcmtsd_values file path provided " \
+                               "does not exists")
+
+        # The yaml_loader.py (SafeLoader) underlying regex has an issue
+        # with reading PCI addresses (processed as double). so the
+        # BaseLoader is used here.
+        with open(vcmtsd_values_filepath) as stream:
+            vcmtsd_values = yaml.load(stream, Loader=yaml.BaseLoader)
+
+        if vcmtsd_values == None:
+            raise RuntimeError("Error reading vcmtsd_values file provided (" +
+                               vcmtsd_values_filepath + ")")
+
+        vnf_options = options.get(self.name, {})
+        sg_id = str(vnf_options['sg_id'])
+        stream_dir = vnf_options['stream_dir']
+
+        try:
+            vcmts_pods_cfg = vcmtsd_values['topology']['vcmts_pods']
+        except KeyError:
+            raise KeyError("Missing vcmts_pods key in the " \
+                           "vcmtsd_values file provided")
+
+        pod_cfg = self.extract_pod_cfg(vcmts_pods_cfg, sg_id)
+        if pod_cfg == None:
+            raise exceptions.IncorrectConfig(error_msg="Service group " + sg_id + " not found")
+
+        self.setup_helper.run_vcmtsd(stream_dir, pod_cfg)
+
+    def _update_collectd_options(self, scenario_cfg, context_cfg):
+        scenario_options = scenario_cfg.get('options', {})
+        generic_options = scenario_options.get('collectd', {})
+        scenario_node_options = scenario_options.get(self.name, {})\
+            .get('collectd', {})
+        context_node_options = context_cfg.get('nodes', {})\
+            .get(self.name, {}).get('collectd', {})
+
+        options = generic_options
+        self._update_options(options, scenario_node_options)
+        self._update_options(options, context_node_options)
+
+        self.setup_helper.collectd_options = options
+
+    def _update_options(self, options, additional_options):
+        for k, v in additional_options.items():
+            if isinstance(v, dict) and k in options:
+                options[k].update(v)
+            else:
+                options[k] = v
+
+    def wait_for_instantiate(self):
+        pass
+
+    def terminate(self):
+        pass
+
+    def scale(self, flavor=""):
+        pass
+
+    def collect_kpi(self):
+        self.influxdb_helper.copy_kpi()
+        return {"n/a": "n/a"}
+
+    def start_collect(self):
+        self.influxdb_helper = InfluxDBHelper(self.vcmts_influxdb_ip,
+                                              self.vcmts_influxdb_port)
+        self.influxdb_helper.start()
+
+    def stop_collect(self):
+        pass
diff --git a/yardstick/tests/unit/network_services/vnf_generic/vnf/test_vcmts_vnf.py b/yardstick/tests/unit/network_services/vnf_generic/vnf/test_vcmts_vnf.py
new file mode 100755 (executable)
index 0000000..11e3d6e
--- /dev/null
@@ -0,0 +1,651 @@
+# Copyright (c) 2019 Viosoft 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.
+
+import unittest
+import mock
+import copy
+import os
+
+from yardstick.tests.unit.network_services.vnf_generic.vnf.test_base import mock_ssh
+from yardstick.network_services.vnf_generic.vnf.base import VnfdHelper
+from yardstick.network_services.vnf_generic.vnf import vcmts_vnf
+from yardstick.common import exceptions
+
+from influxdb.resultset import ResultSet
+
+NAME = "vnf__0"
+
+
+class TestInfluxDBHelper(unittest.TestCase):
+
+    def test___init__(self):
+        influxdb_helper = vcmts_vnf.InfluxDBHelper("localhost", 8086)
+        self.assertEqual(influxdb_helper._vcmts_influxdb_ip, "localhost")
+        self.assertEqual(influxdb_helper._vcmts_influxdb_port, 8086)
+        self.assertIsNotNone(influxdb_helper._last_upstream_rx)
+        self.assertIsNotNone(influxdb_helper._last_values_time)
+
+    def test_start(self):
+        influxdb_helper = vcmts_vnf.InfluxDBHelper("localhost", 8086)
+        influxdb_helper.start()
+        self.assertIsNotNone(influxdb_helper._read_client)
+        self.assertIsNotNone(influxdb_helper._write_client)
+
+    def test__get_last_value_time(self):
+        influxdb_helper = vcmts_vnf.InfluxDBHelper("localhost", 8086)
+        self.assertEqual(influxdb_helper._get_last_value_time('cpu_value'),
+                         vcmts_vnf.InfluxDBHelper.INITIAL_VALUE)
+
+        influxdb_helper._last_values_time['cpu_value'] = "RANDOM"
+        self.assertEqual(influxdb_helper._get_last_value_time('cpu_value'),
+                         "RANDOM")
+
+    def test__set_last_value_time(self):
+        influxdb_helper = vcmts_vnf.InfluxDBHelper("localhost", 8086)
+        influxdb_helper._set_last_value_time('cpu_value', '00:00')
+        self.assertEqual(influxdb_helper._last_values_time['cpu_value'],
+                         "'00:00'")
+
+    def test__query_measurement(self):
+        influxdb_helper = vcmts_vnf.InfluxDBHelper("localhost", 8086)
+        influxdb_helper._read_client = mock.MagicMock()
+
+        resulted_generator = mock.MagicMock()
+        resulted_generator.keys.return_value = []
+        influxdb_helper._read_client.query.return_value = resulted_generator
+        query_result = influxdb_helper._query_measurement('cpu_value')
+        self.assertIsNone(query_result)
+
+        resulted_generator = mock.MagicMock()
+        resulted_generator.keys.return_value = ["", ""]
+        resulted_generator.get_points.return_value = ResultSet({"":""})
+        influxdb_helper._read_client.query.return_value = resulted_generator
+        query_result = influxdb_helper._query_measurement('cpu_value')
+        self.assertIsNotNone(query_result)
+
+    def test__rw_measurment(self):
+        influxdb_helper = vcmts_vnf.InfluxDBHelper("localhost", 8086)
+        influxdb_helper._query_measurement = mock.MagicMock()
+        influxdb_helper._query_measurement.return_value = None
+        influxdb_helper._rw_measurment('cpu_value', [])
+        self.assertEqual(len(influxdb_helper._last_values_time), 0)
+
+        entry = {
+            "type":"type",
+            "host":"host",
+            "time":"time",
+            "id": "1",
+            "value": "1.0"
+        }
+        influxdb_helper._query_measurement.return_value = [entry]
+        influxdb_helper._write_client = mock.MagicMock()
+        influxdb_helper._rw_measurment('cpu_value', ["id", "value"])
+        self.assertEqual(len(influxdb_helper._last_values_time), 1)
+        influxdb_helper._write_client.write_points.assert_called_once()
+
+    def test_copy_kpi(self):
+        influxdb_helper = vcmts_vnf.InfluxDBHelper("localhost", 8086)
+        influxdb_helper._rw_measurment = mock.MagicMock()
+        influxdb_helper.copy_kpi()
+        influxdb_helper._rw_measurment.assert_called()
+
+
+class TestVcmtsdSetupEnvHelper(unittest.TestCase):
+    POD_CFG = {
+        "cm_crypto": "aes",
+        "cpu_socket_id": "0",
+        "ds_core_pool_index": "2",
+        "ds_core_type": "exclusive",
+        "net_ds": "1a:02.1",
+        "net_us": "1a:02.0",
+        "num_ofdm": "1",
+        "num_subs": "100",
+        "power_mgmt": "pm_on",
+        "qat": "qat_off",
+        "service_group_config": "",
+        "sg_id": "0",
+        "vcmtsd_image": "vcmts-d:perf"
+    }
+
+    OPTIONS = {
+        "pktgen_values": "/tmp/pktgen_values.yaml",
+        "tg__0": {
+            "pktgen_id": 0
+        },
+        "vcmts_influxdb_ip": "10.80.5.150",
+        "vcmts_influxdb_port": 8086,
+        "vcmtsd_values": "/tmp/vcmtsd_values.yaml",
+        "vnf__0": {
+            "sg_id": 0,
+            "stream_dir": "us"
+        },
+        "vnf__1": {
+            "sg_id": 0,
+            "stream_dir": "ds"
+        }
+    }
+
+    def setUp(self):
+        vnfd_helper = VnfdHelper(
+            TestVcmtsVNF.VNFD['vnfd:vnfd-catalog']['vnfd'][0])
+        ssh_helper = mock.Mock()
+        scenario_helper = mock.Mock()
+        scenario_helper.options = self.OPTIONS
+
+        self.setup_helper = vcmts_vnf.VcmtsdSetupEnvHelper(
+            vnfd_helper, ssh_helper, scenario_helper)
+
+    def _build_us_parameters(self):
+        return vcmts_vnf.VcmtsdSetupEnvHelper.BASE_PARAMETERS + " " \
+             + " /opt/bin/cmk isolate --conf-dir=/etc/cmk" \
+             + " --socket-id=" + str(self.POD_CFG['cpu_socket_id']) \
+             + " --pool=shared" \
+             + " /vcmts-config/run_upstream.sh " + self.POD_CFG['sg_id'] \
+             + " " + self.POD_CFG['ds_core_type'] \
+             + " " + str(self.POD_CFG['num_ofdm']) + "ofdm" \
+             + " " + str(self.POD_CFG['num_subs']) + "cm" \
+             + " " + self.POD_CFG['cm_crypto'] \
+             + " " + self.POD_CFG['qat'] \
+             + " " + self.POD_CFG['net_us'] \
+             + " " + self.POD_CFG['power_mgmt']
+
+    def test_build_us_parameters(self):
+        constructed = self._build_us_parameters()
+        result = self.setup_helper.build_us_parameters(self.POD_CFG)
+        self.assertEqual(constructed, result)
+
+    def _build_ds_parameters(self):
+        return vcmts_vnf.VcmtsdSetupEnvHelper.BASE_PARAMETERS + " " \
+             + " /opt/bin/cmk isolate --conf-dir=/etc/cmk" \
+             + " --socket-id=" + str(self.POD_CFG['cpu_socket_id']) \
+             + " --pool=" + self.POD_CFG['ds_core_type'] \
+             + " /vcmts-config/run_downstream.sh " + self.POD_CFG['sg_id'] \
+             + " " + self.POD_CFG['ds_core_type'] \
+             + " " + str(self.POD_CFG['ds_core_pool_index']) \
+             + " " + str(self.POD_CFG['num_ofdm']) + "ofdm" \
+             + " " + str(self.POD_CFG['num_subs']) + "cm" \
+             + " " + self.POD_CFG['cm_crypto'] \
+             + " " + self.POD_CFG['qat'] \
+             + " " + self.POD_CFG['net_ds'] \
+             + " " + self.POD_CFG['power_mgmt']
+
+    def test_build_ds_parameters(self):
+        constructed = self._build_ds_parameters()
+        result = self.setup_helper.build_ds_parameters(self.POD_CFG)
+        self.assertEqual(constructed, result)
+
+    def test_build_cmd(self):
+        us_constructed = self._build_us_parameters()
+        us_result = self.setup_helper.build_cmd('us', self.POD_CFG)
+        self.assertEqual(us_constructed, us_result)
+        ds_constructed = self._build_ds_parameters()
+        ds_result = self.setup_helper.build_cmd('ds', self.POD_CFG)
+        self.assertEqual(ds_constructed, ds_result)
+
+    def test_run_vcmtsd(self):
+        us_constructed = self._build_us_parameters()
+
+        vnfd_helper = VnfdHelper(
+            TestVcmtsVNF.VNFD['vnfd:vnfd-catalog']['vnfd'][0])
+        ssh_helper = mock.MagicMock()
+        scenario_helper = mock.Mock()
+        scenario_helper.options = self.OPTIONS
+
+        setup_helper = vcmts_vnf.VcmtsdSetupEnvHelper(
+            vnfd_helper, ssh_helper, scenario_helper)
+
+        setup_helper.run_vcmtsd('us', self.POD_CFG)
+        ssh_helper.send_command.assert_called_with(us_constructed)
+
+    def test_setup_vnf_environment(self):
+        self.assertIsNone(self.setup_helper.setup_vnf_environment())
+
+class TestVcmtsVNF(unittest.TestCase):
+
+    VNFD = {'vnfd:vnfd-catalog':
+            {'vnfd':
+            [{
+            "benchmark": {
+                "kpi": [
+                    "upstream/bits_per_second"
+                ]
+            },
+            "connection-point": [
+                {
+                    "name": "xe0",
+                    "type": "VPORT"
+                },
+                {
+                    "name": "xe1",
+                    "type": "VPORT"
+                }
+            ],
+            "description": "vCMTS Upstream-Downstream Kubernetes",
+            "id": "VcmtsVNF",
+            "mgmt-interface": {
+                "ip": "192.168.100.35",
+                "key_filename": "/tmp/yardstick_key-81dcca91",
+                "user": "root",
+                "vdu-id": "vcmtsvnf-kubernetes"
+            },
+            "name": "vcmtsvnf",
+            "short-name": "vcmtsvnf",
+            "vdu": [
+                {
+                    "description": "vCMTS Upstream-Downstream Kubernetes",
+                    "external-interface": [],
+                    "id": "vcmtsvnf-kubernetes",
+                    "name": "vcmtsvnf-kubernetes"
+                }
+            ],
+            "vm-flavor": {
+                "memory-mb": "4096",
+                "vcpu-count": "4"
+            }
+        }]
+        }
+    }
+
+    POD_CFG = [
+        {
+            "cm_crypto": "aes",
+            "cpu_socket_id": "0",
+            "ds_core_pool_index": "2",
+            "ds_core_type": "exclusive",
+            "net_ds": "1a:02.1",
+            "net_us": "1a:02.0",
+            "num_ofdm": "1",
+            "num_subs": "100",
+            "power_mgmt": "pm_on",
+            "qat": "qat_off",
+            "service_group_config": "",
+            "sg_id": "0",
+            "vcmtsd_image": "vcmts-d:perf"
+        },
+    ]
+
+    SCENARIO_CFG = {
+        "nodes": {
+            "tg__0": "pktgen0-k8syardstick-afae18b2",
+            "vnf__0": "vnf0us-k8syardstick-afae18b2",
+            "vnf__1": "vnf0ds-k8syardstick-afae18b2"
+        },
+        "options": {
+            "pktgen_values": "/tmp/pktgen_values.yaml",
+            "tg__0": {
+                "pktgen_id": 0
+            },
+            "vcmts_influxdb_ip": "10.80.5.150",
+            "vcmts_influxdb_port": 8086,
+            "vcmtsd_values": "/tmp/vcmtsd_values.yaml",
+            "vnf__0": {
+                "sg_id": 0,
+                "stream_dir": "us"
+            },
+            "vnf__1": {
+                "sg_id": 0,
+                "stream_dir": "ds"
+            }
+        },
+        "task_id": "afae18b2-9902-477f-8128-49afde7c3040",
+        "task_path": "samples/vnf_samples/nsut/cmts",
+        "tc": "tc_vcmts_k8s_pktgen",
+        "topology": "k8s_vcmts_topology.yaml",
+        "traffic_profile": "../../traffic_profiles/fixed.yaml",
+        "type": "NSPerf"
+    }
+
+    CONTEXT_CFG = {
+        "networks": {
+            "flannel": {
+                "name": "flannel"
+            },
+            "xe0": {
+                "name": "xe0"
+            },
+            "xe1": {
+                "name": "xe1"
+            }
+        },
+        "nodes": {
+            "tg__0": {
+                "VNF model": "../../vnf_descriptors/tg_vcmts_tpl.yaml",
+                "interfaces": {
+                    "flannel": {
+                        "local_ip": "192.168.24.110",
+                        "local_mac": None,
+                        "network_name": "flannel"
+                    },
+                    "xe0": {
+                        "local_ip": "192.168.24.110",
+                        "local_mac": None,
+                        "network_name": "xe0"
+                    },
+                    "xe1": {
+                        "local_ip": "192.168.24.110",
+                        "local_mac": None,
+                        "network_name": "xe1"
+                    }
+                },
+                "ip": "192.168.24.110",
+                "key_filename": "/tmp/yardstick_key-afae18b2",
+                "member-vnf-index": "1",
+                "name": "pktgen0-k8syardstick-afae18b2",
+                "private_ip": "192.168.24.110",
+                "service_ports": [
+                    {
+                        "name": "ssh",
+                        "node_port": 17153,
+                        "port": 22,
+                        "protocol": "TCP",
+                        "target_port": 22
+                    },
+                    {
+                        "name": "lua",
+                        "node_port": 51250,
+                        "port": 22022,
+                        "protocol": "TCP",
+                        "target_port": 22022
+                    }
+                ],
+                "ssh_port": 17153,
+                "user": "root",
+                "vnfd-id-ref": "tg__0"
+            },
+            "vnf__0": {
+                "VNF model": "../../vnf_descriptors/vnf_vcmts_tpl.yaml",
+                "interfaces": {
+                    "flannel": {
+                        "local_ip": "192.168.100.53",
+                        "local_mac": None,
+                        "network_name": "flannel"
+                    },
+                    "xe0": {
+                        "local_ip": "192.168.100.53",
+                        "local_mac": None,
+                        "network_name": "xe0"
+                    },
+                    "xe1": {
+                        "local_ip": "192.168.100.53",
+                        "local_mac": None,
+                        "network_name": "xe1"
+                    }
+                },
+                "ip": "192.168.100.53",
+                "key_filename": "/tmp/yardstick_key-afae18b2",
+                "member-vnf-index": "3",
+                "name": "vnf0us-k8syardstick-afae18b2",
+                "private_ip": "192.168.100.53",
+                "service_ports": [
+                    {
+                        "name": "ssh",
+                        "node_port": 34027,
+                        "port": 22,
+                        "protocol": "TCP",
+                        "target_port": 22
+                    },
+                    {
+                        "name": "lua",
+                        "node_port": 32580,
+                        "port": 22022,
+                        "protocol": "TCP",
+                        "target_port": 22022
+                    }
+                ],
+                "ssh_port": 34027,
+                "user": "root",
+                "vnfd-id-ref": "vnf__0"
+            },
+            "vnf__1": {
+                "VNF model": "../../vnf_descriptors/vnf_vcmts_tpl.yaml",
+                "interfaces": {
+                    "flannel": {
+                        "local_ip": "192.168.100.52",
+                        "local_mac": None,
+                        "network_name": "flannel"
+                    },
+                    "xe0": {
+                        "local_ip": "192.168.100.52",
+                        "local_mac": None,
+                        "network_name": "xe0"
+                    },
+                    "xe1": {
+                        "local_ip": "192.168.100.52",
+                        "local_mac": None,
+                        "network_name": "xe1"
+                    }
+                },
+                "ip": "192.168.100.52",
+                "key_filename": "/tmp/yardstick_key-afae18b2",
+                "member-vnf-index": "4",
+                "name": "vnf0ds-k8syardstick-afae18b2",
+                "private_ip": "192.168.100.52",
+                "service_ports": [
+                    {
+                        "name": "ssh",
+                        "node_port": 58661,
+                        "port": 22,
+                        "protocol": "TCP",
+                        "target_port": 22
+                    },
+                    {
+                        "name": "lua",
+                        "node_port": 58233,
+                        "port": 22022,
+                        "protocol": "TCP",
+                        "target_port": 22022
+                    }
+                ],
+                "ssh_port": 58661,
+                "user": "root",
+                "vnfd-id-ref": "vnf__1"
+            },
+        }
+    }
+
+    VCMTSD_VALUES_PATH = "/tmp/vcmtsd_values.yaml"
+
+    VCMTSD_VALUES = \
+            "serviceAccount: cmk-serviceaccount\n" \
+            "topology:\n" \
+            "  vcmts_replicas: 16\n" \
+            "  vcmts_pods:\n" \
+            "    - service_group_config:\n" \
+            "      sg_id: 0\n" \
+            "      net_us: 18:02.0\n" \
+            "      net_ds: 18:02.1\n" \
+            "      num_ofdm: 4\n" \
+            "      num_subs: 300\n" \
+            "      cm_crypto: aes\n" \
+            "      qat: qat_off\n" \
+            "      power_mgmt: pm_on\n" \
+            "      cpu_socket_id: 0\n" \
+            "      ds_core_type: exclusive\n" \
+            "      ds_core_pool_index: 0\n" \
+            "      vcmtsd_image: vcmts-d:feat"
+
+    VCMTSD_VALUES_INCOMPLETE = \
+            "serviceAccount: cmk-serviceaccount\n" \
+            "topology:\n" \
+            "  vcmts_replicas: 16"
+
+    def setUp(self):
+        vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0]
+        self.vnf = vcmts_vnf.VcmtsVNF(NAME, vnfd)
+
+    def test___init__(self, *args):
+        self.assertIsNotNone(self.vnf.setup_helper)
+
+    def test_extract_pod_cfg(self):
+        pod_cfg = self.vnf.extract_pod_cfg(self.POD_CFG, "0")
+        self.assertIsNotNone(pod_cfg)
+        self.assertEqual(pod_cfg['sg_id'], '0')
+        pod_cfg = self.vnf.extract_pod_cfg(self.POD_CFG, "1")
+        self.assertIsNone(pod_cfg)
+
+    def test_instantiate_missing_influxdb_info(self):
+        err_scenario_cfg = copy.deepcopy(self.SCENARIO_CFG)
+        err_scenario_cfg['options'].pop('vcmts_influxdb_ip', None)
+        with self.assertRaises(KeyError):
+            self.vnf.instantiate(err_scenario_cfg, self.CONTEXT_CFG)
+
+    def test_instantiate_missing_vcmtsd_values_file(self):
+        if os.path.isfile(self.VCMTSD_VALUES_PATH):
+            os.remove(self.VCMTSD_VALUES_PATH)
+        err_scenario_cfg = copy.deepcopy(self.SCENARIO_CFG)
+        err_scenario_cfg['options']['vcmtsd_values'] = self.VCMTSD_VALUES_PATH
+        with self.assertRaises(RuntimeError):
+            self.vnf.instantiate(err_scenario_cfg, self.CONTEXT_CFG)
+
+    def test_instantiate_empty_vcmtsd_values_file(self):
+        yaml_sample = open(self.VCMTSD_VALUES_PATH, 'w')
+        yaml_sample.write("")
+        yaml_sample.close()
+
+        err_scenario_cfg = copy.deepcopy(self.SCENARIO_CFG)
+        err_scenario_cfg['options']['vcmtsd_values'] = self.VCMTSD_VALUES_PATH
+        with self.assertRaises(RuntimeError):
+            self.vnf.instantiate(err_scenario_cfg, self.CONTEXT_CFG)
+
+        if os.path.isfile(self.VCMTSD_VALUES_PATH):
+            os.remove(self.VCMTSD_VALUES_PATH)
+
+    def test_instantiate_missing_vcmtsd_values_key(self):
+        err_scenario_cfg = copy.deepcopy(self.SCENARIO_CFG)
+        err_scenario_cfg['options'].pop('vcmtsd_values', None)
+        with self.assertRaises(KeyError):
+            self.vnf.instantiate(err_scenario_cfg, self.CONTEXT_CFG)
+
+    def test_instantiate_invalid_vcmtsd_values(self):
+        yaml_sample = open(self.VCMTSD_VALUES_PATH, 'w')
+        yaml_sample.write(self.VCMTSD_VALUES_INCOMPLETE)
+        yaml_sample.close()
+
+        err_scenario_cfg = copy.deepcopy(self.SCENARIO_CFG)
+        with self.assertRaises(KeyError):
+            self.vnf.instantiate(err_scenario_cfg, self.CONTEXT_CFG)
+
+        if os.path.isfile(self.VCMTSD_VALUES_PATH):
+            os.remove(self.VCMTSD_VALUES_PATH)
+
+    def test_instantiate_invalid_sg_id(self):
+        yaml_sample = open(self.VCMTSD_VALUES_PATH, 'w')
+        yaml_sample.write(self.VCMTSD_VALUES)
+        yaml_sample.close()
+
+        err_scenario_cfg = copy.deepcopy(self.SCENARIO_CFG)
+        err_scenario_cfg['options'][NAME]['sg_id'] = 8
+        with self.assertRaises(exceptions.IncorrectConfig):
+            self.vnf.instantiate(err_scenario_cfg, self.CONTEXT_CFG)
+
+        if os.path.isfile(self.VCMTSD_VALUES_PATH):
+            os.remove(self.VCMTSD_VALUES_PATH)
+
+    @mock.patch('yardstick.network_services.vnf_generic.vnf.vcmts_vnf.VnfSshHelper')
+    def test_instantiate_all_valid(self, ssh, *args):
+        mock_ssh(ssh)
+
+        vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0]
+        vnf = vcmts_vnf.VcmtsVNF(NAME, vnfd)
+
+        yaml_sample = open(self.VCMTSD_VALUES_PATH, 'w')
+        yaml_sample.write(self.VCMTSD_VALUES)
+        yaml_sample.close()
+
+        vnf.instantiate(self.SCENARIO_CFG, self.CONTEXT_CFG)
+        self.assertEqual(vnf.vcmts_influxdb_ip, "10.80.5.150")
+        self.assertEqual(vnf.vcmts_influxdb_port, 8086)
+
+        if os.path.isfile(self.VCMTSD_VALUES_PATH):
+            os.remove(self.VCMTSD_VALUES_PATH)
+
+    def test__update_collectd_options(self):
+        scenario_cfg = {'options':
+                            {'collectd':
+                                 {'interval': 3,
+                                  'plugins':
+                                      {'plugin3': {'param': 3}}},
+                             'vnf__0':
+                                 {'collectd':
+                                      {'interval': 2,
+                                       'plugins':
+                                           {'plugin3': {'param': 2},
+                                            'plugin2': {'param': 2}}}}}}
+        context_cfg = {'nodes':
+                           {'vnf__0':
+                                {'collectd':
+                                     {'interval': 1,
+                                      'plugins':
+                                          {'plugin3': {'param': 1},
+                                           'plugin2': {'param': 1},
+                                           'plugin1': {'param': 1}}}}}}
+        expected = {'interval': 1,
+                    'plugins':
+                        {'plugin3': {'param': 1},
+                         'plugin2': {'param': 1},
+                         'plugin1': {'param': 1}}}
+
+        self.vnf._update_collectd_options(scenario_cfg, context_cfg)
+        self.assertEqual(self.vnf.setup_helper.collectd_options, expected)
+
+    def test__update_options(self):
+        options1 = {'interval': 1,
+                    'param1': 'value1',
+                    'plugins':
+                        {'plugin3': {'param': 3},
+                         'plugin2': {'param': 1},
+                         'plugin1': {'param': 1}}}
+        options2 = {'interval': 2,
+                    'param2': 'value2',
+                    'plugins':
+                        {'plugin4': {'param': 4},
+                         'plugin2': {'param': 2},
+                         'plugin1': {'param': 2}}}
+        expected = {'interval': 1,
+                    'param1': 'value1',
+                    'param2': 'value2',
+                    'plugins':
+                        {'plugin4': {'param': 4},
+                         'plugin3': {'param': 3},
+                         'plugin2': {'param': 1},
+                         'plugin1': {'param': 1}}}
+
+        vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0]
+        vnf = vcmts_vnf.VcmtsVNF('vnf1', vnfd)
+        vnf._update_options(options2, options1)
+        self.assertEqual(options2, expected)
+
+    def test_wait_for_instantiate(self):
+        self.assertIsNone(self.vnf.wait_for_instantiate())
+
+    def test_terminate(self):
+        self.assertIsNone(self.vnf.terminate())
+
+    def test_scale(self):
+        self.assertIsNone(self.vnf.scale())
+
+    def test_collect_kpi(self):
+        self.vnf.influxdb_helper = mock.MagicMock()
+        self.vnf.collect_kpi()
+        self.vnf.influxdb_helper.copy_kpi.assert_called_once()
+
+    def test_start_collect(self):
+        self.vnf.vcmts_influxdb_ip = "localhost"
+        self.vnf.vcmts_influxdb_port = 8800
+
+        self.assertIsNone(self.vnf.start_collect())
+        self.assertIsNotNone(self.vnf.influxdb_helper)
+
+    def test_stop_collect(self):
+        self.assertIsNone(self.vnf.stop_collect())