# Copyright 2014 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. '''Module for Openstack compute operations''' import os import subprocess import time import novaclient import novaclient.exceptions as exceptions class Compute(object): def __init__(self, nova_client, config): self.novaclient = nova_client self.config = config def find_image(self, image_name): try: image = self.novaclient.images.find(name=image_name) return image except novaclient.exceptions.NotFound: print 'ERROR: Didnt find the image %s' % (image_name) return None def copy_and_upload_image(self, final_image_name, server_ip, image_path): ''' Copies locally via wget and Uploads image in Nova, if image is not present on Nova post Upload, deletes it ''' wget_cmd = "wget --tries=1 http://" + str(server_ip) + "/" + str(image_path) try: subprocess.check_output(wget_cmd, shell=True) except subprocess.CalledProcessError: print 'ERROR: Failed to download, check filename %s via Wget' % (wget_cmd) return 0 my_cwd = os.getcwd() my_file_name = os.path.basename(image_path) abs_fname_path = my_cwd + "/" + my_file_name rm_file_cmd = "rm " + abs_fname_path if os.path.isfile(abs_fname_path): # upload in glance glance_cmd = "glance image-create --name=\"" + str(final_image_name) + \ "\" --disk-format=qcow2" + " --container-format=bare < " + \ str(my_file_name) subprocess.check_output(glance_cmd, shell=True) # remove the image file from local dir subprocess.check_output(rm_file_cmd, shell=True) # check for the image in glance glance_check_cmd = "glance image-list" print "Will update image to glance via CLI: %s" % (glance_cmd) result = subprocess.check_output(glance_check_cmd, shell=True) if final_image_name in result: print 'Image: %s successfully Uploaded in Nova' % (final_image_name) return 1 else: print 'Glance image status:\n %s' % (result) print 'ERROR: Didnt find %s image in Nova' % (final_image_name) return 0 else: print 'ERROR: image %s not copied over locally via %s' % (my_file_name, wget_cmd) return 0 # Remove keypair name from openstack if exists def remove_public_key(self, name): keypair_list = self.novaclient.keypairs.list() for key in keypair_list: if key.name == name: self.novaclient.keypairs.delete(name) print 'Removed public key %s' % (name) break # Test if keypair file is present if not create it def create_keypair(self, name, private_key_pair_file): self.remove_public_key(name) keypair = self.novaclient.keypairs.create(name) # Now write the keypair to the file kpf = os.open(private_key_pair_file, os.O_WRONLY | os.O_CREAT, 0o600) with os.fdopen(kpf, 'w') as kpf: kpf.write(keypair.private_key) return keypair # Add an existing public key to openstack def add_public_key(self, name, public_key_file): self.remove_public_key(name) # extract the public key from the file public_key = None try: with open(os.path.expanduser(public_key_file)) as pkf: public_key = pkf.read() except IOError as exc: print 'ERROR: Cannot open public key file %s: %s' % \ (public_key_file, exc) return None print 'Adding public key %s' % (name) keypair = self.novaclient.keypairs.create(name, public_key) return keypair def find_network(self, label): net = self.novaclient.networks.find(label=label) return net # Create a server instance with name vmname # if exists delete and recreate def create_server(self, vmname, image, flavor, key_name, nic, sec_group, avail_zone=None, user_data=None, retry_count=10): # Also attach the created security group for the test instance = self.novaclient.servers.create(name=vmname, image=image, flavor=flavor, key_name=key_name, nics=nic, availability_zone=avail_zone, userdata=user_data, security_groups=[sec_group.id]) flag_exist = self.find_server(vmname, retry_count) if flag_exist: return instance else: return None def get_server_list(self): servers_list = self.novaclient.servers.list() return servers_list def find_floating_ips(self): floating_ip = self.novaclient.floating_ips.list() return floating_ip # Return the server network for a server def find_server_network(self, vmname): servers_list = self.get_server_list() for server in servers_list: if server.name == vmname and server.status == "ACTIVE": return server.networks return None # Returns True if server is present false if not. # Retry for a few seconds since after VM creation sometimes # it takes a while to show up def find_server(self, vmname, retry_count): for retry_attempt in range(retry_count): servers_list = self.get_server_list() for server in servers_list: if server.name == vmname and server.status == "ACTIVE": return True # Sleep between retries if self.config.debug: print "[%s] VM not yet found, retrying %s of %s" \ % (vmname, (retry_attempt + 1), retry_count) time.sleep(2) print "[%s] VM not found, after %s attempts" % (vmname, retry_count) return False # Returns True if server is found and deleted/False if not, # retry the delete if there is a delay def delete_server_by_name(self, vmname): servers_list = self.get_server_list() for server in servers_list: if server.name == vmname: print 'deleting server %s' % (server) self.novaclient.servers.delete(server) return True return False def delete_server(self, server): self.novaclient.servers.delete(server) def find_flavor(self, flavor_type): flavor = self.novaclient.flavors.find(name=flavor_type) return flavor # # Return a list of hosts which are in a specific availability zone # May fail per policy in that case return an empty list def list_hypervisor(self, zone_info): if self.config.hypervisors: print 'Using hypervisors:' + ', '.join(self.config.hypervisors) return self.config.hypervisors avail_list = [] try: host_list = self.novaclient.hosts.list() for host in host_list: if host.zone == zone_info: avail_list.append(host.host_name) except novaclient.exceptions.Forbidden: print ('Operation Forbidden: could not retrieve list of servers' ' in AZ (likely no permission)') return avail_list # Given 2 VMs test if they are running on same Host or not def check_vm_placement(self, vm_instance1, vm_instance2): try: server_instance_1 = self.novaclient.servers.get(vm_instance1) server_instance_2 = self.novaclient.servers.get(vm_instance2) if server_instance_1.hostId == server_instance_2.hostId: return True else: return False except novaclient.exceptions: print "Exception in retrieving the hostId of servers" # Create a new security group with appropriate rules def security_group_create(self): # check first the security group exists # May throw exceptions.NoUniqueMatch or NotFound try: group = self.novaclient.security_groups.find(name=self.config.security_group_name) return group except exceptions.NotFound: group = self.novaclient.security_groups.create(name=self.config.security_group_name, description="PNS Security group") # Once security group try to find it iteratively # (this check may no longer be necessary) for _ in range(self.config.generic_retry_count): group = self.novaclient.security_groups.get(group) if group: self.security_group_add_rules(group) return group else: time.sleep(1) return None # except exceptions.NoUniqueMatch as exc: # raise exc # Delete a security group def security_group_delete(self, group): if group: print "Deleting security group" self.novaclient.security_groups.delete(group) # Add rules to the security group def security_group_add_rules(self, group): # Allow ping traffic self.novaclient.security_group_rules.create(group.id, ip_protocol="icmp", from_port=-1, to_port=-1) # Allow SSH traffic self.novaclient.security_group_rules.create(group.id, ip_protocol="tcp", from_port=22, to_port=22) # Allow TCP/UDP traffic for perf tools like iperf/nuttcp # 5001: Data traffic (standard iperf data port) # 5002: Control traffic (non standard) # note that 5000/tcp is already picked by openstack keystone self.novaclient.security_group_rules.create(group.id, ip_protocol="tcp", from_port=5001, to_port=5002) self.novaclient.security_group_rules.create(group.id, ip_protocol="udp", from_port=5001, to_port=5001)