diff options
Diffstat (limited to 'yardstick')
4 files changed, 1791 insertions, 0 deletions
diff --git a/yardstick/network_services/vnf_generic/vnf/tg_vcmts_pktgen.py b/yardstick/network_services/vnf_generic/vnf/tg_vcmts_pktgen.py new file mode 100755 index 000000000..c6df9d04c --- /dev/null +++ b/yardstick/network_services/vnf_generic/vnf/tg_vcmts_pktgen.py @@ -0,0 +1,215 @@ +# 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 time +import socket +import yaml +import os + +from yardstick.network_services.vnf_generic.vnf import sample_vnf +from yardstick.common import exceptions + + +LOG = logging.getLogger(__name__) + + +class PktgenHelper(object): + + RETRY_SECONDS = 0.5 + RETRY_COUNT = 20 + CONNECT_TIMEOUT = 5 + + def __init__(self, host, port=23000): + self.host = host + self.port = port + self.connected = False + + def _connect(self): + self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + ret = True + try: + self._sock.settimeout(self.CONNECT_TIMEOUT) + self._sock.connect((self.host, self.port)) + except (socket.gaierror, socket.error, socket.timeout): + self._sock.close() + ret = False + + return ret + + def connect(self): + if self.connected: + return True + LOG.info("Connecting to pktgen instance at %s...", self.host) + for idx in range(self.RETRY_COUNT): + self.connected = self._connect() + if self.connected: + return True + LOG.debug("Connection attempt %d: Unable to connect to %s, " \ + "retrying in %d seconds", + idx, self.host, self.RETRY_SECONDS) + time.sleep(self.RETRY_SECONDS) + + LOG.error("Unable to connect to pktgen instance on %s !", + self.host) + return False + + + def send_command(self, command): + if not self.connected: + LOG.error("Pktgen socket is not connected") + return False + + try: + self._sock.sendall((command + "\n").encode()) + time.sleep(1) + except (socket.timeout, socket.error): + LOG.error("Error sending command '%s'", command) + return False + + return True + + +class VcmtsPktgenSetupEnvHelper(sample_vnf.SetupEnvHelper): + + BASE_PARAMETERS = "export LUA_PATH=/vcmts/Pktgen.lua;"\ + + "export CMK_PROC_FS=/host/proc;" + + PORTS_COUNT = 8 + + def generate_pcap_filename(self, port_cfg): + return port_cfg['traffic_type'] + "_" + port_cfg['num_subs'] \ + + "cms_" + port_cfg['num_ofdm'] + "ofdm.pcap" + + def find_port_cfg(self, ports_cfg, port_name): + for port_cfg in ports_cfg: + if port_name in port_cfg: + return port_cfg + return None + + def build_pktgen_parameters(self, pod_cfg): + ports_cfg = pod_cfg['ports'] + port_cfg = list() + + for i in range(self.PORTS_COUNT): + port_cfg.append(self.find_port_cfg(ports_cfg, 'port_' + str(i))) + + pktgen_parameters = self.BASE_PARAMETERS + " " \ + + " /pktgen-config/setup.sh " + pod_cfg['pktgen_id'] \ + + " " + pod_cfg['num_ports'] + + for i in range(self.PORTS_COUNT): + pktgen_parameters += " " + port_cfg[i]['net_pktgen'] + + for i in range(self.PORTS_COUNT): + pktgen_parameters += " " + self.generate_pcap_filename(port_cfg[i]) + + return pktgen_parameters + + def start_pktgen(self, pod_cfg): + self.ssh_helper.drop_connection() + cmd = self.build_pktgen_parameters(pod_cfg) + LOG.debug("Executing: '%s'", cmd) + self.ssh_helper.send_command(cmd) + LOG.info("Pktgen executed") + + def setup_vnf_environment(self): + pass + + +class VcmtsPktgen(sample_vnf.SampleVNFTrafficGen): + + TG_NAME = 'VcmtsPktgen' + APP_NAME = 'VcmtsPktgen' + RUN_WAIT = 4 + DEFAULT_RATE = 8.0 + + PKTGEN_BASE_PORT = 23000 + + def __init__(self, name, vnfd, setup_env_helper_type=None, + resource_helper_type=None): + if setup_env_helper_type is None: + setup_env_helper_type = VcmtsPktgenSetupEnvHelper + super(VcmtsPktgen, self).__init__( + name, vnfd, setup_env_helper_type, resource_helper_type) + + self.pktgen_address = vnfd['mgmt-interface']['ip'] + LOG.info("Pktgen container '%s', IP: %s", name, self.pktgen_address) + + def extract_pod_cfg(self, pktgen_pods_cfg, pktgen_id): + for pod_cfg in pktgen_pods_cfg: + if pod_cfg['pktgen_id'] == pktgen_id: + return pod_cfg + return None + + def instantiate(self, scenario_cfg, context_cfg): + super(VcmtsPktgen, self).instantiate(scenario_cfg, context_cfg) + self._start_server() + options = scenario_cfg.get('options', {}) + self.pktgen_rate = options.get('pktgen_rate', self.DEFAULT_RATE) + + try: + pktgen_values_filepath = options['pktgen_values'] + except KeyError: + raise KeyError("Missing pktgen_values key in scenario options" \ + "section of the task definition file") + + if not os.path.isfile(pktgen_values_filepath): + raise RuntimeError("The pktgen_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(pktgen_values_filepath) as stream: + pktgen_values = yaml.load(stream, Loader=yaml.BaseLoader) + + if pktgen_values == None: + raise RuntimeError("Error reading pktgen_values file provided (" + + pktgen_values_filepath + ")") + + self.pktgen_id = int(options[self.name]['pktgen_id']) + self.resource_helper.pktgen_id = self.pktgen_id + + self.pktgen_helper = PktgenHelper(self.pktgen_address, + self.PKTGEN_BASE_PORT + self.pktgen_id) + + pktgen_pods_cfg = pktgen_values['topology']['pktgen_pods'] + + self.pod_cfg = self.extract_pod_cfg(pktgen_pods_cfg, + str(self.pktgen_id)) + + if self.pod_cfg == None: + raise KeyError("Pktgen with id " + str(self.pktgen_id) + \ + " was not found") + + self.setup_helper.start_pktgen(self.pod_cfg) + + def run_traffic(self, traffic_profile): + if not self.pktgen_helper.connect(): + raise exceptions.PktgenActionError(command="connect") + LOG.info("Connected to pktgen instance at %s", self.pktgen_address) + + commands = [] + for i in range(self.setup_helper.PORTS_COUNT): + commands.append('pktgen.set("' + str(i) + '", "rate", ' + + "%0.1f" % self.pktgen_rate + ');') + + commands.append('pktgen.start("all");') + + for command in commands: + if self.pktgen_helper.send_command(command): + LOG.debug("Command '%s' sent to pktgen", command) + LOG.info("Traffic started on %s...", self.name) + return True 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 index 000000000..0b48ef4e9 --- /dev/null +++ b/yardstick/network_services/vnf_generic/vnf/vcmts_vnf.py @@ -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_tg_vcmts_pktgen.py b/yardstick/tests/unit/network_services/vnf_generic/vnf/test_tg_vcmts_pktgen.py new file mode 100755 index 000000000..3b226d3f1 --- /dev/null +++ b/yardstick/tests/unit/network_services/vnf_generic/vnf/test_tg_vcmts_pktgen.py @@ -0,0 +1,652 @@ +# 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 socket +import threading +import time +import os +import copy + +from yardstick.benchmark.contexts import base as ctx_base +from yardstick.network_services.vnf_generic.vnf.base import VnfdHelper +from yardstick.network_services.vnf_generic.vnf import tg_vcmts_pktgen +from yardstick.common import exceptions + + +NAME = "tg__0" + + +class TestPktgenHelper(unittest.TestCase): + + def test___init__(self): + pktgen_helper = tg_vcmts_pktgen.PktgenHelper("localhost", 23000) + self.assertEqual(pktgen_helper.host, "localhost") + self.assertEqual(pktgen_helper.port, 23000) + self.assertFalse(pktgen_helper.connected) + + def _run_fake_server(self): + server_sock = socket.socket() + server_sock.bind(('localhost', 23000)) + server_sock.listen(0) + client_socket, _ = server_sock.accept() + client_socket.close() + server_sock.close() + + def test__connect(self): + pktgen_helper = tg_vcmts_pktgen.PktgenHelper("localhost", 23000) + self.assertFalse(pktgen_helper._connect()) + server_thread = threading.Thread(target=self._run_fake_server) + server_thread.start() + time.sleep(0.5) + self.assertTrue(pktgen_helper._connect()) + pktgen_helper._sock.close() + server_thread.join() + + @mock.patch('yardstick.network_services.vnf_generic.vnf.tg_vcmts_pktgen.time') + def test_connect(self, *args): + pktgen_helper = tg_vcmts_pktgen.PktgenHelper("localhost", 23000) + pktgen_helper.connected = True + self.assertTrue(pktgen_helper.connect()) + pktgen_helper.connected = False + + pktgen_helper._connect = mock.MagicMock(return_value=True) + self.assertTrue(pktgen_helper.connect()) + self.assertTrue(pktgen_helper.connected) + + pktgen_helper = tg_vcmts_pktgen.PktgenHelper("localhost", 23000) + pktgen_helper._connect = mock.MagicMock(return_value=False) + self.assertFalse(pktgen_helper.connect()) + self.assertFalse(pktgen_helper.connected) + + def test_send_command(self): + pktgen_helper = tg_vcmts_pktgen.PktgenHelper("localhost", 23000) + self.assertFalse(pktgen_helper.send_command("")) + + pktgen_helper.connected = True + pktgen_helper._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.assertFalse(pktgen_helper.send_command("")) + + pktgen_helper._sock = mock.MagicMock() + self.assertTrue(pktgen_helper.send_command("")) + + +class TestVcmtsPktgenSetupEnvHelper(unittest.TestCase): + + PKTGEN_PARAMETERS = "export LUA_PATH=/vcmts/Pktgen.lua;"\ + "export CMK_PROC_FS=/host/proc;"\ + " /pktgen-config/setup.sh 0 4 18:02.0 "\ + "18:02.1 18:02.2 18:02.3 00:00.0 00:00.0 "\ + "00:00.0 00:00.0 imix1_100cms_1ofdm.pcap "\ + "imix1_100cms_1ofdm.pcap imix1_100cms_1ofdm.pcap "\ + "imix1_100cms_1ofdm.pcap imix1_100cms_1ofdm.pcap "\ + "imix1_100cms_1ofdm.pcap imix1_100cms_1ofdm.pcap "\ + "imix1_100cms_1ofdm.pcap" + + 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( + TestVcmtsPktgen.VNFD['vnfd:vnfd-catalog']['vnfd'][0]) + ssh_helper = mock.Mock() + scenario_helper = mock.Mock() + scenario_helper.options = self.OPTIONS + + self.setup_helper = tg_vcmts_pktgen.VcmtsPktgenSetupEnvHelper( + vnfd_helper, ssh_helper, scenario_helper) + + def test_generate_pcap_filename(self): + pcap_file_name = self.setup_helper.generate_pcap_filename(\ + TestVcmtsPktgen.PKTGEN_POD_VALUES[0]['ports'][0]) + self.assertEquals(pcap_file_name, "imix1_100cms_1ofdm.pcap") + + def test_find_port_cfg(self): + port_cfg = self.setup_helper.find_port_cfg(\ + TestVcmtsPktgen.PKTGEN_POD_VALUES[0]['ports'], "port_0") + self.assertIsNotNone(port_cfg) + + port_cfg = self.setup_helper.find_port_cfg(\ + TestVcmtsPktgen.PKTGEN_POD_VALUES[0]['ports'], "port_8") + self.assertIsNone(port_cfg) + + def test_build_pktgen_parameters(self): + parameters = self.setup_helper.build_pktgen_parameters( + TestVcmtsPktgen.PKTGEN_POD_VALUES[0]) + self.assertEquals(parameters, self.PKTGEN_PARAMETERS) + + def test_start_pktgen(self): + self.setup_helper.ssh_helper = mock.MagicMock() + self.setup_helper.start_pktgen(TestVcmtsPktgen.PKTGEN_POD_VALUES[0]) + self.setup_helper.ssh_helper.send_command.assert_called_with( + self.PKTGEN_PARAMETERS) + + def test_setup_vnf_environment(self): + self.assertIsNone(self.setup_helper.setup_vnf_environment()) + +class TestVcmtsPktgen(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 Pktgen Kubernetes", + "id": "VcmtsPktgen", + "mgmt-interface": { + "ip": "192.168.24.150", + "key_filename": "/tmp/yardstick_key-a3b663c2", + "user": "root", + "vdu-id": "vcmtspktgen-kubernetes" + }, + "name": "vcmtspktgen", + "short-name": "vcmtspktgen", + "vdu": [ + { + "description": "vCMTS Pktgen Kubernetes", + "external-interface": [], + "id": "vcmtspktgen-kubernetes", + "name": "vcmtspktgen-kubernetes" + } + ], + "vm-flavor": { + "memory-mb": "4096", + "vcpu-count": "4" + } + }] + }} + + PKTGEN_POD_VALUES = [ + { + "num_ports": "4", + "pktgen_id": "0", + "ports": [ + { + "net_pktgen": "18:02.0", + "num_ofdm": "1", + "num_subs": "100", + "port_0": "", + "traffic_type": "imix1" + }, + { + "net_pktgen": "18:02.1", + "num_ofdm": "1", + "num_subs": "100", + "port_1": "", + "traffic_type": "imix1" + }, + { + "net_pktgen": "18:02.2", + "num_ofdm": "1", + "num_subs": "100", + "port_2": "", + "traffic_type": "imix1" + }, + { + "net_pktgen": "18:02.3", + "num_ofdm": "1", + "num_subs": "100", + "port_3": "", + "traffic_type": "imix1" + }, + { + "net_pktgen": "00:00.0", + "num_ofdm": "1", + "num_subs": "100", + "port_4": "", + "traffic_type": "imix1" + }, + { + "net_pktgen": "00:00.0", + "num_ofdm": "1", + "num_subs": "100", + "port_5": "", + "traffic_type": "imix1" + }, + { + "net_pktgen": "00:00.0", + "num_ofdm": "1", + "num_subs": "100", + "port_6": "", + "traffic_type": "imix1" + }, + { + "net_pktgen": "00:00.0", + "num_ofdm": "1", + "num_subs": "100", + "port_7": "", + "traffic_type": "imix1" + } + ] + }, + { + "num_ports": 4, + "pktgen_id": 1, + "ports": [ + { + "net_pktgen": "18:0a.0", + "num_ofdm": "1", + "num_subs": "100", + "port_0": "", + "traffic_type": "imix1" + }, + { + "net_pktgen": "18:0a.1", + "num_ofdm": "1", + "num_subs": "100", + "port_1": "", + "traffic_type": "imix1" + }, + { + "net_pktgen": "18:0a.2", + "num_ofdm": "1", + "num_subs": "100", + "port_2": "", + "traffic_type": "imix1" + }, + { + "net_pktgen": "18:0a.3", + "num_ofdm": "1", + "num_subs": "100", + "port_3": "", + "traffic_type": "imix1" + }, + { + "net_pktgen": "00:00.0", + "num_ofdm": "1", + "num_subs": "100", + "port_4": "", + "traffic_type": "imix1" + }, + { + "net_pktgen": "00:00.0", + "num_ofdm": "1", + "num_subs": "100", + "port_5": "", + "traffic_type": "imix1" + }, + { + "net_pktgen": "00:00.0", + "num_ofdm": "1", + "num_subs": "100", + "port_6": "", + "traffic_type": "imix1" + }, + { + "net_pktgen": "00:00.0", + "num_ofdm": "1", + "num_subs": "100", + "port_7": "", + "traffic_type": "imix1" + } + ] + } + ] + + SCENARIO_CFG = { + "nodes": { + "tg__0": "pktgen0-k8syardstick-a3b663c2", + "vnf__0": "vnf0us-k8syardstick-a3b663c2", + "vnf__1": "vnf0ds-k8syardstick-a3b663c2" + }, + "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": "a3b663c2-e616-4777-b6d0-ec2ea7a06f42", + "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.150", + "local_mac": None, + "network_name": "flannel" + }, + "xe0": { + "local_ip": "192.168.24.150", + "local_mac": None, + "network_name": "xe0" + }, + "xe1": { + "local_ip": "192.168.24.150", + "local_mac": None, + "network_name": "xe1" + } + }, + "ip": "192.168.24.150", + "key_filename": "/tmp/yardstick_key-a3b663c2", + "member-vnf-index": "1", + "name": "pktgen0-k8syardstick-a3b663c2", + "private_ip": "192.168.24.150", + "service_ports": [ + { + "name": "ssh", + "node_port": 60270, + "port": 22, + "protocol": "TCP", + "target_port": 22 + }, + { + "name": "lua", + "node_port": 43619, + "port": 22022, + "protocol": "TCP", + "target_port": 22022 + } + ], + "ssh_port": 60270, + "user": "root", + "vnfd-id-ref": "tg__0" + }, + "vnf__0": { + "VNF model": "../../vnf_descriptors/vnf_vcmts_tpl.yaml", + "interfaces": { + "flannel": { + "local_ip": "192.168.100.132", + "local_mac": None, + "network_name": "flannel" + }, + "xe0": { + "local_ip": "192.168.100.132", + "local_mac": None, + "network_name": "xe0" + }, + "xe1": { + "local_ip": "192.168.100.132", + "local_mac": None, + "network_name": "xe1" + } + }, + "ip": "192.168.100.132", + "key_filename": "/tmp/yardstick_key-a3b663c2", + "member-vnf-index": "3", + "name": "vnf0us-k8syardstick-a3b663c2", + "private_ip": "192.168.100.132", + "service_ports": [ + { + "name": "ssh", + "node_port": 57057, + "port": 22, + "protocol": "TCP", + "target_port": 22 + }, + { + "name": "lua", + "node_port": 29700, + "port": 22022, + "protocol": "TCP", + "target_port": 22022 + } + ], + "ssh_port": 57057, + "user": "root", + "vnfd-id-ref": "vnf__0" + }, + "vnf__1": { + "VNF model": "../../vnf_descriptors/vnf_vcmts_tpl.yaml", + "interfaces": { + "flannel": { + "local_ip": "192.168.100.134", + "local_mac": None, + "network_name": "flannel" + }, + "xe0": { + "local_ip": "192.168.100.134", + "local_mac": None, + "network_name": "xe0" + }, + "xe1": { + "local_ip": "192.168.100.134", + "local_mac": None, + "network_name": "xe1" + } + }, + "ip": "192.168.100.134", + "key_filename": "/tmp/yardstick_key-a3b663c2", + "member-vnf-index": "4", + "name": "vnf0ds-k8syardstick-a3b663c2", + "private_ip": "192.168.100.134", + "service_ports": [ + { + "name": "ssh", + "node_port": 18581, + "port": 22, + "protocol": "TCP", + "target_port": 22 + }, + { + "name": "lua", + "node_port": 18469, + "port": 22022, + "protocol": "TCP", + "target_port": 22022 + } + ], + "ssh_port": 18581, + "user": "root", + "vnfd-id-ref": "vnf__1" + } + } + } + + PKTGEN_VALUES_PATH = "/tmp/pktgen_values.yaml" + + PKTGEN_VALUES = \ + "serviceAccount: cmk-serviceaccount\n" \ + "images:\n" \ + " vcmts_pktgen: vcmts-pktgen:v18.10\n" \ + "topology:\n" \ + " pktgen_replicas: 8\n" \ + " pktgen_pods:\n" \ + " - pktgen_id: 0\n" \ + " num_ports: 4\n" \ + " ports:\n" \ + " - port_0:\n" \ + " traffic_type: 'imix2'\n" \ + " num_ofdm: 4\n" \ + " num_subs: 300\n" \ + " net_pktgen: 8a:02.0\n" \ + " - port_1:\n" \ + " traffic_type: 'imix2'\n" \ + " num_ofdm: 4\n" \ + " num_subs: 300\n" \ + " net_pktgen: 8a:02.1\n" \ + " - port_2:\n" \ + " traffic_type: 'imix2'\n" \ + " num_ofdm: 4\n" \ + " num_subs: 300\n" \ + " net_pktgen: 8a:02.2\n" \ + " - port_3:\n" \ + " traffic_type: 'imix2'\n" \ + " num_ofdm: 4\n" \ + " num_subs: 300\n" \ + " net_pktgen: 8a:02.3\n" \ + " - port_4:\n" \ + " traffic_type: 'imix2'\n" \ + " num_ofdm: 4\n" \ + " num_subs: 300\n" \ + " net_pktgen: 8a:02.4\n" \ + " - port_5:\n" \ + " traffic_type: 'imix2'\n" \ + " num_ofdm: 4\n" \ + " num_subs: 300\n" \ + " net_pktgen: 8a:02.5\n" \ + " - port_6:\n" \ + " traffic_type: 'imix2'\n" \ + " num_ofdm: 4\n" \ + " num_subs: 300\n" \ + " net_pktgen: 8a:02.6\n" \ + " - port_7:\n" \ + " traffic_type: 'imix2'\n" \ + " num_ofdm: 4\n" \ + " num_subs: 300\n" \ + " net_pktgen: 8a:02.7\n" + + def setUp(self): + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + self.vcmts_pktgen = tg_vcmts_pktgen.VcmtsPktgen(NAME, vnfd) + self.vcmts_pktgen._start_server = mock.Mock(return_value=0) + self.vcmts_pktgen.resource_helper = mock.MagicMock() + self.vcmts_pktgen.setup_helper = mock.MagicMock() + + def test___init__(self): + self.assertFalse(self.vcmts_pktgen.traffic_finished) + self.assertIsNotNone(self.vcmts_pktgen.setup_helper) + self.assertIsNotNone(self.vcmts_pktgen.resource_helper) + + def test_extract_pod_cfg(self): + pod_cfg = self.vcmts_pktgen.extract_pod_cfg(self.PKTGEN_POD_VALUES, "0") + self.assertIsNotNone(pod_cfg) + self.assertEqual(pod_cfg["pktgen_id"], "0") + pod_cfg = self.vcmts_pktgen.extract_pod_cfg(self.PKTGEN_POD_VALUES, "4") + self.assertIsNone(pod_cfg) + + @mock.patch.object(ctx_base.Context, 'get_context_from_server', + return_value='fake_context') + def test_instantiate_missing_pktgen_values_key(self, *args): + err_scenario_cfg = copy.deepcopy(self.SCENARIO_CFG) + err_scenario_cfg['options'].pop('pktgen_values', None) + with self.assertRaises(KeyError): + self.vcmts_pktgen.instantiate(err_scenario_cfg, self.CONTEXT_CFG) + + @mock.patch.object(ctx_base.Context, 'get_context_from_server', + return_value='fake_context') + def test_instantiate_missing_pktgen_values_file(self, *args): + if os.path.isfile(self.PKTGEN_VALUES_PATH): + os.remove(self.PKTGEN_VALUES_PATH) + err_scenario_cfg = copy.deepcopy(self.SCENARIO_CFG) + err_scenario_cfg['options']['pktgen_values'] = self.PKTGEN_VALUES_PATH + with self.assertRaises(RuntimeError): + self.vcmts_pktgen.instantiate(err_scenario_cfg, self.CONTEXT_CFG) + + @mock.patch.object(ctx_base.Context, 'get_context_from_server', + return_value='fake_context') + def test_instantiate_empty_pktgen_values_file(self, *args): + yaml_sample = open(self.PKTGEN_VALUES_PATH, 'w') + yaml_sample.write("") + yaml_sample.close() + + err_scenario_cfg = copy.deepcopy(self.SCENARIO_CFG) + err_scenario_cfg['options']['pktgen_values'] = self.PKTGEN_VALUES_PATH + with self.assertRaises(RuntimeError): + self.vcmts_pktgen.instantiate(err_scenario_cfg, self.CONTEXT_CFG) + + if os.path.isfile(self.PKTGEN_VALUES_PATH): + os.remove(self.PKTGEN_VALUES_PATH) + + @mock.patch.object(ctx_base.Context, 'get_context_from_server', + return_value='fake_context') + def test_instantiate_invalid_pktgen_id(self, *args): + yaml_sample = open(self.PKTGEN_VALUES_PATH, 'w') + yaml_sample.write(self.PKTGEN_VALUES) + yaml_sample.close() + + err_scenario_cfg = copy.deepcopy(self.SCENARIO_CFG) + err_scenario_cfg['options'][NAME]['pktgen_id'] = 12 + with self.assertRaises(KeyError): + self.vcmts_pktgen.instantiate(err_scenario_cfg, self.CONTEXT_CFG) + + if os.path.isfile(self.PKTGEN_VALUES_PATH): + os.remove(self.PKTGEN_VALUES_PATH) + + @mock.patch.object(ctx_base.Context, 'get_context_from_server', + return_value='fake_context') + def test_instantiate_all_valid(self, *args): + yaml_sample = open(self.PKTGEN_VALUES_PATH, 'w') + yaml_sample.write(self.PKTGEN_VALUES) + yaml_sample.close() + + self.vcmts_pktgen.instantiate(self.SCENARIO_CFG, self.CONTEXT_CFG) + self.assertIsNotNone(self.vcmts_pktgen.pod_cfg) + self.assertEqual(self.vcmts_pktgen.pod_cfg["pktgen_id"], "0") + + if os.path.isfile(self.PKTGEN_VALUES_PATH): + os.remove(self.PKTGEN_VALUES_PATH) + + def test_run_traffic_failed_connect(self): + self.vcmts_pktgen.pktgen_helper = mock.MagicMock() + self.vcmts_pktgen.pktgen_helper.connect.return_value = False + with self.assertRaises(exceptions.PktgenActionError): + self.vcmts_pktgen.run_traffic({}) + + def test_run_traffic_successful_connect(self): + self.vcmts_pktgen.pktgen_helper = mock.MagicMock() + self.vcmts_pktgen.pktgen_helper.connect.return_value = True + self.vcmts_pktgen.pktgen_rate = 8.0 + self.assertTrue(self.vcmts_pktgen.run_traffic({})) + self.vcmts_pktgen.pktgen_helper.connect.assert_called_once() + self.vcmts_pktgen.pktgen_helper.send_command.assert_called_with( + 'pktgen.start("all");') 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 index 000000000..11e3d6e17 --- /dev/null +++ b/yardstick/tests/unit/network_services/vnf_generic/vnf/test_vcmts_vnf.py @@ -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()) |