Merge "vPE Sample VNF is missing in the installation scripts"
[yardstick.git] / yardstick / network_services / vnf_generic / vnf / vpe_vnf.py
index 310ab67..57ea2ee 100644 (file)
@@ -15,6 +15,8 @@
 
 from __future__ import absolute_import
 from __future__ import print_function
+
+
 import os
 import logging
 import re
@@ -22,18 +24,20 @@ import posixpath
 
 from six.moves import configparser, zip
 
+from yardstick.common.process import check_if_process_failed
+from yardstick.network_services.helpers.samplevnf_helper import PortPairs
 from yardstick.network_services.pipeline import PipelineRules
 from yardstick.network_services.vnf_generic.vnf.sample_vnf import SampleVNF, DpdkVnfSetupEnvHelper
+from yardstick.benchmark.contexts import base as ctx_base
 
 LOG = logging.getLogger(__name__)
 
-VPE_PIPELINE_COMMAND = """sudo {tool_path} -p {ports_len_hex} -f {cfg_file} -s {script}"""
+VPE_PIPELINE_COMMAND = "sudo {tool_path} -p {port_mask_hex} -f {cfg_file} -s {script} {hwlb}"
 
 VPE_COLLECT_KPI = """\
-Pkts in:\s(\d+)\r\n\
-\tPkts dropped by Pkts in:\s(\d+)\r\n\
-\tPkts dropped by AH:\s(\d+)\r\n\\
-\tPkts dropped by other:\s(\d+)\
+Pkts in:\\s(\\d+)\r\n\
+\tPkts dropped by AH:\\s(\\d+)\r\n\
+\tPkts dropped by other:\\s(\\d+)\
 """
 
 
@@ -48,15 +52,35 @@ class ConfigCreate(object):
         config.set(tm_q, 'cfg', '/tmp/full_tm_profile_10G.cfg')
         return config
 
-    def __init__(self, priv_ports, pub_ports, socket):
+    def __init__(self, vnfd_helper, socket):
         super(ConfigCreate, self).__init__()
         self.sw_q = -1
         self.sink_q = -1
         self.n_pipeline = 1
-        self.priv_ports = priv_ports
-        self.pub_ports = pub_ports
+        self.vnfd_helper = vnfd_helper
+        self.uplink_ports = self.vnfd_helper.port_pairs.uplink_ports
+        self.downlink_ports = self.vnfd_helper.port_pairs.downlink_ports
         self.pipeline_per_port = 9
         self.socket = socket
+        self._dpdk_port_to_link_id_map = None
+
+    @property
+    def dpdk_port_to_link_id_map(self):
+        # we need interface name -> DPDK port num (PMD ID) -> LINK ID
+        # LINK ID -> PMD ID is governed by the port mask
+        # LINK instances are created implicitly based on the PORT_MASK application startup
+        # argument. LINK0 is the first port enabled in the PORT_MASK, port 1 is the next one,
+        # etc. The LINK ID is different than the DPDK PMD-level NIC port ID, which is the actual
+        #  position in the bitmask mentioned above. For example, if bit 5 is the first bit set
+        # in the bitmask, then LINK0 is having the PMD ID of 5. This mechanism creates a
+        # contiguous LINK ID space and isolates the configuration file against changes in the
+        # board PCIe slots where NICs are plugged in.
+        if self._dpdk_port_to_link_id_map is None:
+            self._dpdk_port_to_link_id_map = {}
+            for link_id, port_name in enumerate(sorted(self.vnfd_helper.port_pairs.all_ports,
+                                                       key=self.vnfd_helper.port_num)):
+                self._dpdk_port_to_link_id_map[port_name] = link_id
+        return self._dpdk_port_to_link_id_map
 
     def vpe_initialize(self, config):
         config.add_section('EAL')
@@ -74,8 +98,8 @@ class ConfigCreate(object):
         return config
 
     def vpe_rxq(self, config):
-        for port in self.pub_ports:
-            new_section = 'RXQ{0}.0'.format(port)
+        for port in self.downlink_ports:
+            new_section = 'RXQ{0}.0'.format(self.dpdk_port_to_link_id_map[port])
             config.add_section(new_section)
             config.set(new_section, 'mempool', 'MEMPOOL1')
 
@@ -92,24 +116,26 @@ class ConfigCreate(object):
         pktq = "SWQ{0}{1}".format(self.sw_q, sink)
         return pktq
 
