diff options
-rwxr-xr-x | conf/01_testcases.conf | 16 | ||||
-rw-r--r-- | conf/02_vswitch.conf | 3 | ||||
-rw-r--r-- | conf/04_vnf.conf | 39 | ||||
-rw-r--r-- | core/__init__.py | 1 | ||||
-rw-r--r-- | core/component_factory.py | 20 | ||||
-rw-r--r-- | core/loader/loader_servant.py | 6 | ||||
-rw-r--r-- | core/vnf_controller.py | 59 | ||||
-rw-r--r-- | core/vnf_controller_p2p.py | 64 | ||||
-rw-r--r-- | core/vnf_controller_pvp.py | 69 | ||||
-rw-r--r-- | core/vswitch_controller_p2p.py | 28 | ||||
-rw-r--r-- | core/vswitch_controller_pvp.py | 25 | ||||
-rw-r--r-- | core/vswitch_controller_pvvp.py | 114 | ||||
-rw-r--r-- | docs/to-be-reorganized/NEWS.rst | 2 | ||||
-rw-r--r-- | docs/to-be-reorganized/quickstart.rst | 12 | ||||
-rw-r--r-- | src/dpdk/dpdk.py | 38 | ||||
-rw-r--r-- | src/ovs/ofctl.py | 11 | ||||
-rw-r--r-- | testcases/testcase.py | 46 | ||||
-rw-r--r-- | tools/tasks.py | 9 | ||||
-rw-r--r-- | vnfs/qemu/qemu.py | 203 | ||||
-rw-r--r-- | vnfs/qemu/qemu_dpdk.py | 115 | ||||
-rw-r--r-- | vnfs/qemu/qemu_dpdk_vhost_cuse.py | 390 | ||||
-rw-r--r-- | vnfs/qemu/qemu_dpdk_vhost_user.py | 387 | ||||
-rw-r--r-- | vnfs/vnf/vnf.py | 70 | ||||
-rw-r--r-- | vswitches/ovs_dpdk_vhost.py | 6 |
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'] |