aboutsummaryrefslogtreecommitdiffstats
path: root/yardstick
diff options
context:
space:
mode:
authorDino Madarang <dinox.madarang@intel.com>2017-10-03 01:02:17 +0000
committerRoss Brattain <ross.b.brattain@intel.com>2017-12-16 08:56:12 +0000
commit2cbda68cf08d9319cf88a1b221b9c7e55a09d124 (patch)
treeb76ea7b35d678c8fa47df97c7f6afbf35a040c1b /yardstick
parent81313a8b4089fe31451cdc64801c689d43158ae1 (diff)
samples: Add generic L3 forwarder tests
A generic throughput test case that can be used as a stub code for a Linux-based VNF configured as an L3 forwarder. Supported context: * Standalone OVSDPDK and SRIOV * Baremetal Code changes: * Allow pmd-cpu-mask and lcore mask for OVS DPDK * router_vnf.py - configures interface IP addresses and static arp entries using ip command * NFVi KPIs * Allow cputune tag for standalone context to be able to PIN on NUMA 1 cpus SRIOV Test cases: * RFC2544 Ethernet framesizes, 128K Flows * 2,4 and 6 ports * 2 and 3 vcpus per port * OVSDPDK Test cases: * RFC2544 Ethernet framesizes, 128K Flows * 2 and 4 ports * 2 vcpus per port * 2 PMD threads per port TODO: * Documentation * Add 6 ports tests References: * router_vnf.py is based on sample_vnf.py * tc_*.yaml files are based on acl/vfw test case files Added unitests Added get_stats to parse ip -s link Change-Id: Id1b969d5420dfcab7c1e695acbd2cd1655747efe Signed-off-by: Dino Simeon Madarang <dinox.madarang@intel.com> Signed-off-by: Kavindya Deegala <kavindya.s.deegala@intel.com> Reviewed-by: Alain Jebara <alain.jebara@intel.com> Reviewed-by: Deepak S <deepak.s@linux.intel.com> Reviewed-by: Emma Foley <emma.l.foley@intel.com> Reviewed-by: Rodolfo Alonso Hernandez <rodolfo.alonso.hernandez@intel.com> Reviewed-by: Ross Brattain <ross.b.brattain@intel.com> Reviewed-by: Edward MacGillivray <edward.s.macgillivray@intel.com> Signed-off-by: Dhaval Patel <dhaval.r.patel@intel.com> Signed-off-by: Ross Brattain <ross.b.brattain@intel.com> (cherry picked from commit 9bf492e01dad7309287e8491ec7ac29f43032aed)
Diffstat (limited to 'yardstick')
-rw-r--r--yardstick/benchmark/contexts/standalone/model.py5
-rw-r--r--yardstick/benchmark/contexts/standalone/ovs_dpdk.py12
-rw-r--r--yardstick/network_services/vnf_generic/vnf/router_vnf.py185
3 files changed, 199 insertions, 3 deletions
diff --git a/yardstick/benchmark/contexts/standalone/model.py b/yardstick/benchmark/contexts/standalone/model.py
index a8943c3f6..85ae14b1d 100644
--- a/yardstick/benchmark/contexts/standalone/model.py
+++ b/yardstick/benchmark/contexts/standalone/model.py
@@ -43,6 +43,7 @@ VM_TEMPLATE = """
<hugepages />
</memoryBacking>
<vcpu cpuset='{cpuset}'>{vcpu}</vcpu>
+ {cputune}
<os>
<type arch="x86_64" machine="pc-i440fx-utopic">hvm</type>
<boot dev="hd" />
@@ -242,6 +243,7 @@ class Libvirt(object):
hw_socket = flavor.get('hw_socket', '0')
cpuset = Libvirt.pin_vcpu_for_perf(connection, hw_socket)
+ cputune = extra_spec.get('cputune', '')
mac = StandaloneContextHelper.get_mac_address(0x00)
image = cls.create_snapshot_qemu(connection, index,
flavor.get("images", None))
@@ -252,7 +254,7 @@ class Libvirt(object):
memory=memory, vcpu=vcpu, cpu=cpu,
numa_cpus=numa_cpus,
socket=socket, threads=threads,
- vm_image=image, cpuset=cpuset)
+ vm_image=image, cpuset=cpuset, cputune=cputune)
write_file(cfg, vm_xml)
@@ -269,6 +271,7 @@ class Libvirt(object):
sys_obj = CpuSysCores(connection)
soc_cpu = sys_obj.get_core_socket()
sys_cpu = int(soc_cpu["cores_per_socket"])
+ socket = str(socket)
cores = "%s-%s" % (soc_cpu[socket][0], soc_cpu[socket][sys_cpu - 1])
if int(soc_cpu["thread_per_core"]) > 1:
threads = "%s-%s" % (soc_cpu[socket][sys_cpu], soc_cpu[socket][-1])
diff --git a/yardstick/benchmark/contexts/standalone/ovs_dpdk.py b/yardstick/benchmark/contexts/standalone/ovs_dpdk.py
index a6c35de53..3755b84e9 100644
--- a/yardstick/benchmark/contexts/standalone/ovs_dpdk.py
+++ b/yardstick/benchmark/contexts/standalone/ovs_dpdk.py
@@ -129,13 +129,21 @@ class OvsDpdkContext(Context):
ovs_sock_path = '/var/run/openvswitch/db.sock'
log_path = '/var/log/openvswitch/ovs-vswitchd.log'
+ pmd_cpu_mask = self.ovs_properties.get("pmd_cpu_mask", '')
pmd_mask = hex(sum(2 ** num for num in range(pmd_nums)) << 1)
+ if pmd_cpu_mask:
+ pmd_mask = pmd_cpu_mask
+
socket0 = self.ovs_properties.get("ram", {}).get("socket_0", "2048")
socket1 = self.ovs_properties.get("ram", {}).get("socket_1", "2048")
ovs_other_config = "ovs-vsctl {0}set Open_vSwitch . other_config:{1}"
detach_cmd = "ovs-vswitchd unix:{0}{1} --pidfile --detach --log-file={2}"
+ lcore_mask = self.ovs_properties.get("lcore_mask", '')
+ if lcore_mask:
+ lcore_mask = ovs_other_config.format("--no-wait ", "dpdk-lcore-mask='%s'" % lcore_mask)
+
cmd_list = [
"mkdir -p /usr/local/var/run/openvswitch",
"mkdir -p {}".format(os.path.dirname(log_path)),
@@ -143,6 +151,7 @@ class OvsDpdkContext(Context):
ovs_sock_path),
ovs_other_config.format("--no-wait ", "dpdk-init=true"),
ovs_other_config.format("--no-wait ", "dpdk-socket-mem='%s,%s'" % (socket0, socket1)),
+ lcore_mask,
detach_cmd.format(vpath, ovs_sock_path, log_path),
ovs_other_config.format("", "pmd-cpu-mask=%s" % pmd_mask),
]
@@ -296,8 +305,7 @@ class OvsDpdkContext(Context):
except StopIteration:
pass
else:
- raise ValueError("Duplicate nodes!!! Nodes: %s %s",
- (node, duplicate))
+ raise ValueError("Duplicate nodes!!! Nodes: %s %s" % (node, duplicate))
node["name"] = attr_name
return node
diff --git a/yardstick/network_services/vnf_generic/vnf/router_vnf.py b/yardstick/network_services/vnf_generic/vnf/router_vnf.py
new file mode 100644
index 000000000..aea27ffa6
--- /dev/null
+++ b/yardstick/network_services/vnf_generic/vnf/router_vnf.py
@@ -0,0 +1,185 @@
+# Copyright (c) 2016-2017 Intel 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.
+""" Add generic L3 forwarder implementation based on sample_vnf.py"""
+
+from __future__ import absolute_import
+import logging
+import time
+import itertools
+
+import re
+from netaddr import IPRange
+
+from six.moves import zip
+
+from yardstick.benchmark.contexts.base import Context
+from yardstick.network_services.vnf_generic.vnf.sample_vnf import SampleVNF, \
+ DpdkVnfSetupEnvHelper
+
+LOG = logging.getLogger(__name__)
+
+
+class RouterVNF(SampleVNF):
+
+ WAIT_TIME = 1
+
+ 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 = DpdkVnfSetupEnvHelper
+
+ # For heat test cases
+ vnfd['mgmt-interface'].pop("pkey", "")
+ vnfd['mgmt-interface']['password'] = 'password'
+
+ super(RouterVNF, self).__init__(name, vnfd, setup_env_helper_type, resource_helper_type)
+
+ def instantiate(self, scenario_cfg, context_cfg):
+ self.scenario_helper.scenario_cfg = scenario_cfg
+ self.context_cfg = context_cfg
+ self.nfvi_context = Context.get_context_from_server(self.scenario_helper.nodes[self.name])
+ self.configure_routes(self.name, scenario_cfg, context_cfg)
+
+ def wait_for_instantiate(self):
+ time.sleep(self.WAIT_TIME)
+
+ def _run(self):
+ # we can't share ssh paramiko objects to force new connection
+ self.ssh_helper.drop_connection()
+
+ def terminate(self):
+ self._tear_down()
+ self.resource_helper.stop_collect()
+
+ def scale(self, flavor=""):
+ pass
+
+ @staticmethod
+ def row_with_header(header, data):
+ """Returns dictionary per row of values for 'ip show stats'.
+
+ Args:
+ header(str): output header
+ data(str): output data
+
+ Returns:
+ dict: dictionary per row of values for 'ip show stats'
+
+ """
+ prefix, columns = header.strip().split(':')
+ column_names = ["{0}:{1}".format(prefix, h) for h in columns.split()]
+ return dict(list(zip(column_names, data.strip().split())))
+
+ RX_TX_RE = re.compile(r"\s+[RT]X[^:]*:")
+
+ @classmethod
+ def get_stats(cls, stdout):
+ """Returns list of IP statistics.
+
+ Args:
+ stdout(str): command output
+
+ Returns:
+ dict: list of IP statistics
+
+ """
+ input_lines = stdout.splitlines()
+ table = {}
+ for n, row in enumerate(input_lines):
+ if cls.RX_TX_RE.match(row):
+ # use pairs of rows, header and data
+ table.update(cls.row_with_header(*input_lines[n:n + 2]))
+ return table
+
+ def collect_kpi(self):
+ # Implement stats collection
+ ip_link_stats = '/sbin/ip -s link'
+ stdout = self.ssh_helper.execute(ip_link_stats)[1]
+ link_stats = self.get_stats(stdout)
+ # get RX/TX from link_stats and assign to results
+
+ result = {
+ "packets_in": 0,
+ "packets_dropped": 0,
+ "packets_fwd": 0,
+ "link_stats": link_stats
+ }
+
+ LOG.debug("%s collect KPIs %s", "RouterVNF", result)
+ return result
+
+ INTERFACE_WAIT = 2
+
+ def configure_routes(self, node_name, scenario_cfg, context_cfg):
+ # Configure IP of dataplane ports and add static ARP entries
+ #
+ # This function should be modified to configure a 3rd party/commercial VNF.
+ # The current implementation works on a Linux based VNF with "ip" command.
+ #
+ # Flow contains:
+ # {'src_ip': ['152.16.100.26-152.16.100.27'],
+ # 'dst_ip': ['152.16.40.26-152.16.40.27'], 'count': 2}
+
+ ifaces = []
+ dst_macs = []
+
+ ip_cmd_replace = '/sbin/ip addr replace %s/24 dev %s'
+ ip_cmd_up = '/sbin/ip link set %s up'
+ ip_cmd_flush = '/sbin/ip address flush dev %s'
+
+ # Get VNF IPs from test case file
+ for value in context_cfg['nodes'][node_name]['interfaces'].values():
+ dst_macs.append(value['dst_mac'])
+
+ # Get the network interface name using local_mac
+ iname = self.ssh_helper.execute("/sbin/ip a |grep -B 1 %s | head -n 1"
+ % (value['local_mac']))
+ iname = iname[1].split(":")[1].strip()
+ ifaces.append(iname)
+
+ self.ssh_helper.execute(ip_cmd_flush % iname)
+
+ # Get the local_ip from context_cfg and assign to the data ports
+ self.ssh_helper.execute(ip_cmd_replace % (str(value['local_ip']),
+ iname))
+ # Enable interface
+ self.ssh_helper.execute(ip_cmd_up % iname)
+ time.sleep(self.INTERFACE_WAIT)
+
+ # Configure static ARP entries for each IP
+ # using SSH or REST API calls
+ try:
+ src_ips = scenario_cfg['options']['flow']['src_ip']
+ dst_ips = scenario_cfg['options']['flow']['dst_ip']
+ except KeyError:
+ raise KeyError("Missing flow definition in scenario section" +
+ " of the task definition file")
+
+ # Multiport
+ ip_ranges = []
+ for src, dst in zip(src_ips, dst_ips):
+ range1 = itertools.cycle(iter(src.split('-')))
+ range2 = itertools.cycle(iter(dst.split('-')))
+
+ range1 = IPRange(next(range1), next(range1))
+ range2 = IPRange(next(range2), next(range2))
+ ip_ranges.append(range1)
+ ip_ranges.append(range2)
+
+ ip_cmd = '/sbin/ip neigh add %s lladdr %s dev %s nud perm'
+ for idx, iface in enumerate(ifaces):
+ for addr in ip_ranges[idx]:
+ self.ssh_helper.execute(ip_cmd % (addr, dst_macs[idx], iface))
+
+ arp_status = self.ssh_helper.execute("arp -a -n")
+ LOG.debug('arp %s', arp_status)