"""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):
- """Create a network for given chain."""
+ """Create a network for given chain.
+
+ network_config: a dict containing the network properties
+ (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
+ """
self.manager = manager
- self.name = network_config.name
- if chain_id is not None:
- self.name += str(chain_id)
+ 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)
+ self.segmentation_id = self._get_item(network_config.segmentation_id,
+ chain_id, auto_index=True)
+ self.physical_network = self._get_item(network_config.physical_network, chain_id)
+
self.reuse = False
self.network = None
self.vlan = None
self.delete()
raise
+ def _get_item(self, item_field, index, auto_index=False):
+ """Retrieve an item from a list or a single value.
+
+ item_field: can be None, a tuple of a single value
+ index: if None is same as 0, else is the index for a chain
+ auto_index: if true will automatically get the final value by adding the
+ index to the base value (if full list not provided)
+
+ If the item_field is not a tuple, it is considered same as a tuple with same value at any
+ index.
+ If a list is provided, its length must be > index
+ """
+ if not item_field:
+ return None
+ if index is None:
+ index = 0
+ if isinstance(item_field, tuple):
+ try:
+ return item_field[index]
+ except IndexError:
+ raise ChainException("List %s is too short for chain index %d" %
+ (str(item_field), index))
+ # single value is configured
+ if auto_index:
+ return item_field + index
+ return item_field
+
def _setup(self, network_config, lookup_only):
# Lookup if there is a matching network with same name
networks = self.manager.neutron_client.list_networks(name=self.name)
network = networks['networks'][0]
# a network of same name already exists, we need to verify it has the same
# characteristics
- if network_config.segmentation_id:
- if network['provider:segmentation_id'] != network_config.segmentation_id:
+ if self.segmentation_id:
+ if network['provider:segmentation_id'] != self.segmentation_id:
raise ChainException("Mismatch of 'segmentation_id' for reused "
"network '{net}'. Network has id '{seg_id1}', "
"configuration requires '{seg_id2}'."
.format(net=self.name,
seg_id1=network['provider:segmentation_id'],
- seg_id2=network_config.segmentation_id))
+ seg_id2=self.segmentation_id))
- if network_config.physical_network:
- if network['provider:physical_network'] != network_config.physical_network:
+ if self.physical_network:
+ if network['provider:physical_network'] != self.physical_network:
raise ChainException("Mismatch of 'physical_network' for reused "
"network '{net}'. Network has '{phys1}', "
"configuration requires '{phys2}'."
.format(net=self.name,
phys1=network['provider:physical_network'],
- phys2=network_config.physical_network))
+ phys2=self.physical_network))
LOG.info('Reusing existing network %s', self.name)
self.reuse = True
}
if network_config.network_type:
body['network']['provider:network_type'] = network_config.network_type
- if network_config.segmentation_id:
- body['network']['provider:segmentation_id'] = network_config.segmentation_id
- if network_config.physical_network:
- body['network']['provider:physical_network'] = network_config.physical_network
-
+ if self.segmentation_id:
+ body['network']['provider:segmentation_id'] = self.segmentation_id
+ if self.physical_network:
+ body['network']['provider:physical_network'] = self.physical_network
self.network = self.manager.neutron_client.create_network(body)['network']
body = {
'subnet': {'name': network_config.subnet,
subnet = self.manager.neutron_client.create_subnet(body)['subnet']
# add subnet id to the network dict since it has just been added
self.network['subnets'] = [subnet['id']]
- LOG.info('Created network: %s.', self.name)
+ LOG.info('Created network: %s', self.name)
def get_uuid(self):
"""
raise ChainException('Trying to retrieve VLAN id for non VLAN network')
return self.network['provider:segmentation_id']
+ def get_vxlan(self):
+ """
+ Extract VNI for this network.
+
+ :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 delete(self):
"""Delete this network."""
if not self.reuse and self.network:
can use vswitch or SR-IOV based on config.use_sriov_middle_net
"""
if self.manager.config.sriov:
- if self.manager.config.use_sriov_middle_net:
+ chain_length = self.chain.get_length()
+ if self.manager.config.use_sriov_middle_net or chain_length == 1:
return 'direct'
- if self.vnf_id == 0:
+ if self.vnf_id == 0 and port_index == 0:
# first VNF in chain must use sriov for left port
- if port_index == 0:
- return 'direct'
- elif (self.vnf_id == self.chain.get_length() - 1) and (port_index == 1):
+ return 'direct'
+ if (self.vnf_id == chain_length - 1) and (port_index == 1):
# last VNF in chain must use sriov for right port
return 'direct'
return 'normal'
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.
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
port_index = -1
return self.networks[port_index].get_vlan()
+ def get_vxlan(self, port_index):
+ """Get the VXLAN id on a given port.
+
+ port_index: left port is 0, right port is 1
+ return: the vxlan_id or None if there is no vxlan
+ """
+ # for port 1 we need to return the VLAN 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_vxlan()
+
def get_dest_mac(self, port_index):
"""Get the dest MAC on a given port.
if req_az:
self.required_az = req_az + ':' + self.requested_hyp
else:
- # no ":" needed
- self.required_az = self.requested_hyp if req_hyp else ''
+ # need to insert a ':' so nova knows this is the hypervisor name
+ self.required_az = ':' + self.requested_hyp if req_hyp else ''
# placement is resolved when both AZ and hypervisor names are known and set
self.resolved = self.requested_az != '' and self.requested_hyp != ''
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)
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()
+ 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()
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
- # make sure there at least as many entries as chains in each left/right list
- if len(config.vlans) != 2:
- raise ChainException('The config vlans property must be a list '
- 'with 2 lists of VLAN IDs')
- if not config.l2_loopback:
+ if not config.l2_loopback and config.no_arp:
self._get_dest_macs_from_config()
-
- 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)]
+ if config.vlan_tagging:
+ # make sure there at least as many entries as chains in each left/right list
+ if len(config.vlans) != 2:
+ raise ChainException('The config vlans property must be a list '
+ 'with 2 lists of VLAN IDs')
+ 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')
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}$"
# if it is a single int or mac, make it a list of 1 int
if isinstance(ll, (int, str)):
ll = [ll]
- if not ll or len(ll) < self.chain_count:
- raise ChainException('%s=%s must be a list with 1 element per chain' % (list_name, ll))
for item in ll:
if not re.match(pattern, str(item)):
raise ChainException("Invalid format '{item}' specified in {fname}"
.format(item=item, fname=list_name))
+ # must have at least 1 element
+ if not ll:
+ raise ChainException('%s cannot be empty' % (list_name))
+ # for shared network, if 1 element is passed, replicate it as many times
+ # as chains
+ if self.config.service_chain_shared_net and len(ll) == 1:
+ ll = [ll[0]] * self.chain_count
+
+ # number of elements musty be the number of chains
+ elif len(ll) < self.chain_count:
+ raise ChainException('%s=%s must be a list with %d elements per chain' %
+ (list_name, ll, self.chain_count))
return ll
def _setup_image(self):
'segmentation_id': None,
'physical_network': None})
for name in [ext_net.left, ext_net.right]]
+ # segmentation id and subnet should be discovered from neutron
else:
lookup_only = False
int_nets = self.config.internal_networks
+ # VLAN and VxLAN
if self.config.service_chain == ChainType.PVP:
net_cfg = [int_nets.left, int_nets.right]
else:
"""
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():
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
return self.vlans[port_index]
+ def get_chain_vxlans(self, port_index):
+ """Get the list of per chain VNIs id on a given port.
+
+ 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 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 and with admin user')
+
def get_dest_macs(self, port_index):
"""Get the list of per chain dest MACs on a given port.
return self.chains[0].get_host_ips()
# in the case of EXT, the compute node must be retrieved from the port
# associated to any of the dest MACs
- dst_macs = self.chain_runner.traffic_client.gen.get_dest_macs()
+ dst_macs = self.generator_config.get_dest_macs()
# dest MAC on port 0, chain 0
dst_mac = dst_macs[0][0]
host_ip = self.get_host_ip_from_mac(dst_mac)
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 []
def delete(self):
- """Delete resources for all chains.
-
- Will not delete any resource if no-cleanup has been requested.
- """
- if self.config.no_cleanup:
- return
+ """Delete resources for all chains."""
for chain in self.chains:
chain.delete()
for network in self.networks: