X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=blobdiff_plain;f=yardstick%2Fnetwork_services%2Fvnf_generic%2Fvnf%2Fsample_vnf.py;h=f16b4142d41d70a229a7f834a3d1706ba420ceea;hb=8544a361f831f324eae33f8a48b94bf2a34f5ab0;hp=08ec44f65b686fd1bfae76e0b9c351a85a75bd7a;hpb=fc125f816f42a2476f7daa0cb1b3c48d60226601;p=yardstick.git diff --git a/yardstick/network_services/vnf_generic/vnf/sample_vnf.py b/yardstick/network_services/vnf_generic/vnf/sample_vnf.py index 08ec44f65..f16b4142d 100644 --- a/yardstick/network_services/vnf_generic/vnf/sample_vnf.py +++ b/yardstick/network_services/vnf_generic/vnf/sample_vnf.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016-2017 Intel Corporation +# Copyright (c) 2016-2018 Intel Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,91 +13,46 @@ # limitations under the License. """ Base class implementation for generic vnf implementation """ -from __future__ import absolute_import - -import posixpath -import time +from collections import Mapping import logging +from multiprocessing import Queue, Value, Process + import os +import posixpath import re import subprocess -from collections import Mapping - -from multiprocessing import Queue, Value, Process +import time -from six.moves import cStringIO +import six +from trex_stl_lib.trex_stl_client import LoggerApi +from trex_stl_lib.trex_stl_client import STLClient +from trex_stl_lib.trex_stl_exceptions import STLError from yardstick.benchmark.contexts.base import Context -from yardstick.benchmark.scenarios.networking.vnf_generic import find_relative_file +from yardstick.common import exceptions as y_exceptions from yardstick.common.process import check_if_process_failed -from yardstick.network_services.helpers.cpu import CpuSysCores -from yardstick.network_services.helpers.samplevnf_helper import PortPairs +from yardstick.common import utils +from yardstick.network_services.constants import DEFAULT_VNF_TIMEOUT +from yardstick.network_services.constants import PROCESS_JOIN_TIMEOUT +from yardstick.network_services.constants import REMOTE_TMP +from yardstick.network_services.helpers.dpdkbindnic_helper import DpdkBindHelper, DpdkNode from yardstick.network_services.helpers.samplevnf_helper import MultiPortConfig -from yardstick.network_services.helpers.dpdkbindnic_helper import DpdkBindHelper +from yardstick.network_services.helpers.samplevnf_helper import PortPairs from yardstick.network_services.nfvi.resource import ResourceProfile +from yardstick.network_services.utils import get_nsb_option +from yardstick.network_services.vnf_generic.vnf.base import GenericTrafficGen from yardstick.network_services.vnf_generic.vnf.base import GenericVNF from yardstick.network_services.vnf_generic.vnf.base import QueueFileWrapper -from yardstick.network_services.vnf_generic.vnf.base import GenericTrafficGen -from yardstick.network_services.utils import get_nsb_option - -from trex_stl_lib.trex_stl_client import STLClient -from trex_stl_lib.trex_stl_client import LoggerApi -from trex_stl_lib.trex_stl_exceptions import STLError +from yardstick.network_services.vnf_generic.vnf.vnf_ssh_helper import VnfSshHelper -from yardstick.ssh import AutoConnectSSH - -DPDK_VERSION = "dpdk-16.07" LOG = logging.getLogger(__name__) -REMOTE_TMP = "/tmp" -DEFAULT_VNF_TIMEOUT = 3600 -PROCESS_JOIN_TIMEOUT = 3 - - -class VnfSshHelper(AutoConnectSSH): - - def __init__(self, node, bin_path, wait=None): - self.node = node - kwargs = self.args_from_node(self.node) - if wait: - kwargs.setdefault('wait', wait) - - super(VnfSshHelper, self).__init__(**kwargs) - self.bin_path = bin_path - - @staticmethod - def get_class(): - # must return static class name, anything else refers to the calling class - # i.e. the subclass, not the superclass - return VnfSshHelper - - def copy(self): - # this copy constructor is different from SSH classes, since it uses node - return self.get_class()(self.node, self.bin_path) - - def upload_config_file(self, prefix, content): - cfg_file = os.path.join(REMOTE_TMP, prefix) - LOG.debug(content) - file_obj = cStringIO(content) - self.put_file_obj(file_obj, cfg_file) - return cfg_file - - def join_bin_path(self, *args): - return os.path.join(self.bin_path, *args) - - def provision_tool(self, tool_path=None, tool_file=None): - if tool_path is None: - tool_path = self.bin_path - return super(VnfSshHelper, self).provision_tool(tool_path, tool_file) - - class SetupEnvHelper(object): CFG_CONFIG = os.path.join(REMOTE_TMP, "sample_config") CFG_SCRIPT = os.path.join(REMOTE_TMP, "sample_script") - CORES = [] DEFAULT_CONFIG_TPL_CFG = "sample.cfg" PIPELINE_COMMAND = '' VNF_TYPE = "SAMPLE" @@ -125,9 +80,8 @@ class DpdkVnfSetupEnvHelper(SetupEnvHelper): APP_NAME = 'DpdkVnf' FIND_NET_CMD = "find /sys/class/net -lname '*{}*' -printf '%f'" - - HW_DEFAULT_CORE = 3 - SW_DEFAULT_CORE = 2 + NR_HUGEPAGES_PATH = '/proc/sys/vm/nr_hugepages' + HUGEPAGES_KB = 1024 * 1024 * 16 @staticmethod def _update_packet_type(ip_pipeline_cfg, traffic_options): @@ -164,24 +118,22 @@ class DpdkVnfSetupEnvHelper(SetupEnvHelper): self.dpdk_bind_helper = DpdkBindHelper(ssh_helper) def _setup_hugepages(self): - cmd = "awk '/Hugepagesize/ { print $2$3 }' < /proc/meminfo" - hugepages = self.ssh_helper.execute(cmd)[1].rstrip() - - memory_path = \ - '/sys/kernel/mm/hugepages/hugepages-%s/nr_hugepages' % hugepages - self.ssh_helper.execute("awk -F: '{ print $1 }' < %s" % memory_path) - - if hugepages == "2048kB": - pages = 8192 - else: - pages = 16 - - self.ssh_helper.execute("echo %s | sudo tee %s" % (pages, memory_path)) + meminfo = utils.read_meminfo(self.ssh_helper) + hp_size_kb = int(meminfo['Hugepagesize']) + nr_hugepages = int(abs(self.HUGEPAGES_KB / hp_size_kb)) + self.ssh_helper.execute('echo %s | sudo tee %s' % + (nr_hugepages, self.NR_HUGEPAGES_PATH)) + hp = six.BytesIO() + self.ssh_helper.get_file_obj(self.NR_HUGEPAGES_PATH, hp) + nr_hugepages_set = int(hp.getvalue().decode('utf-8').splitlines()[0]) + LOG.info('Hugepages size (kB): %s, number claimed: %s, number set: %s', + hp_size_kb, nr_hugepages, nr_hugepages_set) def build_config(self): vnf_cfg = self.scenario_helper.vnf_cfg task_path = self.scenario_helper.task_path + config_file = vnf_cfg.get('file') lb_count = vnf_cfg.get('lb_count', 3) lb_config = vnf_cfg.get('lb_config', 'SW') worker_config = vnf_cfg.get('worker_config', '1C/1T') @@ -194,7 +146,8 @@ class DpdkVnfSetupEnvHelper(SetupEnvHelper): 'vnf_type': self.VNF_TYPE, } - config_tpl_cfg = find_relative_file(self.DEFAULT_CONFIG_TPL_CFG, task_path) + config_tpl_cfg = utils.find_relative_file(self.DEFAULT_CONFIG_TPL_CFG, + task_path) config_basename = posixpath.basename(self.CFG_CONFIG) script_basename = posixpath.basename(self.CFG_SCRIPT) multiport = MultiPortConfig(self.scenario_helper.topology, @@ -209,12 +162,20 @@ class DpdkVnfSetupEnvHelper(SetupEnvHelper): self.socket) multiport.generate_config() - with open(self.CFG_CONFIG) as handle: - new_config = handle.read() - - new_config = self._update_traffic_type(new_config, traffic_options) - new_config = self._update_packet_type(new_config, traffic_options) - + if config_file: + with utils.open_relative_file(config_file, task_path) as infile: + new_config = ['[EAL]'] + vpci = [] + for port in self.vnfd_helper.port_pairs.all_ports: + interface = self.vnfd_helper.find_interface(name=port) + vpci.append(interface['virtual-interface']["vpci"]) + new_config.extend('w = {0}'.format(item) for item in vpci) + new_config = '\n'.join(new_config) + '\n' + infile.read() + else: + with open(self.CFG_CONFIG) as handle: + new_config = handle.read() + new_config = self._update_traffic_type(new_config, traffic_options) + new_config = self._update_packet_type(new_config, traffic_options) self.ssh_helper.upload_config_file(config_basename, new_config) self.ssh_helper.upload_config_file(script_basename, multiport.generate_script(self.vnfd_helper)) @@ -241,44 +202,8 @@ class DpdkVnfSetupEnvHelper(SetupEnvHelper): 'tool_path': tool_path, } - def _get_app_cpu(self): - if self.CORES: - return self.CORES - - vnf_cfg = self.scenario_helper.vnf_cfg - sys_obj = CpuSysCores(self.ssh_helper) - self.sys_cpu = sys_obj.get_core_socket() - num_core = int(vnf_cfg["worker_threads"]) - if vnf_cfg.get("lb_config", "SW") == 'HW': - num_core += self.HW_DEFAULT_CORE - else: - num_core += self.SW_DEFAULT_CORE - app_cpu = self.sys_cpu[str(self.socket)][:num_core] - return app_cpu - - def _get_cpu_sibling_list(self, cores=None): - if cores is None: - cores = self._get_app_cpu() - sys_cmd_template = "%s/cpu%s/topology/thread_siblings_list" - awk_template = "awk -F: '{ print $1 }' < %s" - sys_path = "/sys/devices/system/cpu/" - cpu_topology = [] - try: - for core in cores: - sys_cmd = sys_cmd_template % (sys_path, core) - cpu_id = self.ssh_helper.execute(awk_template % sys_cmd)[1] - cpu_topology.extend(cpu.strip() for cpu in cpu_id.split(',')) - - return cpu_topology - except Exception: - return [] - - def _validate_cpu_cfg(self): - return self._get_cpu_sibling_list() - def setup_vnf_environment(self): self._setup_dpdk() - self.bound_pci = [v['virtual-interface']["vpci"] for v in self.vnfd_helper.interfaces] self.kill_vnf() # bind before _setup_resources so we can use dpdk_port_num self._detect_and_bind_drivers() @@ -294,21 +219,14 @@ class DpdkVnfSetupEnvHelper(SetupEnvHelper): self.ssh_helper.execute("sudo killall %s" % self.APP_NAME) def _setup_dpdk(self): - """ setup dpdk environment needed for vnf to run """ - + """Setup DPDK environment needed for VNF to run""" self._setup_hugepages() - self.ssh_helper.execute("sudo modprobe uio && sudo modprobe igb_uio") + self.dpdk_bind_helper.load_dpdk_driver() - exit_status = self.ssh_helper.execute("lsmod | grep -i igb_uio")[0] + exit_status = self.dpdk_bind_helper.check_dpdk_driver() if exit_status == 0: return - dpdk = self.ssh_helper.join_bin_path(DPDK_VERSION) - dpdk_setup = self.ssh_helper.provision_tool(tool_file="nsb_setup.sh") - exit_status = self.ssh_helper.execute("which {} >/dev/null 2>&1".format(dpdk))[0] - if exit_status != 0: - self.ssh_helper.execute("bash %s dpdk >/dev/null 2>&1" % dpdk_setup) - def get_collectd_options(self): options = self.scenario_helper.all_options.get("collectd", {}) # override with specific node settings @@ -323,7 +241,6 @@ class DpdkVnfSetupEnvHelper(SetupEnvHelper): else: self.socket = 1 - cores = self._validate_cpu_cfg() # implicit ordering, presumably by DPDK port num, so pre-sort by port_num # this won't work because we don't have DPDK port numbers yet ports = sorted(self.vnfd_helper.interfaces, key=self.vnfd_helper.port_num) @@ -331,13 +248,26 @@ class DpdkVnfSetupEnvHelper(SetupEnvHelper): collectd_options = self.get_collectd_options() plugins = collectd_options.get("plugins", {}) # we must set timeout to be the same as the VNF otherwise KPIs will die before VNF - return ResourceProfile(self.vnfd_helper.mgmt_interface, port_names=port_names, cores=cores, + return ResourceProfile(self.vnfd_helper.mgmt_interface, port_names=port_names, plugins=plugins, interval=collectd_options.get("interval"), timeout=self.scenario_helper.timeout) + def _check_interface_fields(self): + num_nodes = len(self.scenario_helper.nodes) + # OpenStack instance creation time is probably proportional to the number + # of instances + timeout = 120 * num_nodes + dpdk_node = DpdkNode(self.scenario_helper.name, self.vnfd_helper.interfaces, + self.ssh_helper, timeout) + dpdk_node.check() + def _detect_and_bind_drivers(self): interfaces = self.vnfd_helper.interfaces + self._check_interface_fields() + # check for bound after probe + self.bound_pci = [v['virtual-interface']["vpci"] for v in interfaces] + self.dpdk_bind_helper.read_status() self.dpdk_bind_helper.save_used_drivers() @@ -350,7 +280,7 @@ class DpdkVnfSetupEnvHelper(SetupEnvHelper): if vpci == v['virtual-interface']['vpci']) # force to int intf['virtual-interface']['dpdk_port_num'] = int(dpdk_port_num) - except: + except: # pylint: disable=bare-except pass time.sleep(2) @@ -387,7 +317,7 @@ class ResourceHelper(object): def _collect_resource_kpi(self): result = {} - status = self.resource.check_if_sa_running("collectd")[0] + status = self.resource.check_if_system_agent_running("collectd")[0] if status == 0: result = self.resource.amqp_collect_nfvi_kpi() @@ -433,6 +363,10 @@ class ClientResourceHelper(ResourceHelper): self.vnfd_helper.port_nums(self.vnfd_helper.port_pairs.downlink_ports) self.all_ports = self.vnfd_helper.port_nums(self.vnfd_helper.port_pairs.all_ports) + def port_num(self, intf): + # by default return port num + return self.vnfd_helper.port_num(intf) + def get_stats(self, *args, **kwargs): try: return self.client.get_stats(*args, **kwargs) @@ -510,6 +444,11 @@ class ClientResourceHelper(ResourceHelper): self.client.clear_stats(ports=ports) def start(self, ports=None, *args, **kwargs): + # pylint: disable=keyword-arg-before-vararg + # NOTE(ralonsoh): defining keyworded arguments before variable + # positional arguments is a bug. This function definition doesn't work + # in Python 2, although it works in Python 3. Reference: + # https://www.python.org/dev/peps/pep-3102/ if ports is None: ports = self.all_ports self.client.start(ports=ports, *args, **kwargs) @@ -518,8 +457,8 @@ class ClientResourceHelper(ResourceHelper): if not self._queue.empty(): kpi = self._queue.get() self._result.update(kpi) - LOG.debug("Got KPIs from _queue for {0} {1}".format( - self.scenario_helper.name, self.RESOURCE_WORD)) + LOG.debug('Got KPIs from _queue for %s %s', + self.scenario_helper.name, self.RESOURCE_WORD) return self._result def _connect(self, client=None): @@ -708,7 +647,7 @@ class SampleVNF(GenericVNF): self.pipeline_kwargs = {} self.uplink_ports = None self.downlink_ports = None - # TODO(esm): make QueueFileWrapper invert-able so that we + # NOTE(esm): make QueueFileWrapper invert-able so that we # never have to manage the queues self.q_in = Queue() self.q_out = Queue() @@ -789,7 +728,7 @@ class SampleVNF(GenericVNF): if not self._vnf_process.is_alive(): raise RuntimeError("%s VNF process died." % self.APP_NAME) - # TODO(esm): move to QueueFileWrapper + # NOTE(esm): move to QueueFileWrapper while self.q_out.qsize() > 0: buf.append(self.q_out.get()) message = ''.join(buf) @@ -859,12 +798,12 @@ class SampleVNF(GenericVNF): self._vnf_process.terminate() # no terminate children here because we share processes with tg - def get_stats(self, *args, **kwargs): - """ - Method for checking the statistics + def get_stats(self, *args, **kwargs): # pylint: disable=unused-argument + """Method for checking the statistics + + This method could be overridden in children classes. - :return: - VNF statistics + :return: VNF statistics """ cmd = 'p {0} stats'.format(self.APP_WORD) out = self.vnf_execute(cmd) @@ -887,6 +826,11 @@ class SampleVNF(GenericVNF): LOG.debug("%s collect KPIs %s", self.APP_NAME, result) return result + def scale(self, flavor=""): + """The SampleVNF base class doesn't provide the 'scale' feature""" + raise y_exceptions.FunctionNotImplemented( + function_name='scale', class_name='SampleVNFTrafficGen') + class SampleVNFTrafficGen(GenericTrafficGen): """ Class providing file-like API for generic traffic generator """ @@ -933,10 +877,6 @@ class SampleVNFTrafficGen(GenericTrafficGen): self._tg_process = Process(name=name, target=self._start_server) self._tg_process.start() - def wait_for_instantiate(self): - # overridden by subclasses - return self._wait_for_process() - def _check_status(self): raise NotImplementedError @@ -980,24 +920,6 @@ class SampleVNFTrafficGen(GenericTrafficGen): return self._traffic_process.is_alive() - def listen_traffic(self, traffic_profile): - """ Listen to traffic with the given parameters. - Method is non-blocking, returns immediately when traffic process - is running. Optional. - - :param traffic_profile: - :return: True/False - """ - pass - - def verify_traffic(self, traffic_profile): - """ Verify captured traffic after it has ended. Optional. - - :param traffic_profile: - :return: dict - """ - pass - def collect_kpi(self): # check if the tg processes have exited for proc in (self._tg_process, self._traffic_process): @@ -1024,3 +946,8 @@ class SampleVNFTrafficGen(GenericTrafficGen): self._tg_process.join(PROCESS_JOIN_TIMEOUT) self._tg_process.terminate() # no terminate children here because we share processes with vnf + + def scale(self, flavor=""): + """A traffic generator VFN doesn't provide the 'scale' feature""" + raise y_exceptions.FunctionNotImplemented( + function_name='scale', class_name='SampleVNFTrafficGen')