[NFVBENCH-59] Add Unit Testing of the NDR/PDR convergence algorithm using the dummy...
[nfvbench.git] / nfvbench / chain_clients.py
index 4be050f..fa21359 100644 (file)
 #    under the License.
 #
 
-import compute
+import os
+import re
+import time
+
 from glanceclient.v2 import client as glanceclient
-from log import LOG
 from neutronclient.neutron import client as neutronclient
 from novaclient.client import Client
-import os
-import time
 
+import compute
+from log import LOG
 
 class StageClientException(Exception):
     pass
@@ -35,6 +37,7 @@ class BasicStageClient(object):
     def __init__(self, config, cred):
         self.comp = None
         self.image_instance = None
+        self.image_name = None
         self.config = config
         self.cred = cred
         self.nets = []
@@ -48,7 +51,9 @@ class BasicStageClient(object):
         self.host_ips = None
 
     def _ensure_vms_active(self):
-        for _ in range(self.config.generic_retry_count):
+        retry_count = (self.config.check_traffic_time_sec +
+                       self.config.generic_poll_sec - 1) / self.config.generic_poll_sec
+        for _ in range(retry_count):
             for i, instance in enumerate(self.vms):
                 if instance.status == 'ACTIVE':
                     continue
@@ -61,7 +66,8 @@ class BasicStageClient(object):
                     LOG.info('Created instance: %s', instance.name)
                 self.vms[i] = instance
                 setattr(self.vms[i], 'is_reuse', is_reuse)
-            if all(map(lambda instance: instance.status == 'ACTIVE', self.vms)):
+
+            if all([(vm.status == 'ACTIVE') for vm in self.vms]):
                 return
             time.sleep(self.config.generic_poll_sec)
         raise StageClientException('Timed out waiting for VMs to spawn')
@@ -78,11 +84,13 @@ class BasicStageClient(object):
         networks = self.neutron.list_networks(name=network_name)
         return networks['networks'][0] if networks['networks'] else None
 
-    def _create_net(self, name, subnet, cidr, network_type=None, segmentation_id=None):
+    def _create_net(self, name, subnet, cidr, network_type=None,
+                    segmentation_id=None, physical_network=None):
         network = self._lookup_network(name)
         if network:
-            phys_net = self.config.internal_networks.physical_network
-            if segmentation_id is not None and phys_net is not None:
+            # a network of same name already exists, we need to verify it has the same
+            # characteristics
+            if segmentation_id:
                 if network['provider:segmentation_id'] != segmentation_id:
                     raise StageClientException("Mismatch of 'segmentation_id' for reused "
                                                "network '{net}'. Network has id '{seg_id1}', "
@@ -91,15 +99,16 @@ class BasicStageClient(object):
                                                        seg_id1=network['provider:segmentation_id'],
                                                        seg_id2=segmentation_id))
 
-                if network['provider:physical_network'] != phys_net:
+            if physical_network:
+                if network['provider:physical_network'] != physical_network:
                     raise StageClientException("Mismatch of 'physical_network' for reused "
                                                "network '{net}'. Network has '{phys1}', "
                                                "configuration requires '{phys2}'."
                                                .format(net=name,
                                                        phys1=network['provider:physical_network'],
-                                                       phys2=phys_net))
+                                                       phys2=physical_network))
 
-            LOG.info('Reusing existing network: ' + name)
+            LOG.info('Reusing existing network: %s', name)
             network['is_reuse'] = True
             return network
 
@@ -112,10 +121,10 @@ class BasicStageClient(object):
 
         if network_type:
             body['network']['provider:network_type'] = network_type
-            phys_net = self.config.internal_networks.physical_network
-            if segmentation_id is not None and phys_net is not None:
+            if segmentation_id:
                 body['network']['provider:segmentation_id'] = segmentation_id
