From c9cd093f2f441adc9dd33627255326008e021a67 Mon Sep 17 00:00:00 2001 From: Martin Klozik Date: Tue, 16 Aug 2016 14:59:05 +0100 Subject: multi VM: Multi VMs in serial or parallel Support for deployment scenarios with any number of VMs in both serial and parallel configuration. Detailed content of the patch: * VswitchControllerPXP class for multi VM support * pvvpxx and pvpvxx deployments for xx VMs in serial respective parallel configuration * special GUEST_ options expansion to requested number of VMs; * support of GUEST_ options specific macros #VMINDEX, #MAC(), #IP() and #EVAL() * all GUEST specific options are turned to lists to be VM specific * support for VM with 1 NIC * support for VM with multiple NIC pairs; traffic is routed in serial or parallel between NIC paris based on deployment scenario * support for PVVP and PVPV scenarios using VMs with different numbers of NICs JIRA: VSPERF-361 Change-Id: I05bedbdfa9a81ea0166d9b03d83ae49d6cb8b19b Signed-off-by: Martin Klozik Reviewed-by: Maryam Tahhan Reviewed-by: Al Morton Reviewed-by: Christian Trautman Reviewed-by: Bill Michalowski Reviewed-by: Antonio Fischetti --- core/component_factory.py | 25 +++-- core/vnf_controller.py | 55 +++++++--- core/vswitch_controller_pvp.py | 118 --------------------- core/vswitch_controller_pvvp.py | 126 ----------------------- core/vswitch_controller_pxp.py | 221 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 277 insertions(+), 268 deletions(-) delete mode 100644 core/vswitch_controller_pvp.py delete mode 100644 core/vswitch_controller_pvvp.py create mode 100644 core/vswitch_controller_pxp.py (limited to 'core') diff --git a/core/component_factory.py b/core/component_factory.py index 258b7232..7f453bd2 100644 --- a/core/component_factory.py +++ b/core/component_factory.py @@ -18,8 +18,7 @@ from core.traffic_controller_rfc2544 import TrafficControllerRFC2544 from core.vswitch_controller_clean import VswitchControllerClean from core.vswitch_controller_p2p import VswitchControllerP2P -from core.vswitch_controller_pvp import VswitchControllerPVP -from core.vswitch_controller_pvvp import VswitchControllerPVVP +from core.vswitch_controller_pxp import VswitchControllerPXP from core.vswitch_controller_op2p import VswitchControllerOP2P from core.vswitch_controller_ptunp import VswitchControllerPtunP from core.vnf_controller import VnfController @@ -57,7 +56,7 @@ def create_vswitch(deployment_scenario, vswitch_class, traffic, The returned controller is configured with the given vSwitch class. - Deployment scenarios: 'p2p', 'pvp' + Deployment scenarios: e.g. 'p2p', 'pvp', 'pvpv12', etc. :param deployment_scenario: The deployment scenario name :param vswitch_class: Reference to vSwitch class to be used. @@ -66,18 +65,22 @@ def create_vswitch(deployment_scenario, vswitch_class, traffic, :return: IVSwitchController for the deployment_scenario """ deployment_scenario = deployment_scenario.lower() - if deployment_scenario.find("p2p") == 0: + if deployment_scenario.startswith("p2p"): return VswitchControllerP2P(vswitch_class, traffic) - elif deployment_scenario.find("pvp") >= 0: - return VswitchControllerPVP(vswitch_class, traffic) - elif deployment_scenario.find("pvvp") >= 0: - return VswitchControllerPVVP(vswitch_class, traffic) - elif deployment_scenario.find("op2p") >= 0: + elif deployment_scenario.startswith("pvp"): + return VswitchControllerPXP(deployment_scenario, vswitch_class, traffic) + elif deployment_scenario.startswith("pvvp"): + return VswitchControllerPXP(deployment_scenario, vswitch_class, traffic) + elif deployment_scenario.startswith("pvpv"): + return VswitchControllerPXP(deployment_scenario, vswitch_class, traffic) + elif deployment_scenario.startswith("op2p"): return VswitchControllerOP2P(vswitch_class, traffic, tunnel_operation) - elif deployment_scenario.find("ptunp") >= 0: + elif deployment_scenario.startswith("ptunp"): return VswitchControllerPtunP(vswitch_class, traffic) - elif deployment_scenario.find("clean") >= 0: + elif deployment_scenario.startswith("clean"): return VswitchControllerClean(vswitch_class, traffic) + else: + raise RuntimeError("Unknown deployment scenario '{}'.".format(deployment_scenario)) def create_vnf(deployment_scenario, vnf_class): diff --git a/core/vnf_controller.py b/core/vnf_controller.py index 8800ccaf..29700661 100644 --- a/core/vnf_controller.py +++ b/core/vnf_controller.py @@ -16,6 +16,7 @@ import logging import pexpect +from conf import settings from vnfs.vnf.vnf import IVnf class VnfController(object): @@ -25,13 +26,13 @@ class VnfController(object): Attributes: _vnf_class: A class object representing the VNF to be used. - _deployment_scenario: A string describing the scenario to set-up in the + _deployment: 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. + def __init__(self, deployment, vnf_class): + """Sets up the VNF infrastructure based on deployment scenario :param vnf_class: The VNF class to be used. """ @@ -41,17 +42,38 @@ class VnfController(object): # setup controller with requested number of VNFs self._logger = logging.getLogger(__name__) self._vnf_class = vnf_class - self._deployment_scenario = deployment_scenario.upper() - if self._deployment_scenario == 'P2P': - self._vnfs = [] - elif self._deployment_scenario == 'PVP': - self._vnfs = [vnf_class()] - elif self._deployment_scenario == 'PVVP': - self._vnfs = [vnf_class(), vnf_class()] - elif self._deployment_scenario == 'OP2P': - self._vnfs = [] + self._deployment = deployment.lower() + self._vnfs = [] + if self._deployment == 'pvp': + vm_number = 1 + elif (self._deployment.startswith('pvvp') or + self._deployment.startswith('pvpv')): + if len(self._deployment) > 4: + vm_number = int(self._deployment[4:]) + else: + vm_number = 2 else: - self._vnfs = [] + raise RuntimeError('Deployment {} is not supported by ' + 'VnfController.'.format(self._deployment)) + + if vm_number: + self._logger.debug('Check configuration for %s guests.', vm_number) + settings.check_vm_settings(vm_number) + # enforce that GUEST_NIC_NR is 1 or even number of NICs + updated = False + nics_nr = settings.getValue('GUEST_NICS_NR') + for index in range(len(nics_nr)): + if nics_nr[index] > 1 and nics_nr[index] % 2: + updated = True + nics_nr[index] = int(nics_nr[index] / 2) * 2 + if updated: + settings.setValue('GUEST_NICS_NR', nics_nr) + self._logger.warning('Odd number of NICs was detected. Configuration ' + 'was updated to GUEST_NICS_NR = %s', + settings.getValue('GUEST_NICS_NR')) + + self._vnfs = [vnf_class() for _ in range(vm_number)] + self._logger.debug('__init__ ' + str(len(self._vnfs)) + ' VNF[s] with ' + ' '.join(map(str, self._vnfs))) @@ -62,6 +84,13 @@ class VnfController(object): ' VNF[s] with ' + ' '.join(map(str, self._vnfs))) return self._vnfs + def get_vnfs_number(self): + """Returns a number of vnfs controlled by this controller. + """ + self._logger.debug('get_vnfs_number ' + str(len(self._vnfs)) + + ' VNF[s]') + return len(self._vnfs) + def start(self): """Boots all VNFs set-up by __init__. diff --git a/core/vswitch_controller_pvp.py b/core/vswitch_controller_pvp.py deleted file mode 100644 index a4f61961..00000000 --- a/core/vswitch_controller_pvp.py +++ /dev/null @@ -1,118 +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. - -"""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 VswitchControllerPVP(IVswitchController): - """VSwitch controller for PVP 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, traffic): - """Initializes up the prerequisites for the PVP 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 = "PVP" - self._traffic = traffic.copy() - self._logger.debug('Creation using ' + str(self._vswitch_class)) - - def setup(self): - """ Sets up the switch for pvp - """ - 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) - - self._vswitch.del_flow(bridge) - - # configure flows according to the TC definition - flow_template = _FLOW_TEMPLATE.copy() - if self._traffic['flow_type'] == 'IP': - flow_template.update({'dl_type':'0x0800', 'nw_src':self._traffic['l3']['srcip'], - 'nw_dst':self._traffic['l3']['dstip']}) - - 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, flow1) - self._vswitch.add_flow(bridge, flow2) - - if self._traffic['bidir'] == 'True': - 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, flow3) - self._vswitch.add_flow(bridge, flow4) - - 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')) - - def dump_vswitch_flows(self): - """See IVswitchController for description - """ - self._vswitch.dump_flows(settings.getValue('VSWITCH_BRIDGE_NAME')) diff --git a/core/vswitch_controller_pvvp.py b/core/vswitch_controller_pvvp.py deleted file mode 100644 index 729aca3f..00000000 --- a/core/vswitch_controller_pvvp.py +++ /dev/null @@ -1,126 +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. - -"""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, traffic): - """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._traffic = traffic.copy() - 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) - - # configure flows according to the TC definition - flow_template = _FLOW_TEMPLATE.copy() - if self._traffic['flow_type'] == 'IP': - flow_template.update({'dl_type':'0x0800', 'nw_src':self._traffic['l3']['srcip'], - 'nw_dst':self._traffic['l3']['dstip']}) - - 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._traffic['bidir'] == 'True': - 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')) - - def dump_vswitch_flows(self): - """See IVswitchController for description - """ - self._vswitch.dump_flows(settings.getValue('VSWITCH_BRIDGE_NAME')) diff --git a/core/vswitch_controller_pxp.py b/core/vswitch_controller_pxp.py new file mode 100644 index 00000000..6f53b5ac --- /dev/null +++ b/core/vswitch_controller_pxp.py @@ -0,0 +1,221 @@ +# Copyright 2016 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 multi VM scenarios with serial or parallel connection +""" + +import logging +import netaddr + +from core.vswitch_controller import IVswitchController +from vswitches.utils import add_ports_to_flow +from conf import settings + +_FLOW_TEMPLATE = { + 'idle_timeout': '0' +} + +_PROTO_TCP = 6 +_PROTO_UDP = 17 + +class VswitchControllerPXP(IVswitchController): + """VSwitch controller for PXP deployment scenario. + """ + def __init__(self, deployment, vswitch_class, traffic): + """Initializes up the prerequisites for the PXP deployment scenario. + + :vswitch_class: the vSwitch class to be used. + :deployment: the deployment scenario to configure + :traffic: dictionary with detailed traffic definition + """ + self._logger = logging.getLogger(__name__) + self._vswitch_class = vswitch_class + self._vswitch = vswitch_class() + self._pxp_topology = 'parallel' if deployment.startswith('pvpv') else 'serial' + if deployment == 'pvp': + self._pxp_vm_count = 1 + elif deployment.startswith('pvvp') or deployment.startswith('pvpv'): + if len(deployment) > 4: + self._pxp_vm_count = int(deployment[4:]) + else: + self._pxp_vm_count = 2 + else: + raise RuntimeError('Unknown number of VMs involved in {} deployment.'.format(deployment)) + + self._deployment_scenario = deployment + + self._traffic = traffic.copy() + self._bidir = True if self._traffic['bidir'] == 'True' else False + self._logger.debug('Creation using ' + str(self._vswitch_class)) + self._bridge = settings.getValue('VSWITCH_BRIDGE_NAME') + + def setup(self): + """ Sets up the switch for PXP + """ + self._logger.debug('Setup using ' + str(self._vswitch_class)) + + try: + self._vswitch.start() + + self._vswitch.add_switch(self._bridge) + + # create physical ports + (_, phy1_number) = self._vswitch.add_phy_port(self._bridge) + (_, phy2_number) = self._vswitch.add_phy_port(self._bridge) + + # create VM ports + # initialize vport array to requested number of VMs + guest_nics = settings.getValue('GUEST_NICS_NR') + vm_ports = [[] for _ in range(self._pxp_vm_count)] + # create as many VM ports as requested by configuration, but configure + # only even number of NICs or just one + for vmindex in range(self._pxp_vm_count): + # just for case, enforce even number of NICs or 1 + nics_nr = int(guest_nics[vmindex] / 2) * 2 if guest_nics[vmindex] > 1 else 1 + self._logger.debug('Create %s vports for %s. VM with index %s', + nics_nr, vmindex + 1, vmindex) + for _ in range(nics_nr): + (_, vport) = self._vswitch.add_vport(self._bridge) + vm_ports[vmindex].append(vport) + + self._vswitch.del_flow(self._bridge) + + # configure flows according to the TC definition + if self._pxp_topology == 'serial': + flow = _FLOW_TEMPLATE.copy() + if self._traffic['flow_type'] == 'IP': + flow.update({'dl_type':'0x0800', + 'nw_src':self._traffic['l3']['srcip'], + 'nw_dst':self._traffic['l3']['dstip']}) + + # insert flows for phy ports first + # from 1st PHY to 1st vport of 1st VM + self._add_flow(flow, + phy1_number, + vm_ports[0][0], + self._bidir) + # from last vport of last VM to 2nd phy + self._add_flow(flow, + vm_ports[self._pxp_vm_count-1][-1], + phy2_number, + self._bidir) + + # add serial connections among VMs and VM NICs pairs if needed + # in case of multiple NICs pairs per VM, the pairs are chained + # first, before flow to the next VM is created + for vmindex in range(self._pxp_vm_count): + # connect VMs NICs pairs in case of 4 and more NICs per VM + connections = [(vm_ports[vmindex][2*(x+1)-1], + vm_ports[vmindex][2*(x+1)]) + for x in range(int(len(vm_ports[vmindex])/2)-1)] + for connection in connections: + self._add_flow(flow, + connection[0], + connection[1], + self._bidir) + # connect last NICs to the next VM if there is any + if self._pxp_vm_count > vmindex + 1: + self._add_flow(flow, + vm_ports[vmindex][-1], + vm_ports[vmindex+1][0], + self._bidir) + else: + proto = _PROTO_TCP if self._traffic['l3']['proto'].lower() == 'tcp' else _PROTO_UDP + dst_mac_value = netaddr.EUI(self._traffic['l2']['dstmac']).value + dst_ip_value = netaddr.IPAddress(self._traffic['l3']['dstip']).value + # initialize stream index; every NIC pair of every VM uses unique stream + stream = 0 + for vmindex in range(self._pxp_vm_count): + # iterate through all VMs NIC pairs... + if len(vm_ports[vmindex]) > 1: + port_pairs = [(vm_ports[vmindex][2*x], + vm_ports[vmindex][2*x+1]) for x in range(int(len(vm_ports[vmindex])/2))] + else: + # ...or connect VM with just one NIC to both phy ports + port_pairs = [(vm_ports[vmindex][0], vm_ports[vmindex][0])] + + for port_pair in port_pairs: + flow_p = _FLOW_TEMPLATE.copy() + flow_v = _FLOW_TEMPLATE.copy() + + # update flow based on trafficgen settings + if self._traffic['stream_type'] == 'L2': + tmp_mac = netaddr.EUI(dst_mac_value + stream) + tmp_mac.dialect = netaddr.mac_unix_expanded + flow_p.update({'dl_dst':tmp_mac}) + elif self._traffic['stream_type'] == 'L3': + tmp_ip = netaddr.IPAddress(dst_ip_value + stream) + flow_p.update({'dl_type':'0x800', 'nw_dst':tmp_ip}) + elif self._traffic['stream_type'] == 'L4': + flow_p.update({'dl_type':'0x800', 'nw_proto':proto, 'tp_dst':stream}) + else: + raise RuntimeError('Unknown stream_type {}'.format(self._traffic['stream_type'])) + + # insert flow to dispatch traffic from physical ports + # to VMs based on stream type; all traffic from VMs is + # sent to physical ports to avoid issues with MAC swapping + # and upper layer mods performed inside guests + self._add_flow(flow_p, phy1_number, port_pair[0]) + self._add_flow(flow_v, port_pair[1], phy2_number) + if self._bidir: + self._add_flow(flow_p, phy2_number, port_pair[1]) + self._add_flow(flow_v, port_pair[0], phy1_number) + + # every NIC pair needs its own unique traffic stream + stream += 1 + + 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 _add_flow(self, flow, port1, port2, reverse_flow=False): + """ Helper method to insert flow into the vSwitch + """ + self._vswitch.add_flow(self._bridge, + add_ports_to_flow(flow, + port1, + port2)) + if reverse_flow: + self._vswitch.add_flow(self._bridge, + add_ports_to_flow(flow, + port2, + port1)) + + 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(self._bridge) + + def dump_vswitch_flows(self): + """See IVswitchController for description + """ + self._vswitch.dump_flows(self._bridge) -- cgit 1.2.3-korg