-    def vpe_upstream(self, vnf_cfg, intf):
+    def vpe_upstream(self, vnf_cfg, index=0):  # pragma: no cover
+        # NOTE(ralonsoh): this function must be covered in UTs.
         parser = configparser.ConfigParser()
         parser.read(os.path.join(vnf_cfg, 'vpe_upstream'))
+
         for pipeline in parser.sections():
             for k, v in parser.items(pipeline):
                 if k == "pktq_in":
-                    index = intf['index']
                     if "RXQ" in v:
-                        value = "RXQ{0}.0".format(index)
+                        port = self.dpdk_port_to_link_id_map[self.uplink_ports[index]]
+                        value = "RXQ{0}.0".format(port)
                     else:
                         value = self.get_sink_swq(parser, pipeline, k, index)
 
                     parser.set(pipeline, k, value)
 
                 elif k == "pktq_out":
-                    index = intf['peer_intf']['index']
                     if "TXQ" in v:
-                        value = "TXQ{0}.0".format(index)
+                        port = self.dpdk_port_to_link_id_map[self.downlink_ports[index]]
+                        value = "TXQ{0}.0".format(port)
                     else:
                         self.sw_q += 1
                         value = self.get_sink_swq(parser, pipeline, k, index)
@@ -123,32 +149,33 @@ class ConfigCreate(object):
             self.n_pipeline += 1
         return parser
 
-    def vpe_downstream(self, vnf_cfg, intf):
+    def vpe_downstream(self, vnf_cfg, index):  # pragma: no cover
+        # NOTE(ralonsoh): this function must be covered in UTs.
         parser = configparser.ConfigParser()
         parser.read(os.path.join(vnf_cfg, 'vpe_downstream'))
         for pipeline in parser.sections():
             for k, v in parser.items(pipeline):
-                index = intf['dpdk_port_num']
-                peer_index = intf['peer_intf']['dpdk_port_num']
 
                 if k == "pktq_in":
+                    port = self.dpdk_port_to_link_id_map[self.downlink_ports[index]]
                     if "RXQ" not in v:
                         value = self.get_sink_swq(parser, pipeline, k, index)
                     elif "TM" in v:
-                        value = "RXQ{0}.0 TM{1}".format(peer_index, index)
+                        value = "RXQ{0}.0 TM{1}".format(port, index)
                     else:
-                        value = "RXQ{0}.0".format(peer_index)
+                        value = "RXQ{0}.0".format(port)
 
                     parser.set(pipeline, k, value)
 
                 if k == "pktq_out":
+                    port = self.dpdk_port_to_link_id_map[self.uplink_ports[index]]
                     if "TXQ" not in v:
                         self.sw_q += 1
                         value = self.get_sink_swq(parser, pipeline, k, index)
                     elif "TM" in v:
-                        value = "TXQ{0}.0 TM{1}".format(peer_index, index)
+                        value = "TXQ{0}.0 TM{1}".format(port, index)
                     else:
-                        value = "TXQ{0}.0".format(peer_index)
+                        value = "TXQ{0}.0".format(port)
 
                     parser.set(pipeline, k, value)
 
@@ -166,23 +193,28 @@ class ConfigCreate(object):
             config = self.vpe_initialize(config)
             config = self.vpe_rxq(config)
             config.write(cfg_file)
-            for index, priv_port in enumerate(self.priv_ports):
-                config = self.vpe_upstream(vnf_cfg, priv_port)
+            for index, _ in enumerate(self.uplink_ports):
+                config = self.vpe_upstream(vnf_cfg, index)
                 config.write(cfg_file)
-                config = self.vpe_downstream(vnf_cfg, priv_port)
+                config = self.vpe_downstream(vnf_cfg, index)
                 config = self.vpe_tmq(config, index)
                 config.write(cfg_file)
 
     def generate_vpe_script(self, interfaces):
         rules = PipelineRules(pipeline_id=1)
-        for priv_port, pub_port in zip(self.priv_ports, self.pub_ports):
-            priv_intf = interfaces[priv_port]["virtual-interface"]
-            pub_intf = interfaces[pub_port]["virtual-interface"]
+        for uplink_port, downlink_port in zip(self.uplink_ports, self.downlink_ports):
 
