X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=blobdiff_plain;f=yardstick%2Fnetwork_services%2Fvnf_generic%2Fvnf%2Fsample_vnf.py;h=fbaaa0ca82089846dd3c22c7036c9c616abe344a;hb=944523e14720956402e1904f9838abe7a020d581;hp=5cf234514e23e5cec05c94afabcfd97e8bd9b1cc;hpb=816c5b7ad281d792b972609ad08af5986cc22235;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 5cf234514..fbaaa0ca8 100644 --- a/yardstick/network_services/vnf_generic/vnf/sample_vnf.py +++ b/yardstick/network_services/vnf_generic/vnf/sample_vnf.py @@ -13,44 +13,42 @@ # 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 - from six.moves import cStringIO +import subprocess +import time +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.network_services.helpers.cpu import CpuSysCores +from yardstick.common import exceptions as y_exceptions +from yardstick.common.process import check_if_process_failed +from yardstick.network_services.helpers.dpdkbindnic_helper import DpdkBindHelper from yardstick.network_services.helpers.samplevnf_helper import PortPairs from yardstick.network_services.helpers.samplevnf_helper import MultiPortConfig -from yardstick.network_services.helpers.dpdknicbind_helper import DpdkBindHelper 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 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.base import QueueFileWrapper 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): @@ -94,7 +92,6 @@ 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" @@ -105,13 +102,6 @@ class SetupEnvHelper(object): self.ssh_helper = ssh_helper self.scenario_helper = scenario_helper - def _get_ports_gateway(self, name): - routing_table = self.vnfd_helper.vdu0.get('routing_table', []) - for route in routing_table: - if name == route['if']: - return route['gateway'] - return None - def build_config(self): raise NotImplementedError @@ -130,9 +120,6 @@ class DpdkVnfSetupEnvHelper(SetupEnvHelper): APP_NAME = 'DpdkVnf' FIND_NET_CMD = "find /sys/class/net -lname '*{}*' -printf '%f'" - HW_DEFAULT_CORE = 3 - SW_DEFAULT_CORE = 2 - @staticmethod def _update_packet_type(ip_pipeline_cfg, traffic_options): match_str = 'pkt_type = ipv4' @@ -245,41 +232,6 @@ 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] @@ -290,8 +242,12 @@ class DpdkVnfSetupEnvHelper(SetupEnvHelper): return resource def kill_vnf(self): + # pkill is not matching, debug with pgrep + self.ssh_helper.execute("sudo pgrep -lax %s" % self.APP_NAME) + self.ssh_helper.execute("sudo ps aux | grep -i %s" % self.APP_NAME) # have to use exact match - self.ssh_helper.execute("sudo pkill -x %s" % self.APP_NAME) + # try using killall to match + self.ssh_helper.execute("sudo killall %s" % self.APP_NAME) def _setup_dpdk(self): """ setup dpdk environment needed for vnf to run """ @@ -323,15 +279,16 @@ 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) port_names = (intf["name"] for intf in ports) collectd_options = self.get_collectd_options() plugins = collectd_options.get("plugins", {}) - return ResourceProfile(self.vnfd_helper.mgmt_interface, port_names=port_names, cores=cores, - plugins=plugins, interval=collectd_options.get("interval")) + # 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, + plugins=plugins, interval=collectd_options.get("interval"), + timeout=self.scenario_helper.timeout) def _detect_and_bind_drivers(self): interfaces = self.vnfd_helper.interfaces @@ -348,7 +305,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) @@ -385,8 +342,8 @@ class ResourceHelper(object): def _collect_resource_kpi(self): result = {} - status = self.resource.check_if_sa_running("collectd")[0] - if status: + status = self.resource.check_if_system_agent_running("collectd")[0] + if status == 0: result = self.resource.amqp_collect_nfvi_kpi() result = {"core": result} @@ -423,7 +380,6 @@ class ClientResourceHelper(ResourceHelper): self._queue = Queue() self._result = {} self._terminated = Value('i', 0) - self._vpci_ascending = None def _build_ports(self): self.networks = self.vnfd_helper.port_pairs.networks @@ -432,6 +388,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) @@ -509,6 +469,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) @@ -517,8 +482,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): @@ -607,13 +572,7 @@ class SampleVNFDeployHelper(object): self.ssh_helper = ssh_helper self.vnfd_helper = vnfd_helper - DISABLE_DEPLOY = True - def deploy_vnfs(self, app_name): - # temp disable for now - if self.DISABLE_DEPLOY: - return - vnf_bin = self.ssh_helper.join_bin_path(app_name) exit_status = self.ssh_helper.execute("which %s" % vnf_bin)[0] if not exit_status: @@ -673,12 +632,19 @@ class ScenarioHelper(object): def topology(self): return self.scenario_cfg['topology'] + @property + def timeout(self): + return self.options.get('timeout', DEFAULT_VNF_TIMEOUT) + class SampleVNF(GenericVNF): """ Class providing file-like API for generic VNF implementation """ VNF_PROMPT = "pipeline>" WAIT_TIME = 1 + WAIT_TIME_FOR_SCRIPT = 10 + APP_NAME = "SampleVNF" + # we run the VNF interactively, so the ssh command will timeout after this long def __init__(self, name, vnfd, setup_env_helper_type=None, resource_helper_type=None): super(SampleVNF, self).__init__(name, vnfd) @@ -706,7 +672,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() @@ -761,7 +727,8 @@ class SampleVNF(GenericVNF): def _start_vnf(self): self.queue_wrapper = QueueFileWrapper(self.q_in, self.q_out, self.VNF_PROMPT) - self._vnf_process = Process(target=self._run) + name = "{}-{}-{}".format(self.name, self.APP_NAME, os.getpid()) + self._vnf_process = Process(name=name, target=self._run) self._vnf_process.start() def _vnf_up_post(self): @@ -773,7 +740,9 @@ class SampleVNF(GenericVNF): self.nfvi_context = Context.get_context_from_server(self.scenario_helper.nodes[self.name]) # self.nfvi_context = None - self.deploy_helper.deploy_vnfs(self.APP_NAME) + # vnf deploy is unsupported, use ansible playbooks + if self.scenario_helper.options.get("vnf_deploy", False): + self.deploy_helper.deploy_vnfs(self.APP_NAME) self.resource_helper.setup() self._start_vnf() @@ -784,7 +753,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) @@ -800,7 +769,7 @@ class SampleVNF(GenericVNF): self.APP_NAME) LOG.info("Waiting for %s VNF to start.. ", self.APP_NAME) - time.sleep(1) + time.sleep(self.WAIT_TIME_FOR_SCRIPT) # Send ENTER to display a new prompt in case the prompt text was corrupted # by other VNF output self.q_in.put('\r\n') @@ -811,6 +780,7 @@ class SampleVNF(GenericVNF): 'stdout': self.queue_wrapper, 'keep_stdin_open': True, 'pty': True, + 'timeout': self.scenario_helper.timeout, } def _build_config(self): @@ -843,24 +813,30 @@ class SampleVNF(GenericVNF): def terminate(self): self.vnf_execute("quit") - if self._vnf_process: - self._vnf_process.terminate() 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(PROCESS_JOIN_TIMEOUT) + 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 - :return: - VNF statistics + This method could be overridden in children classes. + + :return: VNF statistics """ cmd = 'p {0} stats'.format(self.APP_WORD) out = self.vnf_execute(cmd) return out def collect_kpi(self): + # we can't get KPIs if the VNF is down + check_if_process_failed(self._vnf_process) stats = self.get_stats() m = re.search(self.COLLECT_KPI, stats, re.MULTILINE) if m: @@ -875,6 +851,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 """ @@ -885,7 +866,6 @@ class SampleVNFTrafficGen(GenericTrafficGen): def __init__(self, name, vnfd, setup_env_helper_type=None, resource_helper_type=None): super(SampleVNFTrafficGen, self).__init__(name, vnfd) self.bin_path = get_nsb_option('bin_path', '') - self.name = "tgen__1" # name in topology file self.scenario_helper = ScenarioHelper(self.name) self.ssh_helper = VnfSshHelper(self.vnfd_helper.mgmt_interface, self.bin_path, wait=True) @@ -913,17 +893,15 @@ class SampleVNFTrafficGen(GenericTrafficGen): def instantiate(self, scenario_cfg, context_cfg): self.scenario_helper.scenario_cfg = scenario_cfg - self.resource_helper.generate_cfg() self.resource_helper.setup() + # must generate_cfg after DPDK bind because we need port number + self.resource_helper.generate_cfg() LOG.info("Starting %s server...", self.APP_NAME) - self._tg_process = Process(target=self._start_server) + name = "{}-{}-{}".format(self.name, self.APP_NAME, os.getpid()) + 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 @@ -953,7 +931,9 @@ class SampleVNFTrafficGen(GenericTrafficGen): :param traffic_profile: :return: True/False """ - self._traffic_process = Process(target=self._traffic_runner, + name = "{}-{}-{}-{}".format(self.name, self.APP_NAME, traffic_profile.__class__.__name__, + os.getpid()) + self._traffic_process = Process(name=name, target=self._traffic_runner, args=(traffic_profile,)) self._traffic_process.start() # Wait for traffic process to start @@ -965,25 +945,10 @@ 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): + check_if_process_failed(proc) result = self.resource_helper.collect_kpi() LOG.debug("%s collect KPIs %s", self.APP_NAME, result) return result @@ -994,5 +959,20 @@ class SampleVNFTrafficGen(GenericTrafficGen): :return: True/False """ self.traffic_finished = True + # we must kill client before we kill the server, or the client will raise exception if self._traffic_process is not None: + # be proper and try to join before terminating + LOG.debug("joining before terminate %s", self._traffic_process.name) + self._traffic_process.join(PROCESS_JOIN_TIMEOUT) self._traffic_process.terminate() + if self._tg_process is not None: + # be proper and try to join before terminating + LOG.debug("joining before terminate %s", self._tg_process.name) + 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')