NFVBENCH-131 Allow list of arbitrary network names for EXT chains
[nfvbench.git] / nfvbench / chaining.py
index 5f9e1e8..ee8a8e0 100644 (file)
@@ -197,17 +197,24 @@ class ChainNetwork(object):
         """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
         """
         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)
         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
         self.network = None
         self.vlan = None
@@ -329,7 +336,7 @@ class ChainNetwork(object):
 
         :return: VNI ID for this network
         """
-        if self.network['provider:network_type'] != 'vxlan':
+        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']
 
@@ -522,7 +529,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 +549,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
@@ -851,6 +869,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 +883,12 @@ 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()
+                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 +896,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 +917,26 @@ 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')
+
     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
@@ -1091,11 +1132,11 @@ 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():
@@ -1103,18 +1144,29 @@ class ChainManager(object):
                 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 +1178,11 @@ 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_dest_macs(self, port_index):
         """Get the list of per chain dest MACs on a given port.
@@ -1178,7 +1230,18 @@ 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 []