-            dst_port0_ip = priv_intf["dst_ip"]
-            dst_port1_ip = pub_intf["dst_ip"]
-            dst_port0_mac = priv_intf["dst_mac"]
-            dst_port1_mac = pub_intf["dst_mac"]
+            uplink_intf = \
+                next(intf["virtual-interface"] for intf in interfaces
+                     if intf["name"] == uplink_port)
+            downlink_intf = \
+                next(intf["virtual-interface"] for intf in interfaces
+                     if intf["name"] == downlink_port)
+
+            dst_port0_ip = uplink_intf["dst_ip"]
+            dst_port1_ip = downlink_intf["dst_ip"]
+            dst_port0_mac = uplink_intf["dst_mac"]
+            dst_port1_mac = downlink_intf["dst_mac"]
 
             rules.add_firewall_script(dst_port0_ip)
             rules.next_pipeline()
@@ -199,36 +231,40 @@ class ConfigCreate(object):
 
         return rules.get_string()
 
+    def generate_tm_cfg(self, vnf_cfg):
+        vnf_cfg = os.path.join(vnf_cfg, "full_tm_profile_10G.cfg")
+        if os.path.exists(vnf_cfg):
+            return open(vnf_cfg).read()
+
 
 class VpeApproxSetupEnvHelper(DpdkVnfSetupEnvHelper):
 
+    APP_NAME = 'vPE'
     CFG_CONFIG = "/tmp/vpe_config"
     CFG_SCRIPT = "/tmp/vpe_script"
+    TM_CONFIG = "/tmp/full_tm_profile_10G.cfg"
     CORES = ['0', '1', '2', '3', '4', '5']
     PIPELINE_COMMAND = VPE_PIPELINE_COMMAND
 
+    def _build_vnf_ports(self):
+        self._port_pairs = PortPairs(self.vnfd_helper.interfaces)
+        self.uplink_ports = self._port_pairs.uplink_ports
+        self.downlink_ports = self._port_pairs.downlink_ports
+        self.all_ports = self._port_pairs.all_ports
+
     def build_config(self):
         vpe_vars = {
             "bin_path": self.ssh_helper.bin_path,
             "socket": self.socket,
         }
 
-        all_ports = []
-        priv_ports = []
-        pub_ports = []
-        for interface in self.vnfd_helper.interfaces:
-            all_ports.append(interface['name'])
-            vld_id = interface['virtual-interface']['vld_id']
-            if vld_id.startswith('private'):
-                priv_ports.append(interface)
-            elif vld_id.startswith('public'):
-                pub_ports.append(interface)
-
-        vpe_conf = ConfigCreate(priv_ports, pub_ports, self.socket)
+        self._build_vnf_ports()
+        vpe_conf = ConfigCreate(self.vnfd_helper, self.socket)
         vpe_conf.create_vpe_config(self.scenario_helper.vnf_cfg)
 
         config_basename = posixpath.basename(self.CFG_CONFIG)
         script_basename = posixpath.basename(self.CFG_SCRIPT)
+        tm_basename = posixpath.basename(self.TM_CONFIG)
         with open(self.CFG_CONFIG) as handle:
             vpe_config = handle.read()
 
@@ -237,11 +273,20 @@ class VpeApproxSetupEnvHelper(DpdkVnfSetupEnvHelper):
         vpe_script = vpe_conf.generate_vpe_script(self.vnfd_helper.interfaces)
         self.ssh_helper.upload_config_file(script_basename, vpe_script.format(**vpe_vars))
 
+        tm_config = vpe_conf.generate_tm_cfg(self.scenario_helper.vnf_cfg)
+        self.ssh_helper.upload_config_file(tm_basename, tm_config)
+
+        LOG.info("Provision and start the %s", self.APP_NAME)
+        LOG.info(self.CFG_CONFIG)
+        LOG.info(self.CFG_SCRIPT)
+        self._build_pipeline_kwargs()
+        return self.PIPELINE_COMMAND.format(**self.pipeline_kwargs)
+
 
 class VpeApproxVnf(SampleVNF):
     """ This class handles vPE VNF model-driver definitions """
 
-    APP_NAME = 'vPE_vnf'
+    APP_NAME = 'vPE'
     APP_WORD = 'vpe'
     COLLECT_KPI = VPE_COLLECT_KPI
     WAIT_TIME = 20
@@ -256,7 +301,13 @@ class VpeApproxVnf(SampleVNF):
         raise NotImplementedError
 
     def collect_kpi(self):
+        # we can't get KPIs if the VNF is down
+        check_if_process_failed(self._vnf_process)
+        physical_node = ctx_base.Context.get_physical_node_from_server(
+            self.scenario_helper.nodes[self.name])
+
         result = {
+            "physical_node": physical_node,
             'pkt_in_up_stream': 0,
             'pkt_drop_up_stream': 0,
             'pkt_in_down_stream': 0,