Merge "Enable heat context to support existing network"
[yardstick.git] / yardstick / network_services / vnf_generic / vnf / sample_vnf.py
index b5cf034..f16b414 100644 (file)
@@ -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.
 # 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 yardstick.network_services.vnf_generic.vnf.vnf_ssh_helper import VnfSshHelper
 
-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.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
 
-        :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)
@@ -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 """
@@ -924,18 +868,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)
         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
 
@@ -979,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):
@@ -1023,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')