-                body['network']['provider:physical_network'] = phys_net
+            if physical_network:
+                body['network']['provider:physical_network'] = physical_network
 
         network = self.neutron.create_network(body)['network']
         body = {
@@ -132,14 +141,14 @@ class BasicStageClient(object):
         # add subnet id to the network dict since it has just been added
         network['subnets'] = [subnet['id']]
         network['is_reuse'] = False
-        LOG.info('Created network: %s.' % name)
+        LOG.info('Created network: %s.', name)
         return network
 
-    def _create_port(self, net):
+    def _create_port(self, net, vnic_type='normal'):
         body = {
             "port": {
                 'network_id': net['id'],
-                'binding:vnic_type': 'direct' if self.config.sriov else 'normal'
+                'binding:vnic_type': vnic_type
             }
         }
         port = self.neutron.create_port(body)
@@ -154,7 +163,7 @@ class BasicStageClient(object):
             except Exception:
                 retry += 1
                 time.sleep(self.config.generic_poll_sec)
-        LOG.error('Unable to delete port: %s' % (port['id']))
+        LOG.error('Unable to delete port: %s', port['id'])
 
     def __delete_net(self, network):
         retry = 0
@@ -165,7 +174,7 @@ class BasicStageClient(object):
             except Exception:
                 retry += 1
                 time.sleep(self.config.generic_poll_sec)
-        LOG.error('Unable to delete network: %s' % (network['name']))
+        LOG.error('Unable to delete network: %s', network['name'])
 
     def __get_server_az(self, server):
         availability_zone = getattr(server, 'OS-EXT-AZ:availability_zone', None)
@@ -178,7 +187,7 @@ class BasicStageClient(object):
 
     def _lookup_servers(self, name=None, nets=None, az=None, flavor_id=None):
         error_msg = 'VM with the same name, but non-matching {} found. Aborting.'
-        networks = set(map(lambda net: net['name'], nets)) if nets else None
+        networks = set([net['name'] for net in nets]) if nets else None
         server_list = self.comp.get_server_list()
         matching_servers = []
 
@@ -186,9 +195,6 @@ class BasicStageClient(object):
             if name and server.name != name:
                 continue
 
-            if az and self.__get_server_az(server) != az:
-                raise StageClientException(error_msg.format('availability zones'))
-
             if flavor_id and server.flavor['id'] != flavor_id:
                 raise StageClientException(error_msg.format('flavors'))
 
@@ -204,7 +210,7 @@ class BasicStageClient(object):
         return matching_servers
 
     def _create_server(self, name, ports, az, nfvbenchvm_config):
-        port_ids = map(lambda port: {'port-id': port['id']}, ports)
+        port_ids = [{'port-id': port['id']} for port in ports]
         nfvbenchvm_config_location = os.path.join('/etc/', self.nfvbenchvm_config_name)
         server = self.comp.create_server(name,
                                          self.image_instance,
@@ -218,31 +224,50 @@ class BasicStageClient(object):
                                          files={nfvbenchvm_config_location: nfvbenchvm_config})
         if server:
             setattr(server, 'is_reuse', False)
-            LOG.info('Creating instance: %s on %s' % (name, az))
+            LOG.info('Creating instance: %s on %s', name, az)
         else:
             raise StageClientException('Unable to create instance: %s.' % (name))
         return server
 
     def _setup_resources(self):
-        if not self.image_instance:
-            self.image_instance = self.comp.find_image(self.config.image_name)
-        if self.image_instance is None:
+        # To avoid reuploading image in server mode, check whether image_name is set or not
+        if self.image_name:
+            self.image_instance = self.comp.find_image(self.image_name)
+        if self.image_instance:
+            LOG.info("Reusing image %s", self.image_name)
+        else:
+            image_name_search_pattern = r'(nfvbenchvm-\d+(\.\d+)*).qcow2'
             if self.config.vm_image_file:
-                LOG.info('%s: image for VM not found, trying to upload it ...'
-                         % self.config.image_name)
-                res = self.comp.upload_image_via_url(self.config.image_name,
+                match = re.search(image_name_search_pattern, self.config.vm_image_file)
+                if match:
+                    self.image_name = match.group(1)
+                    LOG.info('Using provided VM image file %s', self.config.vm_image_file)
+                else:
+                    raise StageClientException('Provided VM image file name %s must start with '
+                                               '"nfvbenchvm-<version>"' % self.config.vm_image_file)
+            else:
+                pkg_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+                for f in os.listdir(pkg_root):
+                    if re.search(image_name_search_pattern, f):
+                        self.config.vm_image_file = pkg_root + '/' + f
+                        self.image_name = f.replace('.qcow2', '')
+                        LOG.info('Found built-in VM image file %s', f)
+                        break
+                else:
+                    raise StageClientException('Cannot find any built-in VM image file.')
+            if self.image_name:
+                self.image_instance = self.comp.find_image(self.image_name)
+            if not self.image_instance:
+                LOG.info('Uploading %s', self.image_name)
+                res = self.comp.upload_image_via_url(self.image_name,
                                                      self.config.vm_image_file)
 
                 if not res:
                     raise StageClientException('Error uploading image %s from %s. ABORTING.'
-                                               % (self.config.image_name,
+                                               % (self.image_name,
                                                   self.config.vm_image_file))
-                self.image_instance = self.comp.find_image(self.config.image_name)
-            else:
-                raise StageClientException('%s: image to launch VM not found. ABORTING.'
-                                           % self.config.image_name)
-
-        LOG.info('Found image %s to launch VM' % self.config.image_name)
+                LOG.info('Image %s successfully uploaded.', self.image_name)
+                self.image_instance = self.comp.find_image(self.image_name)
 
         self.__setup_flavor()
 
@@ -261,7 +286,7 @@ class BasicStageClient(object):
                                                                  override=True,
                                                                  **flavor_dict)
 
-            LOG.info("Flavor '%s' was created." % self.config.flavor_type)
+            LOG.info("Flavor '%s' was created.", self.config.flavor_type)
 
             if extra_specs:
                 self.flavor_type['flavor'].set_keys(extra_specs)
@@ -274,23 +299,25 @@ class BasicStageClient(object):
 
     def __delete_flavor(self, flavor):
         if self.comp.delete_flavor(flavor=flavor):
-            LOG.info("Flavor '%s' deleted" % self.config.flavor_type)
+            LOG.info("Flavor '%s' deleted", self.config.flavor_type)
             self.flavor_type = {'is_reuse': False, 'flavor': None}
         else:
-            LOG.error('Unable to delete flavor: %s' % self.config.flavor_type)
+            LOG.error('Unable to delete flavor: %s', self.config.flavor_type)
 
-    def get_config_file(self, chain_index, src_mac, dst_mac):
+    def get_config_file(self, chain_index, src_mac, dst_mac, intf_mac1, intf_mac2):
         boot_script_file = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                                         'nfvbenchvm/', self.nfvbenchvm_config_name)
 
         with open(boot_script_file, 'r') as boot_script:
             content = boot_script.read()
 
-        g1cidr = self.config.generator_config.src_device.gateway_ip_list[chain_index] + '/8'
-        g2cidr = self.config.generator_config.dst_device.gateway_ip_list[chain_index] + '/8'
+        g1cidr = self.config.generator_config.src_device.get_gw_ip(chain_index) + '/8'
+        g2cidr = self.config.generator_config.dst_device.get_gw_ip(chain_index) + '/8'
 
         vm_config = {
             'forwarder': self.config.vm_forwarder,
+            'intf_mac1': intf_mac1,
+            'intf_mac2': intf_mac2,
             'tg_gateway1_ip': self.config.traffic_generator.tg_gateway_ip_addrs[0],
             'tg_gateway2_ip': self.config.traffic_generator.tg_gateway_ip_addrs[1],
             'tg_net1': self.config.traffic_generator.ip_addrs[0],
@@ -315,7 +342,7 @@ class BasicStageClient(object):
         """
         Disable security at port level.
         """
-        vm_ids = map(lambda vm: vm.id, self.vms)
+        vm_ids = [vm.id for vm in self.vms]
         for net in self.nets:
             for port in self.ports[net['id']]:
                 if port['device_id'] in vm_ids:
@@ -325,7 +352,7 @@ class BasicStageClient(object):
                             'port_security_enabled': False,
                         }
                     })
