diff options
Diffstat (limited to 'yardstick/network_services/vnf_generic/vnf')
6 files changed, 580 insertions, 2 deletions
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/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/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 |