From 3af55a78fcd572f93b1a46178bffc4c8e90534f2 Mon Sep 17 00:00:00 2001 From: Dino Simeon Madarang Date: Wed, 15 Jul 2015 09:22:07 +0100 Subject: vnfs: Enable PVP using vhost-user Enable booting of a VM with DPDK and run testpmd for PVP testing. * Added throughput and back2back tests with pvp deployment scenario in 01_testcases.conf * PVP requires DPDK 2.0 with VHOST_USER enabled and QEMU 2.2.0 * Tested on CentOS7 and Fedora 20 * Fix conflict with change 1078 Recent Changes: * Fix merge conflict (testcase.py and testcases.conf) * Remove QEMU_DIR. User must set QEMU_BIN * Set bidir traffic to True * Add flow for bi-directional traffic * Use working OVS_TAG ad2e649834be20dd01b1632799fe778106a96a2d * Merge change 1096 (src: Add QEMU makefile) * Set virtio-net-pci csum=off and other variables to off * Move hardcoded values to conf/* JIRA: VSPERF-56 Change-Id: I4ad184531064855493483d9833a7722c9f7d3576 Signed-off-by: Madarang, Dino Simeon Signed-off-by: Meghan Halton Reviewed-by: Billy O Mahony Reviewed-by: Maryam Tahhan Reviewed-by: Martin Klozik --- conf/01_testcases.conf | 17 +- conf/04_vnf.conf | 32 +++- core/component_factory.py | 6 +- core/loader/loader.py | 39 +++- core/vnf_controller_p2p.py | 6 + core/vnf_controller_pvp.py | 21 ++- core/vswitch_controller.py | 5 +- core/vswitch_controller_pvp.py | 60 ++++-- docs/NEWS.md | 9 + docs/quickstart.md | 6 + src/ovs/ofctl.py | 2 + src/package-list.mk | 4 +- testcases/testcase.py | 19 +- vnfs/__init__.py | 20 ++ vnfs/qemu/__init__.py | 20 ++ vnfs/qemu/qemu_dpdk_vhost_user.py | 379 ++++++++++++++++++++++++++++++++++++++ vnfs/vnf/__init__.py | 18 ++ vsperf | 17 +- vswitches/ovs_dpdk_vhost.py | 14 +- 19 files changed, 641 insertions(+), 53 deletions(-) create mode 100644 vnfs/__init__.py create mode 100644 vnfs/qemu/__init__.py create mode 100644 vnfs/qemu/qemu_dpdk_vhost_user.py create mode 100644 vnfs/vnf/__init__.py diff --git a/conf/01_testcases.conf b/conf/01_testcases.conf index cfe0b466..82252fb2 100755 --- a/conf/01_testcases.conf +++ b/conf/01_testcases.conf @@ -74,5 +74,20 @@ PERFORMANCE_TESTS = [ # allowed range: 0-65535; value 0 disables MultiStream feature "MultiStream": "8000", }, - + { + "Name": "pvp_tput", + "Traffic Type": "rfc2544", + "Collector": "cpu", + "Deployment": "pvp", + "Description": "LTD.Throughput.RFC2544.PacketLossRatio", + "biDirectional": "True", + }, + { + "Name": "pvp_back2back", + "Traffic Type": "back2back", + "Collector": "cpu", + "Deployment": "pvp", + "Description": "LTD.Throughput.RFC2544.BackToBackFrames", + "biDirectional": "True", + }, ] diff --git a/conf/04_vnf.conf b/conf/04_vnf.conf index 2603d589..7d1399d1 100644 --- a/conf/04_vnf.conf +++ b/conf/04_vnf.conf @@ -15,13 +15,8 @@ # ############################ # VNF configuration # ############################ -QEMU_DIR = '' - -# ############################ -# Executables -# ############################ - -QEMU_BIN = 'qemu-system-x86_64' +VNF_DIR = 'vnfs/' +VNF = 'QemuDpdkVhost' # ############################ # Guest configuration @@ -56,3 +51,26 @@ LOG_FILE_QEMU = 'qemu.log' # multiple guests will result in log files with the guest number appended LOG_FILE_GUEST_CMDS = 'guest-cmds.log' +# ############################ +# Executables +# ############################ + +QEMU_BIN = 'qemu-system-x86_64' + +# Guest shell prompt when inside DPDK dir +# for example: root@ovdk_guest DPDK]#' +QEMU_GUEST_DPDK_PROMPT = '' + +# Guest shell prompt when inside the +# test-pmd directory of DPDK +# for example: 'root@ovdk_guest test-pmd]#' +QEMU_GUEST_TEST_PMD_PROMPT = '' + +OVS_VAR_DIR = '/usr/local/var/run/openvswitch/' + +GUEST_NET1_MAC = '00:00:00:00:00:01' +GUEST_NET2_MAC = '00:00:00:00:00:02' + +GUEST_NET1_PCI_ADDRESS = '00:04.0' +GUEST_NET2_PCI_ADDRESS = '00:05.0' + diff --git a/core/component_factory.py b/core/component_factory.py index c101f5d2..1fe0964e 100644 --- a/core/component_factory.py +++ b/core/component_factory.py @@ -45,7 +45,7 @@ def create_traffic(traffic_type, trafficgen_class): return TrafficControllerRFC2544(trafficgen_class) -def create_vswitch(deployment_scenario, vswitch_class): +def create_vswitch(deployment_scenario, vswitch_class, bidir=True): """Return a new IVSwitchController for the deployment_scenario. The returned controller is configured with the given vSwitch class. @@ -62,7 +62,7 @@ def create_vswitch(deployment_scenario, vswitch_class): if deployment_scenario.find("p2p") >= 0: return VswitchControllerP2P(vswitch_class) elif deployment_scenario.find("pvp") >= 0: - return VswitchControllerPVP(vswitch_class) + return VswitchControllerPVP(vswitch_class, bidir) def create_vnf(deployment_scenario, vnf_class): """Return a new IVnfController for the deployment_scenario. @@ -79,7 +79,7 @@ def create_vnf(deployment_scenario, vnf_class): #correct controller class deployment_scenario = deployment_scenario.lower() if deployment_scenario.find("p2p") >= 0: - return VnfControllerP2P(vnf_class) + return VnfControllerP2P(None) elif deployment_scenario.find("pvp") >= 0: return VnfControllerPVP(vnf_class) diff --git a/core/loader/loader.py b/core/loader/loader.py index 57787751..39b50f09 100755 --- a/core/loader/loader.py +++ b/core/loader/loader.py @@ -20,6 +20,7 @@ from core.loader.loader_servant import LoaderServant from tools.pkt_gen.trafficgen import ITrafficGenerator from tools.collectors.collector import ICollector from vswitches.vswitch import IVSwitch +from vnfs.vnf.vnf import IVnf class Loader(object): """Loader class - main object context holder. @@ -27,6 +28,7 @@ class Loader(object): _trafficgen_loader = None _metrics_loader = None _vswitch_loader = None + _vnf_loader = None def __init__(self): """Loader ctor - initialization method. @@ -50,6 +52,11 @@ class Loader(object): settings.getValue('VSWITCH'), IVSwitch) + self._vnf_loader = LoaderServant( + settings.getValue('VNF_DIR'), + settings.getValue('VNF'), + IVnf) + def get_trafficgen(self): """Returns a new instance configured traffic generator. @@ -144,10 +151,34 @@ class Loader(object): """ return self._vswitch_loader.get_classes_printable() + def get_vnf(self): + """Returns instance of currently configured vnf implementation. + + :return: IVnf implementation if available, None otherwise. + """ + return self._vnf_loader.get_class()() + def get_vnf_class(self): - """Returns a new instance of the configured VNF + """Returns type of currently configured vnf implementation. + + :return: Type of IVnf implementation if available. + None otherwise. + """ + return self._vnf_loader.get_class() + + def get_vnfs(self): + """Returns dictionary of all available vnfs. + + :return: Dictionary of vnfs. + - key: name of the class which implements IVnf, + - value: Type of vnf which implements IVnf. + """ + return self._vnf_loader.get_classes() + + def get_vnfs_printable(self): + """Returns all available vnfs in printable format. - Currently always returns None + :return: String containing printable list of vnfs. """ - #TODO: Load the VNF class - return None + return self._vnf_loader.get_classes_printable() + diff --git a/core/vnf_controller_p2p.py b/core/vnf_controller_p2p.py index 60161480..a881d345 100644 --- a/core/vnf_controller_p2p.py +++ b/core/vnf_controller_p2p.py @@ -56,3 +56,9 @@ class VnfControllerP2P(IVnfController): """Stops nothing. """ self._logger.debug('stop with ' + str(self._vnf_class)) + + def __enter__(self): + self.start() + + def __exit__(self, type_, value, traceback): + self.stop() diff --git a/core/vnf_controller_pvp.py b/core/vnf_controller_pvp.py index 16c21869..1878db10 100644 --- a/core/vnf_controller_pvp.py +++ b/core/vnf_controller_pvp.py @@ -37,24 +37,33 @@ class VnfControllerPVP(IVnfController): :param vnf_class: The VNF class to be used. """ self._logger = logging.getLogger(__name__) - self._vnf_class = vnf_class + self._vnf_class = vnf_class() self._deployment_scenario = "PVP" - self._vnfs = [] - self._logger.debug('__init__ with ' + str(self._vnf_class)) + self._vnfs = [vnf_class(deployment=self._deployment_scenario)] + self._logger.debug('__init__ with ' + str(self._vnfs[0])) #TODO call vnf.xxx to carry out the required setup def get_vnfs(self): """See IVnfController for description """ - self._logger.debug('get_vnfs with ' + str(self._vnf_class)) + self._logger.debug('get_vnfs with ' + str(self._vnfs[0])) return self._vnfs def start(self): """See IVnfController for description """ - self._logger.debug('start with ' + str(self._vnf_class)) + self._logger.debug('start with ' + str(self._vnfs[0])) + for vnf in self._vnfs: + vnf.start() def stop(self): """See IVnfController for description """ - self._logger.debug('stop with ' + str(self._vnf_class)) + self._logger.debug('stop with ' + str(self._vnfs[0])) + self._vnfs[0].stop() + + def __enter__(self): + self.start() + + def __exit__(self, type_, value, traceback): + self.stop() diff --git a/core/vswitch_controller.py b/core/vswitch_controller.py index 1caf94fb..619e1d8b 100644 --- a/core/vswitch_controller.py +++ b/core/vswitch_controller.py @@ -20,12 +20,13 @@ class IVswitchController(object): This interface is used to setup and control a vSwitch provider for a particular deployment scenario. """ - def setup(self): + def __enter__(self): """Sets up the switch for the particular deployment scenario """ raise NotImplementedError( "The VswitchController does not implement the \"setup\" function.") - def stop(self): + + def __exit__(self, type_, value, traceback): """Tears down the switch created in setup() """ raise NotImplementedError( diff --git a/core/vswitch_controller_pvp.py b/core/vswitch_controller_pvp.py index c0286323..8c409dc2 100644 --- a/core/vswitch_controller_pvp.py +++ b/core/vswitch_controller_pvp.py @@ -18,6 +18,12 @@ import logging from core.vswitch_controller import IVswitchController +from vswitches.utils import add_ports_to_flow + +_FLOW_TEMPLATE = { + 'idle_timeout': '0' +} +BRIDGE_NAME = 'br0' class VswitchControllerPVP(IVswitchController): """VSwitch controller for PVP deployment scenario. @@ -28,7 +34,7 @@ class VswitchControllerPVP(IVswitchController): _deployment_scenario: A string describing the scenario to set-up in the constructor. """ - def __init__(self, vswitch_class): + def __init__(self, vswitch_class, bidir=False): """Initializes up the prerequisites for the PVP deployment scenario. :vswitch_class: the vSwitch class to be used. @@ -37,22 +43,55 @@ class VswitchControllerPVP(IVswitchController): self._vswitch_class = vswitch_class self._vswitch = vswitch_class() self._deployment_scenario = "PVP" + self._bidir = bidir self._logger.debug('Creation using ' + str(self._vswitch_class)) def setup(self): + """ Sets up the switch for pvp """ - Sets up the switch for the particular deployment scenario passed in to - the constructor. - """ - # TODO call IVSwitch methods to configure VSwitch for PVP scenario. self._logger.debug('Setup using ' + str(self._vswitch_class)) + try: + self._vswitch.start() + + self._vswitch.add_switch(BRIDGE_NAME) + + (_, phy1_number) = self._vswitch.add_phy_port(BRIDGE_NAME) + (_, phy2_number) = self._vswitch.add_phy_port(BRIDGE_NAME) + (_, vport1_number) = self._vswitch.add_vport(BRIDGE_NAME) + (_, vport2_number) = self._vswitch.add_vport(BRIDGE_NAME) + + self._vswitch.del_flow(BRIDGE_NAME) + flow1 = add_ports_to_flow(_FLOW_TEMPLATE, phy1_number, + vport1_number) + flow2 = add_ports_to_flow(_FLOW_TEMPLATE, vport2_number, + phy2_number) + self._vswitch.add_flow(BRIDGE_NAME, flow1) + self._vswitch.add_flow(BRIDGE_NAME, flow2) + + if self._bidir: + flow3 = add_ports_to_flow(_FLOW_TEMPLATE, phy2_number, + vport2_number) + flow4 = add_ports_to_flow(_FLOW_TEMPLATE, vport1_number, + phy1_number) + self._vswitch.add_flow(BRIDGE_NAME, flow3) + self._vswitch.add_flow(BRIDGE_NAME, flow4) + + except: + self._vswitch.stop() + raise + def stop(self): + """Tears down the switch created in setup(). """ - Tears down the switch created in setup(). - """ - # TODO call IVSwitch methods to stop VSwitch for PVP scenario. self._logger.debug('Stop using ' + str(self._vswitch_class)) + self._vswitch.stop() + + def __enter__(self): + self.setup() + + def __exit__(self, type_, value, traceback): + self.stop() def get_vswitch(self): """See IVswitchController for description @@ -63,7 +102,4 @@ class VswitchControllerPVP(IVswitchController): """See IVswitchController for description """ self._logger.debug('get_ports_info using ' + str(self._vswitch_class)) - return [] - - - + return self._vswitch.get_ports(BRIDGE_NAME) diff --git a/docs/NEWS.md b/docs/NEWS.md index 8d92c2c5..618dbebb 100644 --- a/docs/NEWS.md +++ b/docs/NEWS.md @@ -1,3 +1,12 @@ +#July 2015 + +## New + +* PVP deployment scenario testing using vhost-user as guest access method + * Verified on CentOS7 and Fedora 20 + * Requires QEMU 2.2.0 and DPDK 2.0 + + #May 2015 This is the initial release of a re-designed version of the software based on diff --git a/docs/quickstart.md b/docs/quickstart.md index 45bca6c5..b6cc3242 100755 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -36,6 +36,12 @@ cd src make WITH_LINUX=/lib/modules/`uname -r`/build ``` +To build DPDK and OVS for PVP testing, use: + +```bash +make VHOST_USER=y +``` + To delete a src subdirectory and its contents to allow you to re-clone simply use: ```bash diff --git a/src/ovs/ofctl.py b/src/ovs/ofctl.py index a2a15ce1..1c5e6513 100644 --- a/src/ovs/ofctl.py +++ b/src/ovs/ofctl.py @@ -32,6 +32,8 @@ _OVS_VSCTL_BIN = os.path.join(settings.getValue('OVS_DIR'), 'utilities', _OVS_OFCTL_BIN = os.path.join(settings.getValue('OVS_DIR'), 'utilities', 'ovs-ofctl') +_OVS_VAR_DIR = '/usr/local/var/run/openvswitch/' + class OFBase(object): """Add/remove/show datapaths using ``ovs-ofctl``. """ diff --git a/src/package-list.mk b/src/package-list.mk index cb45f9d5..cec80a2f 100644 --- a/src/package-list.mk +++ b/src/package-list.mk @@ -6,11 +6,11 @@ # dpdk section # DPDK_URL ?= git://dpdk.org/dpdk DPDK_URL ?= http://dpdk.org/git/dpdk -DPDK_TAG ?= d307f7957c9da6dee264ab7c9b349871c5a4c5fc +DPDK_TAG ?= v2.0.0 # OVS section OVS_URL ?= https://github.com/openvswitch/ovs -OVS_TAG ?= 943f394ea332837d8e5285986c5182e9746c6c62 +OVS_TAG ?= ad2e649834be20dd01b1632799fe778106a96a2d # QEMU section QEMU_URL ?= https://github.com/qemu/qemu.git diff --git a/testcases/testcase.py b/testcases/testcase.py index f5b35782..77d5992d 100644 --- a/testcases/testcase.py +++ b/testcases/testcase.py @@ -14,7 +14,6 @@ """TestCase base class """ -import time import csv import os import logging @@ -66,7 +65,8 @@ class TestCase(object): loader.get_vnf_class()) vswitch_ctl = component_factory.create_vswitch( self._deployment, - loader.get_vswitch_class()) + loader.get_vswitch_class(), + self._bidir) collector_ctl = component_factory.create_collector( self._collector, loader.get_collector_class()) @@ -75,21 +75,22 @@ class TestCase(object): self._logger.debug("Setup:") collector_ctl.log_cpu_stats() with vswitch_ctl: - if vnf_ctl: - vnf_ctl.start() + with vnf_ctl: traffic = {'traffic_type': self._traffic_type, 'bidir': self._bidir, 'multistream': self._multistream} + vswitch = vswitch_ctl.get_vswitch() if self._frame_mod == "vlan": - flow = {'table':'2', 'priority':'1000', 'metadata':'2', 'actions': ['push_vlan:0x8100','goto_table:3']} + flow = {'table':'2', 'priority':'1000', 'metadata':'2', + 'actions': ['push_vlan:0x8100', 'goto_table:3']} vswitch.add_flow('br0', flow) - flow = {'table':'2', 'priority':'1000', 'metadata':'1', 'actions': ['push_vlan:0x8100','goto_table:3']} + flow = {'table':'2', 'priority':'1000', 'metadata':'1', + 'actions': ['push_vlan:0x8100', 'goto_table:3']} vswitch.add_flow('br0', flow) - with traffic_ctl: - traffic_ctl.send_traffic(traffic) - + with traffic_ctl: + traffic_ctl.send_traffic(traffic) self._logger.debug("Traffic Results:") traffic_ctl.print_results() diff --git a/vnfs/__init__.py b/vnfs/__init__.py new file mode 100644 index 00000000..34cacf4f --- /dev/null +++ b/vnfs/__init__.py @@ -0,0 +1,20 @@ +# Copyright 2015 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. + +"""Package for vnf wrappers for use with VSPERF. + +This package contains an interface the VSPERF core uses for controlling +VNFs and VNF-specific implementation modules of this interface. +""" + diff --git a/vnfs/qemu/__init__.py b/vnfs/qemu/__init__.py new file mode 100644 index 00000000..82f32eb9 --- /dev/null +++ b/vnfs/qemu/__init__.py @@ -0,0 +1,20 @@ +# Copyright 2015 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. + +"""Package for vnf wrappers for use with VSPERF. + +This package contains an implementation of the interface the VSPERF core +uses for controlling VNFs using QEMU and DPDK's testpmd application. +""" + diff --git a/vnfs/qemu/qemu_dpdk_vhost_user.py b/vnfs/qemu/qemu_dpdk_vhost_user.py new file mode 100644 index 00000000..a3b96b18 --- /dev/null +++ b/vnfs/qemu/qemu_dpdk_vhost_user.py @@ -0,0 +1,379 @@ +# Copyright 2015 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. + +"""Automation of QEMU hypervisor for launching vhost-user enabled guests. +""" + +import os +import time +import logging +import locale +import re +import subprocess + +from tools import tasks +from conf import settings +from vnfs.vnf.vnf import IVnf + +_QEMU_BIN = settings.getValue('QEMU_BIN') +_RTE_TARGET = settings.getValue('RTE_TARGET') + +GUEST_MEMORY = '4096M' +GUEST_SMP = '2' +GUEST_CORE_BINDING = [(4, 5), (6, 7), (9, 10)] + +GUEST_IMAGE = settings.getValue('GUEST_IMAGE') +GUEST_SHARE_DIR = settings.getValue('GUEST_SHARE_DIR') + +GUEST_USERNAME = settings.getValue('GUEST_USERNAME') +GUEST_PASSWORD = settings.getValue('GUEST_PASSWORD') + +GUEST_PROMPT_LOGIN = settings.getValue('GUEST_PROMPT_LOGIN') +GUEST_PROMPT_PASSWORD = settings.getValue('GUEST_PROMPT_PASSWORD') +GUEST_PROMPT = settings.getValue('GUEST_PROMPT') + +_QEMU_GUEST_DPDK_PROMPT = settings.getValue('QEMU_GUEST_DPDK_PROMPT') +_QEMU_GUEST_TEST_PMD_PROMPT = settings.getValue('QEMU_GUEST_TEST_PMD_PROMPT') +_HUGEPAGE_DIR = settings.getValue('HUGEPAGE_DIR') + +_GUEST_OVS_DPDK_DIR = '/root/ovs_dpdk' +_OVS_DPDK_SHARE = '/mnt/ovs_dpdk_share' + +LOG_FILE_QEMU = os.path.join( + settings.getValue('LOG_DIR'), settings.getValue('LOG_FILE_QEMU')) +LOG_FILE_GUEST_CMDS = os.path.join( + settings.getValue('LOG_DIR'), settings.getValue('LOG_FILE_GUEST_CMDS')) + +VHOST_DEV_PATH = os.path.join('/dev', settings.getValue('VHOST_DEV_FILE')) + +_OVS_VAR_DIR = settings.getValue('OVS_VAR_DIR') +_GUEST_NET1_MAC = settings.getValue('GUEST_NET1_MAC') +_GUEST_NET2_MAC = settings.getValue('GUEST_NET2_MAC') +_GUEST_NET1_PCI_ADDRESS = settings.getValue('GUEST_NET1_PCI_ADDRESS') +_GUEST_NET2_PCI_ADDRESS = settings.getValue('GUEST_NET2_PCI_ADDRESS') + +class QemuDpdkVhost(tasks.Process, IVnf): + """ + Control an instance of QEMU with vHost user guest communication. + """ + _bin = _QEMU_BIN + _logfile = LOG_FILE_QEMU + _cmd = None + _expect = GUEST_PROMPT_LOGIN + _proc_name = 'qemu' + _number_vnfs = 0 + + class GuestCommandFilter(logging.Filter): + """ + Filter out strings beginning with 'guestcmd :'. + """ + def filter(self, record): + return record.getMessage().startswith(self.prefix) + + def __init__(self, memory=GUEST_MEMORY, cpus=GUEST_SMP, + monitor_path='/tmp', shared_path_host=GUEST_SHARE_DIR, + args='', timeout=120, deployment="P2P"): + """ + Initialisation function. + + :param timeout: Time to wait for login prompt. If set to + 0 do not wait. + :param number: Number of QEMU instance, used when multiple QEMU + instances are started at once. + :param args: Arguments to pass to QEMU. + + :returns: None + """ + self._logger = logging.getLogger(__name__) + self._number = self._number_vnfs + self._number_vnfs = self._number_vnfs + 1 + self._logfile = self._logfile + str(self._number) + self._log_prefix = 'guest_%d_cmd : ' % self._number + self._timeout = timeout + self._monitor = '%s/vm%dmonitor' % (monitor_path, self._number) + + name = 'Client%d' % self._number + vnc = ':%d' % self._number + self._cmd = ['sudo', '-E', self._bin, '-m', str(memory), + '-smp', str(cpus), '-cpu', 'host', + '-drive', 'if=scsi,file='+GUEST_IMAGE, + '-drive', + 'if=scsi,file=fat:rw:%s,snapshot=off' % shared_path_host, + '-boot', 'c', '--enable-kvm', '-pidfile', '/tmp/vm1.pid', + '-monitor', 'unix:%s,server,nowait' % self._monitor, + '-object', + 'memory-backend-file,id=mem,size=4096M,' + + 'mem-path=' + _HUGEPAGE_DIR + ',share=on', + '-numa', 'node,memdev=mem -mem-prealloc', + '-chardev', + 'socket,id=char0,path=' + _OVS_VAR_DIR + 'dpdkvhostuser0', + '-chardev', + 'socket,id=char1,path=' + _OVS_VAR_DIR + 'dpdkvhostuser1', + '-netdev', + 'type=vhost-user,id=net1,chardev=char0,vhostforce', + '-device', + 'virtio-net-pci,mac=' + _GUEST_NET1_MAC + + ',netdev=net1,csum=off,gso=off,' + + 'guest_tso4=off,guest_tso6=off,guest_ecn=off', + '-netdev', + 'type=vhost-user,id=net2,chardev=char1,vhostforce', + '-device', + 'virtio-net-pci,mac=' + _GUEST_NET2_MAC + + ',netdev=net2,csum=off,gso=off,' + + 'guest_tso4=off,guest_tso6=off,guest_ecn=off', + '-nographic', '-vnc', str(vnc), '-name', name, + '-snapshot', + ] + self._cmd.extend(args) + self._configure_logging() + + def _configure_logging(self): + """ + Configure logging. + """ + self.GuestCommandFilter.prefix = self._log_prefix + + logger = logging.getLogger() + cmd_logger = logging.FileHandler( + filename=LOG_FILE_GUEST_CMDS + str(self._number)) + cmd_logger.setLevel(logging.DEBUG) + cmd_logger.addFilter(self.GuestCommandFilter()) + logger.addHandler(cmd_logger) + + # startup/Shutdown + + def start(self): + """ + Start QEMU instance, login and prepare for commands. + """ + super(QemuDpdkVhost, self).start() + self._affinitize() + + if self._timeout: + self._login() + self._config_guest_loopback() + + def stop(self): + """ + Kill QEMU instance if it is alive. + """ + self._logger.info('Killing QEMU...') + + super(QemuDpdkVhost, self).kill() + + # helper functions + + def _login(self, timeout=120): + """ + Login to QEMU instance. + + This can be used immediately after booting the machine, provided a + sufficiently long ``timeout`` is given. + + :param timeout: Timeout to wait for login to complete. + + :returns: None + """ + # if no timeout was set, we likely started QEMU without waiting for it + # to boot. This being the case, we best check that it has finished + # first. + if not self._timeout: + self._expect_process(timeout=timeout) + + self._child.sendline(GUEST_USERNAME) + self._child.expect(GUEST_PROMPT_PASSWORD, timeout=5) + self._child.sendline(GUEST_PASSWORD) + + self._expect_process(GUEST_PROMPT, timeout=5) + + def execute(self, cmd, delay=0): + """ + Send ``cmd`` with no wait. + + Useful for asynchronous commands. + + :param cmd: Command to send to guest. + :param timeout: Delay to wait after sending command before returning. + + :returns: None + """ + self._logger.debug('%s%s', self._log_prefix, cmd) + self._child.sendline(cmd) + time.sleep(delay) + + def wait(self, msg=GUEST_PROMPT, timeout=30): + """ + Wait for ``msg``. + + :param msg: Message to wait for from guest. + :param timeout: Time to wait for message. + + :returns: None + """ + self._child.expect(msg, timeout=timeout) + + def execute_and_wait(self, cmd, timeout=30, prompt=GUEST_PROMPT): + """ + Send ``cmd`` and wait ``timeout`` seconds for prompt. + + :param cmd: Command to send to guest. + :param timeout: Time to wait for prompt. + + :returns: None + """ + self.execute(cmd) + self.wait(prompt, timeout=timeout) + + def send_and_pass(self, cmd, timeout=30): + """ + Send ``cmd`` and wait ``timeout`` seconds for it to pass. + + :param cmd: Command to send to guest. + :param timeout: Time to wait for prompt before checking return code. + + :returns: None + """ + self.execute(cmd) + self.wait(GUEST_PROMPT, timeout=timeout) + self.execute('echo $?') + self._child.expect('^0$', timeout=1) # expect a 0 + self.wait(GUEST_PROMPT, timeout=timeout) + + def _affinitize(self): + """ + Affinitize the SMP cores of a QEMU instance. + + This is a bit of a hack. The 'socat' utility is used to + interact with the QEMU HMP. This is necessary due to the lack + of QMP in older versions of QEMU, like v1.6.2. In future + releases, this should be replaced with calls to libvirt or + another Python-QEMU wrapper library. + + :returns: None + """ + thread_id = (r'.* CPU #%d: .* thread_id=(\d+)') + + self._logger.info('Affinitizing guest...') + + cur_locale = locale.getlocale()[1] + proc = subprocess.Popen( + ('echo', 'info cpus'), stdout=subprocess.PIPE) + output = subprocess.check_output( + ('sudo', 'socat', '-', 'UNIX-CONNECT:%s' % self._monitor), + stdin=proc.stdout) + proc.wait() + + for cpu in range(0, int(GUEST_SMP)): + match = None + for line in output.decode(cur_locale).split('\n'): + match = re.search(thread_id % cpu, line) + if match: + self._affinitize_pid( + GUEST_CORE_BINDING[self._number - 1][cpu], + match.group(1)) + break + + if not match: + self._logger.error('Failed to affinitize guest core #%d. Could' + ' not parse tid.', cpu) + + def _config_guest_loopback(self): + '''# mount hugepages + # Guest images _should_ have 1024 hugepages by default, + # but just in case:''' + self.execute_and_wait('sysctl vm.nr_hugepages=1024') + self.execute_and_wait('mkdir -p /dev/hugepages') + self.execute_and_wait( + 'mount -t hugetlbfs hugetlbfs /dev/hugepages') + + # mount shared directory + self.execute_and_wait('umount ' + _OVS_DPDK_SHARE) + self.execute_and_wait('rm -rf ' + _GUEST_OVS_DPDK_DIR) + self.execute_and_wait('mkdir -p ' + _OVS_DPDK_SHARE) + self.execute_and_wait('mount -o iocharset=utf8 /dev/sdb1 ' + + _OVS_DPDK_SHARE) + self.execute_and_wait('mkdir -p ' + _GUEST_OVS_DPDK_DIR) + self.execute_and_wait('cp -a ' + _OVS_DPDK_SHARE + '/* ' + _GUEST_OVS_DPDK_DIR) + # Get VM info + self.execute_and_wait('cat /etc/default/grub') + + # Disable services (F16) + self.execute_and_wait('systemctl status iptables.service') + self.execute_and_wait('systemctl stop iptables.service') + + # build and configure system for dpdk + self.execute_and_wait('cd ' + _GUEST_OVS_DPDK_DIR + '/DPDK', + prompt=_QEMU_GUEST_DPDK_PROMPT) + + self.execute_and_wait('export CC=gcc', prompt=_QEMU_GUEST_DPDK_PROMPT) + self.execute_and_wait('export RTE_SDK=' + _GUEST_OVS_DPDK_DIR + '/DPDK', + prompt=_QEMU_GUEST_DPDK_PROMPT) + self.execute_and_wait('export RTE_TARGET=%s' % _RTE_TARGET, + prompt=_QEMU_GUEST_DPDK_PROMPT) + + self.execute_and_wait('make uninstall', prompt=_QEMU_GUEST_DPDK_PROMPT) + self.execute_and_wait('make install T=%s -j 2' % _RTE_TARGET, + timeout=300, prompt=_QEMU_GUEST_DPDK_PROMPT) + + self.execute_and_wait('modprobe uio', prompt=_QEMU_GUEST_DPDK_PROMPT) + self.execute_and_wait('insmod %s/kmod/igb_uio.ko' % _RTE_TARGET, + prompt=_QEMU_GUEST_DPDK_PROMPT) + self.execute_and_wait('./tools/dpdk_nic_bind.py --status', + prompt=_QEMU_GUEST_DPDK_PROMPT) + self.execute_and_wait('./tools/dpdk_nic_bind.py -b igb_uio' + ' ' + _GUEST_NET1_PCI_ADDRESS + ' ' + + _GUEST_NET2_PCI_ADDRESS, + prompt=_QEMU_GUEST_DPDK_PROMPT) + + # build and run 'test-pmd' + self.execute_and_wait('cd ' + _GUEST_OVS_DPDK_DIR + + '/DPDK/app/test-pmd', + prompt=_QEMU_GUEST_TEST_PMD_PROMPT) + self.execute_and_wait('make clean', prompt=_QEMU_GUEST_TEST_PMD_PROMPT) + self.execute_and_wait('make', prompt=_QEMU_GUEST_TEST_PMD_PROMPT) + self.execute_and_wait('./testpmd -c 0x3 -n 4 --socket-mem 512 --' + ' --burst=64 -i --txqflags=0xf00 ' + + '--disable-hw-vlan', 20, "Done") + self.execute('set fwd mac_retry', 1) + self.execute_and_wait('start', 20, + 'TX RS bit threshold=0 - TXQ flags=0xf00') + + +if __name__ == '__main__': + import sys + + with QemuDpdkVhost() as vm1: + print( + '\n\n************************\n' + 'Basic command line suitable for ls, cd, grep and cat.\n If you' + ' try to run Vim from here you\'re going to have a bad time.\n' + 'For more complex tasks please use \'vncviewer :1\' to connect to' + ' this VM\nUsername: %s Password: %s\nPress ctrl-C to quit\n' + '************************\n' % (GUEST_USERNAME, GUEST_PASSWORD)) + + if sys.argv[1]: + with open(sys.argv[1], 'r') as file_: + for logline in file_: + # lines are of format: + # guest_N_cmd : + # and we only want the piece + cmdline = logline.split(':')[1].strip() + + # use a no timeout since we don't know how long we + # should wait + vm1.send_and_wait(cmdline, timeout=-1) + + while True: + USER_INPUT = input() + vm1.send_and_wait(USER_INPUT, timeout=5) diff --git a/vnfs/vnf/__init__.py b/vnfs/vnf/__init__.py new file mode 100644 index 00000000..b7c43217 --- /dev/null +++ b/vnfs/vnf/__init__.py @@ -0,0 +1,18 @@ +# Copyright 2015 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. + +"""VNF interface and helpers. +""" + +from vnfs import * diff --git a/vsperf b/vsperf index 34f42ca5..67141a7e 100755 --- a/vsperf +++ b/vsperf @@ -35,6 +35,8 @@ from testcases import TestCase from tools import tasks from tools.collectors import collector from tools.pkt_gen import trafficgen +from vswitches import vswitch +from vnfs import vnf VERBOSITY_LEVELS = { 'debug': logging.DEBUG, @@ -117,6 +119,8 @@ def parse_arguments(): help='list all system metrics loggers and exit') parser.add_argument('--list-vswitches', action='store_true', help='list all system vswitches and exit') + parser.add_argument('--list-vnfs', action='store_true', + help='list all system vnfs and exit') parser.add_argument('--list-settings', action='store_true', help='list effective settings configuration and exit') parser.add_argument('test', nargs='*', help='test specification(s)') @@ -131,6 +135,7 @@ def parse_arguments(): help='debug level') group.add_argument('--trafficgen', help='traffic generator to use') group.add_argument('--vswitch', help='vswitch implementation to use') + group.add_argument('--vnf', help='vnf to use') group.add_argument('--sysmetrics', help='system metrics logger to use') group = parser.add_argument_group('test behavior options') group.add_argument('--xunit', action='store_true', @@ -311,7 +316,13 @@ def main(): settings.getValue('VSWITCH_DIR')) sys.exit(1) - + if args['vnf']: + vnfs = Loader().get_vnfs() + if args['vnf'] not in vnfs: + logging.error('there are no vnfs matching \'%s\' found in' + ' \'%s\'. exiting...', args['vnf'], + settings.getValue('vnf_dir')) + sys.exit(1) # generate results directory name date = datetime.datetime.fromtimestamp(time.time()) @@ -353,6 +364,10 @@ def main(): print(Loader().get_vswitches_printable()) exit() + if args['list_vnfs']: + print(Loader().get_vnfs_printable()) + exit() + if args['list_settings']: print(str(settings)) exit() diff --git a/vswitches/ovs_dpdk_vhost.py b/vswitches/ovs_dpdk_vhost.py index 7b5034c5..3ff41260 100644 --- a/vswitches/ovs_dpdk_vhost.py +++ b/vswitches/ovs_dpdk_vhost.py @@ -20,7 +20,7 @@ from vswitches.vswitch import IVSwitch from src.ovs import VSwitchd, OFBridge from src.dpdk import dpdk -VSWITCHD_CONST_ARGS = ['--', '--log-file'] +_VSWITCHD_CONST_ARGS = ['--', '--log-file'] class OvsDpdkVhost(IVSwitch): """VSwitch implementation using DPDK and vhost ports @@ -36,10 +36,11 @@ class OvsDpdkVhost(IVSwitch): def __init__(self): vswitchd_args = ['--dpdk'] vswitchd_args += settings.getValue('VSWITCHD_DPDK_ARGS') - vswitchd_args += VSWITCHD_CONST_ARGS + vswitchd_args += _VSWITCHD_CONST_ARGS self._vswitchd = VSwitchd(vswitchd_args=vswitchd_args, - expected_cmd=r'EAL: Master l*core \d+ is ready') + expected_cmd= + r'EAL: Master l*core \d+ is ready') self._bridges = {} def start(self): @@ -98,9 +99,10 @@ class OvsDpdkVhost(IVSwitch): from 0 """ bridge = self._bridges[switch_name] - vhost_count = self._get_port_count(bridge, 'type=dpdkvhost') - port_name = 'dpdkvhost' + str(vhost_count) - params = ['--', 'set', 'Interface', port_name, 'type=dpdkvhost'] + # Changed dpdkvhost to dpdkvhostuser to be able to run in Qemu 2.2 + vhost_count = self._get_port_count(bridge, 'type=dpdkvhostuser') + port_name = 'dpdkvhostuser' + str(vhost_count) + params = ['--', 'set', 'Interface', port_name, 'type=dpdkvhostuser'] of_port = bridge.add_port(port_name, params) return (port_name, of_port) -- cgit 1.2.3-korg