-                    LOG.info('Security disabled on port {}'.format(port['id']))
+                    LOG.info('Security disabled on port %s', port['id'])
 
     def get_loop_vm_hostnames(self):
         return [getattr(vm, 'OS-EXT-SRV-ATTR:hypervisor_hostname') for vm in self.vms]
@@ -337,8 +364,7 @@ class BasicStageClient(object):
         if not self.host_ips:
             #  get the hypervisor object from the host name
             self.host_ips = [self.comp.get_hypervisor(
-                             getattr(vm, 'OS-EXT-SRV-ATTR:hypervisor_hostname')).host_ip
-                             for vm in self.vms]
+                getattr(vm, 'OS-EXT-SRV-ATTR:hypervisor_hostname')).host_ip for vm in self.vms]
         return self.host_ips
 
     def get_loop_vm_compute_nodes(self):
@@ -354,11 +380,10 @@ class BasicStageClient(object):
                                        flavor_id=self.flavor_type['flavor'].id)
         if servers:
             server = servers[0]
-            LOG.info('Reusing existing server: ' + name)
+            LOG.info('Reusing existing server: %s', name)
             setattr(server, 'is_reuse', True)
             return server
-        else:
-            return None
+        return None
 
     def get_networks_uuids(self):
         """
@@ -376,7 +401,7 @@ class BasicStageClient(object):
         """
         vlans = []
         for net in self.nets:
-            assert(net['provider:network_type'] == 'vlan')
+            assert net['provider:network_type'] == 'vlan'
             vlans.append(net['provider:segmentation_id'])
 
         return vlans
@@ -397,7 +422,7 @@ class BasicStageClient(object):
                 if not getattr(vm, 'is_reuse', True):
                     self.comp.delete_server(vm)
                 else:
-                    LOG.info('Server %s not removed since it is reused' % vm.name)
+                    LOG.info('Server %s not removed since it is reused', vm.name)
 
         for port in self.created_ports:
             self.__delete_port(port)
@@ -407,17 +432,13 @@ class BasicStageClient(object):
                 if 'is_reuse' in net and not net['is_reuse']:
                     self.__delete_net(net)
                 else:
