Merge "Added method to return OpenStackVmInstance from Heat."
[snaps.git] / snaps / openstack / utils / nova_utils.py
index bee526c..fe53211 100644 (file)
@@ -24,6 +24,7 @@ from novaclient.exceptions import NotFound
 
 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
 
@@ -45,7 +46,8 @@ def nova_client(os_creds):
     """
     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,
@@ -64,8 +66,8 @@ 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()
@@ -79,11 +81,10 @@ def create_server(nova, neutron, glance, instance_settings, image_settings,
 
     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:
         args = {'name': instance_settings.name,
                 'flavor': flavor,
@@ -92,31 +93,59 @@ 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,
-                'availability_zone':
-                    instance_settings.availability_zone}
+                'userdata': instance_settings.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,
-                      networks=server.networks)
+
+        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 snaps.domain.VmInst objects
-    """
-    out = list()
-    servers = nova.servers.list(search_opts={'name': name})
+    :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,
-                          networks=server.networks))
-    return out
+        return __map_os_server_obj_to_vm_inst(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'))
+
+    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)
 
 
 def __get_latest_server_os_object(nova, server):
@@ -126,7 +155,17 @@ def __get_latest_server_os_object(nova, server):
     :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):
@@ -163,8 +202,18 @@ def get_latest_server_object(nova, server):
     :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,
-                  networks=server.networks)
+    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):
@@ -215,46 +264,6 @@ 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 = open(pub_expand_file, '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_expand_file, 0o400)
-            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 = open(priv_expand_file, '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_expand_file, 0o400)
-            logger.info("Saved private key to - " + priv_expand_file)
-
-
 def upload_keypair_file(nova, name, file_path):
     """
     Uploads a public key from a file
@@ -263,9 +272,14 @@ 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):
@@ -278,7 +292,8 @@ def upload_keypair(nova, name, key):
     """
     logger.info('Creating keypair with name - ' + name)
     os_kp = nova.keypairs.create(name=name, public_key=key.decode('utf-8'))
-    return Keypair(name=os_kp.name, id=os_kp.id, public_key=os_kp.public_key)
+    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):
@@ -290,7 +305,7 @@ def keypair_exists(nova, keypair_obj):
     """
     try:
         os_kp = nova.keypairs.get(keypair_obj)
-        return Keypair(name=os_kp.name, id=os_kp.id,
+        return Keypair(name=os_kp.name, kp_id=os_kp.id,
                        public_key=os_kp.public_key)
     except:
         return None
@@ -307,7 +322,7 @@ def get_keypair_by_name(nova, name):
 
     for keypair in keypairs:
         if keypair.name == name:
-            return Keypair(name=keypair.name, id=keypair.id,
+            return Keypair(name=keypair.name, kp_id=keypair.id,
                            public_key=keypair.public_key)
 
     return None
@@ -323,16 +338,17 @@ def delete_keypair(nova, 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)
@@ -349,15 +365,15 @@ def delete_vm_instance(nova, vm_inst):
     nova.servers.delete(vm_inst.id)
 
 
-def __get_os_flavor(nova, flavor):
+def __get_os_flavor(nova, flavor_id):
     """
     Returns to OpenStack flavor object by name
     :param nova: the Nova client
-    :param flavor: the SNAPS flavor domain object
+    :param flavor_id: the flavor's ID value
     :return: the OpenStack Flavor object
     """
     try:
-        return nova.flavors.get(flavor.id)
+        return nova.flavors.get(flavor_id)
     except NotFound:
         return None
 
@@ -369,7 +385,7 @@ def get_flavor(nova, flavor):
     :param flavor: the SNAPS flavor domain object
     :return: the SNAPS Flavor domain object
     """
-    os_flavor = __get_os_flavor(nova, flavor)
+    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,
@@ -382,6 +398,22 @@ def get_flavor(nova, flavor):
         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
@@ -447,11 +479,22 @@ def set_flavor_keys(nova, flavor, metadata):
     :param flavor: the SNAPS flavor domain object
     :param metadata: the metadata to set
     """
-    os_flavor = __get_os_flavor(nova, 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):
     """
     Adds a security group to an existing VM
@@ -482,3 +525,43 @@ def add_floating_ip_to_server(nova, vm, floating_ip, ip_addr):
     """
     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)
+
+
+class NovaException(Exception):
+    """
+    Exception when calls to the Keystone client cannot be served properly
+    """