diff options
Diffstat (limited to 'yardstick/network_services')
12 files changed, 1414 insertions, 9 deletions
diff --git a/yardstick/network_services/helpers/cpu.py b/yardstick/network_services/helpers/cpu.py index 8c21754ff..8cdd829a0 100644 --- a/yardstick/network_services/helpers/cpu.py +++ b/yardstick/network_services/helpers/cpu.py @@ -33,11 +33,11 @@ class CpuSysCores(object): core_lines = {} for line in lines: if line.strip(): - name, value = line.split(":", 1) - core_lines[name.strip()] = value.strip() + name, value = line.split(":", 1) + core_lines[name.strip()] = value.strip() else: - core_details.append(core_lines) - core_lines = {} + core_details.append(core_lines) + core_lines = {} return core_details @@ -51,7 +51,7 @@ class CpuSysCores(object): lines = self._open_cpuinfo() core_details = self._get_core_details(lines) for core in core_details: - for k, v in core.items(): + for k, _ in core.items(): if k == "physical id": if core["physical id"] not in self.core_map: self.core_map[core['physical id']] = [] @@ -60,6 +60,16 @@ class CpuSysCores(object): return self.core_map + def get_cpu_layout(self): + _, stdout, _ = self.connection.execute("lscpu -p") + cpuinfo = {} + cpuinfo['cpuinfo'] = list() + for line in stdout.split("\n"): + if line and line[0] != "#": + cpuinfo['cpuinfo'].append([CpuSysCores._str2int(x) for x in + line.split(",")]) + return cpuinfo + def validate_cpu_cfg(self, vnf_cfg=None): if vnf_cfg is None: vnf_cfg = { @@ -78,3 +88,10 @@ class CpuSysCores(object): return -1 return 0 + + @staticmethod + def _str2int(string): + try: + return int(string) + except ValueError: + return 0 diff --git a/yardstick/network_services/traffic_profile/__init__.py b/yardstick/network_services/traffic_profile/__init__.py index 72a61b6b4..c5d8eff0b 100644 --- a/yardstick/network_services/traffic_profile/__init__.py +++ b/yardstick/network_services/traffic_profile/__init__.py @@ -30,6 +30,7 @@ def register_modules(): 'yardstick.network_services.traffic_profile.rfc2544', 'yardstick.network_services.traffic_profile.pktgen', 'yardstick.network_services.traffic_profile.landslide_profile', + 'yardstick.network_services.traffic_profile.vpp_rfc2544', ] for module in modules: diff --git a/yardstick/network_services/traffic_profile/vpp_rfc2544.py b/yardstick/network_services/traffic_profile/vpp_rfc2544.py new file mode 100644 index 000000000..0f8185f90 --- /dev/null +++ b/yardstick/network_services/traffic_profile/vpp_rfc2544.py @@ -0,0 +1,321 @@ +# 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 datetime +import logging +from random import choice +from string import ascii_letters + +from ipaddress import ip_address +from trex_stl_lib import api as Pkt +from trex_stl_lib import trex_stl_client +from trex_stl_lib import trex_stl_packet_builder_scapy +from trex_stl_lib import trex_stl_streams + +from yardstick.common import constants +from yardstick.network_services.traffic_profile.rfc2544 import RFC2544Profile, \ + PortPgIDMap +from yardstick.network_services.traffic_profile.trex_traffic_profile import IP, \ + DST + +LOGGING = logging.getLogger(__name__) + + +class VppRFC2544Profile(RFC2544Profile): + + def __init__(self, traffic_generator): + super(VppRFC2544Profile, self).__init__(traffic_generator) + + self.duration = self.config.duration + self.precision = self.config.test_precision + self.lower_bound = self.config.lower_bound + self.upper_bound = self.config.upper_bound + self.step_interval = self.config.step_interval + self.enable_latency = self.config.enable_latency + + self.pkt_size = None + self.flow = None + + self.tolerance_low = 0 + self.tolerance_high = 0 + + self.queue = None + self.port_pg_id = None + + self.current_lower = self.lower_bound + self.current_upper = self.upper_bound + + self.ports = [] + self.profiles = {} + + @property + def delta(self): + return self.current_upper - self.current_lower + + @property + def mid_point(self): + return (self.current_lower + self.current_upper) / 2 + + @staticmethod + def calculate_frame_size(imix): + if not imix: + return 64, 100 + + imix_count = {size.upper().replace('B', ''): int(weight) + for size, weight in imix.items()} + imix_sum = sum(imix_count.values()) + if imix_sum <= 0: + return 64, 100 + packets_total = sum([int(size) * weight + for size, weight in imix_count.items()]) + return packets_total / imix_sum, imix_sum + + @staticmethod + def _gen_payload(length): + payload = "" + for _ in range(length): + payload += choice(ascii_letters) + + return payload + + def bounds_iterator(self, logger=None): + self.current_lower = self.lower_bound + self.current_upper = self.upper_bound + + test_value = self.current_upper + while abs(self.delta) >= self.precision: + if logger: + logger.debug("New interval [%s, %s), precision: %d", + self.current_lower, + self.current_upper, self.step_interval) + logger.info("Testing with value %s", test_value) + + yield test_value + test_value = self.mid_point + + def register_generator(self, generator): + super(VppRFC2544Profile, self).register_generator(generator) + self.init_traffic_params(generator) + + def init_queue(self, queue): + self.queue = queue + self.queue.cancel_join_thread() + + def init_traffic_params(self, generator): + if generator.rfc2544_helper.latency: + self.enable_latency = True + self.tolerance_low = generator.rfc2544_helper.tolerance_low + self.tolerance_high = generator.rfc2544_helper.tolerance_high + self.max_rate = generator.scenario_helper.all_options.get('vpp_config', + {}).get( + 'max_rate', self.rate) + + def create_profile(self, profile_data, current_port): + streams = [] + for packet_name in profile_data: + imix = (profile_data[packet_name]. + get('outer_l2', {}).get('framesize')) + self.pkt_size, imix_sum = self.calculate_frame_size(imix) + self._create_vm(profile_data[packet_name]) + if self.max_rate > 100: + imix_data = self._create_imix_data(imix, + constants.DISTRIBUTION_IN_PACKETS) + else: + imix_data = self._create_imix_data(imix) + _streams = self._create_single_stream(current_port, imix_data, + imix_sum) + streams.extend(_streams) + return trex_stl_streams.STLProfile(streams) + + def _set_outer_l3v4_fields(self, outer_l3v4): + """ setup outer l3v4 fields from traffic profile """ + ip_params = {} + if 'proto' in outer_l3v4: + ip_params['proto'] = outer_l3v4['proto'] + self._set_proto_fields(IP, **ip_params) + + self.flow = int(outer_l3v4['count']) + src_start_ip, _ = outer_l3v4['srcip4'].split('-') + dst_start_ip, _ = outer_l3v4['dstip4'].split('-') + + self.ip_packet = Pkt.IP(src=src_start_ip, + dst=dst_start_ip, + proto=outer_l3v4['proto']) + if self.flow > 1: + dst_start_int = int(ip_address(str(dst_start_ip))) + dst_end_ip_new = ip_address(dst_start_int + self.flow - 1) + # self._set_proto_addr(IP, SRC, outer_l3v4['srcip4'], outer_l3v4['count']) + self._set_proto_addr(IP, DST, + "{start_ip}-{end_ip}".format( + start_ip=dst_start_ip, + end_ip=str(dst_end_ip_new)), + self.flow) + + def _create_single_packet(self, size=64): + ether_packet = self.ether_packet + ip_packet = self.ip6_packet if self.ip6_packet else self.ip_packet + base_pkt = ether_packet / ip_packet + payload_len = max(0, size - len(base_pkt) - 4) + packet = trex_stl_packet_builder_scapy.STLPktBuilder( + pkt=base_pkt / self._gen_payload(payload_len), + vm=self.trex_vm) + packet_lat = trex_stl_packet_builder_scapy.STLPktBuilder( + pkt=base_pkt / self._gen_payload(payload_len)) + + return packet, packet_lat + + def _create_single_stream(self, current_port, imix_data, imix_sum, + isg=0.0): + streams = [] + for size, weight in ((int(size), float(weight)) for (size, weight) + in imix_data.items() if float(weight) > 0): + if current_port == 1: + isg += 10.0 + if self.max_rate > 100: + mode = trex_stl_streams.STLTXCont( + pps=int(weight * imix_sum / 100)) + mode_lat = mode + else: + mode = trex_stl_streams.STLTXCont( + percentage=weight * self.max_rate / 100) + mode_lat = trex_stl_streams.STLTXCont(pps=9000) + + packet, packet_lat = self._create_single_packet(size) + streams.append( + trex_stl_client.STLStream(isg=isg, packet=packet, mode=mode)) + if self.enable_latency: + pg_id = self.port_pg_id.increase_pg_id(current_port) + stl_flow = trex_stl_streams.STLFlowLatencyStats(pg_id=pg_id) + stream_lat = trex_stl_client.STLStream(isg=isg, + packet=packet_lat, + mode=mode_lat, + flow_stats=stl_flow) + streams.append(stream_lat) + return streams + + def execute_traffic(self, traffic_generator=None): + if traffic_generator is not None and self.generator is None: + self.generator = traffic_generator + + self.ports = [] + self.profiles = {} + self.port_pg_id = PortPgIDMap() + for vld_id, intfs in sorted(self.generator.networks.items()): + profile_data = self.params.get(vld_id) + if not profile_data: + continue + if (vld_id.startswith(self.DOWNLINK) and + self.generator.rfc2544_helper.correlated_traffic): + continue + for intf in intfs: + current_port = int(self.generator.port_num(intf)) + self.port_pg_id.add_port(current_port) + profile = self.create_profile(profile_data, current_port) + self.generator.client.add_streams(profile, + ports=[current_port]) + + self.ports.append(current_port) + self.profiles[current_port] = profile + + timeout = self.generator.scenario_helper.scenario_cfg["runner"][ + "duration"] + test_data = { + "test_duration": timeout, + "test_precision": self.precision, + "tolerated_loss": self.tolerance_high, + "duration": self.duration, + "packet_size": self.pkt_size, + "flow": self.flow + } + + if self.max_rate > 100: + self.binary_search_with_optimized(self.generator, self.duration, + timeout, test_data) + else: + self.binary_search(self.generator, self.duration, + self.tolerance_high, test_data) + + def binary_search_with_optimized(self, traffic_generator, duration, + timeout, test_data): + # TODO Support FD.io Multiple Loss Ratio search (MLRsearch) + pass + + def binary_search(self, traffic_generator, duration, tolerance_value, + test_data): + theor_max_thruput = 0 + result_samples = {} + + for test_value in self.bounds_iterator(LOGGING): + stats = traffic_generator.send_traffic_on_tg(self.ports, + self.port_pg_id, + duration, + str( + test_value / self.max_rate / 2), + latency=self.enable_latency) + traffic_generator.client.reset(ports=self.ports) + traffic_generator.client.clear_stats(ports=self.ports) + traffic_generator.client.remove_all_streams(ports=self.ports) + for port, profile in self.profiles.items(): + traffic_generator.client.add_streams(profile, ports=[port]) + + loss_ratio = (float(traffic_generator.loss) / float( + traffic_generator.sent)) * 100 + + samples = traffic_generator.generate_samples(stats, self.ports, + self.port_pg_id, + self.enable_latency) + samples.update(test_data) + LOGGING.info("Collect TG KPIs %s %s %s", datetime.datetime.now(), + test_value, samples) + self.queue.put(samples) + + if float(loss_ratio) > float(tolerance_value): + LOGGING.debug("Failure... Decreasing upper bound") + self.current_upper = test_value + else: + LOGGING.debug("Success! Increasing lower bound") + self.current_lower = test_value + + rate_total = float(traffic_generator.sent) / float(duration) + bandwidth_total = float(rate_total) * ( + float(self.pkt_size) + 20) * 8 / (10 ** 9) + + success_samples = {'Result_' + key: value for key, value in + samples.items()} + success_samples["Result_{}".format('PDR')] = { + "rate_total_pps": float(rate_total), + "bandwidth_total_Gbps": float(bandwidth_total), + "packet_loss_ratio": float(loss_ratio), + "packets_lost": int(traffic_generator.loss), + } + self.queue.put(success_samples) + + # Store Actual throughput for result samples + for intf in traffic_generator.vnfd_helper.interfaces: + name = intf["name"] + result_samples[name] = { + "Result_Actual_throughput": float( + success_samples["Result_{}".format(name)][ + "rx_throughput_bps"]), + } + + for intf in traffic_generator.vnfd_helper.interfaces: + name = intf["name"] + if theor_max_thruput < samples[name]["tx_throughput_bps"]: + theor_max_thruput = samples[name]['tx_throughput_bps'] + self.queue.put({'theor_max_throughput': theor_max_thruput}) + + result_samples["Result_theor_max_throughput"] = theor_max_thruput + self.queue.put(result_samples) + return result_samples diff --git a/yardstick/network_services/vnf_generic/vnf/base.py b/yardstick/network_services/vnf_generic/vnf/base.py index 8064ae927..8ef96b744 100644 --- a/yardstick/network_services/vnf_generic/vnf/base.py +++ b/yardstick/network_services/vnf_generic/vnf/base.py @@ -94,7 +94,7 @@ class VnfdHelper(dict): for interface in self.interfaces: virtual_intf = interface["virtual-interface"] if virtual_intf[key] == value: - return interface + return virtual_intf raise KeyError() def find_interface(self, **kwargs): diff --git a/yardstick/network_services/vnf_generic/vnf/ipsec_vnf.py b/yardstick/network_services/vnf_generic/vnf/ipsec_vnf.py new file mode 100644 index 000000000..75a8cce06 --- /dev/null +++ b/yardstick/network_services/vnf_generic/vnf/ipsec_vnf.py @@ -0,0 +1,219 @@ +# 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 re +import time + +from yardstick.benchmark.contexts.base import Context +from yardstick.common.process import check_if_process_failed +from yardstick.network_services import constants +from yardstick.network_services.helpers.cpu import CpuSysCores +from yardstick.network_services.vnf_generic.vnf.sample_vnf import SampleVNF +from yardstick.network_services.vnf_generic.vnf.vpp_helpers import \ + VppSetupEnvHelper + +LOG = logging.getLogger(__name__) + + +class VipsecApproxSetupEnvHelper(VppSetupEnvHelper): + DEFAULT_IPSEC_VNF_CFG = { + 'crypto_type': 'SW_cryptodev', + 'rxq': 1, + 'worker_config': '1C/1T', + 'worker_threads': 1, + } + + def __init__(self, vnfd_helper, ssh_helper, scenario_helper): + super(VipsecApproxSetupEnvHelper, self).__init__( + vnfd_helper, ssh_helper, scenario_helper) + + def _get_crypto_type(self): + vnf_cfg = self.scenario_helper.options.get('vnf_config', + self.DEFAULT_IPSEC_VNF_CFG) + return vnf_cfg.get('crypto_type', 'SW_cryptodev') + + def _get_crypto_algorithms(self): + vpp_cfg = self.scenario_helper.all_options.get('vpp_config', {}) + return vpp_cfg.get('crypto_algorithms', 'aes-gcm') + + def _get_n_tunnels(self): + vpp_cfg = self.scenario_helper.all_options.get('vpp_config', {}) + return vpp_cfg.get('tunnels', 1) + + def _get_n_connections(self): + try: + flow_cfg = self.scenario_helper.all_options['flow'] + return flow_cfg['count'] + except KeyError: + raise KeyError("Missing flow definition in scenario section" + + " of the task definition file") + + def _get_flow_src_start_ip(self): + node_name = self.find_encrypted_data_interface()["node_name"] + try: + flow_cfg = self.scenario_helper.all_options['flow'] + src_ips = flow_cfg['src_ip'] + dst_ips = flow_cfg['dst_ip'] + except KeyError: + raise KeyError("Missing flow definition in scenario section" + + " of the task definition file") + + for src, dst in zip(src_ips, dst_ips): + flow_src_start_ip, _ = src.split('-') + flow_dst_start_ip, _ = dst.split('-') + + if node_name == "vnf__0": + return flow_src_start_ip + elif node_name == "vnf__1": + return flow_dst_start_ip + + def _get_flow_dst_start_ip(self): + node_name = self.find_encrypted_data_interface()["node_name"] + try: + flow_cfg = self.scenario_helper.all_options['flow'] + src_ips = flow_cfg['src_ip'] + dst_ips = flow_cfg['dst_ip'] + except KeyError: + raise KeyError("Missing flow definition in scenario section" + + " of the task definition file") + + for src, dst in zip(src_ips, dst_ips): + flow_src_start_ip, _ = src.split('-') + flow_dst_start_ip, _ = dst.split('-') + + if node_name == "vnf__0": + return flow_dst_start_ip + elif node_name == "vnf__1": + return flow_src_start_ip + + def build_config(self): + # TODO Implement later + pass + + def setup_vnf_environment(self): + resource = super(VipsecApproxSetupEnvHelper, + self).setup_vnf_environment() + + self.start_vpp_service() + + sys_cores = CpuSysCores(self.ssh_helper) + self._update_vnfd_helper(sys_cores.get_cpu_layout()) + + return resource + + @staticmethod + def calculate_frame_size(frame_cfg): + if not frame_cfg: + return 64 + + imix_count = {size.upper().replace('B', ''): int(weight) + for size, weight in frame_cfg.items()} + imix_sum = sum(imix_count.values()) + if imix_sum <= 0: + return 64 + packets_total = sum([int(size) * weight + for size, weight in imix_count.items()]) + return packets_total / imix_sum + + def check_status(self): + ipsec_created = False + cmd = "vppctl show int" + _, stdout, _ = self.ssh_helper.execute(cmd) + entries = re.split(r"\n+", stdout) + tmp = [re.split(r"\s\s+", entry, 5) for entry in entries] + + for item in tmp: + if isinstance(item, list): + if item[0] and item[0] != 'local0': + if "ipsec" in item[0] and not ipsec_created: + ipsec_created = True + if len(item) > 2 and item[2] == 'down': + return False + return ipsec_created + + def get_vpp_statistics(self): + # TODO Implement later + return None + + def create_ipsec_tunnels(self): + # TODO Implement later + pass + + def find_raw_data_interface(self): + try: + return self.vnfd_helper.find_virtual_interface(vld_id="uplink_0") + except KeyError: + return self.vnfd_helper.find_virtual_interface(vld_id="downlink_0") + + def find_encrypted_data_interface(self): + return self.vnfd_helper.find_virtual_interface(vld_id="ciphertext") + + +class VipsecApproxVnf(SampleVNF): + """ This class handles vIPSEC VNF model-driver definitions """ + + APP_NAME = 'vIPSEC' + APP_WORD = 'vipsec' + WAIT_TIME = 20 + + 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 = VipsecApproxSetupEnvHelper + super(VipsecApproxVnf, self).__init__( + name, vnfd, setup_env_helper_type, + resource_helper_type) + + def _run(self): + # we can't share ssh paramiko objects to force new connection + self.ssh_helper.drop_connection() + # kill before starting + self.setup_helper.kill_vnf() + self._build_config() + self.setup_helper.create_ipsec_tunnels() + + def wait_for_instantiate(self): + time.sleep(self.WAIT_TIME) + while True: + status = self.setup_helper.check_status() + if not self._vnf_process.is_alive() and not status: + raise RuntimeError("%s VNF process died." % self.APP_NAME) + LOG.info("Waiting for %s VNF to start.. ", self.APP_NAME) + time.sleep(self.WAIT_TIME_FOR_SCRIPT) + status = self.setup_helper.check_status() + if status: + LOG.info("%s VNF is up and running.", self.APP_NAME) + self._vnf_up_post() + return self._vnf_process.exitcode + + def terminate(self): + self.setup_helper.kill_vnf() + self._tear_down() + self.resource_helper.stop_collect() + if self._vnf_process is not None: + # be proper and join first before we kill + LOG.debug("joining before terminate %s", self._vnf_process.name) + self._vnf_process.join(constants.PROCESS_JOIN_TIMEOUT) + self._vnf_process.terminate() + + def collect_kpi(self): + # we can't get KPIs if the VNF is down + check_if_process_failed(self._vnf_process, 0.01) + physical_node = Context.get_physical_node_from_server( + self.scenario_helper.nodes[self.name]) + result = {"physical_node": physical_node} + result["collect_stats"] = self.setup_helper.get_vpp_statistics() + LOG.debug("%s collect KPIs %s", self.APP_NAME, result) + return result diff --git a/yardstick/network_services/vnf_generic/vnf/prox_helpers.py b/yardstick/network_services/vnf_generic/vnf/prox_helpers.py index a3d0c19cd..3507315f2 100644 --- a/yardstick/network_services/vnf_generic/vnf/prox_helpers.py +++ b/yardstick/network_services/vnf_generic/vnf/prox_helpers.py @@ -347,7 +347,7 @@ class ProxSocketHelper(object): LOG.debug("Received data from socket: [%s]", ret_str) return status, ret_str - def get_data(self, pkt_dump_only=False, timeout=0.01): + def get_data(self, pkt_dump_only=False, timeout=10.0): """ read data from the socket """ # This method behaves slightly differently depending on whether it is diff --git a/yardstick/network_services/vnf_generic/vnf/sample_vnf.py b/yardstick/network_services/vnf_generic/vnf/sample_vnf.py index 2a477bb8d..a369a3ae6 100644 --- a/yardstick/network_services/vnf_generic/vnf/sample_vnf.py +++ b/yardstick/network_services/vnf_generic/vnf/sample_vnf.py @@ -719,8 +719,8 @@ class SampleVNF(GenericVNF): scenarios: - type: NSPerf nodes: - tg__0: trafficgen_1.yardstick - vnf__0: vnf.yardstick + tg__0: trafficgen_0.yardstick + vnf__0: vnf_0.yardstick options: collectd: <options> # COLLECTD priority 3 diff --git a/yardstick/network_services/vnf_generic/vnf/tg_trex_vpp.py b/yardstick/network_services/vnf_generic/vnf/tg_trex_vpp.py new file mode 100644 index 000000000..846304880 --- /dev/null +++ b/yardstick/network_services/vnf_generic/vnf/tg_trex_vpp.py @@ -0,0 +1,178 @@ +# 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 + +from trex_stl_lib.trex_stl_exceptions import STLError + +from yardstick.common.utils import safe_cast +from yardstick.network_services.vnf_generic.vnf.sample_vnf import \ + Rfc2544ResourceHelper +from yardstick.network_services.vnf_generic.vnf.sample_vnf import \ + SampleVNFTrafficGen +from yardstick.network_services.vnf_generic.vnf.tg_trex import \ + TrexDpdkVnfSetupEnvHelper +from yardstick.network_services.vnf_generic.vnf.tg_trex import \ + TrexResourceHelper + +LOGGING = logging.getLogger(__name__) + + +class TrexVppResourceHelper(TrexResourceHelper): + + def __init__(self, setup_helper, rfc_helper_type=None): + super(TrexVppResourceHelper, self).__init__(setup_helper) + + if rfc_helper_type is None: + rfc_helper_type = Rfc2544ResourceHelper + + self.rfc2544_helper = rfc_helper_type(self.scenario_helper) + + self.loss = None + self.sent = None + self.latency = None + + def generate_samples(self, stats=None, ports=None, port_pg_id=None, + latency=False): + samples = {} + if stats is None: + stats = self.get_stats(ports) + for pname in (intf['name'] for intf in self.vnfd_helper.interfaces): + port_num = self.vnfd_helper.port_num(pname) + port_stats = stats.get(port_num, {}) + samples[pname] = { + 'rx_throughput_fps': float(port_stats.get('rx_pps', 0.0)), + 'tx_throughput_fps': float(port_stats.get('tx_pps', 0.0)), + 'rx_throughput_bps': float(port_stats.get('rx_bps', 0.0)), + 'tx_throughput_bps': float(port_stats.get('tx_bps', 0.0)), + 'in_packets': int(port_stats.get('ipackets', 0)), + 'out_packets': int(port_stats.get('opackets', 0)), + } + + if latency: + pg_id_list = port_pg_id.get_pg_ids(port_num) + samples[pname]['latency'] = {} + for pg_id in pg_id_list: + latency_global = stats.get('latency', {}) + pg_latency = latency_global.get(pg_id, {}).get('latency') + + t_min = safe_cast(pg_latency.get("total_min", 0.0), float, + -1.0) + t_avg = safe_cast(pg_latency.get("average", 0.0), float, + -1.0) + t_max = safe_cast(pg_latency.get("total_max", 0.0), float, + -1.0) + + latency = { + "min_latency": t_min, + "max_latency": t_max, + "avg_latency": t_avg, + } + samples[pname]['latency'][pg_id] = latency + + return samples + + def _run_traffic_once(self, traffic_profile): + self.client_started.value = 1 + traffic_profile.execute_traffic(self) + return True + + def run_traffic(self, traffic_profile): + self._queue.cancel_join_thread() + traffic_profile.init_queue(self._queue) + super(TrexVppResourceHelper, self).run_traffic(traffic_profile) + + @staticmethod + def fmt_latency(lat_min, lat_avg, lat_max): + t_min = int(round(safe_cast(lat_min, float, -1.0))) + t_avg = int(round(safe_cast(lat_avg, float, -1.0))) + t_max = int(round(safe_cast(lat_max, float, -1.0))) + + return "/".join(str(tmp) for tmp in (t_min, t_avg, t_max)) + + def send_traffic_on_tg(self, ports, port_pg_id, duration, rate, + latency=False): + try: + # Choose rate and start traffic: + self.client.start(ports=ports, mult=rate, duration=duration) + # Block until done: + try: + self.client.wait_on_traffic(ports=ports, timeout=duration + 20) + except STLError as err: + self.client.stop(ports) + LOGGING.error("TRex stateless timeout error: %s", err) + + if self.client.get_warnings(): + for warning in self.client.get_warnings(): + LOGGING.warning(warning) + + # Read the stats after the test + stats = self.client.get_stats() + + packets_in = [] + packets_out = [] + for port in ports: + packets_in.append(stats[port]["ipackets"]) + packets_out.append(stats[port]["opackets"]) + + if latency: + self.latency = [] + pg_id_list = port_pg_id.get_pg_ids(port) + for pg_id in pg_id_list: + latency_global = stats.get('latency', {}) + pg_latency = latency_global.get(pg_id, {}).get( + 'latency') + lat = self.fmt_latency( + str(pg_latency.get("total_min")), + str(pg_latency.get("average")), + str(pg_latency.get("total_max"))) + LOGGING.info( + "latencyStream%s(usec)=%s", pg_id, lat) + self.latency.append(lat) + + self.sent = sum(packets_out) + total_rcvd = sum(packets_in) + self.loss = self.sent - total_rcvd + LOGGING.info("rate=%s, totalReceived=%s, totalSent=%s," + " frameLoss=%s", rate, total_rcvd, self.sent, + self.loss) + return stats + except STLError as err: + LOGGING.error("TRex stateless runtime error: %s", err) + raise RuntimeError('TRex stateless runtime error') + + +class TrexTrafficGenVpp(SampleVNFTrafficGen): + APP_NAME = 'TRex' + WAIT_TIME = 20 + + 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 = TrexDpdkVnfSetupEnvHelper + if resource_helper_type is None: + resource_helper_type = TrexVppResourceHelper + + super(TrexTrafficGenVpp, self).__init__( + name, vnfd, setup_env_helper_type, resource_helper_type) + + def _check_status(self): + return self.resource_helper.check_status() + + def _start_server(self): + super(TrexTrafficGenVpp, self)._start_server() + self.resource_helper.start() + + def wait_for_instantiate(self): + return self._wait_for_process() 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/network_services/vnf_generic/vnf/vims_vnf.py b/yardstick/network_services/vnf_generic/vnf/vims_vnf.py new file mode 100644 index 000000000..0e339b171 --- /dev/null +++ b/yardstick/network_services/vnf_generic/vnf/vims_vnf.py @@ -0,0 +1,105 @@ +# 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 + +from yardstick.network_services.vnf_generic.vnf import sample_vnf + +LOG = logging.getLogger(__name__) + + +class VimsSetupEnvHelper(sample_vnf.SetupEnvHelper): + + def setup_vnf_environment(self): + LOG.debug('VimsSetupEnvHelper:\n') + + +class VimsResourceHelper(sample_vnf.ClientResourceHelper): + pass + + +class VimsPcscfVnf(sample_vnf.SampleVNF): + + APP_NAME = "VimsPcscf" + APP_WORD = "VimsPcscf" + + def __init__(self, name, vnfd, setup_env_helper_type=None, + resource_helper_type=None): + if resource_helper_type is None: + resource_helper_type = VimsResourceHelper + if setup_env_helper_type is None: + setup_env_helper_type = VimsSetupEnvHelper + super(VimsPcscfVnf, self).__init__(name, vnfd, setup_env_helper_type, + resource_helper_type) + + def wait_for_instantiate(self): + pass + + def _run(self): + pass + + def start_collect(self): + # TODO + pass + + def collect_kpi(self): + # TODO + pass + + +class VimsHssVnf(sample_vnf.SampleVNF): + + APP_NAME = "VimsHss" + APP_WORD = "VimsHss" + CMD = "sudo /media/generate_user.sh {} {} >> /dev/null 2>&1" + + def __init__(self, name, vnfd, setup_env_helper_type=None, + resource_helper_type=None): + if resource_helper_type is None: + resource_helper_type = VimsResourceHelper + if setup_env_helper_type is None: + setup_env_helper_type = VimsSetupEnvHelper + super(VimsHssVnf, self).__init__(name, vnfd, setup_env_helper_type, + resource_helper_type) + self.start_user = 1 + self.end_user = 10000 + self.WAIT_TIME = 600 + + def instantiate(self, scenario_cfg, context_cfg): + LOG.debug("scenario_cfg=%s\n", scenario_cfg) + self.start_user = scenario_cfg.get("options", {}).get("start_user", self.start_user) + self.end_user = scenario_cfg.get("options", {}).get("end_user", self.end_user) + # TODO + # Need to check HSS services are ready before generating user accounts + # Now, adding time sleep that manually configured by user + # to wait for HSS services. + # Note: for heat, waiting time is too long (~ 600s) + self.WAIT_TIME = scenario_cfg.get("options", {}).get("wait_time", self.WAIT_TIME) + time.sleep(self.WAIT_TIME) + LOG.debug("Generate user accounts from %d to %d\n", + self.start_user, self.end_user) + cmd = self.CMD.format(self.start_user, self.end_user) + self.ssh_helper.execute(cmd, None, 3600, False) + + def wait_for_instantiate(self): + pass + + def start_collect(self): + # TODO + pass + + def collect_kpi(self): + # TODO + pass diff --git a/yardstick/network_services/vnf_generic/vnf/vpp_helpers.py b/yardstick/network_services/vnf_generic/vnf/vpp_helpers.py new file mode 100644 index 000000000..86c42ecfd --- /dev/null +++ b/yardstick/network_services/vnf_generic/vnf/vpp_helpers.py @@ -0,0 +1,76 @@ +# 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 + +from yardstick.network_services.vnf_generic.vnf.sample_vnf import \ + DpdkVnfSetupEnvHelper + +LOG = logging.getLogger(__name__) + + +class VppSetupEnvHelper(DpdkVnfSetupEnvHelper): + APP_NAME = "vpp" + CFG_CONFIG = "/etc/vpp/startup.conf" + CFG_SCRIPT = "" + PIPELINE_COMMAND = "" + VNF_TYPE = "IPSEC" + VAT_BIN_NAME = 'vpp_api_test' + + def __init__(self, vnfd_helper, ssh_helper, scenario_helper): + super(VppSetupEnvHelper, self).__init__(vnfd_helper, ssh_helper, + scenario_helper) + + def kill_vnf(self): + ret_code, _, _ = \ + self.ssh_helper.execute( + 'service {name} stop'.format(name=self.APP_NAME)) + if int(ret_code): + raise RuntimeError( + 'Failed to stop service {name}'.format(name=self.APP_NAME)) + + def tear_down(self): + pass + + def start_vpp_service(self): + ret_code, _, _ = \ + self.ssh_helper.execute( + 'service {name} restart'.format(name=self.APP_NAME)) + if int(ret_code): + raise RuntimeError( + 'Failed to start service {name}'.format(name=self.APP_NAME)) + + def _update_vnfd_helper(self, additional_data, iface_key=None): + for k, v in additional_data.items(): + if iface_key is None: + if isinstance(v, dict) and k in self.vnfd_helper: + self.vnfd_helper[k].update(v) + else: + self.vnfd_helper[k] = v + else: + if isinstance(v, + dict) and k in self.vnfd_helper.find_virtual_interface( + ifname=iface_key): + self.vnfd_helper.find_virtual_interface(ifname=iface_key)[ + k].update(v) + else: + self.vnfd_helper.find_virtual_interface(ifname=iface_key)[ + k] = v + + def get_value_by_interface_key(self, interface, key): + try: + return self.vnfd_helper.find_virtual_interface( + ifname=interface).get(key) + except (KeyError, ValueError): + return None |