aboutsummaryrefslogtreecommitdiffstats
path: root/nfvbench/chaining.py
diff options
context:
space:
mode:
Diffstat (limited to 'nfvbench/chaining.py')
-rw-r--r--nfvbench/chaining.py503
1 files changed, 435 insertions, 68 deletions
diff --git a/nfvbench/chaining.py b/nfvbench/chaining.py
index a97cd0b..d6f67f9 100644
--- a/nfvbench/chaining.py
+++ b/nfvbench/chaining.py
@@ -49,18 +49,21 @@ import os
import re
import time
-from glanceclient.v2 import client as glanceclient
+import glanceclient
from neutronclient.neutron import client as neutronclient
from novaclient.client import Client
from attrdict import AttrDict
-import compute
-from log import LOG
-from specs import ChainType
-
+from .chain_router import ChainRouter
+from . import compute
+from .log import LOG
+from .specs import ChainType
# Left and right index for network and port lists
LEFT = 0
RIGHT = 1
+# L3 traffic edge networks are at the end of networks list
+EDGE_LEFT = -2
+EDGE_RIGHT = -1
# Name of the VM config file
NFVBENCH_CFG_FILENAME = 'nfvbenchvm.conf'
# full pathame of the VM config in the VM
@@ -74,8 +77,6 @@ BOOT_SCRIPT_PATHNAME = os.path.join(os.path.dirname(os.path.abspath(__file__)),
class ChainException(Exception):
"""Exception while operating the chains."""
- pass
-
class NetworkEncaps(object):
"""Network encapsulation."""
@@ -131,6 +132,7 @@ class ChainVnfPort(object):
self.manager = vnf.manager
self.reuse = False
self.port = None
+ self.floating_ip = None
if vnf.instance:
# VNF instance is reused, we need to find an existing port that matches this instance
# and network
@@ -156,6 +158,10 @@ class ChainVnfPort(object):
'binding:vnic_type': vnic_type
}
}
+ subnet_id = chain_network.get_subnet_uuid()
+ if subnet_id:
+ body['port']['fixed_ips'] = [{'subnet_id': subnet_id}]
+
port = self.manager.neutron_client.create_port(body)
self.port = port['port']
LOG.info('Created port %s', name)
@@ -174,18 +180,39 @@ class ChainVnfPort(object):
"""Get the MAC address for this port."""
return self.port['mac_address']
+ def get_ip(self):
+ """Get the IP address for this port."""
+ return self.port['fixed_ips'][0]['ip_address']
+
+ def set_floating_ip(self, chain_network):
+ # create and add floating ip to port
+ try:
+ self.floating_ip = self.manager.neutron_client.create_floatingip({
+ 'floatingip': {
+ 'floating_network_id': chain_network.get_uuid(),
+ 'port_id': self.port['id'],
+ 'description': 'nfvbench floating ip for port:' + self.port['name'],
+ }})['floatingip']
+ LOG.info('Floating IP %s created and associated on port %s',
+ self.floating_ip['floating_ip_address'], self.name)
+ return self.floating_ip['floating_ip_address']
+ except Exception:
+ LOG.info('Failed to created and associated floating ip on port %s (ignored)', self.name)
+ return self.port['fixed_ips'][0]['ip_address']
+
def delete(self):
"""Delete this port instance."""
if self.reuse or not self.port:
return
- retry = 0
- while retry < self.manager.config.generic_retry_count:
+ for _ in range(0, self.manager.config.generic_retry_count):
try:
self.manager.neutron_client.delete_port(self.port['id'])
LOG.info("Deleted port %s", self.name)
+ if self.floating_ip:
+ self.manager.neutron_client.delete_floatingip(self.floating_ip['id'])
+ LOG.info("Deleted floating IP %s", self.floating_ip['description'])
return
except Exception:
- retry += 1
time.sleep(self.manager.config.generic_poll_sec)
LOG.error('Unable to delete port: %s', self.name)
@@ -193,24 +220,39 @@ class ChainVnfPort(object):
class ChainNetwork(object):
"""Could be a shared network across all chains or a chain private network."""
- def __init__(self, manager, network_config, chain_id=None, lookup_only=False):
+ def __init__(self, manager, network_config, chain_id=None, lookup_only=False,
+ suffix=None):
"""Create a network for given chain.
network_config: a dict containing the network properties
- (segmentation_id and physical_network)
+ (name, segmentation_id and physical_network)
chain_id: to which chain the networks belong.
a None value will mean that these networks are shared by all chains
+ suffix: a suffix to add to the network name (if not None)
"""
self.manager = manager
- self.name = network_config.name
+ if chain_id is None:
+ self.name = network_config.name
+ else:
+ # the name itself can be either a string or a list of names indexed by chain ID
+ if isinstance(network_config.name, tuple):
+ self.name = network_config.name[chain_id]
+ else:
+ # network_config.name is a prefix string
+ self.name = network_config.name + str(chain_id)
+ if suffix:
+ self.name = self.name + suffix
self.segmentation_id = self._get_item(network_config.segmentation_id,
chain_id, auto_index=True)
+ self.subnet_name = self._get_item(network_config.subnet, chain_id)
self.physical_network = self._get_item(network_config.physical_network, chain_id)
- if chain_id is not None:
- self.name += str(chain_id)
+
self.reuse = False
self.network = None
self.vlan = None
+ self.router_name = None
+ if manager.config.l3_router and hasattr(network_config, 'router_name'):
+ self.router_name = network_config.router_name
try:
self._setup(network_config, lookup_only)
except Exception:
@@ -242,7 +284,7 @@ class ChainNetwork(object):
return item_field[index]
except IndexError:
raise ChainException("List %s is too short for chain index %d" %
- (str(item_field), index))
+ (str(item_field), index)) from IndexError
# single value is configured
if auto_index:
return item_field + index
@@ -283,7 +325,7 @@ class ChainNetwork(object):
'network': {
'name': self.name,
'admin_state_up': True
- }
+ }
}
if network_config.network_type:
body['network']['provider:network_type'] = network_config.network_type
@@ -292,6 +334,8 @@ class ChainNetwork(object):
if self.physical_network:
body['network']['provider:physical_network'] = self.physical_network
self.network = self.manager.neutron_client.create_network(body)['network']
+ # create associated subnet, all subnets have the same name (which is ok since
+ # we do not need to address them directly by name)
body = {
'subnet': {'name': network_config.subnet,
'cidr': network_config.cidr,
@@ -313,6 +357,18 @@ class ChainNetwork(object):
"""
return self.network['id']
+ def get_subnet_uuid(self):
+ """
+ Extract UUID of this subnet network.
+
+ :return: UUID of this subnet network
+ """
+ for subnet in self.network['subnets']:
+ if self.subnet_name == self.manager.neutron_client \
+ .show_subnet(subnet)['subnet']['name']:
+ return subnet
+ return None
+
def get_vlan(self):
"""
Extract vlan for this network.
@@ -329,24 +385,30 @@ class ChainNetwork(object):
:return: VNI ID for this network
"""
- if 'vxlan' not in self.network['provider:network_type']:
- raise ChainException('Trying to retrieve VNI for non VXLAN network')
+
+ return self.network['provider:segmentation_id']
+
+ def get_mpls_inner_label(self):
+ """
+ Extract MPLS VPN Label for this network.
+
+ :return: MPLS VPN Label for this network
+ """
+
return self.network['provider:segmentation_id']
def delete(self):
"""Delete this network."""
if not self.reuse and self.network:
- retry = 0
- while retry < self.manager.config.generic_retry_count:
+ for retry in range(0, self.manager.config.generic_retry_count):
try:
self.manager.neutron_client.delete_network(self.network['id'])
LOG.info("Deleted network: %s", self.name)
return
except Exception:
- retry += 1
LOG.info('Error deleting network %s (retry %d/%d)...',
self.name,
- retry,
+ retry + 1,
self.manager.config.generic_retry_count)
time.sleep(self.manager.config.generic_poll_sec)
LOG.error('Unable to delete network: %s', self.name)
@@ -369,15 +431,27 @@ class ChainVnf(object):
if len(networks) > 2:
# we will have more than 1 VM in each chain
self.name += '-' + str(vnf_id)
+ # A list of ports for this chain
+ # There are normally 2 ports carrying traffic (index 0, and index 1) and
+ # potentially multiple idle ports not carrying traffic (index 2 and up)
+ # For example if 7 idle interfaces are requested, the corresp. ports will be
+ # at index 2 to 8
self.ports = []
+ self.management_port = None
+ self.routers = []
self.status = None
self.instance = None
self.reuse = False
self.host_ip = None
+ self.idle_networks = []
+ self.idle_ports = []
try:
# the vnf_id is conveniently also the starting index in networks
# for the left and right networks associated to this VNF
- self._setup(networks[vnf_id:vnf_id + 2])
+ if self.manager.config.l3_router:
+ self._setup(networks[vnf_id:vnf_id + 4])
+ else:
+ self._setup(networks[vnf_id:vnf_id + 2])
except Exception:
LOG.error("Error creating VNF %s", self.name)
self.delete()
@@ -386,29 +460,85 @@ class ChainVnf(object):
def _get_vm_config(self, remote_mac_pair):
config = self.manager.config
devices = self.manager.generator_config.devices
- with open(BOOT_SCRIPT_PATHNAME, 'r') as boot_script:
+
+ if config.l3_router:
+ tg_gateway1_ip = self.routers[LEFT].ports[1]['fixed_ips'][0][
+ 'ip_address'] # router edge ip left
+ tg_gateway2_ip = self.routers[RIGHT].ports[1]['fixed_ips'][0][
+ 'ip_address'] # router edge ip right
+ tg_mac1 = self.routers[LEFT].ports[1]['mac_address'] # router edge mac left
+ tg_mac2 = self.routers[RIGHT].ports[1]['mac_address'] # router edge mac right
+ # edge cidr mask left
+ vnf_gateway1_cidr = \
+ self.ports[LEFT].get_ip() + self.__get_network_mask(
+ self.manager.config.edge_networks.left.cidr)
+ # edge cidr mask right
+ vnf_gateway2_cidr = \
+ self.ports[RIGHT].get_ip() + self.__get_network_mask(
+ self.manager.config.edge_networks.right.cidr)
+ if config.vm_forwarder != 'vpp':
+ raise ChainException(
+ 'L3 router mode imply to set VPP as VM forwarder.'
+ 'Please update your config file with: vm_forwarder: vpp')
+ else:
+ tg_gateway1_ip = devices[LEFT].tg_gateway_ip_addrs
+ tg_gateway2_ip = devices[RIGHT].tg_gateway_ip_addrs
+ if not config.loop_vm_arp:
+ tg_mac1 = remote_mac_pair[0]
+ tg_mac2 = remote_mac_pair[1]
+ else:
+ tg_mac1 = ""
+ tg_mac2 = ""
+
+ g1cidr = devices[LEFT].get_gw_ip(
+ self.chain.chain_id) + self.__get_network_mask(
+ self.manager.config.internal_networks.left.cidr)
+ g2cidr = devices[RIGHT].get_gw_ip(
+ self.chain.chain_id) + self.__get_network_mask(
+ self.manager.config.internal_networks.right.cidr)
+
+ vnf_gateway1_cidr = g1cidr
+ vnf_gateway2_cidr = g2cidr
+
+ with open(BOOT_SCRIPT_PATHNAME, 'r', encoding="utf-8") as boot_script:
content = boot_script.read()
- g1cidr = devices[LEFT].get_gw_ip(self.chain.chain_id) + '/8'
- g2cidr = devices[RIGHT].get_gw_ip(self.chain.chain_id) + '/8'
vm_config = {
'forwarder': config.vm_forwarder,
'intf_mac1': self.ports[LEFT].get_mac(),
'intf_mac2': self.ports[RIGHT].get_mac(),
- 'tg_gateway1_ip': devices[LEFT].tg_gateway_ip_addrs,
- 'tg_gateway2_ip': devices[RIGHT].tg_gateway_ip_addrs,
+ 'tg_gateway1_ip': tg_gateway1_ip,
+ 'tg_gateway2_ip': tg_gateway2_ip,
'tg_net1': devices[LEFT].ip_addrs,
'tg_net2': devices[RIGHT].ip_addrs,
- 'vnf_gateway1_cidr': g1cidr,
- 'vnf_gateway2_cidr': g2cidr,
- 'tg_mac1': remote_mac_pair[0],
- 'tg_mac2': remote_mac_pair[1]
+ 'vnf_gateway1_cidr': vnf_gateway1_cidr,
+ 'vnf_gateway2_cidr': vnf_gateway2_cidr,
+ 'tg_mac1': tg_mac1,
+ 'tg_mac2': tg_mac2,
+ 'vif_mq_size': config.vif_multiqueue_size,
+ 'num_mbufs': config.num_mbufs
}
+ if self.manager.config.use_management_port:
+ mgmt_ip = self.management_port.port['fixed_ips'][0]['ip_address']
+ mgmt_mask = self.__get_network_mask(self.manager.config.management_network.cidr)
+ vm_config['intf_mgmt_cidr'] = mgmt_ip + mgmt_mask
+ vm_config['intf_mgmt_ip_gw'] = self.manager.config.management_network.gateway
+ vm_config['intf_mac_mgmt'] = self.management_port.port['mac_address']
+ else:
+ # Interface management config left empty to avoid error in VM spawn
+ # if nfvbench config has values for management network but use_management_port=false
+ vm_config['intf_mgmt_cidr'] = ''
+ vm_config['intf_mgmt_ip_gw'] = ''
+ vm_config['intf_mac_mgmt'] = ''
return content.format(**vm_config)
+ @staticmethod
+ def __get_network_mask(network):
+ return '/' + network.split('/')[1]
+
def _get_vnic_type(self, port_index):
"""Get the right vnic type for given port indexself.
- If SR-IOV is speficied, middle ports in multi-VNF chains
+ If SR-IOV is specified, middle ports in multi-VNF chains
can use vswitch or SR-IOV based on config.use_sriov_middle_net
"""
if self.manager.config.sriov:
@@ -423,45 +553,164 @@ class ChainVnf(object):
return 'direct'
return 'normal'
+ def _get_idle_networks_ports(self):
+ """Get the idle networks for PVP or PVVP chain (non shared net only)
+
+ For EXT packet path or shared net, returns empty list.
+ For PVP, PVVP these networks will be created if they do not exist.
+ chain_id: to which chain the networks belong.
+ a None value will mean that these networks are shared by all chains
+ """
+ networks = []
+ ports = []
+ config = self.manager.config
+ chain_id = self.chain.chain_id
+ idle_interfaces_per_vm = config.idle_interfaces_per_vm
+ if config.service_chain == ChainType.EXT or chain_id is None or \
+ idle_interfaces_per_vm == 0:
+ return
+
+ # Make a copy of the idle networks dict as we may have to modify the
+ # segmentation ID
+ idle_network_cfg = AttrDict(config.idle_networks)
+ if idle_network_cfg.segmentation_id:
+ segmentation_id = idle_network_cfg.segmentation_id + \
+ chain_id * idle_interfaces_per_vm
+ else:
+ segmentation_id = None
+ try:
+ # create as many idle networks and ports as requested
+ for idle_index in range(idle_interfaces_per_vm):
+ if config.service_chain == ChainType.PVP:
+ suffix = '.%d' % (idle_index)
+ else:
+ suffix = '.%d.%d' % (self.vnf_id, idle_index)
+ port_name = self.name + '-idle' + str(idle_index)
+ # update the segmentation id based on chain id and idle index
+ if segmentation_id:
+ idle_network_cfg.segmentation_id = segmentation_id + idle_index
+ port_name = port_name + "." + str(segmentation_id)
+
+ networks.append(ChainNetwork(self.manager,
+ idle_network_cfg,
+ chain_id,
+ suffix=suffix))
+ ports.append(ChainVnfPort(port_name,
+ self,
+ networks[idle_index],
+ 'normal'))
+ except Exception:
+ # need to cleanup all successful networks
+ for net in networks:
+ net.delete()
+ for port in ports:
+ port.delete()
+ raise
+ self.idle_networks = networks
+ self.idle_ports = ports
+
def _setup(self, networks):
flavor_id = self.manager.flavor.flavor.id
# Check if we can reuse an instance with same name
for instance in self.manager.existing_instances:
if instance.name == self.name:
+ instance_left = LEFT
+ instance_right = RIGHT
+ # In case of L3 traffic instance use edge networks
+ if self.manager.config.l3_router:
+ instance_left = EDGE_LEFT
+ instance_right = EDGE_RIGHT
# Verify that other instance characteristics match
if instance.flavor['id'] != flavor_id:
self._reuse_exception('Flavor mismatch')
if instance.status != "ACTIVE":
self._reuse_exception('Matching instance is not in ACTIVE state')
# The 2 networks for this instance must also be reused
- if not networks[LEFT].reuse:
- self._reuse_exception('network %s is new' % networks[LEFT].name)
- if not networks[RIGHT].reuse:
- self._reuse_exception('network %s is new' % networks[RIGHT].name)
+ if not networks[instance_left].reuse:
+ self._reuse_exception('network %s is new' % networks[instance_left].name)
+ if not networks[instance_right].reuse:
+ self._reuse_exception('network %s is new' % networks[instance_right].name)
# instance.networks have the network names as keys:
# {'nfvbench-rnet0': ['192.168.2.10'], 'nfvbench-lnet0': ['192.168.1.8']}
- if networks[LEFT].name not in instance.networks:
+ if networks[instance_left].name not in instance.networks:
self._reuse_exception('Left network mismatch')
- if networks[RIGHT].name not in instance.networks:
+ if networks[instance_right].name not in instance.networks:
self._reuse_exception('Right network mismatch')
self.reuse = True
self.instance = instance
LOG.info('Reusing existing instance %s on %s',
self.name, self.get_hypervisor_name())
+ # create management port if needed
+ if self.manager.config.use_management_port:
+ self.management_port = ChainVnfPort(self.name + '-mgmt', self,
+ self.manager.management_network, 'normal')
+ ip = self.management_port.port['fixed_ips'][0]['ip_address']
+ if self.manager.config.use_floating_ip:
+ ip = self.management_port.set_floating_ip(self.manager.floating_ip_network)
+ LOG.info("Management interface will be active using IP: %s, "
+ "and you can connect over SSH with login: nfvbench and password: nfvbench", ip)
# create or reuse/discover 2 ports per instance
- self.ports = [ChainVnfPort(self.name + '-' + str(index),
- self,
- networks[index],
- self._get_vnic_type(index)) for index in [0, 1]]
+ if self.manager.config.l3_router:
+ for index in [0, 1]:
+ self.ports.append(ChainVnfPort(self.name + '-' + str(index),
+ self,
+ networks[index + 2],
+ self._get_vnic_type(index)))
+ else:
+ for index in [0, 1]:
+ self.ports.append(ChainVnfPort(self.name + '-' + str(index),
+ self,
+ networks[index],
+ self._get_vnic_type(index)))
+
+ # create idle networks and ports only if instance is not reused
+ # if reused, we do not care about idle networks/ports
+ if not self.reuse:
+ self._get_idle_networks_ports()
+
+ # Create neutron routers for L3 traffic use case
+ if self.manager.config.l3_router and self.manager.openstack:
+ internal_nets = networks[:2]
+ if self.manager.config.service_chain == ChainType.PVP:
+ edge_nets = networks[2:]
+ else:
+ edge_nets = networks[3:]
+ subnets_left = [internal_nets[0], edge_nets[0]]
+ routes_left = [{'destination': self.manager.config.traffic_generator.ip_addrs[0],
+ 'nexthop': self.manager.config.traffic_generator.tg_gateway_ip_addrs[
+ 0]},
+ {'destination': self.manager.config.traffic_generator.ip_addrs[1],
+ 'nexthop': self.ports[0].get_ip()}]
+ self.routers.append(
+ ChainRouter(self.manager, edge_nets[0].router_name, subnets_left, routes_left))
+ subnets_right = [internal_nets[1], edge_nets[1]]
+ routes_right = [{'destination': self.manager.config.traffic_generator.ip_addrs[0],
+ 'nexthop': self.ports[1].get_ip()},
+ {'destination': self.manager.config.traffic_generator.ip_addrs[1],
+ 'nexthop': self.manager.config.traffic_generator.tg_gateway_ip_addrs[
+ 1]}]
+ self.routers.append(
+ ChainRouter(self.manager, edge_nets[1].router_name, subnets_right, routes_right))
+ # Overload gateway_ips property with router ip address for ARP and traffic calls
+ self.manager.generator_config.devices[LEFT].set_gw_ip(
+ self.routers[LEFT].ports[0]['fixed_ips'][0]['ip_address']) # router edge ip left)
+ self.manager.generator_config.devices[RIGHT].set_gw_ip(
+ self.routers[RIGHT].ports[0]['fixed_ips'][0]['ip_address']) # router edge ip right)
+
# if no reuse, actual vm creation is deferred after all ports in the chain are created
# since we need to know the next mac in a multi-vnf chain
def create_vnf(self, remote_mac_pair):
"""Create the VNF instance if it does not already exist."""
if self.instance is None:
- port_ids = [{'port-id': vnf_port.port['id']}
- for vnf_port in self.ports]
+ port_ids = []
+ if self.manager.config.use_management_port:
+ port_ids.append({'port-id': self.management_port.port['id']})
+ port_ids.extend([{'port-id': vnf_port.port['id']} for vnf_port in self.ports])
+ # add idle ports
+ for idle_port in self.idle_ports:
+ port_ids.append({'port-id': idle_port.port['id']})
vm_config = self._get_vm_config(remote_mac_pair)
az = self.manager.placer.get_required_az()
server = self.manager.comp.create_server(self.name,
@@ -485,8 +734,8 @@ class ChainVnf(object):
# here we MUST wait until this instance is resolved otherwise subsequent
# VNF creation can be placed in other hypervisors!
config = self.manager.config
- max_retries = (config.check_traffic_time_sec +
- config.generic_poll_sec - 1) / config.generic_poll_sec
+ max_retries = int((config.check_traffic_time_sec +
+ config.generic_poll_sec - 1) / config.generic_poll_sec)
retry = 0
for retry in range(max_retries):
status = self.get_status()
@@ -522,7 +771,13 @@ class ChainVnf(object):
def get_hostname(self):
"""Get the hypervisor host name running this VNF instance."""
- return getattr(self.instance, 'OS-EXT-SRV-ATTR:hypervisor_hostname')
+ if self.manager.is_admin:
+ hypervisor_hostname = getattr(self.instance, 'OS-EXT-SRV-ATTR:hypervisor_hostname')
+ else:
+ hypervisor_hostname = self.manager.config.hypervisor_hostname
+ if not hypervisor_hostname:
+ raise ChainException('Hypervisor hostname parameter is mandatory')
+ return hypervisor_hostname
def get_host_ip(self):
"""Get the IP address of the host where this instance runs.
@@ -536,7 +791,12 @@ class ChainVnf(object):
def get_hypervisor_name(self):
"""Get hypervisor name (az:hostname) for this VNF instance."""
if self.instance:
- az = getattr(self.instance, 'OS-EXT-AZ:availability_zone')
+ if self.manager.is_admin:
+ az = getattr(self.instance, 'OS-EXT-AZ:availability_zone')
+ else:
+ az = self.manager.config.availability_zone
+ if not az:
+ raise ChainException('Availability zone parameter is mandatory')
hostname = self.get_hostname()
if az:
return az + ':' + hostname
@@ -555,8 +815,15 @@ class ChainVnf(object):
if self.instance:
self.manager.comp.delete_server(self.instance)
LOG.info("Deleted instance %s", self.name)
+ if self.manager.config.use_management_port:
+ self.management_port.delete()
for port in self.ports:
port.delete()
+ for port in self.idle_ports:
+ port.delete()
+ for network in self.idle_networks:
+ network.delete()
+
class Chain(object):
"""A class to manage a single chain.
@@ -619,7 +886,8 @@ class Chain(object):
def get_length(self):
"""Get the number of VNF in the chain."""
- return len(self.networks) - 1
+ # Take into account 2 edge networks for routers
+ return len(self.networks) - 3 if self.manager.config.l3_router else len(self.networks) - 1
def _get_remote_mac_pairs(self):
"""Get the list of remote mac pairs for every VNF in the chain.
@@ -674,7 +942,10 @@ class Chain(object):
if port_index:
# this will pick the last item in array
port_index = -1
- return self.networks[port_index].get_vlan()
+ # This string filters networks connected to TG, in case of
+ # l3-router feature we have 4 networks instead of 2
+ networks = [x for x in self.networks if not x.router_name]
+ return networks[port_index].get_vlan()
def get_vxlan(self, port_index):
"""Get the VXLAN id on a given port.
@@ -690,6 +961,20 @@ class Chain(object):
port_index = -1
return self.networks[port_index].get_vxlan()
+ def get_mpls_inner_label(self, port_index):
+ """Get the MPLS VPN Label on a given port.
+
+ port_index: left port is 0, right port is 1
+ return: the mpls_label_id or None if there is no mpls
+ """
+ # for port 1 we need to return the MPLS Label of the last network in the chain
+ # The networks array contains 2 networks for PVP [left, right]
+ # and 3 networks in the case of PVVP [left.middle,right]
+ if port_index:
+ # this will pick the last item in array
+ port_index = -1
+ return self.networks[port_index].get_mpls_inner_label()
+
def get_dest_mac(self, port_index):
"""Get the dest MAC on a given port.
@@ -851,6 +1136,7 @@ class ChainManager(object):
if self.openstack:
# openstack only
session = chain_runner.cred.get_session()
+ self.is_admin = chain_runner.cred.is_admin
self.nova_client = Client(2, session=session)
self.neutron_client = neutronclient.Client('2.0', session=session)
self.glance_client = glanceclient.Client('2', session=session)
@@ -864,6 +1150,22 @@ class ChainManager(object):
self.flavor = ChainFlavor(config.flavor_type, config.flavor, self.comp)
# Get list of all existing instances to check if some instances can be reused
self.existing_instances = self.comp.get_server_list()
+ # If management port is requested for VMs, create management network (shared)
+ if self.config.use_management_port:
+ self.management_network = ChainNetwork(self, self.config.management_network,
+ None, False)
+ # If floating IP is used for management, create and share
+ # across chains the floating network
+ if self.config.use_floating_ip:
+ self.floating_ip_network = ChainNetwork(self,
+ self.config.floating_network,
+ None, False)
+ else:
+ # For EXT chains, the external_networks left and right fields in the config
+ # must be either a prefix string or a list of at least chain-count strings
+ self._check_extnet('left', config.external_networks.left)
+ self._check_extnet('right', config.external_networks.right)
+
# If networks are shared across chains, get the list of networks
if config.service_chain_shared_net:
self.networks = self.get_networks()
@@ -871,18 +1173,20 @@ class ChainManager(object):
for chain_id in range(self.chain_count):
self.chains.append(Chain(chain_id, self))
if config.service_chain == ChainType.EXT:
- # if EXT and no ARP we need to read dest MACs from config
- if config.no_arp:
+ # if EXT and no ARP or VxLAN we need to read dest MACs from config
+ if config.no_arp or config.vxlan:
self._get_dest_macs_from_config()
else:
# Make sure all instances are active before proceeding
self._ensure_instances_active()
+ # network API call do not show VLANS ID if not admin read from config
+ if not self.is_admin and config.vlan_tagging:
+ self._get_config_vlans()
except Exception:
self.delete()
raise
else:
# no openstack, no need to create chains
-
if not config.l2_loopback and config.no_arp:
self._get_dest_macs_from_config()
if config.vlan_tagging:
@@ -890,12 +1194,27 @@ class ChainManager(object):
if len(config.vlans) != 2:
raise ChainException('The config vlans property must be a list '
'with 2 lists of VLAN IDs')
- re_vlan = "[0-9]*$"
- self.vlans = [self._check_list('vlans[0]', config.vlans[0], re_vlan),
- self._check_list('vlans[1]', config.vlans[1], re_vlan)]
+ self._get_config_vlans()
if config.vxlan:
raise ChainException('VxLAN is only supported with OpenStack')
+ def _check_extnet(self, side, name):
+ if not name:
+ raise ChainException('external_networks.%s must contain a valid network'
+ ' name prefix or a list of network names' % side)
+ if isinstance(name, tuple) and len(name) < self.chain_count:
+ raise ChainException('external_networks.%s %s'
+ ' must have at least %d names' % (side, name, self.chain_count))
+
+ def _get_config_vlans(self):
+ re_vlan = "[0-9]*$"
+ try:
+ self.vlans = [self._check_list('vlans[0]', self.config.vlans[0], re_vlan),
+ self._check_list('vlans[1]', self.config.vlans[1], re_vlan)]
+ except IndexError:
+ raise ChainException(
+ 'vlans parameter is mandatory. Set valid value in config file') from IndexError
+
def _get_dest_macs_from_config(self):
re_mac = "[0-9a-fA-F]{2}([-:])[0-9a-fA-F]{2}(\\1[0-9a-fA-F]{2}){4}$"
tg_config = self.config.traffic_generator
@@ -908,6 +1227,8 @@ class ChainManager(object):
# if it is a single int or mac, make it a list of 1 int
if isinstance(ll, (int, str)):
ll = [ll]
+ else:
+ ll = list(ll)
for item in ll:
if not re.match(pattern, str(item)):
raise ChainException("Invalid format '{item}' specified in {fname}"
@@ -965,12 +1286,16 @@ class ChainManager(object):
LOG.info('Image %s successfully uploaded.', self.image_name)
self.image_instance = self.comp.find_image(self.image_name)
+ # image multiqueue property must be set according to the vif_multiqueue_size
+ # config value (defaults to 1 or disabled)
+ self.comp.image_set_multiqueue(self.image_instance, self.config.vif_multiqueue_size > 1)
+
def _ensure_instances_active(self):
instances = []
for chain in self.chains:
instances.extend(chain.get_instances())
initial_instance_count = len(instances)
- max_retries = (self.config.check_traffic_time_sec +
+ max_retries = (self.config.check_traffic_time_sec + (initial_instance_count - 1) * 10 +
self.config.generic_poll_sec - 1) / self.config.generic_poll_sec
retry = 0
while instances:
@@ -1016,6 +1341,7 @@ class ChainManager(object):
lookup_only = True
ext_net = self.config.external_networks
net_cfg = [AttrDict({'name': name,
+ 'subnet': None,
'segmentation_id': None,
'physical_network': None})
for name in [ext_net.left, ext_net.right]]
@@ -1028,6 +1354,10 @@ class ChainManager(object):
net_cfg = [int_nets.left, int_nets.right]
else:
net_cfg = [int_nets.left, int_nets.middle, int_nets.right]
+ if self.config.l3_router:
+ edge_nets = self.config.edge_networks
+ net_cfg.append(edge_nets.left)
+ net_cfg.append(edge_nets.right)
networks = []
try:
for cfg in net_cfg:
@@ -1091,30 +1421,41 @@ class ChainManager(object):
"""
return self.get_existing_ports().get(chain_network.get_uuid(), None)
- def get_host_ip_from_mac(self, mac):
- """Get the host IP address matching a MAC.
+ def get_hypervisor_from_mac(self, mac):
+ """Get the hypervisor that hosts a VM MAC.
mac: MAC address to look for
- return: the IP address of the host where the matching port runs or None if not found
+ return: the hypervisor where the matching port runs or None if not found
"""
# _existing_ports is a dict of list of ports indexed by network id
- for port_list in self.get_existing_ports().values():
+ for port_list in list(self.get_existing_ports().values()):
for port in port_list:
try:
if port['mac_address'] == mac:
host_id = port['binding:host_id']
- return self.comp.get_hypervisor(host_id).host_ip
+ return self.comp.get_hypervisor(host_id)
except KeyError:
pass
return None
+ def get_host_ip_from_mac(self, mac):
+ """Get the host IP address matching a MAC.
+
+ mac: MAC address to look for
+ return: the IP address of the host where the matching port runs or None if not found
+ """
+ hypervisor = self.get_hypervisor_from_mac(mac)
+ if hypervisor:
+ return hypervisor.host_ip
+ return None
+
def get_chain_vlans(self, port_index):
"""Get the list of per chain VLAN id on a given port.
port_index: left port is 0, right port is 1
return: a VLAN ID list indexed by the chain index or None if no vlan tagging
"""
- if self.chains:
+ if self.chains and self.is_admin:
return [self.chains[chain_index].get_vlan(port_index)
for chain_index in range(self.chain_count)]
# no openstack
@@ -1126,11 +1467,23 @@ class ChainManager(object):
port_index: left port is 0, right port is 1
return: a VNIs ID list indexed by the chain index or None if no vlan tagging
"""
- if self.chains:
+ if self.chains and self.is_admin:
return [self.chains[chain_index].get_vxlan(port_index)
for chain_index in range(self.chain_count)]
# no openstack
- raise ChainException('VxLAN is only supported with OpenStack')
+ raise ChainException('VxLAN is only supported with OpenStack and with admin user')
+
+ def get_chain_mpls_inner_labels(self, port_index):
+ """Get the list of per chain MPLS VPN Labels on a given port.
+
+ port_index: left port is 0, right port is 1
+ return: a MPLSs ID list indexed by the chain index or None if no mpls
+ """
+ if self.chains and self.is_admin:
+ return [self.chains[chain_index].get_mpls_inner_label(port_index)
+ for chain_index in range(self.chain_count)]
+ # no openstack
+ raise ChainException('MPLS is only supported with OpenStack and with admin user')
def get_dest_macs(self, port_index):
"""Get the list of per chain dest MACs on a given port.
@@ -1178,7 +1531,17 @@ class ChainManager(object):
if self.chains:
# in the case of EXT, the compute node must be retrieved from the port
# associated to any of the dest MACs
- return self.chains[0].get_compute_nodes()
+ if self.config.service_chain != ChainType.EXT:
+ return self.chains[0].get_compute_nodes()
+ # in the case of EXT, the compute node must be retrieved from the port
+ # associated to any of the dest MACs
+ dst_macs = self.generator_config.get_dest_macs()
+ # dest MAC on port 0, chain 0
+ dst_mac = dst_macs[0][0]
+ hypervisor = self.get_hypervisor_from_mac(dst_mac)
+ if hypervisor:
+ LOG.info('Found hypervisor for EXT chain: %s', hypervisor.hypervisor_hostname)
+ return [':' + hypervisor.hypervisor_hostname]
# no openstack = no chains
return []
@@ -1188,5 +1551,9 @@ class ChainManager(object):
chain.delete()
for network in self.networks:
network.delete()
+ if self.config.use_management_port and hasattr(self, 'management_network'):
+ self.management_network.delete()
+ if self.config.use_floating_ip and hasattr(self, 'floating_ip_network'):
+ self.floating_ip_network.delete()
if self.flavor:
self.flavor.delete()