diff options
Diffstat (limited to 'nfvbench/chain_clients.py')
-rw-r--r-- | nfvbench/chain_clients.py | 633 |
1 files changed, 0 insertions, 633 deletions
diff --git a/nfvbench/chain_clients.py b/nfvbench/chain_clients.py deleted file mode 100644 index 71c6c97..0000000 --- a/nfvbench/chain_clients.py +++ /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() |