diff options
author | 2017-10-03 01:02:17 +0000 | |
---|---|---|
committer | 2017-12-16 00:18:27 -0800 | |
commit | 9bf492e01dad7309287e8491ec7ac29f43032aed (patch) | |
tree | 852528f5a17b6fa1fef4028afee6eb79fd32c79a /yardstick | |
parent | 6cfec77db6b95af5b31b741d513955ee3dfa3bb2 (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>
Diffstat (limited to 'yardstick')
-rw-r--r-- | yardstick/benchmark/contexts/standalone/model.py | 5 | ||||
-rw-r--r-- | yardstick/benchmark/contexts/standalone/ovs_dpdk.py | 12 | ||||
-rw-r--r-- | yardstick/network_services/vnf_generic/vnf/router_vnf.py | 185 |
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) |