277 lines
11 KiB
Python
277 lines
11 KiB
Python
# 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)
|