-                    LOG.info('Network %s not removed since it is reused' % (net['name']))
+                    LOG.info('Network %s not removed since it is reused', net['name'])
 
             if not self.flavor_type['is_reuse']:
                 self.__delete_flavor(self.flavor_type['flavor'])
 
 
 class EXTStageClient(BasicStageClient):
-
-    def __init__(self, config, cred):
-        super(EXTStageClient, self).__init__(config, cred)
-
     def setup(self):
         super(EXTStageClient, self).setup()
 
@@ -431,14 +452,10 @@ class EXTStageClient(BasicStageClient):
 
 
 class PVPStageClient(BasicStageClient):
-
-    def __init__(self, config, cred):
-        super(PVPStageClient, self).__init__(config, cred)
-
     def get_end_port_macs(self):
-        vm_ids = map(lambda vm: vm.id, self.vms)
+        vm_ids = [vm.id for vm in self.vms]
         port_macs = []
-        for index, net in enumerate(self.nets):
+        for _index, net in enumerate(self.nets):
             vm_mac_map = {port['device_id']: port['mac_address'] for port in self.ports[net['id']]}
             port_macs.append([vm_mac_map[vm_id] for vm_id in vm_ids])
         return port_macs
@@ -463,11 +480,13 @@ class PVPStageClient(BasicStageClient):
             if reusable_vm:
                 self.vms.append(reusable_vm)
             else:
+                vnic_type = 'direct' if self.config.sriov else 'normal'
+                ports = [self._create_port(net, vnic_type) for net in self.nets]
                 config_file = self.get_config_file(chain_index,
                                                    self.config.generator_config.src_device.mac,
-                                                   self.config.generator_config.dst_device.mac)
-
-                ports = [self._create_port(net) for net in self.nets]
+                                                   self.config.generator_config.dst_device.mac,
+                                                   ports[0]['mac_address'],
+                                                   ports[1]['mac_address'])
                 self.created_ports.extend(ports)
                 self.vms.append(self._create_server(name, ports, az, config_file))
         self._ensure_vms_active()
@@ -475,14 +494,10 @@ class PVPStageClient(BasicStageClient):
 
 
 class PVVPStageClient(BasicStageClient):
-
-    def __init__(self, config, cred):
-        super(PVVPStageClient, self).__init__(config, cred)
-
     def get_end_port_macs(self):
         port_macs = []
         for index, net in enumerate(self.nets[:2]):
-            vm_ids = map(lambda vm: vm.id, self.vms[index::2])
+            vm_ids = [vm.id for vm in self.vms[index::2]]
             vm_mac_map = {port['device_id']: port['mac_address'] for port in self.ports[net['id']]}
             port_macs.append([vm_mac_map[vm_id] for vm_id in vm_ids])
         return port_macs
@@ -530,11 +545,15 @@ class PVVPStageClient(BasicStageClient):
             if reusable_vm0 and reusable_vm1:
                 self.vms.extend([reusable_vm0, reusable_vm1])
             else:
-                vm0_port_net0 = self._create_port(vm0_nets[0])
-                vm0_port_net2 = self._create_port(vm0_nets[1])
+                edge_vnic_type = 'direct' if self.config.sriov else 'normal'
+                middle_vnic_type = 'direct' \
+                    if self.config.sriov and self.config.use_sriov_middle_net \
+                    else 'normal'
+                vm0_port_net0 = self._create_port(vm0_nets[0], edge_vnic_type)
+                vm0_port_net2 = self._create_port(vm0_nets[1], middle_vnic_type)
 
-                vm1_port_net2 = self._create_port(vm1_nets[1])
-                vm1_port_net1 = self._create_port(vm1_nets[0])
+                vm1_port_net2 = self._create_port(vm1_nets[1], middle_vnic_type)
+                vm1_port_net1 = self._create_port(vm1_nets[0], edge_vnic_type)
 
                 self.created_ports.extend([vm0_port_net0,
                                            vm0_port_net2,
@@ -546,10 +565,14 @@ class PVVPStageClient(BasicStageClient):
                 # TG0 (net0) -> VM0 (net2) -> VM1 (net2) -> TG1 (net1)
                 config_file0 = self.get_config_file(chain_index,
                                                     self.config.generator_config.src_device.mac,
-                                                    vm1_port_net2['mac_address'])
+                                                    vm1_port_net2['mac_address'],
+                                                    vm0_port_net0['mac_address'],
+                                                    vm0_port_net2['mac_address'])
                 config_file1 = self.get_config_file(chain_index,
                                                     vm0_port_net2['mac_address'],
-                                                    self.config.generator_config.dst_device.mac)
+                                                    self.config.generator_config.dst_device.mac,
+                                                    vm1_port_net2['mac_address'],
+                                                    vm1_port_net1['mac_address'])
 
                 self.vms.append(self._create_server(name0,
                                                     [vm0_port_net0, vm0_port_net2],