Merge "Added logging when a heat stack fails."
[snaps.git] / snaps / openstack / utils / heat_utils.py
index a631b35..5b47280 100644 (file)
@@ -17,12 +17,14 @@ import logging
 import yaml
 from heatclient.client import Client
 from heatclient.common.template_format import yaml_loader
+from novaclient.exceptions import NotFound
 from oslo_serialization import jsonutils
 
 from snaps import file_utils
-from snaps.domain.stack import Stack
+from snaps.domain.stack import Stack, Resource, Output
 
-from snaps.openstack.utils import keystone_utils
+from snaps.openstack.utils import (
+    keystone_utils, neutron_utils, nova_utils, cinder_utils)
 
 __author__ = 'spisarski'
 
@@ -37,22 +39,33 @@ def heat_client(os_creds):
     """
     logger.debug('Retrieving Nova Client')
     return Client(os_creds.heat_api_version,
-                  session=keystone_utils.keystone_session(os_creds))
+                  session=keystone_utils.keystone_session(os_creds),
+                  region_name=os_creds.region_name)
 
 
-def get_stack_by_name(heat_cli, stack_name):
+def get_stack(heat_cli, stack_settings=None, stack_name=None):
     """
-    Returns a domain Stack object
+    Returns the first domain Stack object found. When stack_setting
+    is not None, the filter created will take the name attribute. When
+    stack_settings is None and stack_name is not, stack_name will be used
+    instead. When both are None, the first stack object received will be
+    returned, else None
     :param heat_cli: the OpenStack heat client
-    :param stack_name: the name of the heat stack
+    :param stack_settings: a StackSettings object
+    :param stack_name: the name of the heat stack to return
     :return: the Stack domain object else None
     """
-    stacks = heat_cli.stacks.list(**{'name': stack_name})
+
+    stack_filter = dict()
+    if stack_settings:
+        stack_filter['stack_name'] = stack_settings.name
+    elif stack_name:
+        stack_filter['stack_name'] = stack_name
+
+    stacks = heat_cli.stacks.list(**stack_filter)
     for stack in stacks:
         return Stack(name=stack.identifier, stack_id=stack.id)
 
-    return None
-
 
 def get_stack_by_id(heat_cli, stack_id):
     """
@@ -75,15 +88,14 @@ def get_stack_status(heat_cli, stack_id):
     return heat_cli.stacks.get(stack_id).stack_status
 
 
-def get_stack_outputs(heat_cli, stack_id):
+def get_stack_status_reason(heat_cli, stack_id):
     """
-    Returns a domain Stack object for a given ID
+    Returns the current status of the Heat stack
     :param heat_cli: the OpenStack heat client
     :param stack_id: the ID of the heat stack to retrieve
-    :return: the Stack domain object else None
+    :return: reason for stack creation failure
     """
-    stack = heat_cli.stacks.get(stack_id)
-    return stack.outputs
+    return heat_cli.stacks.get(stack_id).stack_status_reason
 
 
 def create_stack(heat_cli, stack_settings):
@@ -119,6 +131,212 @@ def delete_stack(heat_cli, stack):
     heat_cli.stacks.delete(stack.id)
 
 
+def __get_os_resources(heat_cli, stack):
+    """
+    Returns all of the OpenStack resource objects for a given stack
+    :param heat_cli: the OpenStack heat client
+    :param stack: the SNAPS-OO Stack domain object
+    :return: a list
+    """
+    return heat_cli.resources.list(stack.id)
+
+
+def get_resources(heat_cli, stack, res_type=None):
+    """
+    Returns all of the OpenStack resource objects for a given stack
+    :param heat_cli: the OpenStack heat client
+    :param stack: the SNAPS-OO Stack domain object
+    :param res_type: the type name to filter
+    :return: a list of Resource domain objects
+    """
+    os_resources = __get_os_resources(heat_cli, stack)
+
+    if os_resources:
+        out = list()
+        for os_resource in os_resources:
+            if ((res_type and os_resource.resource_type == res_type)
+                or not res_type):
+                out.append(Resource(
+                    name=os_resource.resource_name,
+                    resource_type=os_resource.resource_type,
+                    resource_id=os_resource.physical_resource_id,
+                    status=os_resource.resource_status,
+                    status_reason=os_resource.resource_status_reason))
+        return out
+
+
+def get_outputs(heat_cli, stack):
+    """
+    Returns all of the SNAPS-OO Output domain objects for the defined outputs
+    for given stack
+    :param heat_cli: the OpenStack heat client
+    :param stack: the SNAPS-OO Stack domain object
+    :return: a list of Output domain objects
+    """
+    out = list()
+
+    os_stack = heat_cli.stacks.get(stack.id)
+
+    outputs = None
+    if os_stack:
+        outputs = os_stack.outputs
+
+    if outputs:
+        for output in outputs:
+            out.append(Output(**output))
+
+    return out
+
+
+def get_stack_networks(heat_cli, neutron, stack):
+    """
+    Returns a list of Network domain objects deployed by this stack
+    :param heat_cli: the OpenStack heat client object
+    :param neutron: the OpenStack neutron client object
+    :param stack: the SNAPS-OO Stack domain object
+    :return: a list of Network objects
+    """
+
+    out = list()
+    resources = get_resources(heat_cli, stack, 'OS::Neutron::Net')
+    for resource in resources:
+        network = neutron_utils.get_network_by_id(neutron, resource.id)
+        if network:
+            out.append(network)
+
+    return out
+
+
+def get_stack_routers(heat_cli, neutron, stack):
+    """
+    Returns a list of Network domain objects deployed by this stack
+    :param heat_cli: the OpenStack heat client object
+    :param neutron: the OpenStack neutron client object
+    :param stack: the SNAPS-OO Stack domain object
+    :return: a list of Network objects
+    """
+
+    out = list()
+    resources = get_resources(heat_cli, stack, 'OS::Neutron::Router')
+    for resource in resources:
+        router = neutron_utils.get_router_by_id(neutron, resource.id)
+        if router:
+            out.append(router)
+
+    return out
+
+
+def get_stack_servers(heat_cli, nova, stack):
+    """
+    Returns a list of VMInst domain objects associated with a Stack
+    :param heat_cli: the OpenStack heat client object
+    :param nova: the OpenStack nova client object
+    :param stack: the SNAPS-OO Stack domain object
+    :return: a list of VMInst domain objects
+    """
+
+    out = list()
+    resources = get_resources(heat_cli, stack, 'OS::Nova::Server')
+    for resource in resources:
+        try:
+            server = nova_utils.get_server_object_by_id(nova, resource.id)
+            if server:
+                out.append(server)
+        except NotFound:
+            logger.warn('VmInst cannot be located with ID %s', resource.id)
+
+    return out
+
+
+def get_stack_keypairs(heat_cli, nova, stack):
+    """
+    Returns a list of Keypair domain objects associated with a Stack
+    :param heat_cli: the OpenStack heat client object
+    :param nova: the OpenStack nova client object
+    :param stack: the SNAPS-OO Stack domain object
+    :return: a list of VMInst domain objects
+    """
+
+    out = list()
+    resources = get_resources(heat_cli, stack, 'OS::Nova::KeyPair')
+    for resource in resources:
+        try:
+            keypair = nova_utils.get_keypair_by_id(nova, resource.id)
+            if keypair:
+                out.append(keypair)
+        except NotFound:
+            logger.warn('Keypair cannot be located with ID %s', resource.id)
+
+    return out
+
+
+def get_stack_volumes(heat_cli, cinder, stack):
+    """
+    Returns an instance of Volume domain objects created by this stack
+    :param heat_cli: the OpenStack heat client object
+    :param cinder: the OpenStack cinder client object
+    :param stack: the SNAPS-OO Stack domain object
+    :return: a list of Volume domain objects
+    """
+
+    out = list()
+    resources = get_resources(heat_cli, stack, 'OS::Cinder::Volume')
+    for resource in resources:
+        try:
+            server = cinder_utils.get_volume_by_id(cinder, resource.id)
+            if server:
+                out.append(server)
+        except NotFound:
+            logger.warn('Volume cannot be located with ID %s', resource.id)
+
+    return out
+
+
+def get_stack_volume_types(heat_cli, cinder, stack):
+    """
+    Returns an instance of VolumeType domain objects created by this stack
+    :param heat_cli: the OpenStack heat client object
+    :param cinder: the OpenStack cinder client object
+    :param stack: the SNAPS-OO Stack domain object
+    :return: a list of VolumeType domain objects
+    """
+
+    out = list()
+    resources = get_resources(heat_cli, stack, 'OS::Cinder::VolumeType')
+    for resource in resources:
+        try:
+            vol_type = cinder_utils.get_volume_type_by_id(cinder, resource.id)
+            if vol_type:
+                out.append(vol_type)
+        except NotFound:
+            logger.warn('VolumeType cannot be located with ID %s', resource.id)
+
+    return out
+
+
+def get_stack_flavors(heat_cli, nova, stack):
+    """
+    Returns an instance of Flavor SNAPS domain object for each flavor created
+    by this stack
+    :param heat_cli: the OpenStack heat client object
+    :param nova: the OpenStack cinder client object
+    :param stack: the SNAPS-OO Stack domain object
+    :return: a list of Volume domain objects
+    """
+
+    out = list()
+    resources = get_resources(heat_cli, stack, 'OS::Nova::Flavor')
+    for resource in resources:
+        try:
+            flavor = nova_utils.get_flavor_by_id(nova, resource.id)
+            if flavor:
+                out.append(flavor)
+        except NotFound:
+            logger.warn('Flavor cannot be located with ID %s', resource.id)
+
+    return out
+
+
 def parse_heat_template_str(tmpl_str):
     """
     Takes a heat template string, performs some simple validation and returns a