NFVBENCH-120 No admin support patch
[nfvbench.git] / nfvbench / chaining.py
index fa9b799..1a977da 100644 (file)
@@ -194,9 +194,18 @@ 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):
-        """Create a network for given chain."""
+        """Create a network for given chain.
+
+        network_config: a dict containing the network properties
+                        (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
+        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)
         if chain_id is not None:
             self.name += str(chain_id)
         self.reuse = False
@@ -212,6 +221,33 @@ class ChainNetwork(object):
             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)
@@ -219,23 +255,23 @@ class ChainNetwork(object):
             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
@@ -251,11 +287,10 @@ class ChainNetwork(object):
             }
             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,
@@ -268,7 +303,7 @@ class ChainNetwork(object):
             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):
         """
@@ -288,6 +323,16 @@ class ChainNetwork(object):
             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:
@@ -367,13 +412,13 @@ class ChainVnf(object):
         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'
@@ -477,7 +522,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.
@@ -491,7 +542,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
@@ -631,6 +687,20 @@ class Chain(object):
             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.
 
@@ -792,6 +862,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)
@@ -818,21 +889,32 @@ class ChainManager(object):
                 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:
+                    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 _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}$"
@@ -846,12 +928,22 @@ 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]
-        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):
@@ -947,9 +1039,11 @@ class ChainManager(object):
                                  '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:
@@ -1040,12 +1134,24 @@ class ChainManager(object):
         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:
+            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')
+
     def get_dest_macs(self, port_index):
         """Get the list of per chain dest MACs on a given port.
 
@@ -1073,7 +1179,7 @@ class ChainManager(object):
                 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)
@@ -1097,12 +1203,7 @@ class ChainManager(object):
         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: