2.0 beta NFVBENCH-91 Allow multi-chaining with separate edge networks
[nfvbench.git] / nfvbench / chain_clients.py
diff --git a/nfvbench/chain_clients.py b/nfvbench/chain_clients.py
deleted file mode 100644 (file)
index 71c6c97..0000000
+++ /dev/null
@@ -1,633 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2016 Cisco Systems, Inc.  All rights reserved.
-#
-#    Licensed under the Apache License, Version 2.0 (the "License"); you may
-#    not use this file except in compliance with the License. You may obtain
-#    a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-#    License for the specific language governing permissions and limitations
-#    under the License.
-#
-
-import os
-import re
-import time
-
-from glanceclient.v2 import client as glanceclient
-from neutronclient.neutron import client as neutronclient
-from novaclient.client import Client
-
-import compute
-from log import LOG
-
-class StageClientException(Exception):
-    pass
-
-
-class BasicStageClient(object):
-    """Client for spawning and accessing the VM setup"""
-
-    nfvbenchvm_config_name = 'nfvbenchvm.conf'
-
-    def __init__(self, config, cred):
-        self.comp = None
-        self.image_instance = None
-        self.image_name = None
-        self.config = config
-        self.cred = cred
-        self.nets = []
-        self.vms = []
-        self.created_ports = []
-        self.ports = {}
-        self.compute_nodes = set([])
-        self.comp = None
-        self.neutron = None
-        self.flavor_type = {'is_reuse': True, 'flavor': None}
-        self.host_ips = None
-
-    def _ensure_vms_active(self):
-        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
-                is_reuse = getattr(instance, 'is_reuse', True)
-                instance = self.comp.poll_server(instance)
-                if instance.status == 'ERROR':
-                    raise StageClientException('Instance creation error: %s' %
-                                               instance.fault['message'])
-                if instance.status == 'ACTIVE':
-                    LOG.info('Created instance: %s', instance.name)
-                self.vms[i] = instance
-                setattr(self.vms[i], 'is_reuse', is_reuse)
-
-            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')
-
-    def _setup_openstack_clients(self):
-        self.session = self.cred.get_session()
-        nova_client = Client(2, session=self.session)
-        self.neutron = neutronclient.Client('2.0', session=self.session)
-        self.glance_client = glanceclient.Client('2',
-                                                 session=self.session)
-        self.comp = compute.Compute(nova_client, self.glance_client, self.neutron, self.config)
-
-    def _lookup_network(self, network_name):
-        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, physical_network=None):
-        network = self._lookup_network(name)
-        if network:
-            # 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}', "
-                                               "configuration requires '{seg_id2}'."
-                                               .format(net=name,
-                                                       seg_id1=network['provider:segmentation_id'],
-                                                       seg_id2=segmentation_id))
-
-            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=physical_network))
-
-            LOG.info('Reusing existing network: %s', name)
-            network['is_reuse'] = True
-            return network
-
-        body = {
-            'network': {
-                'name': name,
-                'admin_state_up': True
-            }
-        }
-
-        if network_type:
-            body['network']['provider:network_type'] = network_type
-            if segmentation_id:
-                body['network']['provider:segmentation_id'] = segmentation_id
-            if physical_network:
-                body['network']['provider:physical_network'] = physical_network
-
-        network = self.neutron.create_network(body)['network']
-        body = {
-            'subnet': {
-                'name': subnet,
-                'cidr': cidr,
-                'network_id': network['id'],
-                'enable_dhcp': False,
-                'ip_version': 4,
-                'dns_nameservers': []
-            }
-        }
-        subnet = self.neutron.create_subnet(body)['subnet']
-        # 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)
-        return network
-
-    def _create_port(self, net, vnic_type='normal'):
-        body = {
-            "port": {
-                'network_id': net['id'],
-                'binding:vnic_type': vnic_type
-            }
-        }
-        port = self.neutron.create_port(body)
-        return port['port']
-
-    def __delete_port(self, port):
-        retry = 0
-        while retry < self.config.generic_retry_count:
-            try:
-                self.neutron.delete_port(port['id'])
-                return
-            except Exception:
-                retry += 1
-                time.sleep(self.config.generic_poll_sec)
-        LOG.error('Unable to delete port: %s', port['id'])
-
-    def __delete_net(self, network):
-        retry = 0
-        while retry < self.config.generic_retry_count:
-            try:
-                self.neutron.delete_network(network['id'])
-                return
-            except Exception:
-                retry += 1
-                time.sleep(self.config.generic_poll_sec)
-        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)
-        host = getattr(server, 'OS-EXT-SRV-ATTR:host', None)
-        if availability_zone is None:
-            return None
-        if host is None:
-            return None
-        return availability_zone + ':' + host
-
-    def _lookup_servers(self, name=None, nets=None, flavor_id=None):
-        error_msg = 'VM with the same name, but non-matching {} found. Aborting.'
-        networks = set([net['name'] for net in nets]) if nets else None
-        server_list = self.comp.get_server_list()
-        matching_servers = []
-
-        for server in server_list:
-            if name and server.name != name:
-                continue
-
-            if flavor_id and server.flavor['id'] != flavor_id:
-                raise StageClientException(error_msg.format('flavors'))
-
-            if networks and not set(server.networks.keys()).issuperset(networks):
-                raise StageClientException(error_msg.format('networks'))
-
-            if server.status != "ACTIVE":
-                raise StageClientException(error_msg.format('state'))
-
-            # everything matches
-            matching_servers.append(server)
-
-        return matching_servers
-
-    def _create_server(self, name, ports, az, nfvbenchvm_config):
-        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,
-                                         self.flavor_type['flavor'],
-                                         None,
-                                         port_ids,
-                                         None,
-                                         avail_zone=az,
-                                         user_data=None,
-                                         config_drive=True,
-                                         files={nfvbenchvm_config_location: nfvbenchvm_config})
-        if server:
-            setattr(server, 'is_reuse', False)
-            msg = 'Creating instance: %s' % name
-            if az:
-                msg += ' on %s' % az
-            LOG.info(msg)
-        else:
-            raise StageClientException('Unable to create instance: %s.' % (name))
-        return server
-
-    def _setup_resources(self):
-        # 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:
-                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.image_name,
-                                                  self.config.vm_image_file))
-                LOG.info('Image %s successfully uploaded.', self.image_name)
-                self.image_instance = self.comp.find_image(self.image_name)
-
-        self.__setup_flavor()
-
-    def __setup_flavor(self):
-        if self.flavor_type.get('flavor', False):
-            return
-
-        self.flavor_type['flavor'] = self.comp.find_flavor(self.config.flavor_type)
-        if self.flavor_type['flavor']:
-            self.flavor_type['is_reuse'] = True
-        else:
-            flavor_dict = self.config.flavor
-            extra_specs = flavor_dict.pop('extra_specs', None)
-
-            self.flavor_type['flavor'] = self.comp.create_flavor(self.config.flavor_type,
-                                                                 override=True,
-                                                                 **flavor_dict)
-
-            LOG.info("Flavor '%s' was created.", self.config.flavor_type)
-
-            if extra_specs:
-                self.flavor_type['flavor'].set_keys(extra_specs)
-
-            self.flavor_type['is_reuse'] = False
-
-        if self.flavor_type['flavor'] is None:
-            raise StageClientException('%s: flavor to launch VM not found. ABORTING.'
-                                       % self.config.flavor_type)
-
-    def __delete_flavor(self, flavor):
-        if self.comp.delete_flavor(flavor=flavor):
-            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)
-
-    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.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],
-            'tg_net2': self.config.traffic_generator.ip_addrs[1],
-            'vnf_gateway1_cidr': g1cidr,
-            'vnf_gateway2_cidr': g2cidr,
-            'tg_mac1': src_mac,
-            'tg_mac2': dst_mac
-        }
-
-        return content.format(**vm_config)
-
-    def set_ports(self):
-        """Stores all ports of NFVbench networks."""
-        nets = self.get_networks_uuids()
-        for port in self.neutron.list_ports()['ports']:
-            if port['network_id'] in nets:
-                ports = self.ports.setdefault(port['network_id'], [])
-                ports.append(port)
-
-    def disable_port_security(self):
-        """
-        Disable security at port level.
-        """
-        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:
-                    try:
-                        self.neutron.update_port(port['id'], {
-                            'port': {
-                                'security_groups': [],
-                                'port_security_enabled': False,
-                            }
-                        })
-                        LOG.info('Security disabled on port %s', port['id'])
-                    except Exception:
-                        LOG.warning('Failed to disable port security on port %s, ignoring...',
-                                    port['id'])
-
-
-    def get_loop_vm_hostnames(self):
-        return [getattr(vm, 'OS-EXT-SRV-ATTR:hypervisor_hostname') for vm in self.vms]
-
-    def get_host_ips(self):
-        '''Return the IP adresss(es) of the host compute nodes for this VMclient instance.
-        Returns a list of 1 IP adress or 2 IP addresses (PVVP inter-node)
-        '''
-        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]
-        return self.host_ips
-
-    def get_loop_vm_compute_nodes(self):
-        compute_nodes = []
-        for vm in self.vms:
-            az = getattr(vm, 'OS-EXT-AZ:availability_zone')
-            hostname = getattr(vm, 'OS-EXT-SRV-ATTR:hypervisor_hostname')
-            compute_nodes.append(az + ':' + hostname)
-        return compute_nodes
-
-    def get_reusable_vm(self, name, nets):
-        servers = self._lookup_servers(name=name, nets=nets,
-                                       flavor_id=self.flavor_type['flavor'].id)
-        if servers:
-            server = servers[0]
-            LOG.info('Reusing existing server: %s', name)
-            setattr(server, 'is_reuse', True)
-            return server
-        return None
-
-    def get_networks_uuids(self):
-        """
-        Extract UUID of used networks. Order is important.
-
-        :return: list of UUIDs of created networks
-        """
-        return [net['id'] for net in self.nets]
-
-    def get_vlans(self):
-        """
-        Extract vlans of used networks. Order is important.
-
-        :return: list of UUIDs of created networks
-        """
-        vlans = []
-        for net in self.nets:
-            assert net['provider:network_type'] == 'vlan'
-            vlans.append(net['provider:segmentation_id'])
-
-        return vlans
-
-    def setup(self):
-        """
-        Creates two networks and spawn a VM which act as a loop VM connected
-        with the two networks.
-        """
-        if self.cred:
-            self._setup_openstack_clients()
-
-    def dispose(self, only_vm=False):
-        """
-        Deletes the created two networks and the VM.
-        """
-        for vm in self.vms:
-            if vm:
-                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)
-
-        for port in self.created_ports:
-            self.__delete_port(port)
-
-        if not only_vm:
-            for net in self.nets:
-                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'])
-
-            if not self.flavor_type['is_reuse']:
-                self.__delete_flavor(self.flavor_type['flavor'])
-
-
-class EXTStageClient(BasicStageClient):
-    def setup(self):
-        super(EXTStageClient, self).setup()
-
-        # Lookup two existing networks
-        if self.cred:
-            for net_name in [self.config.external_networks.left,
-                             self.config.external_networks.right]:
-                net = self._lookup_network(net_name)
-                if net:
-                    self.nets.append(net)
-                else:
-                    raise StageClientException('Existing network {} cannot be found.'.
-                                               format(net_name))
-
-
-class PVPStageClient(BasicStageClient):
-    def get_end_port_macs(self):
-        vm_ids = [vm.id for vm in self.vms]
-        port_macs = []
-        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
-
-    def setup(self):
-        super(PVPStageClient, self).setup()
-        self._setup_resources()
-
-        # Create two networks
-        nets = self.config.internal_networks
-        self.nets.extend([self._create_net(**n) for n in [nets.left, nets.right]])
-
-        if self.comp.config.compute_nodes:
-            az_list = self.comp.get_enabled_az_host_list(required_count=1)
-            if not az_list:
-                raise Exception('Not enough hosts found.')
-            az = az_list[0]
-        else:
-            az = None
-
-        for chain_index in xrange(self.config.service_chain_count):
-            name = self.config.loop_vm_name + str(chain_index)
-            server = self.get_reusable_vm(name, self.nets)
-            if server:
-                self.vms.append(server)
-            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[0]['mac_address'],
-                                                   ports[1]['mac_address'])
-                self.created_ports.extend(ports)
-                server = self._create_server(name, ports, az, config_file)
-                self.vms.append(server)
-
-            if chain_index == 0:
-                # First VM, save the hypervisor name. Used in future for
-                # maintain affinity.
-                self._ensure_vms_active()
-                server = self.comp.poll_server(server)
-                az = "%s:%s" % (getattr(server, 'OS-EXT-AZ:availability_zone'),
-                                getattr(server, 'OS-EXT-SRV-ATTR:hypervisor_hostname'))
-
-        self._ensure_vms_active()
-        self.compute_nodes = set(self.get_loop_vm_compute_nodes())
-        self.set_ports()
-
-
-class PVVPStageClient(BasicStageClient):
-    def get_end_port_macs(self):
-        port_macs = []
-        for index, net in enumerate(self.nets[: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
-
-    def setup(self):
-        super(PVVPStageClient, self).setup()
-        self._setup_resources()
-
-        # Create two networks
-        nets = self.config.internal_networks
-        self.nets.extend([self._create_net(**n) for n in [nets.left, nets.right, nets.middle]])
-
-        if self.comp.config.compute_nodes:
-            required_count = 2 if self.config.inter_node else 1
-            az_list = self.comp.get_enabled_az_host_list(required_count=required_count)
-            if not az_list:
-                raise Exception('Not enough hosts found.')
-
-            az1 = az2 = az_list[0]
-            if self.config.inter_node:
-                if len(az_list) > 1:
-                    az1 = az_list[0]
-                    az2 = az_list[1]
-                else:
-                    # fallback to intra-node
-                    az1 = az2 = az_list[0]
-                    self.config.inter_node = False
-                    LOG.info('Using intra-node instead of inter-node.')
-        else:
-            az1 = az2 = None
-
-        # Create loop VMs
-        for chain_index in xrange(self.config.service_chain_count):
-            name0 = self.config.loop_vm_name + str(chain_index) + 'a'
-            # Attach first VM to net0 and net2
-            vm0_nets = self.nets[0::2]
-            reusable_vm0 = self.get_reusable_vm(name0, vm0_nets)
-
-            name1 = self.config.loop_vm_name + str(chain_index) + 'b'
-            # Attach second VM to net1 and net2
-            vm1_nets = self.nets[1:]
-            reusable_vm1 = self.get_reusable_vm(name1, vm1_nets)
-
-            if reusable_vm0 and reusable_vm1:
-                self.vms.extend([reusable_vm0, reusable_vm1])
-            else:
-                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], 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,
-                                           vm1_port_net2,
-                                           vm1_port_net1])
-
-                # order of ports is important for sections below
-                # order of MAC addresses needs to follow order of interfaces
-                # 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'],
-                                                    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,
-                                                    vm1_port_net2['mac_address'],
-                                                    vm1_port_net1['mac_address'])
-
-                vm1 = self._create_server(name0, [vm0_port_net0, vm0_port_net2], az1, config_file0)
-                self.vms.append(vm1)
-                if chain_index == 0:
-                    # First VM on first chain, save the hypervisor name. Used
-                    # in future for maintain affinity.
-                    self._ensure_vms_active()
-                    vm1 = self.comp.poll_server(vm1)
-                    az1 = "%s:%s" % (getattr(vm1, 'OS-EXT-AZ:availability_zone'),
-                                     getattr(vm1, 'OS-EXT-SRV-ATTR:hypervisor_hostname'))
-                    if not self.config.inter_node:
-                        # By default, NOVA scheduler will try first with
-                        # different hypervisor for workload balance, but when
-                        # inter-node is not configured, use the same AZ to run
-                        # intra-node test case.
-                        az2 = az1
-
-                vm2 = self._create_server(name1, [vm1_port_net2, vm1_port_net1], az2, config_file1)
-                self.vms.append(vm2)
-                if chain_index == 0 and self.config.inter_node:
-                    # Second VM on first chain, save the hypervisor name. Used
-                    # in future for maintain affinity.
-                    self._ensure_vms_active()
-                    vm2 = self.comp.poll_server(vm2)
-                    az2 = "%s:%s" % (getattr(vm2, 'OS-EXT-AZ:availability_zone'),
-                                     getattr(vm2, 'OS-EXT-SRV-ATTR:hypervisor_hostname'))
-                    if az1 == az2:
-                        # Configure to run inter-node, but not enough node to run
-                        self.config.inter_node = False
-                        LOG.info('Using intra-node instead of inter-node.')
-
-        self._ensure_vms_active()
-        self.compute_nodes = set(self.get_loop_vm_compute_nodes())
-        self.set_ports()