From 89e23da8e473387b2e6cb37a2b882d2d3c68655c Mon Sep 17 00:00:00 2001 From: Martin Klozik Date: Fri, 25 Sep 2015 18:00:17 +0100 Subject: [PATCH] Enable PVVP deployment for DPDK Vhost User and Vhost Cuse Generic PVVP deployment support has been added. Two new testcase scenarios for throughput and back2back tests with PVVP deployment were added. Original implementation of PVP has been refactored Following files were affected: modified: conf/01_testcases.conf modified: conf/02_vswitch.conf modified: conf/04_vnf.conf modified: core/__init__.py modified: core/component_factory.py modified: core/loader/loader_servant.py modified: core/vnf_controller.py deleted: core/vnf_controller_p2p.py deleted: core/vnf_controller_pvp.py modified: core/vswitch_controller_p2p.py modified: core/vswitch_controller_pvp.py new file: core/vswitch_controller_pvvp.py modified: docs/to-be-reorganized/NEWS.rst modified: docs/to-be-reorganized/quickstart.rst modified: src/dpdk/dpdk.py modified: src/ovs/ofctl.py modified: testcases/testcase.py modified: tools/tasks.py new file: vnfs/qemu/qemu.py new file: vnfs/qemu/qemu_dpdk.py modified: vnfs/qemu/qemu_dpdk_vhost_cuse.py modified: vnfs/qemu/qemu_dpdk_vhost_user.py modified: vnfs/vnf/vnf.py modified: vswitches/ovs_dpdk_vhost.py Change-Id: Ib6869a29337a184cb58c57fd96bba1183aba00ab JIRA: VSPERF-68, VSPERF-69 Signed-off-by: Martin Klozik (martinx.klozik@intel.com) Reviewed-by: Maryam Tahhan Reviewed-by: Brian Castelli --- conf/01_testcases.conf | 16 ++ conf/02_vswitch.conf | 3 + conf/04_vnf.conf | 39 ++-- core/__init__.py | 1 - core/component_factory.py | 20 +- core/loader/loader_servant.py | 6 +- core/vnf_controller.py | 59 +++-- core/vnf_controller_p2p.py | 64 ------ core/vnf_controller_pvp.py | 69 ------ core/vswitch_controller_p2p.py | 28 +-- core/vswitch_controller_pvp.py | 25 +-- core/vswitch_controller_pvvp.py | 114 ++++++++++ docs/to-be-reorganized/NEWS.rst | 2 + docs/to-be-reorganized/quickstart.rst | 12 +- src/dpdk/dpdk.py | 38 ++-- src/ovs/ofctl.py | 11 +- testcases/testcase.py | 46 ++-- tools/tasks.py | 9 +- vnfs/qemu/qemu.py | 203 ++++++++++++++++++ vnfs/qemu/qemu_dpdk.py | 115 ++++++++++ vnfs/qemu/qemu_dpdk_vhost_cuse.py | 390 +++------------------------------- vnfs/qemu/qemu_dpdk_vhost_user.py | 387 ++++----------------------------- vnfs/vnf/vnf.py | 70 +++--- vswitches/ovs_dpdk_vhost.py | 6 +- 24 files changed, 728 insertions(+), 1005 deletions(-) delete mode 100644 core/vnf_controller_p2p.py delete mode 100644 core/vnf_controller_pvp.py create mode 100644 core/vswitch_controller_pvvp.py create mode 100644 vnfs/qemu/qemu.py create mode 100644 vnfs/qemu/qemu_dpdk.py 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 @@ -115,6 +115,22 @@ PERFORMANCE_TESTS = [ "Description": "LTD.Throughput.RFC2544.BackToBackFrames", "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", 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 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 - 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 - 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 : - # and we only want the piece - cmdline = logline.split(':')[1].strip() - - # use a no timeout since we don't know how long we - # should wait - vm1.send_and_wait(cmdline, timeout=-1) - - while True: - USER_INPUT = input() - vm1.send_and_wait(USER_INPUT, timeout=5) + "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 : - # and we only want the piece - cmdline = logline.split(':')[1].strip() - # use a no timeout since we don't know how long we - # should wait - vm1.send_and_wait(cmdline, timeout=-1) + # 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'] -- 2.16.6