import logging
import os
+import time
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from novaclient.client import Client
from novaclient.exceptions import NotFound
+
+from snaps import file_utils
+from snaps.domain.flavor import Flavor
+from snaps.domain.keypair import Keypair
+from snaps.domain.project import ComputeQuotas
from snaps.domain.vm_inst import VmInst
from snaps.openstack.utils import keystone_utils, glance_utils, neutron_utils
"""
logger.debug('Retrieving Nova Client')
return Client(os_creds.compute_api_version,
- session=keystone_utils.keystone_session(os_creds))
+ session=keystone_utils.keystone_session(os_creds),
+ region_name=os_creds.region_name)
def create_server(nova, neutron, glance, instance_settings, image_settings,
ports = list()
for port_setting in instance_settings.port_settings:
- ports.append(neutron_utils.get_port_by_name(
- neutron, port_setting.name))
+ ports.append(neutron_utils.get_port(
+ neutron, port_settings=port_setting))
nics = []
for port in ports:
kv = dict()
- kv['port-id'] = port['port']['id']
+ kv['port-id'] = port.id
nics.append(kv)
logger.info('Creating VM with name - ' + instance_settings.name)
flavor = get_flavor_by_name(nova, instance_settings.flavor)
if not flavor:
- raise Exception(
- 'Flavor not found with name - %s',
- instance_settings.flavor)
+ raise NovaException(
+ 'Flavor not found with name - %s', instance_settings.flavor)
- image = glance_utils.get_image(glance, image_settings.name)
+ image = glance_utils.get_image(glance, image_settings=image_settings)
if image:
+ userdata = None
+ if instance_settings.userdata:
+ if isinstance(instance_settings.userdata, str):
+ userdata = instance_settings.userdata + '\n'
+ elif (isinstance(instance_settings.userdata, dict) and
+ 'script_file' in instance_settings.userdata):
+ try:
+ userdata = file_utils.read_file(
+ instance_settings.userdata['script_file'])
+ except Exception as e:
+ logger.warn('error reading userdata file %s - %s',
+ instance_settings.userdata, e)
args = {'name': instance_settings.name,
'flavor': flavor,
'image': image,
'key_name': keypair_name,
'security_groups':
instance_settings.security_group_names,
- 'userdata': instance_settings.userdata,
- 'availability_zone':
- instance_settings.availability_zone}
+ 'userdata': userdata}
+
+ if instance_settings.availability_zone:
+ args['availability_zone'] = instance_settings.availability_zone
+
server = nova.servers.create(**args)
- return VmInst(name=server.name, inst_id=server.id)
+
+ return __map_os_server_obj_to_vm_inst(server)
else:
- raise Exception(
+ raise NovaException(
'Cannot create instance, image cannot be located with name %s',
image_settings.name)
-def get_servers_by_name(nova, name):
+def get_server(nova, vm_inst_settings=None, server_name=None):
"""
- Returns a list of servers with a given name
+ Returns a VmInst object for the first server instance found.
:param nova: the Nova client
- :param name: the server name
- :return: the list of servers
+ :param vm_inst_settings: the VmInstanceSettings object from which to build
+ the query if not None
+ :param server_name: the server with this name to return if vm_inst_settings
+ is not None
+ :return: a snaps.domain.VmInst object or None if not found
+ """
+ search_opts = dict()
+ if vm_inst_settings:
+ search_opts['name'] = vm_inst_settings.name
+ elif server_name:
+ search_opts['name'] = server_name
+
+ servers = nova.servers.list(search_opts=search_opts)
+ for server in servers:
+ return __map_os_server_obj_to_vm_inst(server)
+
+
+def get_server_connection(nova, vm_inst_settings=None, server_name=None):
"""
- out = list()
- servers = nova.servers.list(search_opts={'name': name})
+ Returns a VmInst object for the first server instance found.
+ :param nova: the Nova client
+ :param vm_inst_settings: the VmInstanceSettings object from which to build
+ the query if not None
+ :param server_name: the server with this name to return if vm_inst_settings
+ is not None
+ :return: a snaps.domain.VmInst object or None if not found
+ """
+ search_opts = dict()
+ if vm_inst_settings:
+ search_opts['name'] = vm_inst_settings.name
+ elif server_name:
+ search_opts['name'] = server_name
+
+ servers = nova.servers.list(search_opts=search_opts)
for server in servers:
- out.append(VmInst(name=server.name, inst_id=server.id))
- return out
+ return server.links[0]
-def get_latest_server_os_object(nova, server):
+def __map_os_server_obj_to_vm_inst(os_server):
+ """
+ Returns a VmInst object for an OpenStack Server object
+ :param os_server: the OpenStack server object
+ :return: an equivalent SNAPS-OO VmInst domain object
+ """
+ sec_grp_names = list()
+ # VM must be active for 'security_groups' attr to be initialized
+ if hasattr(os_server, 'security_groups'):
+ for sec_group in os_server.security_groups:
+ if sec_group.get('name'):
+ sec_grp_names.append(sec_group.get('name'))
+
+ volumes = None
+ if hasattr(os_server, 'os-extended-volumes:volumes_attached'):
+ volumes = getattr(os_server, 'os-extended-volumes:volumes_attached')
+
+ return VmInst(
+ name=os_server.name, inst_id=os_server.id,
+ image_id=os_server.image['id'], flavor_id=os_server.flavor['id'],
+ networks=os_server.networks, keypair_name=os_server.key_name,
+ sec_grp_names=sec_grp_names, volume_ids=volumes)
+
+
+def __get_latest_server_os_object(nova, server):
"""
Returns a server with a given id
:param nova: the Nova client
:param server: the domain VmInst object
:return: the list of servers or None if not found
"""
- return nova.servers.get(server.id)
+ return __get_latest_server_os_object_by_id(nova, server.id)
+
+
+def __get_latest_server_os_object_by_id(nova, server_id):
+ """
+ Returns a server with a given id
+ :param nova: the Nova client
+ :param server_id: the server's ID
+ :return: the list of servers or None if not found
+ """
+ return nova.servers.get(server_id)
+
+
+def get_server_status(nova, server):
+ """
+ Returns the a VM instance's status from OpenStack
+ :param nova: the Nova client
+ :param server: the domain VmInst object
+ :return: the VM's string status or None if not founc
+ """
+ server = __get_latest_server_os_object(nova, server)
+ if server:
+ return server.status
+ return None
+
+
+def get_server_console_output(nova, server):
+ """
+ Returns the console object for parsing VM activity
+ :param nova: the Nova client
+ :param server: the domain VmInst object
+ :return: the console output object or None if server object is not found
+ """
+ server = __get_latest_server_os_object(nova, server)
+ if server:
+ return server.get_console_output()
+ return None
def get_latest_server_object(nova, server):
:param server: the old server object
:return: the list of servers or None if not found
"""
- server = get_latest_server_os_object(nova, server)
- return VmInst(name=server.name, inst_id=server.id)
+ server = __get_latest_server_os_object(nova, server)
+ return __map_os_server_obj_to_vm_inst(server)
+
+
+def get_server_object_by_id(nova, server_id):
+ """
+ Returns a server with a given id
+ :param nova: the Nova client
+ :param server_id: the server's id
+ :return: an SNAPS-OO VmInst object or None if not found
+ """
+ server = __get_latest_server_os_object_by_id(nova, server_id)
+ return __map_os_server_obj_to_vm_inst(server)
+
+
+def get_server_security_group_names(nova, server):
+ """
+ Returns a server with a given id
+ :param nova: the Nova client
+ :param server: the old server object
+ :return: the list of security groups associated with a VM
+ """
+ out = list()
+ os_vm_inst = __get_latest_server_os_object(nova, server)
+ for sec_grp_dict in os_vm_inst.security_groups:
+ out.append(sec_grp_dict['name'])
+ return out
+
+
+def get_server_info(nova, server):
+ """
+ Returns a dictionary of a VMs info as returned by OpenStack
+ :param nova: the Nova client
+ :param server: the old server object
+ :return: a dict of the info if VM exists else None
+ """
+ vm = __get_latest_server_os_object(nova, server)
+ if vm:
+ return vm._info
+ return None
def create_keys(key_size=2048):
:param keys: the keys to save generated by cryptography
:param pub_file_path: the path to the public keys
:param priv_file_path: the path to the private keys
- :return: None
"""
if keys:
if pub_file_path:
- pub_dir = os.path.dirname(pub_file_path)
+ # To support '~'
+ pub_expand_file = os.path.expanduser(pub_file_path)
+ pub_dir = os.path.dirname(pub_expand_file)
+
if not os.path.isdir(pub_dir):
os.mkdir(pub_dir)
- public_handle = open(pub_file_path, 'wb')
- public_bytes = keys.public_key().public_bytes(
- serialization.Encoding.OpenSSH,
- serialization.PublicFormat.OpenSSH)
- public_handle.write(public_bytes)
- public_handle.close()
- os.chmod(pub_file_path, 0o400)
- logger.info("Saved public key to - " + pub_file_path)
+
+ public_handle = None
+ try:
+ public_handle = open(pub_expand_file, 'wb')
+ public_bytes = keys.public_key().public_bytes(
+ serialization.Encoding.OpenSSH,
+ serialization.PublicFormat.OpenSSH)
+ public_handle.write(public_bytes)
+ finally:
+ if public_handle:
+ public_handle.close()
+
+ os.chmod(pub_expand_file, 0o600)
+ logger.info("Saved public key to - " + pub_expand_file)
if priv_file_path:
- priv_dir = os.path.dirname(priv_file_path)
+ # To support '~'
+ priv_expand_file = os.path.expanduser(priv_file_path)
+ priv_dir = os.path.dirname(priv_expand_file)
if not os.path.isdir(priv_dir):
os.mkdir(priv_dir)
- private_handle = open(priv_file_path, 'wb')
- private_handle.write(
- keys.private_bytes(
- encoding=serialization.Encoding.PEM,
- format=serialization.PrivateFormat.TraditionalOpenSSL,
- encryption_algorithm=serialization.NoEncryption()))
- private_handle.close()
- os.chmod(priv_file_path, 0o400)
- logger.info("Saved private key to - " + priv_file_path)
+
+ private_handle = None
+ try:
+ private_handle = open(priv_expand_file, 'wb')
+ private_handle.write(
+ keys.private_bytes(
+ encoding=serialization.Encoding.PEM,
+ format=serialization.PrivateFormat.TraditionalOpenSSL,
+ encryption_algorithm=serialization.NoEncryption()))
+ finally:
+ if private_handle:
+ private_handle.close()
+
+ os.chmod(priv_expand_file, 0o600)
+ logger.info("Saved private key to - " + priv_expand_file)
def upload_keypair_file(nova, name, file_path):
:param file_path: the path to the public key file
:return: the keypair object
"""
- with open(os.path.expanduser(file_path), 'rb') as fpubkey:
- logger.info('Saving keypair to - ' + file_path)
- return upload_keypair(nova, name, fpubkey.read())
+ fpubkey = None
+ try:
+ with open(os.path.expanduser(file_path), 'rb') as fpubkey:
+ logger.info('Saving keypair to - ' + file_path)
+ return upload_keypair(nova, name, fpubkey.read())
+ finally:
+ if fpubkey:
+ fpubkey.close()
def upload_keypair(nova, name, key):
:return: the keypair object
"""
logger.info('Creating keypair with name - ' + name)
- return nova.keypairs.create(name=name, public_key=key.decode('utf-8'))
+ os_kp = nova.keypairs.create(name=name, public_key=key.decode('utf-8'))
+ return Keypair(name=os_kp.name, kp_id=os_kp.id,
+ public_key=os_kp.public_key, fingerprint=os_kp.fingerprint)
def keypair_exists(nova, keypair_obj):
:return: the keypair object or None if not found
"""
try:
- return nova.keypairs.get(keypair_obj)
+ os_kp = nova.keypairs.get(keypair_obj)
+ return Keypair(name=os_kp.name, kp_id=os_kp.id,
+ public_key=os_kp.public_key)
except:
return None
for keypair in keypairs:
if keypair.name == name:
- return keypair
+ return Keypair(name=keypair.name, kp_id=keypair.id,
+ public_key=keypair.public_key)
return None
+def get_keypair_by_id(nova, kp_id):
+ """
+ Returns a list of all available keypairs
+ :param nova: the Nova client
+ :param kp_id: the ID of the keypair to return
+ :return: the keypair object
+ """
+ keypair = nova.keypairs.get(kp_id)
+ return Keypair(name=keypair.name, kp_id=keypair.id,
+ public_key=keypair.public_key)
+
+
def delete_keypair(nova, key):
"""
Deletes a keypair object from OpenStack
:param nova: the Nova client
- :param key: the keypair object to delete
+ :param key: the SNAPS-OO keypair domain object to delete
"""
logger.debug('Deleting keypair - ' + key.name)
- nova.keypairs.delete(key)
+ nova.keypairs.delete(key.id)
-def get_nova_availability_zones(nova):
+def get_availability_zone_hosts(nova, zone_name='nova'):
"""
Returns the names of all nova active compute servers
:param nova: the Nova client
+ :param zone_name: the Nova client
:return: a list of compute server names
"""
out = list()
zones = nova.availability_zones.list()
for zone in zones:
- if zone.zoneName == 'nova':
+ if zone.zoneName == zone_name and zone.hosts:
for key, host in zone.hosts.items():
if host['nova-compute']['available']:
out.append(zone.zoneName + ':' + key)
nova.servers.delete(vm_inst.id)
-def get_flavor_by_name(nova, name):
+def __get_os_flavor(nova, flavor_id):
"""
- Returns a flavor by name
+ Returns to OpenStack flavor object by name
:param nova: the Nova client
- :param name: the flavor name to return
- :return: the OpenStack flavor object or None if not exists
+ :param flavor_id: the flavor's ID value
+ :return: the OpenStack Flavor object
+ """
+ try:
+ return nova.flavors.get(flavor_id)
+ except NotFound:
+ return None
+
+
+def get_flavor(nova, flavor):
+ """
+ Returns to OpenStack flavor object by name
+ :param nova: the Nova client
+ :param flavor: the SNAPS flavor domain object
+ :return: the SNAPS Flavor domain object
+ """
+ os_flavor = __get_os_flavor(nova, flavor.id)
+ if os_flavor:
+ return Flavor(
+ name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
+ disk=os_flavor.disk, vcpus=os_flavor.vcpus,
+ ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
+ rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
+ try:
+ return nova.flavors.get(flavor.id)
+ except NotFound:
+ return None
+
+
+def get_flavor_by_id(nova, flavor_id):
+ """
+ Returns to OpenStack flavor object by name
+ :param nova: the Nova client
+ :param flavor_id: the flavor ID value
+ :return: the SNAPS Flavor domain object
+ """
+ os_flavor = __get_os_flavor(nova, flavor_id)
+ if os_flavor:
+ return Flavor(
+ name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
+ disk=os_flavor.disk, vcpus=os_flavor.vcpus,
+ ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
+ rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
+
+
+def __get_os_flavor_by_name(nova, name):
+ """
+ Returns to OpenStack flavor object by name
+ :param nova: the Nova client
+ :param name: the name of the flavor to query
+ :return: OpenStack flavor object
"""
try:
return nova.flavors.find(name=name)
return None
+def get_flavor_by_name(nova, name):
+ """
+ Returns a flavor by name
+ :param nova: the Nova client
+ :param name: the flavor name to return
+ :return: the SNAPS flavor domain object or None if not exists
+ """
+ os_flavor = __get_os_flavor_by_name(nova, name)
+ if os_flavor:
+ return Flavor(
+ name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
+ disk=os_flavor.disk, vcpus=os_flavor.vcpus,
+ ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
+ rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
+
+
def create_flavor(nova, flavor_settings):
"""
Creates and returns and OpenStack flavor object
:param nova: the Nova client
:param flavor_settings: the flavor settings
- :return: the Flavor
+ :return: the SNAPS flavor domain object
"""
- return nova.flavors.create(name=flavor_settings.name,
- flavorid=flavor_settings.flavor_id,
- ram=flavor_settings.ram,
- vcpus=flavor_settings.vcpus,
- disk=flavor_settings.disk,
- ephemeral=flavor_settings.ephemeral,
- swap=flavor_settings.swap,
- rxtx_factor=flavor_settings.rxtx_factor,
- is_public=flavor_settings.is_public)
+ os_flavor = nova.flavors.create(
+ name=flavor_settings.name, flavorid=flavor_settings.flavor_id,
+ ram=flavor_settings.ram, vcpus=flavor_settings.vcpus,
+ disk=flavor_settings.disk, ephemeral=flavor_settings.ephemeral,
+ swap=flavor_settings.swap, rxtx_factor=flavor_settings.rxtx_factor,
+ is_public=flavor_settings.is_public)
+ return Flavor(
+ name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
+ disk=os_flavor.disk, vcpus=os_flavor.vcpus,
+ ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
+ rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
def delete_flavor(nova, flavor):
"""
Deletes a flavor
:param nova: the Nova client
- :param flavor: the OpenStack flavor object
+ :param flavor: the SNAPS flavor domain object
+ """
+ nova.flavors.delete(flavor.id)
+
+
+def set_flavor_keys(nova, flavor, metadata):
+ """
+ Sets metadata on the flavor
+ :param nova: the Nova client
+ :param flavor: the SNAPS flavor domain object
+ :param metadata: the metadata to set
"""
- nova.flavors.delete(flavor)
+ os_flavor = __get_os_flavor(nova, flavor.id)
+ if os_flavor:
+ os_flavor.set_keys(metadata)
+
+
+def get_flavor_keys(nova, flavor):
+ """
+ Sets metadata on the flavor
+ :param nova: the Nova client
+ :param flavor: the SNAPS flavor domain object
+ """
+ os_flavor = __get_os_flavor(nova, flavor.id)
+ if os_flavor:
+ return os_flavor.get_keys()
def add_security_group(nova, vm, security_group_name):
Removes a security group from an existing VM
:param nova: the nova client
:param vm: the OpenStack server object (VM) to alter
- :param security_group: the OpenStack security group object to add
+ :param security_group: the SNAPS SecurityGroup domain object to add
"""
- nova.servers.remove_security_group(
- str(vm.id), security_group['security_group']['name'])
+ nova.servers.remove_security_group(str(vm.id), security_group.name)
def add_floating_ip_to_server(nova, vm, floating_ip, ip_addr):
:param floating_ip: FloatingIp domain object
:param ip_addr: the IP to which to bind the floating IP to
"""
- vm = get_latest_server_os_object(nova, vm)
+ vm = __get_latest_server_os_object(nova, vm)
vm.add_floating_ip(floating_ip.ip, ip_addr)
+
+
+def get_compute_quotas(nova, project_id):
+ """
+ Returns a list of all available keypairs
+ :param nova: the Nova client
+ :param project_id: the project's ID of the quotas to lookup
+ :return: an object of type ComputeQuotas or None if not found
+ """
+ quotas = nova.quotas.get(tenant_id=project_id)
+ if quotas:
+ return ComputeQuotas(quotas)
+
+
+def update_quotas(nova, project_id, compute_quotas):
+ """
+ Updates the compute quotas for a given project
+ :param nova: the Nova client
+ :param project_id: the project's ID that requires quota updates
+ :param compute_quotas: an object of type ComputeQuotas containing the
+ values to update
+ :return:
+ """
+ update_values = dict()
+ update_values['metadata_items'] = compute_quotas.metadata_items
+ update_values['cores'] = compute_quotas.cores
+ update_values['instances'] = compute_quotas.instances
+ update_values['injected_files'] = compute_quotas.injected_files
+ update_values['injected_file_content_bytes'] = (
+ compute_quotas.injected_file_content_bytes)
+ update_values['ram'] = compute_quotas.ram
+ update_values['fixed_ips'] = compute_quotas.fixed_ips
+ update_values['key_pairs'] = compute_quotas.key_pairs
+
+ return nova.quotas.update(project_id, **update_values)
+
+
+def attach_volume(nova, server, volume, timeout=None):
+ """
+ Attaches a volume to a server
+ :param nova: the nova client
+ :param server: the VMInst domain object
+ :param volume: the Volume domain object
+ :param timeout: denotes the amount of time to block to determine if the
+ has been properly attached. When None, do not wait.
+ :return: the value from the nova call
+ """
+ nova.volumes.create_server_volume(server.id, volume.id)
+
+ if timeout:
+ start_time = time.time()
+ while time.time() < start_time + timeout:
+ vm = get_server_object_by_id(nova, server.id)
+ for vol_dict in vm.volume_ids:
+ if volume.id == vol_dict['id']:
+ return vm
+
+ return None
+ else:
+ return get_server_object_by_id(nova, server.id)
+
+
+def detach_volume(nova, server, volume, timeout=None):
+ """
+ Attaches a volume to a server
+ :param nova: the nova client
+ :param server: the VMInst domain object
+ :param volume: the Volume domain object
+ :param timeout: denotes the amount of time to block to determine if the
+ has been properly detached. When None, do not wait.
+ :return: the value from the nova call
+ """
+ nova.volumes.delete_server_volume(server.id, volume.id)
+
+ if timeout:
+ start_time = time.time()
+ while time.time() < start_time + timeout:
+ vm = get_server_object_by_id(nova, server.id)
+ found = False
+ for vol_dict in vm.volume_ids:
+ if volume.id == vol_dict['id']:
+ found = True
+
+ if not found:
+ return vm
+
+ return None
+ else:
+ return get_server_object_by_id(nova, server.id)
+
+
+class NovaException(Exception):
+ """
+ Exception when calls to the Keystone client cannot be served properly
+ """