aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xconf/01_testcases.conf16
-rw-r--r--conf/02_vswitch.conf3
-rw-r--r--conf/04_vnf.conf39
-rw-r--r--core/__init__.py1
-rw-r--r--core/component_factory.py20
-rw-r--r--core/loader/loader_servant.py6
-rw-r--r--core/vnf_controller.py59
-rw-r--r--core/vnf_controller_p2p.py64
-rw-r--r--core/vnf_controller_pvp.py69
-rw-r--r--core/vswitch_controller_p2p.py28
-rw-r--r--core/vswitch_controller_pvp.py25
-rw-r--r--core/vswitch_controller_pvvp.py114
-rw-r--r--docs/to-be-reorganized/NEWS.rst2
-rw-r--r--docs/to-be-reorganized/quickstart.rst12
-rw-r--r--src/dpdk/dpdk.py38
-rw-r--r--src/ovs/ofctl.py11
-rw-r--r--testcases/testcase.py46
-rw-r--r--tools/tasks.py9
-rw-r--r--vnfs/qemu/qemu.py203
-rw-r--r--vnfs/qemu/qemu_dpdk.py115
-rw-r--r--vnfs/qemu/qemu_dpdk_vhost_cuse.py390
-rw-r--r--vnfs/qemu/qemu_dpdk_vhost_user.py387
-rw-r--r--vnfs/vnf/vnf.py70
-rw-r--r--vswitches/ovs_dpdk_vhost.py6
24 files changed, 728 insertions, 1005 deletions
diff --git a/conf/01_testcases.conf b/conf/01_testcases.conf
index 815e3a76..39e924bd 100755
--- a/conf/01_testcases.conf
+++ b/conf/01_testcases.conf
@@ -116,6 +116,22 @@ PERFORMANCE_TESTS = [
"biDirectional": "True",
},
{
+ "Name": "pvvp_tput",
+ "Traffic Type": "rfc2544",
+ "Collector": "cpu",
+ "Deployment": "pvvp",
+ "Description": "LTD.Throughput.RFC2544.PacketLossRatio",
+ "biDirectional": "True",
+ },
+ {
+ "Name": "pvvp_back2back",
+ "Traffic Type": "back2back",
+ "Collector": "cpu",
+ "Deployment": "pvvp",
+ "Description": "LTD.Throughput.RFC2544.BackToBackFrames",
+ "biDirectional": "True",
+ },
+ {
"Name": "phy2phy_cpu_load",
"Traffic Type": "rfc2544",
"Deployment": "p2p",
diff --git a/conf/02_vswitch.conf b/conf/02_vswitch.conf
index 73c42a9a..32f4c0e1 100644
--- a/conf/02_vswitch.conf
+++ b/conf/02_vswitch.conf
@@ -42,6 +42,9 @@ VSWITCHD_VANILLA_ARGS = ['--pidfile']
VSWITCH_VANILLA_PHY_PORT_NAMES = ['', '']
VSWITCH_VANILLA_KERNEL_MODULES = ['openvswitch']
+# Bridge name to be used by VSWTICH
+VSWITCH_BRIDGE_NAME = 'br0'
+
# directory where hugepages will be mounted on system init
HUGEPAGE_DIR = '/dev/hugepages'
diff --git a/conf/04_vnf.conf b/conf/04_vnf.conf
index 1c3712ed..034e4108 100644
--- a/conf/04_vnf.conf
+++ b/conf/04_vnf.conf
@@ -16,18 +16,20 @@
# VNF configuration
# ############################
VNF_DIR = 'vnfs/'
-VNF = 'QemuDpdkVhost'
+VNF = 'QemuDpdkVhostUser'
# ############################
# Guest configuration
# ############################
# directory which is shared to QEMU guests. Useful for exchanging files
-# between host and guest
-GUEST_SHARE_DIR = '/tmp/qemu_share'
+# between host and guest, VNF specific share will be created
+# For 2 VNFs you may use ['/tmp/qemu0_share', '/tmp/qemu1_share']
+GUEST_SHARE_DIR = ['/tmp/qemu0_share', '/tmp/qemu1_share']
# location of guest disk image
-GUEST_IMAGE = ''
+# For 2 VNFs you may use ['guest1.img', 'guest2.img']
+GUEST_IMAGE = ['', '']
# username for guest image
GUEST_USERNAME = ''
@@ -57,34 +59,25 @@ LOG_FILE_GUEST_CMDS = 'guest-cmds.log'
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'
+# For 2 VNFs you may use ['00:00:00:00:00:01', '00:00:00:00:00:03']
+GUEST_NET1_MAC = ['00:00:00:00:00:01', '00:00:00:00:00:03']
+GUEST_NET2_MAC = ['00:00:00:00:00:02', '00:00:00:00:00:04']
-GUEST_NET1_PCI_ADDRESS = '00:04.0'
-GUEST_NET2_PCI_ADDRESS = '00:05.0'
-GUEST_MEMORY = '3072'
+# For 2 VNFs you may use ['00:04.0', '00:04.0']
+GUEST_NET1_PCI_ADDRESS = ['00:04.0', '00:04.0']
+GUEST_NET2_PCI_ADDRESS = ['00:05.0', '00:05.0']
+
+GUEST_MEMORY = ['4096', '4096']
# test-pmd requires 2 VM cores
-GUEST_SMP = '2'
+GUEST_SMP = ['2', '2']
# Host cores to use to affinitize the SMP cores of a QEMU instance
# For 2 VNFs you may use [(4,5), (6, 7)]
-GUEST_CORE_BINDING = [(4, 5)]
-
-# Starte Qemu on cores 3, 4,5 (0x038)
-QEMU_CORE = '38'
+GUEST_CORE_BINDING = [(6, 7), (9, 10)]
GUEST_OVS_DPDK_DIR = '/root/ovs_dpdk'
OVS_DPDK_SHARE = '/mnt/ovs_dpdk_share'
diff --git a/core/__init__.py b/core/__init__.py
index 0b41aaca..e39ec88e 100644
--- a/core/__init__.py
+++ b/core/__init__.py
@@ -16,5 +16,4 @@
"""
import core.component_factory
from core.traffic_controller import (ITrafficController)
-from core.vnf_controller import (IVnfController)
from core.vswitch_controller import (IVswitchController)
diff --git a/core/component_factory.py b/core/component_factory.py
index e8bb4de3..4da37fb7 100644
--- a/core/component_factory.py
+++ b/core/component_factory.py
@@ -18,8 +18,8 @@
from core.traffic_controller_rfc2544 import TrafficControllerRFC2544
from core.vswitch_controller_p2p import VswitchControllerP2P
from core.vswitch_controller_pvp import VswitchControllerPVP
-from core.vnf_controller_p2p import VnfControllerP2P
-from core.vnf_controller_pvp import VnfControllerPVP
+from core.vswitch_controller_pvvp import VswitchControllerPVVP
+from core.vnf_controller import VnfController
from tools.load_gen.stress.stress import Stress
from tools.load_gen.stress_ng.stress_ng import StressNg
from tools.load_gen.dummy.dummy import DummyLoadGen
@@ -58,16 +58,16 @@ def create_vswitch(deployment_scenario, vswitch_class, bidir=True):
:param vswitch_class: Reference to vSwitch class to be used.
:return: IVSwitchController for the deployment_scenario
"""
- #TODO - full mapping from all deployment_scenarios to
- #correct controller class
deployment_scenario = deployment_scenario.lower()
if deployment_scenario.find("p2p") >= 0:
return VswitchControllerP2P(vswitch_class)
elif deployment_scenario.find("pvp") >= 0:
return VswitchControllerPVP(vswitch_class, bidir)
+ elif deployment_scenario.find("pvvp") >= 0:
+ return VswitchControllerPVVP(vswitch_class, bidir)
def create_vnf(deployment_scenario, vnf_class):
- """Return a new IVnfController for the deployment_scenario.
+ """Return a new VnfController for the deployment_scenario.
The returned controller is configured with the given VNF class.
@@ -75,15 +75,9 @@ def create_vnf(deployment_scenario, vnf_class):
:param deployment_scenario: The deployment scenario name
:param vswitch_class: Reference to vSwitch class to be used.
- :return: IVnfController for the deployment_scenario
+ :return: VnfController for the deployment_scenario
"""
- #TODO - full mapping from all deployment_scenarios to
- #correct controller class
- deployment_scenario = deployment_scenario.lower()
- if deployment_scenario.find("p2p") >= 0:
- return VnfControllerP2P(None)
- elif deployment_scenario.find("pvp") >= 0:
- return VnfControllerPVP(vnf_class)
+ return VnfController(deployment_scenario, vnf_class)
def create_collector(collector_class, result_dir, test_name):
"""Return a new Collector of the given class
diff --git a/core/loader/loader_servant.py b/core/loader/loader_servant.py
index 7966532c..3b729c23 100644
--- a/core/loader/loader_servant.py
+++ b/core/loader/loader_servant.py
@@ -138,10 +138,12 @@ class LoaderServant(object):
result = {}
for _, mod in LoaderServant._load_all_modules(path):
- # find all system metric loggers defined in the module
+ # find all classes derived from given interface, but suppress
+ # interface itself and any abstract class starting with iface name
gens = dict((k, v) for (k, v) in list(mod.__dict__.items())
if type(v) == type and
- issubclass(v, interface) and k != interface.__name__)
+ issubclass(v, interface) and
+ not k.startswith(interface.__name__))
if gens:
for (genname, gen) in list(gens.items()):
result[genname] = gen
diff --git a/core/vnf_controller.py b/core/vnf_controller.py
index be1c7c4a..3d3be040 100644
--- a/core/vnf_controller.py
+++ b/core/vnf_controller.py
@@ -14,35 +14,66 @@
""" VNF Controller interface
"""
-class IVnfController(object):
- """Abstract class which defines a VNF controller
+import logging
- Used to set-up and control a VNF provider for a particular
- deployment scenario.
+class VnfController(object):
+ """VNF controller class
+
+ Used to set-up and control VNFs for specified scenario
+
+ Attributes:
+ _vnf_class: A class object representing the VNF to be used.
+ _deployment_scenario: A string describing the scenario to set-up in the
+ constructor.
+ _vnfs: A list of vnfs controlled by the controller.
"""
+ def __init__(self, deployment_scenario, vnf_class):
+ """Sets up the VNF infrastructure for the PVP deployment scenario.
+
+ :param vnf_class: The VNF class to be used.
+ """
+ self._logger = logging.getLogger(__name__)
+ self._vnf_class = vnf_class
+ self._deployment_scenario = deployment_scenario.upper()
+ if self._deployment_scenario == 'P2P':
+ self._vnfs = []
+ if self._deployment_scenario == 'PVP':
+ self._vnfs = [vnf_class()]
+ elif self._deployment_scenario == 'PVVP':
+ self._vnfs = [vnf_class(), vnf_class()]
+ self._logger.debug('__init__ ' + str(len(self._vnfs)) +
+ ' VNF[s] with ' + ' '.join(map(str, self._vnfs)))
+
def get_vnfs(self):
"""Returns a list of vnfs controlled by this controller.
"""
- raise NotImplementedError(
- "The VnfController does not implement",
- "the \"get_vnfs\" function.")
+ self._logger.debug('get_vnfs ' + str(len(self._vnfs)) +
+ ' VNF[s] with ' + ' '.join(map(str, self._vnfs)))
+ return self._vnfs
- #TODO: Decide on contextmanager or __enter/exit__ strategy <MH 2015-05-01>
def start(self):
"""Boots all VNFs set-up by __init__.
This is a blocking function.
"""
- raise NotImplementedError(
- "The VnfController does not implement",
- "the \"start\" function.")
+ self._logger.debug('start ' + str(len(self._vnfs)) +
+ ' VNF[s] with ' + ' '.join(map(str, self._vnfs)))
+ for vnf in self._vnfs:
+ vnf.start()
def stop(self):
"""Stops all VNFs set-up by __init__.
This is a blocking function.
"""
- raise NotImplementedError(
- "The VnfController does not implement",
- "the \"stop\" function.")
+ self._logger.debug('stop ' + str(len(self._vnfs)) +
+ ' VNF[s] with ' + ' '.join(map(str, self._vnfs)))
+ for vnf in self._vnfs:
+ vnf.stop()
+
+ def __enter__(self):
+ self.start()
+
+ def __exit__(self, type_, value, traceback):
+ self.stop()
diff --git a/core/vnf_controller_p2p.py b/core/vnf_controller_p2p.py
deleted file mode 100644
index a881d345..00000000
--- a/core/vnf_controller_p2p.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# 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 Controller for the P2P scenario
-"""
-
-import logging
-
-from core.vnf_controller import IVnfController
-
-class VnfControllerP2P(IVnfController):
- """VNF controller for the P2P scenario.
-
- Does nothing as there is no VNF in P2P
-
- Attributes:
- _vnf_class: A class object representing the VNF to be used.
- _deployment_scenario: A string describing the scenario to set-up in the
- constructor.
- _vnfs: A list of vnfs controlled by the controller.
- """
-
- #TODO: Decide on contextmanager or __enter/exit__ strategy <MH 2015-05-01>
- def __init__(self, vnf_class):
- """Sets up the VNF infrastructure for the P2P deployment scenario.
-
- :param vnf_class: The VNF class to be used, this is mostly ignored.
- """
- self._logger = logging.getLogger(__name__)
- self._vnf_class = vnf_class
- self._deployment_scenario = "P2P"
- self._logger.debug('__init__ with ' + str(self._vnf_class))
-
- def get_vnfs(self):
- """Returns an empty list of vnfs.
- """
- self._logger.debug('get_vnfs with ' + str(self._vnf_class))
- return []
-
- def start(self):
- """Starts nothing.
- """
- self._logger.debug('start with ' + str(self._vnf_class))
-
- def stop(self):
- """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
deleted file mode 100644
index 1878db10..00000000
--- a/core/vnf_controller_pvp.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# 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 Controller for the PVP scenario
-"""
-
-import logging
-
-from core.vnf_controller import IVnfController
-
-class VnfControllerPVP(IVnfController):
- """VNF controller for the PVP scenario.
-
- Used to set-up and control a VNF provider for the PVP scenario.
-
- Attributes:
- _vnf_class: A class object representing the VNF to be used.
- _deployment_scenario: A string describing the scenario to set-up in the
- constructor.
- _vnfs: A list of vnfs controlled by the controller.
- """
-
- #TODO: Decide on contextmanager or __enter/exit__ strategy <MH 2015-05-01>
- def __init__(self, vnf_class):
- """Sets up the VNF infrastructure for the PVP deployment scenario.
-
- :param vnf_class: The VNF class to be used.
- """
- self._logger = logging.getLogger(__name__)
- self._vnf_class = vnf_class()
- self._deployment_scenario = "PVP"
- 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._vnfs[0]))
- return self._vnfs
-
- def start(self):
- """See IVnfController for description
- """
- 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._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_p2p.py b/core/vswitch_controller_p2p.py
index a1158d4e..c15f7ef8 100644
--- a/core/vswitch_controller_p2p.py
+++ b/core/vswitch_controller_p2p.py
@@ -18,12 +18,11 @@
import logging
from core.vswitch_controller import IVswitchController
-from vswitches.utils import add_ports_to_flow
+from conf import settings
_FLOW_TEMPLATE = {
'idle_timeout': '0'
}
-BRIDGE_NAME = 'br0'
class VswitchControllerP2P(IVswitchController):
"""VSwitch controller for P2P deployment scenario.
@@ -53,41 +52,42 @@ class VswitchControllerP2P(IVswitchController):
try:
self._vswitch.start()
- self._vswitch.add_switch(BRIDGE_NAME)
+ bridge = settings.getValue('VSWITCH_BRIDGE_NAME')
+ self._vswitch.add_switch(bridge)
- (_, phy1_number) = self._vswitch.add_phy_port(BRIDGE_NAME)
- (_, phy2_number) = self._vswitch.add_phy_port(BRIDGE_NAME)
+ (_, _) = self._vswitch.add_phy_port(bridge)
+ (_, _) = self._vswitch.add_phy_port(bridge)
- self._vswitch.del_flow(BRIDGE_NAME)
+ self._vswitch.del_flow(bridge)
# table#0 - flows designed to force 5 & 13 tuple matches go here
flow = {'table':'0', 'priority':'1', 'actions': ['goto_table:1']}
- self._vswitch.add_flow(BRIDGE_NAME, flow)
+ self._vswitch.add_flow(bridge, flow)
# table#1 - flows to route packets between ports goes here. The
# chosen port is communicated to subsequent tables by setting the
# metadata value to the egress port number
flow = {'table':'1', 'priority':'1', 'in_port':'1',
'actions': ['write_actions(output:2)', 'write_metadata:2',
- 'goto_table:2']}
- self._vswitch.add_flow(BRIDGE_NAME, flow)
+ 'goto_table:2']}
+ self._vswitch.add_flow(bridge, flow)
flow = {'table':'1', 'priority':'1', 'in_port':'2',
'actions': ['write_actions(output:1)', 'write_metadata:1',
- 'goto_table:2']}
- self._vswitch.add_flow(BRIDGE_NAME, flow)
+ 'goto_table:2']}
+ self._vswitch.add_flow(bridge, flow)
# Frame modification table. Frame modification flow rules are
# isolated in this table so that they can be turned on or off
# without affecting the routing or tuple-matching flow rules.
flow = {'table':'2', 'priority':'1', 'actions': ['goto_table:3']}
- self._vswitch.add_flow(BRIDGE_NAME, flow)
+ self._vswitch.add_flow(bridge, flow)
# Egress table
# (TODO) Billy O'Mahony - the drop action here actually required in
# order to egress the packet. This is the subject of a thread on
# ovs-discuss 2015-06-30.
flow = {'table':'3', 'priority':'1', 'actions': ['drop']}
- self._vswitch.add_flow(BRIDGE_NAME, flow)
+ self._vswitch.add_flow(bridge, flow)
except:
self._vswitch.stop()
raise
@@ -113,4 +113,4 @@ class VswitchControllerP2P(IVswitchController):
"""See IVswitchController for description
"""
self._logger.debug('get_ports_info using ' + str(self._vswitch_class))
- return self._vswitch.get_ports(BRIDGE_NAME)
+ return self._vswitch.get_ports(settings.getValue('VSWITCH_BRIDGE_NAME'))
diff --git a/core/vswitch_controller_pvp.py b/core/vswitch_controller_pvp.py
index 8c409dc2..80c0fdb2 100644
--- a/core/vswitch_controller_pvp.py
+++ b/core/vswitch_controller_pvp.py
@@ -19,11 +19,11 @@ import logging
from core.vswitch_controller import IVswitchController
from vswitches.utils import add_ports_to_flow
+from conf import settings
_FLOW_TEMPLATE = {
'idle_timeout': '0'
}
-BRIDGE_NAME = 'br0'
class VswitchControllerPVP(IVswitchController):
"""VSwitch controller for PVP deployment scenario.
@@ -54,28 +54,29 @@ class VswitchControllerPVP(IVswitchController):
try:
self._vswitch.start()
- self._vswitch.add_switch(BRIDGE_NAME)
+ bridge = settings.getValue('VSWITCH_BRIDGE_NAME')
+ self._vswitch.add_switch(bridge)
- (_, 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)
+ (_, phy1_number) = self._vswitch.add_phy_port(bridge)
+ (_, phy2_number) = self._vswitch.add_phy_port(bridge)
+ (_, vport1_number) = self._vswitch.add_vport(bridge)
+ (_, vport2_number) = self._vswitch.add_vport(bridge)
- self._vswitch.del_flow(BRIDGE_NAME)
+ self._vswitch.del_flow(bridge)
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)
+ self._vswitch.add_flow(bridge, flow1)
+ self._vswitch.add_flow(bridge, 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)
+ self._vswitch.add_flow(bridge, flow3)
+ self._vswitch.add_flow(bridge, flow4)
except:
self._vswitch.stop()
@@ -102,4 +103,4 @@ class VswitchControllerPVP(IVswitchController):
"""See IVswitchController for description
"""
self._logger.debug('get_ports_info using ' + str(self._vswitch_class))
- return self._vswitch.get_ports(BRIDGE_NAME)
+ return self._vswitch.get_ports(settings.getValue('VSWITCH_BRIDGE_NAME'))
diff --git a/core/vswitch_controller_pvvp.py b/core/vswitch_controller_pvvp.py
new file mode 100644
index 00000000..b445f9bd
--- /dev/null
+++ b/core/vswitch_controller_pvvp.py
@@ -0,0 +1,114 @@
+# 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.
+
+"""VSwitch controller for Physical to VM to Physical deployment
+"""
+
+import logging
+
+from core.vswitch_controller import IVswitchController
+from vswitches.utils import add_ports_to_flow
+from conf import settings
+
+_FLOW_TEMPLATE = {
+ 'idle_timeout': '0'
+}
+
+class VswitchControllerPVVP(IVswitchController):
+ """VSwitch controller for PVVP deployment scenario.
+
+ Attributes:
+ _vswitch_class: The vSwitch class to be used.
+ _vswitch: The vSwitch object controlled by this controller
+ _deployment_scenario: A string describing the scenario to set-up in the
+ constructor.
+ """
+ def __init__(self, vswitch_class, bidir=False):
+ """Initializes up the prerequisites for the PVVP deployment scenario.
+
+ :vswitch_class: the vSwitch class to be used.
+ """
+ self._logger = logging.getLogger(__name__)
+ self._vswitch_class = vswitch_class
+ self._vswitch = vswitch_class()
+ self._deployment_scenario = "PVVP"
+ self._bidir = bidir
+ self._logger.debug('Creation using ' + str(self._vswitch_class))
+
+ def setup(self):
+ """ Sets up the switch for PVVP
+ """
+ self._logger.debug('Setup using ' + str(self._vswitch_class))
+
+ try:
+ self._vswitch.start()
+
+ bridge = settings.getValue('VSWITCH_BRIDGE_NAME')
+ self._vswitch.add_switch(bridge)
+
+ (_, phy1_number) = self._vswitch.add_phy_port(bridge)
+ (_, phy2_number) = self._vswitch.add_phy_port(bridge)
+ (_, vport1_number) = self._vswitch.add_vport(bridge)
+ (_, vport2_number) = self._vswitch.add_vport(bridge)
+ (_, vport3_number) = self._vswitch.add_vport(bridge)
+ (_, vport4_number) = self._vswitch.add_vport(bridge)
+
+ self._vswitch.del_flow(bridge)
+ flow1 = add_ports_to_flow(_FLOW_TEMPLATE, phy1_number,
+ vport1_number)
+ flow2 = add_ports_to_flow(_FLOW_TEMPLATE, vport2_number,
+ vport3_number)
+ flow3 = add_ports_to_flow(_FLOW_TEMPLATE, vport4_number,
+ phy2_number)
+ self._vswitch.add_flow(bridge, flow1)
+ self._vswitch.add_flow(bridge, flow2)
+ self._vswitch.add_flow(bridge, flow3)
+
+ if self._bidir:
+ flow4 = add_ports_to_flow(_FLOW_TEMPLATE, phy2_number,
+ vport4_number)
+ flow5 = add_ports_to_flow(_FLOW_TEMPLATE, vport3_number,
+ vport2_number)
+ flow6 = add_ports_to_flow(_FLOW_TEMPLATE, vport1_number,
+ phy1_number)
+ self._vswitch.add_flow(bridge, flow4)
+ self._vswitch.add_flow(bridge, flow5)
+ self._vswitch.add_flow(bridge, flow6)
+
+ except:
+ self._vswitch.stop()
+ raise
+
+ def stop(self):
+ """Tears down the switch created in setup().
+ """
+ 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
+ """
+ return self._vswitch
+
+ def get_ports_info(self):
+ """See IVswitchController for description
+ """
+ self._logger.debug('get_ports_info using ' + str(self._vswitch_class))
+ return self._vswitch.get_ports(settings.getValue('VSWITCH_BRIDGE_NAME'))
diff --git a/docs/to-be-reorganized/NEWS.rst b/docs/to-be-reorganized/NEWS.rst
index 93be5daa..69df7478 100644
--- a/docs/to-be-reorganized/NEWS.rst
+++ b/docs/to-be-reorganized/NEWS.rst
@@ -3,6 +3,8 @@ September 2015
New
---
- Implementation of system statistics based upon pidstat command line tool.
+- Support of PVVP deployment scenario using bhost-cuse and vhost user access
+ methods
August 2015
===========
diff --git a/docs/to-be-reorganized/quickstart.rst b/docs/to-be-reorganized/quickstart.rst
index 8dead99c..485bf1ae 100644
--- a/docs/to-be-reorganized/quickstart.rst
+++ b/docs/to-be-reorganized/quickstart.rst
@@ -63,8 +63,8 @@ to specify path to the kernel sources by WITH\_LINUX parameter:
cd src
make WITH_LINUX=/lib/modules/`uname -r`/build
-To build DPDK and OVS for PVP testing with vhost_user as the guest access
-method, use:
+To build DPDK and OVS for PVP and PVVP testing with vhost_user as the guest
+access method, use:
.. code-block:: console
@@ -155,8 +155,8 @@ For all available options, check out the help dialog:
./vsperf --help
-Executing PVP tests
--------------------
+Executing PVP and PVVP tests
+----------------------------
To run tests using vhost-user as guest access method:
1. Set VHOST_METHOD and VNF of your settings file to:
@@ -212,8 +212,8 @@ OVS with DPDK and QEMU
~~~~~~~~~~~~~~~~~~~~~~~
If you encounter the following error: "before (last 100 chars):
'-path=/dev/hugepages,share=on: unable to map backing store for
-hugepages: Cannot allocate memory\r\n\r\n" with the PVP deployment
-scenario, check the amount of hugepages on your system:
+hugepages: Cannot allocate memory\r\n\r\n" with the PVP or PVVP
+deployment scenario, check the amount of hugepages on your system:
.. code:: bash
diff --git a/src/dpdk/dpdk.py b/src/dpdk/dpdk.py
index 093670c4..0633c7a1 100644
--- a/src/dpdk/dpdk.py
+++ b/src/dpdk/dpdk.py
@@ -302,7 +302,7 @@ def _unbind_nics_get_driver():
for line in _output.decode(_my_encoding).split('\n'):
for nic in settings.getValue('WHITELIST_NICS'):
if nic in line:
- _driver_list.append((line.split("unused=",1)[1]))
+ _driver_list.append((line.split("unused=", 1)[1]))
return _driver_list
def _unbind_nics():
@@ -325,10 +325,10 @@ def _unbind_nics():
try:
if nic_drivers[i] != '':
tasks.run_task(['sudo', RTE_PCI_TOOL, '--bind',
- nic_drivers[i], nic],
- _LOGGER, 'Binding NIC %s...' %
- nic,
- True)
+ nic_drivers[i], nic],
+ _LOGGER, 'Binding NIC %s...' %
+ nic,
+ True)
except subprocess.CalledProcessError:
_LOGGER.error('Unable to bind NICs %s to drivers %s',
str(settings.getValue('WHITELIST_NICS')),
@@ -336,23 +336,23 @@ def _unbind_nics():
def _copy_dpdk_for_guest():
- """Copy dpdk code to GUEST_SHARE_DIR for use by guests.
+ """Copy dpdk code to GUEST_SHARE_DIR[s] for use by guests.
"""
- guest_share_dir = os.path.join(
- settings.getValue('GUEST_SHARE_DIR'), 'DPDK')
+ for guest_dir in settings.getValue('GUEST_SHARE_DIR'):
+ guest_share_dir = os.path.join(guest_dir, 'DPDK')
- if not os.path.exists(guest_share_dir):
- os.makedirs(guest_share_dir)
+ if not os.path.exists(guest_share_dir):
+ os.makedirs(guest_share_dir)
- try:
- tasks.run_task(['rsync', '-a', '-r', '-l', r'--exclude="\.git"',
- os.path.join(settings.getValue('RTE_SDK'), ''),
- guest_share_dir],
- _LOGGER,
- 'Copying DPDK to shared directory...',
- True)
- except subprocess.CalledProcessError:
- _LOGGER.error('Unable to copy DPDK to shared directory')
+ try:
+ tasks.run_task(['rsync', '-a', '-r', '-l', r'--exclude="\.git"',
+ os.path.join(settings.getValue('RTE_SDK'), ''),
+ guest_share_dir],
+ _LOGGER,
+ 'Copying DPDK to shared directory...',
+ True)
+ except subprocess.CalledProcessError:
+ _LOGGER.error('Unable to copy DPDK to shared directory')
#
diff --git a/src/ovs/ofctl.py b/src/ovs/ofctl.py
index 1c5e6513..7cbdfe2c 100644
--- a/src/ovs/ofctl.py
+++ b/src/ovs/ofctl.py
@@ -34,6 +34,8 @@ _OVS_OFCTL_BIN = os.path.join(settings.getValue('OVS_DIR'), 'utilities',
_OVS_VAR_DIR = '/usr/local/var/run/openvswitch/'
+_OVS_BRIDGE_NAME = settings.getValue('VSWITCH_BRIDGE_NAME')
+
class OFBase(object):
"""Add/remove/show datapaths using ``ovs-ofctl``.
"""
@@ -63,7 +65,7 @@ class OFBase(object):
# datapath management
- def add_br(self, br_name='br0'):
+ def add_br(self, br_name=_OVS_BRIDGE_NAME):
"""Add datapath.
:param br_name: Name of bridge
@@ -75,7 +77,7 @@ class OFBase(object):
return OFBridge(br_name, self.timeout)
- def del_br(self, br_name='br0'):
+ def del_br(self, br_name=_OVS_BRIDGE_NAME):
"""Delete datapath.
:param br_name: Name of bridge
@@ -89,7 +91,7 @@ class OFBase(object):
class OFBridge(OFBase):
"""Control a bridge instance using ``ovs-vsctl`` and ``ovs-ofctl``.
"""
- def __init__(self, br_name='br0', timeout=10):
+ def __init__(self, br_name=_OVS_BRIDGE_NAME, timeout=10):
"""Initialise bridge.
:param br_name: Bridge name
@@ -126,7 +128,8 @@ class OFBridge(OFBase):
:return: None
"""
- cmd = ['sudo', _OVS_OFCTL_BIN, '-O', 'OpenFlow13', '--timeout', str(self.timeout)] + args
+ cmd = ['sudo', _OVS_OFCTL_BIN, '-O', 'OpenFlow13', '--timeout',
+ str(self.timeout)] + args
return tasks.run_task(
cmd, self.logger, 'Running ovs-ofctl...', check_error)
diff --git a/testcases/testcase.py b/testcases/testcase.py
index 6d37ce52..e15572d8 100644
--- a/testcases/testcase.py
+++ b/testcases/testcase.py
@@ -23,6 +23,7 @@ from core.results.results_constants import ResultsConstants
import core.component_factory as component_factory
from core.loader import Loader
from tools.report import report
+from conf import settings
class TestCase(object):
"""TestCase base class
@@ -96,62 +97,67 @@ class TestCase(object):
# physical ports are ports 1 & 2. The actual numbers
# need to be retrived from the vSwitch and the metadata value
# updated accordingly.
+ bridge = settings.getValue('VSWITCH_BRIDGE_NAME')
if self._frame_mod == "vlan":
# 0x8100 => VLAN ethertype
self._logger.debug(" **** VLAN ***** ")
flow = {'table':'2', 'priority':'1000', 'metadata':'2',
'actions': ['push_vlan:0x8100', 'goto_table:3']}
- vswitch.add_flow('br0', flow)
+ vswitch.add_flow(bridge, flow)
flow = {'table':'2', 'priority':'1000', 'metadata':'1',
'actions': ['push_vlan:0x8100', 'goto_table:3']}
- vswitch.add_flow('br0', flow)
+ vswitch.add_flow(bridge, flow)
elif self._frame_mod == "mpls":
# 0x8847 => MPLS unicast ethertype
self._logger.debug(" **** MPLS ***** ")
flow = {'table':'2', 'priority':'1000', 'metadata':'2',
'actions': ['push_mpls:0x8847', 'goto_table:3']}
- vswitch.add_flow('br0', flow)
+ vswitch.add_flow(bridge, flow)
flow = {'table':'2', 'priority':'1000', 'metadata':'1',
'actions': ['push_mpls:0x8847', 'goto_table:3']}
- vswitch.add_flow('br0', flow)
+ vswitch.add_flow(bridge, flow)
elif self._frame_mod == "mac":
flow = {'table':'2', 'priority':'1000', 'metadata':'2',
- 'actions': ['mod_dl_src:22:22:22:22:22:22', 'goto_table:3']}
- vswitch.add_flow('br0', flow)
+ 'actions': ['mod_dl_src:22:22:22:22:22:22',
+ 'goto_table:3']}
+ vswitch.add_flow(bridge, flow)
flow = {'table':'2', 'priority':'1000', 'metadata':'1',
- 'actions': ['mod_dl_src:11:11:11:11:11:11', 'goto_table:3']}
- vswitch.add_flow('br0', flow)
+ 'actions': ['mod_dl_src:11:11:11:11:11:11',
+ 'goto_table:3']}
+ vswitch.add_flow(bridge, flow)
elif self._frame_mod == "dscp":
# DSCP 184d == 0x4E<<2 => 'Expedited Forwarding'
flow = {'table':'2', 'priority':'1000', 'metadata':'2',
'dl_type':'0x0800',
'actions': ['mod_nw_tos:184', 'goto_table:3']}
- vswitch.add_flow('br0', flow)
+ vswitch.add_flow(bridge, flow)
flow = {'table':'2', 'priority':'1000', 'metadata':'1',
'dl_type':'0x0800',
'actions': ['mod_nw_tos:184', 'goto_table:3']}
- vswitch.add_flow('br0', flow)
+ vswitch.add_flow(bridge, flow)
elif self._frame_mod == "ttl":
# 251 and 241 are the highest prime numbers < 255
flow = {'table':'2', 'priority':'1000', 'metadata':'2',
'dl_type':'0x0800',
'actions': ['mod_nw_ttl:251', 'goto_table:3']}
- vswitch.add_flow('br0', flow)
+ vswitch.add_flow(bridge, flow)
flow = {'table':'2', 'priority':'1000', 'metadata':'1',
'dl_type':'0x0800',
'actions': ['mod_nw_ttl:241', 'goto_table:3']}
- vswitch.add_flow('br0', flow)
+ vswitch.add_flow(bridge, flow)
elif self._frame_mod == "ip_addr":
flow = {'table':'2', 'priority':'1000', 'metadata':'2',
'dl_type':'0x0800',
'actions': ['mod_nw_src:10.10.10.10',
- 'mod_nw_dst:20.20.20.20', 'goto_table:3']}
- vswitch.add_flow('br0', flow)
+ 'mod_nw_dst:20.20.20.20',
+ 'goto_table:3']}
+ vswitch.add_flow(bridge, flow)
flow = {'table':'2', 'priority':'1000', 'metadata':'1',
'dl_type':'0x0800',
'actions': ['mod_nw_src:20.20.20.20',
- 'mod_nw_dst:10.10.10.10', 'goto_table:3']}
- vswitch.add_flow('br0', flow)
+ 'mod_nw_dst:10.10.10.10',
+ 'goto_table:3']}
+ vswitch.add_flow(bridge, flow)
elif self._frame_mod == "ip_port":
# TODO BOM 15-08-27 The traffic generated is assumed
# to be UDP (nw_proto 17d) which is the default case but
@@ -159,13 +165,13 @@ class TestCase(object):
flow = {'table':'2', 'priority':'1000', 'metadata':'2',
'dl_type':'0x0800', 'nw_proto':'17',
'actions': ['mod_tp_src:44444',
- 'mod_tp_dst:44444', 'goto_table:3']}
- vswitch.add_flow('br0', flow)
+ 'mod_tp_dst:44444', 'goto_table:3']}
+ vswitch.add_flow(bridge, flow)
flow = {'table':'2', 'priority':'1000', 'metadata':'1',
'dl_type':'0x0800', 'nw_proto':'17',
'actions': ['mod_tp_src:44444',
- 'mod_tp_dst:44444', 'goto_table:3']}
- vswitch.add_flow('br0', flow)
+ 'mod_tp_dst:44444', 'goto_table:3']}
+ vswitch.add_flow(bridge, flow)
else:
pass
diff --git a/tools/tasks.py b/tools/tasks.py
index 555a5929..33a5931a 100644
--- a/tools/tasks.py
+++ b/tools/tasks.py
@@ -241,14 +241,17 @@ class Process(object):
self.kill()
raise exc
- def kill(self):
+ def kill(self, signal='-15', sleep=2):
"""Kill process instance if it is alive.
+
+ :param signal: signal to be sent to the process
+ :param sleep: delay in seconds after signal is sent
"""
if self._child and self._child.isalive():
- run_task(['sudo', 'kill', '-15', str(self._child.pid)],
+ run_task(['sudo', 'kill', signal, str(self._child.pid)],
self._logger)
self._logger.debug('Wait for process to terminate')
- time.sleep(2)
+ time.sleep(sleep)
if self.is_relinquished():
self._relinquish_thread.join()
diff --git a/vnfs/qemu/qemu.py b/vnfs/qemu/qemu.py
new file mode 100644
index 00000000..338ca771
--- /dev/null
+++ b/vnfs/qemu/qemu.py
@@ -0,0 +1,203 @@
+# 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-cuse enabled guests.
+"""
+
+import os
+import logging
+import locale
+import re
+import subprocess
+
+from conf import settings as S
+from vnfs.vnf.vnf import IVnf
+
+class IVnfQemu(IVnf):
+ """
+ Abstract class for controling an instance of QEMU
+ """
+ _cmd = None
+ _expect = S.getValue('GUEST_PROMPT_LOGIN')
+ _proc_name = 'qemu'
+
+ class GuestCommandFilter(logging.Filter):
+ """
+ Filter out strings beginning with 'guestcmd :'.
+ """
+ def filter(self, record):
+ return record.getMessage().startswith(self.prefix)
+
+ def __init__(self):
+ """
+ :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
+ """
+ super(IVnfQemu, self).__init__()
+ self._logger = logging.getLogger(__name__)
+ self._logfile = os.path.join(
+ S.getValue('LOG_DIR'),
+ S.getValue('LOG_FILE_QEMU')) + str(self._number)
+ self._timeout = 120
+ self._monitor = '%s/vm%dmonitor' % ('/tmp', self._number)
+
+ name = 'Client%d' % self._number
+ vnc = ':%d' % self._number
+ # don't use taskset to affinize main qemu process; It causes hangup
+ # of 2nd VM in case of DPDK. It also slows down VM responsivnes.
+ self._cmd = ['sudo', '-E', S.getValue('QEMU_BIN'),
+ '-m', S.getValue('GUEST_MEMORY')[self._number],
+ '-smp', str(S.getValue('GUEST_SMP')[self._number]),
+ '-cpu', 'host',
+ '-drive', 'if=scsi,file=' +
+ S.getValue('GUEST_IMAGE')[self._number],
+ '-drive',
+ 'if=scsi,file=fat:rw:%s,snapshot=off' %
+ S.getValue('GUEST_SHARE_DIR')[self._number],
+ '-boot', 'c', '--enable-kvm',
+ '-monitor', 'unix:%s,server,nowait' % self._monitor,
+ '-object',
+ 'memory-backend-file,id=mem,size=' +
+ str(S.getValue('GUEST_MEMORY')[self._number]) + 'M,' +
+ 'mem-path=' + S.getValue('HUGEPAGE_DIR') + ',share=on',
+ '-numa', 'node,memdev=mem -mem-prealloc',
+ '-nographic', '-vnc', str(vnc), '-name', name,
+ '-snapshot', '-net none', '-no-reboot',
+ ]
+ self._configure_logging()
+
+ def _configure_logging(self):
+ """
+ Configure logging.
+ """
+ self.GuestCommandFilter.prefix = self._log_prefix
+
+ logger = logging.getLogger()
+ cmd_logger = logging.FileHandler(
+ filename=os.path.join(S.getValue('LOG_DIR'),
+ S.getValue('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(IVnfQemu, self).start()
+ self._affinitize()
+
+ if self._timeout:
+ self._login()
+ self._config_guest_loopback()
+
+ # 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(S.getValue('GUEST_USERNAME'))
+ self._child.expect(S.getValue('GUEST_PROMPT_PASSWORD'), timeout=5)
+ self._child.sendline(S.getValue('GUEST_PASSWORD'))
+
+ self._expect_process(S.getValue('GUEST_PROMPT'), timeout=5)
+
+ 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(S.getValue('GUEST_PROMPT'), timeout=timeout)
+ self.execute('echo $?')
+ self._child.expect('^0$', timeout=1) # expect a 0
+ self.wait(S.getValue('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(S.getValue('GUEST_SMP')[self._number])):
+ match = None
+ for line in output.decode(cur_locale).split('\n'):
+ match = re.search(thread_id % cpu, line)
+ if match:
+ self._affinitize_pid(
+ S.getValue('GUEST_CORE_BINDING')[self._number][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):
+ """
+ Configure VM to run VNF (e.g. port forwarding application)
+ """
+ pass
+
+ def wait(self, prompt=S.getValue('GUEST_PROMPT'), timeout=30):
+ super(IVnfQemu, self).wait(prompt=prompt, timeout=timeout)
+
+ def execute_and_wait(self, cmd, timeout=30,
+ prompt=S.getValue('GUEST_PROMPT')):
+ super(IVnfQemu, self).execute_and_wait(cmd, timeout=timeout,
+ prompt=prompt)
diff --git a/vnfs/qemu/qemu_dpdk.py b/vnfs/qemu/qemu_dpdk.py
new file mode 100644
index 00000000..0b8f90a0
--- /dev/null
+++ b/vnfs/qemu/qemu_dpdk.py
@@ -0,0 +1,115 @@
+# 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-cuse enabled guests.
+"""
+
+from vnfs.qemu.qemu import IVnfQemu
+from conf import settings as S
+
+class IVnfQemuDpdk(IVnfQemu):
+ """
+ An abstract class for controling an instance of QEMU with DPDK vHost support
+ """
+
+ def __init__(self):
+ """
+ :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
+ """
+ super(IVnfQemuDpdk, self).__init__()
+ self._cmd += []
+
+ def _modify_dpdk_makefile(self):
+ """
+ Modifies DPDK makefile in Guest before compilation
+ """
+ pass
+
+ def _config_guest_loopback(self):
+ """
+ Configure VM to run testpmd
+
+ Configure performs the following:
+ * Mount hugepages
+ * mount shared directory for copying DPDK
+ * Disable firewall
+ * Compile DPDK
+ * DPDK NIC bind
+ * Run testpmd
+ """
+
+ # Guest images _should_ have 1024 hugepages by default,
+ # but just in case:'''
+ self.execute_and_wait('sysctl vm.nr_hugepages=1024')
+
+ # Mount hugepages
+ 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 ' + S.getValue('OVS_DPDK_SHARE'))
+ self.execute_and_wait('rm -rf ' + S.getValue('GUEST_OVS_DPDK_DIR'))
+ self.execute_and_wait('mkdir -p ' + S.getValue('OVS_DPDK_SHARE'))
+ self.execute_and_wait('mount -o iocharset=utf8 /dev/sdb1 ' +
+ S.getValue('OVS_DPDK_SHARE'))
+ self.execute_and_wait('mkdir -p ' + S.getValue('GUEST_OVS_DPDK_DIR'))
+ self.execute_and_wait('cp -a ' + S.getValue('OVS_DPDK_SHARE') + '/* ' +
+ S.getValue('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 ' + S.getValue('GUEST_OVS_DPDK_DIR') +
+ '/DPDK')
+ self.execute_and_wait('export CC=gcc')
+ self.execute_and_wait('export RTE_SDK=' +
+ S.getValue('GUEST_OVS_DPDK_DIR') + '/DPDK')
+ self.execute_and_wait('export RTE_TARGET=%s' % S.getValue('RTE_TARGET'))
+
+ # modify makefile if needed
+ self._modify_dpdk_makefile()
+
+ self.execute_and_wait('make RTE_OUTPUT=$RTE_SDK/$RTE_TARGET -C '
+ '$RTE_SDK/lib/librte_eal/linuxapp/igb_uio')
+ self.execute_and_wait('modprobe uio')
+ self.execute_and_wait('insmod %s/kmod/igb_uio.ko' %
+ S.getValue('RTE_TARGET'))
+ self.execute_and_wait('./tools/dpdk_nic_bind.py --status')
+ self.execute_and_wait(
+ './tools/dpdk_nic_bind.py -b igb_uio' ' ' +
+ S.getValue('GUEST_NET1_PCI_ADDRESS')[self._number] + ' ' +
+ S.getValue('GUEST_NET2_PCI_ADDRESS')[self._number])
+
+ # build and run 'test-pmd'
+ self.execute_and_wait('cd ' + S.getValue('GUEST_OVS_DPDK_DIR') +
+ '/DPDK/app/test-pmd')
+ self.execute_and_wait('make clean')
+ self.execute_and_wait('make')
+ self.execute_and_wait('./testpmd -c 0x3 -n 4 --socket-mem 512 --'
+ ' --burst=64 -i --txqflags=0xf00 ' +
+ '--disable-hw-vlan', 60, "Done")
+ self.execute('set fwd mac_retry', 1)
+ self.execute_and_wait('start', 20,
+ 'TX RS bit threshold=0 - TXQ flags=0xf00')
diff --git a/vnfs/qemu/qemu_dpdk_vhost_cuse.py b/vnfs/qemu/qemu_dpdk_vhost_cuse.py
index 43b732fc..e5351813 100644
--- a/vnfs/qemu/qemu_dpdk_vhost_cuse.py
+++ b/vnfs/qemu/qemu_dpdk_vhost_cuse.py
@@ -15,374 +15,54 @@
"""Automation of QEMU hypervisor for launching vhost-cuse 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
+from conf import settings as S
+from vnfs.qemu.qemu_dpdk import IVnfQemuDpdk
-_QEMU_BIN = settings.getValue('QEMU_BIN')
-_RTE_TARGET = settings.getValue('RTE_TARGET')
-
-_GUEST_MEMORY = settings.getValue('GUEST_MEMORY')
-_GUEST_SMP = settings.getValue('GUEST_SMP')
-_GUEST_CORE_BINDING = settings.getValue('GUEST_CORE_BINDING')
-_QEMU_CORE = settings.getValue('QEMU_CORE')
-
-_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 = settings.getValue('GUEST_OVS_DPDK_DIR')
-_OVS_DPDK_SHARE = settings.getValue('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'))
-
-_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 QemuDpdkVhostCuse(tasks.Process, IVnf):
+class QemuDpdkVhostCuse(IVnfQemuDpdk):
"""
- Control an instance of QEMU with vHost user guest communication.
+ Control an instance of QEMU with vHost cuse 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="PVP"):
+ def __init__(self):
"""
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
"""
+ super(QemuDpdkVhostCuse, self).__init__()
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', 'taskset ' + str(_QEMU_CORE), 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',
- '-monitor', 'unix:%s,server,nowait' % self._monitor,
- '-object',
- 'memory-backend-file,id=mem,size=' + str(memory) + 'M,' +
- 'mem-path=' + _HUGEPAGE_DIR + ',share=on',
- '-numa', 'node,memdev=mem -mem-prealloc',
- '-net', 'none', '-no-reboot',
- '-netdev',
- 'type=tap,id=net1,script=no,downscript=no,' +
- 'ifname=dpdkvhostcuse0,vhost=on',
- '-device',
- 'virtio-net-pci,netdev=net1,mac=' + _GUEST_NET1_MAC,
- '-netdev',
- 'type=tap,id=net2,script=no,downscript=no,' +
- 'ifname=dpdkvhostcuse1,vhost=on',
- '-device',
- 'virtio-net-pci,netdev=net2,mac=' + _GUEST_NET2_MAC,
- '-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(QemuDpdkVhostCuse, 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(QemuDpdkVhostCuse, self).kill()
+ # calculate indexes of guest devices (e.g. charx, dpdkvhostuserx)
+ i = self._number * 2
+ if1 = str(i)
+ if2 = str(i + 1)
+ net1 = 'net' + str(i + 1)
+ net2 = 'net' + str(i + 2)
+
+ self._cmd += ['-netdev',
+ 'type=tap,id=' + net1 + ',script=no,downscript=no,' +
+ 'ifname=dpdkvhostcuse' + if1 + ',vhost=on',
+ '-device',
+ 'virtio-net-pci,mac=' +
+ S.getValue('GUEST_NET1_MAC')[self._number] +
+ ',netdev=' + net1 + ',csum=off,gso=off,' +
+ 'guest_tso4=off,guest_tso6=off,guest_ecn=off',
+ '-netdev',
+ 'type=tap,id=' + net2 +
+ ',script=no,downscript=no,' +
+ 'ifname=dpdkvhostcuse' + if2 + ',vhost=on',
+ '-device',
+ 'virtio-net-pci,mac=' +
+ S.getValue('GUEST_NET2_MAC')[self._number] +
+ ',netdev=' + net2 + ',csum=off,gso=off,' +
+ 'guest_tso4=off,guest_tso6=off,guest_ecn=off',
+ ]
# helper functions
- def _login(self, timeout=120):
+ def _modify_dpdk_makefile(self):
"""
- 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
+ Modifies DPDK makefile in Guest before compilation
"""
- # 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):
- """
- Configure VM to run testpmd
-
- Configure performs the following:
- * Mount hugepages
- * mount shared directory for copying DPDK
- * Disable firewall
- * Compile DPDK
- * DPDK NIC bind
- * Run testpmd
- """
-
- # Guest images _should_ have 1024 hugepages by default,
- # but just in case:'''
- self.execute_and_wait('sysctl vm.nr_hugepages=1024')
-
- # Mount hugepages
- 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)
-
- # 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("sed -i -e 's/CONFIG_RTE_LIBRTE_VHOST_USER=n/" +
- "CONFIG_RTE_LIBRTE_VHOST_USER=y/g' config/common_linuxapp",
- 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)
-
- self.execute_and_wait('./tools/dpdk_nic_bind.py --status',
- prompt=_QEMU_GUEST_DPDK_PROMPT)
-
- self.execute_and_wait('cd ' + _RTE_TARGET + '/build/app/test-pmd',
- 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 QemuDpdkVhostCuse() 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)
+ "CONFIG_RTE_LIBRTE_VHOST_USER=y/g'" +
+ "config/common_linuxapp")
diff --git a/vnfs/qemu/qemu_dpdk_vhost_user.py b/vnfs/qemu/qemu_dpdk_vhost_user.py
index a3b96b18..94d87f9e 100644
--- a/vnfs/qemu/qemu_dpdk_vhost_user.py
+++ b/vnfs/qemu/qemu_dpdk_vhost_user.py
@@ -15,365 +15,52 @@
"""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
+from conf import settings as S
+from vnfs.qemu.qemu_dpdk import IVnfQemuDpdk
-_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):
+class QemuDpdkVhostUser(IVnfQemuDpdk):
"""
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"):
+ def __init__(self):
"""
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
"""
+ super(QemuDpdkVhostUser, self).__init__()
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)
+ # calculate indexes of guest devices (e.g. charx, dpdkvhostuserx)
+ i = self._number * 2
+ if1 = str(i)
+ if2 = str(i + 1)
+ net1 = 'net' + str(i + 1)
+ net2 = 'net' + str(i + 2)
+
+ self._cmd += ['-chardev',
+ 'socket,id=char' + if1 +
+ ',path=' + S.getValue('OVS_VAR_DIR') +
+ 'dpdkvhostuser' + if1,
+ '-chardev',
+ 'socket,id=char' + if2 +
+ ',path=' + S.getValue('OVS_VAR_DIR') +
+ 'dpdkvhostuser' + if2,
+ '-netdev',
+ 'type=vhost-user,id=' + net1 +
+ ',chardev=char' + if1 + ',vhostforce',
+ '-device',
+ 'virtio-net-pci,mac=' +
+ S.getValue('GUEST_NET1_MAC')[self._number] +
+ ',netdev=' + net1 + ',csum=off,gso=off,' +
+ 'guest_tso4=off,guest_tso6=off,guest_ecn=off',
+ '-netdev',
+ 'type=vhost-user,id=' + net2 +
+ ',chardev=char' + if2 + ',vhostforce',
+ '-device',
+ 'virtio-net-pci,mac=' +
+ S.getValue('GUEST_NET2_MAC')[self._number] +
+ ',netdev=' + net2 + ',csum=off,gso=off,' +
+ 'guest_tso4=off,guest_tso6=off,guest_ecn=off',
+ ]
- while True:
- USER_INPUT = input()
- vm1.send_and_wait(USER_INPUT, timeout=5)
diff --git a/vnfs/vnf/vnf.py b/vnfs/vnf/vnf.py
index c746aa83..f8d2df90 100644
--- a/vnfs/vnf/vnf.py
+++ b/vnfs/vnf/vnf.py
@@ -16,55 +16,56 @@
Interface for VNF.
"""
+import time
+from tools import tasks
-class IVnf(object):
+class IVnf(tasks.Process):
"""
Interface for VNF.
"""
- def __init__(self, memory, cpus,
- monitor_path, shared_path_host,
- shared_path_guest, guest_prompt):
+ _number_vnfs = 0
+
+ def __init__(self):
"""
Initialization method.
Purpose of this method is to initialize all
common Vnf data, no services should be started by
this call (use ``start`` method instead).
-
- :param memory: Virtual RAM size in megabytes.
- :param cpus: Number of Processors.
- :param monitor_path: Configure monitor to given path.
- :param shared_path_host: HOST path to shared location.
- :param shared_path_guest: GUEST path to shared location.
- :param guest_prompt: preconfigured command prompt which is used
- in execute_and_wait & wait methods
- to detect if particular call is finished.
"""
- raise NotImplementedError()
+ self._number = IVnf._number_vnfs
+ IVnf._number_vnfs = IVnf._number_vnfs + 1
+ self._log_prefix = 'vnf_%d_cmd : ' % self._number
def start(self):
"""
Starts VNF instance.
+
+ This is a blocking function
"""
- raise NotImplementedError()
+ super(IVnf, self).start()
def stop(self):
"""
Stops VNF instance.
"""
- raise NotImplementedError()
+ self._logger.info('Killing VNF...')
+
+ # force termination of VNF and wait for it to terminate; It will avoid
+ # sporadic reboot of host. (caused by hugepages or DPDK ports)
+ super(IVnf, self).kill(signal='-9', sleep=10)
- def execute(self, command, delay=30):
+ def execute(self, cmd, delay=0):
"""
- execute ``command`` with given ``delay``.
+ execute ``cmd`` with given ``delay``.
This method makes asynchronous call to guest system
and waits given ``delay`` before returning. Can be
used with ``wait`` method to create synchronous call.
- :param command: Command to execute on guest system.
+ :param cmd: Command to execute on guest system.
:param delay: Delay (in seconds) to wait after sending
command before returning. Please note that
this value can be floating point which
@@ -72,17 +73,19 @@ class IVnf(object):
:returns: None.
"""
- raise NotImplementedError()
+ self._logger.debug('%s%s', self._log_prefix, cmd)
+ self._child.sendline(cmd)
+ time.sleep(delay)
- def wait(self, guest_prompt, timeout=30):
+ def wait(self, prompt='', timeout=30):
"""
- wait for ``guest_prompt`` on guest system for given ``timeout``.
+ wait for ``prompt`` on guest system for given ``timeout``.
This method ends based on two conditions:
- * ``guest_prompt`` has been detected
+ * ``prompt`` has been detected
* ``timeout`` has been reached.
- :param guest_prompt: method end condition. If ``guest_prompt``
+ :param prompt: method end condition. If ``prompt``
won't be detected during given timeout,
method will return False.
:param timeout: Time to wait for prompt (in seconds).
@@ -92,28 +95,29 @@ class IVnf(object):
:returns: True if result_cmd has been detected before
timeout has been reached, False otherwise.
"""
- raise NotImplementedError()
+ self._child.expect(prompt, timeout=timeout)
- def execute_and_wait(self, command, timeout=30, guest_prompt=None):
+ def execute_and_wait(self, cmd, timeout=30, prompt=''):
"""
- execute ``command`` with given ``timeout``.
+ execute ``cmd`` with given ``timeout``.
This method makes synchronous call to guest system
- and waits till ``command`` execution is finished
- (based on ``guest_prompt value) or ''timeout'' has
+ and waits till ``cmd`` execution is finished
+ (based on ``prompt value) or ''timeout'' has
been reached.
- :param command: Command to execute on guest system.
+ :param cmd: Command to execute on guest system.
:param timeout: Timeout till the end of execution is not
detected.
- :param guest_prompt: method end condition. If ``guest_prompt``
+ :param prompt: method end condition. If ``prompt``
won't be detected during given timeout,
method will return False. If no argument
or None value will be passed, default
- ``guest_prompt`` passed in __init__
+ ``prompt`` passed in __init__
method will be used.
:returns: True if end of execution has been detected
before timeout has been reached, False otherwise.
"""
- raise NotImplementedError()
+ self.execute(cmd)
+ self.wait(prompt=prompt, timeout=timeout)
diff --git a/vswitches/ovs_dpdk_vhost.py b/vswitches/ovs_dpdk_vhost.py
index b1fd08bf..1a53bd6d 100644
--- a/vswitches/ovs_dpdk_vhost.py
+++ b/vswitches/ovs_dpdk_vhost.py
@@ -43,8 +43,8 @@ class OvsDpdkVhost(IVSwitch):
vswitchd_args += settings.getValue('VSWITCHD_DPDK_ARGS')
vswitchd_args += _VSWITCHD_CONST_ARGS
- if _VHOST_METHOD == "cuse":
- self._logger.info("Inserting VHOST modules into kernel...")
+ if settings.getValue('VNF').endswith('Cuse'):
+ self._logger.info("Inserting VHOST Cuse modules into kernel...")
dpdk.insert_vhost_modules()
self._vswitchd = VSwitchd(vswitchd_args=vswitchd_args,
@@ -119,7 +119,7 @@ class OvsDpdkVhost(IVSwitch):
"""
bridge = self._bridges[switch_name]
# Changed dpdkvhost to dpdkvhostuser to be able to run in Qemu 2.2
- if _VHOST_METHOD == "cuse":
+ if settings.getValue('VNF').endswith('Cuse'):
vhost_count = self._get_port_count(bridge, 'type=dpdkvhostcuse')
port_name = 'dpdkvhostcuse' + str(vhost_count)
params = ['--', 'set', 'Interface', port_name, 'type=dpdkvhostcuse']