Added method to OpenStackHeatStack to return OpenStackKeypair objects.
[snaps.git] / snaps / openstack / utils / nova_utils.py
index fe53211..0820289 100644 (file)
 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
@@ -86,6 +88,18 @@ def create_server(nova, neutron, glance, instance_settings, image_settings,
 
     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,
@@ -93,7 +107,7 @@ def create_server(nova, neutron, glance, instance_settings, image_settings,
                 'key_name': keypair_name,
                 'security_groups':
                     instance_settings.security_group_names,
-                'userdata': instance_settings.userdata}
+                'userdata': userdata}
 
         if instance_settings.availability_zone:
             args['availability_zone'] = instance_settings.availability_zone
@@ -128,6 +142,27 @@ def get_server(nova, vm_inst_settings=None, server_name=None):
         return __map_os_server_obj_to_vm_inst(server)
 
 
+def get_server_connection(nova, vm_inst_settings=None, server_name=None):
+    """
+    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:
+        return server.links[0]
+
+
 def __map_os_server_obj_to_vm_inst(os_server):
     """
     Returns a VmInst object for an OpenStack Server object
@@ -141,11 +176,15 @@ def __map_os_server_obj_to_vm_inst(os_server):
             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)
+        sec_grp_names=sec_grp_names, volume_ids=volumes)
 
 
 def __get_latest_server_os_object(nova, server):
@@ -264,6 +303,58 @@ def public_key_openssh(keys):
                                           serialization.PublicFormat.OpenSSH)
 
 
+def save_keys_to_files(keys=None, pub_file_path=None, priv_file_path=None):
+    """
+    Saves the generated RSA generated keys to the filesystem
+    :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
+    """
+    if keys:
+        if 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 = 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:
+            # 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 = 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):
     """
     Uploads a public key from a file
@@ -328,6 +419,18 @@ def get_keypair_by_name(nova, name):
     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
@@ -553,7 +656,8 @@ def update_quotas(nova, project_id, compute_quotas):
     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['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
@@ -561,6 +665,60 @@ def update_quotas(nova, project_id, compute_quotas):
     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