summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDino Simeon Madarang <dino.simeonx.madarang@intel.com>2015-07-15 09:22:07 +0100
committerMaryam Tahhan <maryam.tahhan@intel.com>2015-08-18 14:51:12 +0000
commit3af55a78fcd572f93b1a46178bffc4c8e90534f2 (patch)
treefeece4539f8d62c4a261ff777723205b918bc32a
parentc4541ffb52274b3917c76e32733c0257ca3bbb76 (diff)
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 <dino.simeonx.madarang@intel.com> Signed-off-by: Meghan Halton <meghan.halton@intel.com> Reviewed-by: Billy O Mahony <billy.o.mahony@intel.com> Reviewed-by: Maryam Tahhan <maryam.tahhan@intel.com> Reviewed-by: Martin Klozik <martinx.klozik@intel.com>
-rwxr-xr-xconf/01_testcases.conf17
-rw-r--r--conf/04_vnf.conf32
-rw-r--r--core/component_factory.py6
-rwxr-xr-xcore/loader/loader.py39
-rw-r--r--core/vnf_controller_p2p.py6
-rw-r--r--core/vnf_controller_pvp.py21
-rw-r--r--core/vswitch_controller.py5
-rw-r--r--core/vswitch_controller_pvp.py60
-rw-r--r--docs/NEWS.md9
-rwxr-xr-xdocs/quickstart.md6
-rw-r--r--src/ovs/ofctl.py2
-rw-r--r--src/package-list.mk4
-rw-r--r--testcases/testcase.py19
-rw-r--r--vnfs/__init__.py20
-rw-r--r--vnfs/qemu/__init__.py20
-rw-r--r--vnfs/qemu/qemu_dpdk_vhost_user.py379
-rw-r--r--vnfs/vnf/__init__.py18
-rwxr-xr-xvsperf17
-rw-r--r--vswitches/ovs_dpdk_vhost.py14
19 files changed, 641 insertions, 53 deletions
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 : <command>
+ # and we only want the <command> 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)