]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
Add describe resource API calls
authorTomas Sedovic <tomas@sedovic.cz>
Thu, 14 Jun 2012 15:51:45 +0000 (17:51 +0200)
committerTomas Sedovic <tomas@sedovic.cz>
Mon, 18 Jun 2012 11:36:10 +0000 (13:36 +0200)
Fixes #62.

This commit implements the `DescribeStackResource`,
`DescribeStackResources` and `ListStackResources` AWS API calls.

Change-Id: Id9161b3c3eb527d5936c5b8978e32a67ba6c12bb

bin/heat
heat/api/v1/__init__.py
heat/api/v1/stacks.py
heat/client.py
heat/cloudformations.py
heat/db/api.py
heat/db/sqlalchemy/api.py
heat/engine/manager.py

index 6d71b69ca735deacec8ae0b84857f5d7e6f2acab..8ee6b92a6a817e493f29e874800e8d4b57dddb3f 100755 (executable)
--- a/bin/heat
+++ b/bin/heat
@@ -297,6 +297,61 @@ def stack_events_list(options, arguments):
     print result
 
 
+@utils.catch_error('resource')
+def stack_resource_show(options, arguments):
+    '''
+    Display details of the specified resource.
+    '''
+    c = get_client(options)
+    try:
+        stack_name, resource_name = arguments
+    except ValueError:
+        print 'Enter stack name and logical resource id'
+        return
+
+    parameters = {
+        'StackName': stack_name,
+        'LogicalResourceId': resource_name,
+    }
+    result = c.describe_stack_resource(**parameters)
+    print result
+
+
+@utils.catch_error('resource-list')
+def stack_resources_list(options, arguments):
+    '''
+    Display summary of all resources in the specified stack.
+    '''
+    c = get_client(options)
+    try:
+        stack_name = arguments.pop(0)
+    except IndexError:
+        print 'Enter stack name'
+        return
+
+    parameters = {
+        'StackName': stack_name,
+    }
+    result = c.list_stack_resources(**parameters)
+    print result
+
+
+@utils.catch_error('resource-list-details')
+def stack_resources_list_details(options, arguments):
+    '''
+    Display details of all resources in the specified stack.
+    '''
+    c = get_client(options)
+    logical_resource_id = arguments.pop(0) if arguments else None
+    parameters = {
+        'StackName': options.stack_name,
+        'PhysicalResourceId': options.physical_resource_id,
+        'LogicalResourceId': logical_resource_id,
+    }
+    result = c.describe_stack_resources(**parameters)
+    print result
+
+
 @utils.catch_error('list')
 def stack_list(options, arguments):
     '''
@@ -397,6 +452,10 @@ def create_options(parser):
 
     parser.add_option('-P', '--parameters', metavar="parameters", default=None,
                       help="Parameter values used to create the stack.")
+    parser.add_option('-n', '--stack-name', default=None,
+                      help="Name of the queried stack")
+    parser.add_option('-c', '--physical-resource-id', default=None,
+                      help="Physical ID of the queried resource")
 
 
 def credentials_from_env():
@@ -484,6 +543,9 @@ def lookup_command(parser, command_name):
                 'list': stack_list,
                 'events_list': stack_events_list,  # DEPRECATED
                 'event-list': stack_events_list,
+                'resource': stack_resource_show,
+                'resource-list': stack_resources_list,
+                'resource-list-details': stack_resources_list_details,
                 'validate': template_validate,
                 'gettemplate': get_template,
                 'estimate-template-cost': estimate_template_cost,
@@ -530,6 +592,12 @@ Commands:
 
     event-list      List events for a stack
 
+    resource        Describe the resource
+
+    resource-list   Show list of resources belonging to a stack
+
+    resource-list-details    Detailed view of resources belonging to a stack
+
 """
 
     oparser = optparse.OptionParser(version='%%prog %s'
index f54a1843316a83a5b48e9b310921c2a7612c66c7..609166571093c8234f9e63479114ed1c9a1761f4 100644 (file)
@@ -127,6 +127,9 @@ class API(wsgi.Router):
         'validate_template': 'ValidateTemplate',
         'get_template': 'GetTemplate',
         'estimate_template_cost': 'EstimateTemplateCost',
+        'describe_stack_resource': 'DescribeStackResource',
+        'describe_stack_resources': 'DescribeStackResources',
+        'list_stack_resources': 'ListStackResources',
     }
 
     def __init__(self, conf, **local_conf):
index 5831163615092deeba6d8eb3c3fce94dd22b4287..53c4b7bb00faf6dac9b269e5745ef2eacfc0aa19 100644 (file)
@@ -243,6 +243,98 @@ class StackController(object):
 
         return {'DescribeStackEventsResult': {'StackEvents': events}}
 
+    def describe_stack_resource(self, req):
+        """
+        Return the details of the given resource belonging to the given stack.
+        """
+        con = req.context
+        args = {
+            'stack_name': req.params.get('StackName'),
+            'resource_name': req.params.get('LogicalResourceId'),
+        }
+
+        try:
+            resource_details = rpc.call(con, 'engine',
+                              {'method': 'describe_stack_resource',
+                               'args': args})
+
+        except rpc_common.RemoteError as ex:
+            return webob.exc.HTTPBadRequest(str(ex))
+
+        return {
+            'DescribeStackResourceResponse': {
+                'DescribeStackResourceResult': {
+                    'StackResourceDetail': resource_details,
+                },
+            },
+        }
+
+    def describe_stack_resources(self, req):
+        """
+        Return details of resources specified by the parameters.
+
+        `StackName`: returns all resources belonging to the stack
+        `PhysicalResourceId`: returns all resources belonging to the stack this
+                              resource is associated with.
+
+        Only one of the parameters may be specified.
+
+        Optional parameter:
+
+        `LogicalResourceId`: filter the resources list by the logical resource
+        id.
+        """
+        con = req.context
+        stack_name = req.params.get('StackName')
+        physical_resource_id = req.params.get('PhysicalResourceId')
+        if stack_name and physical_resource_id:
+            msg = 'Use `StackName` or `PhysicalResourceId` but not both'
+            return webob.exc.HTTPBadRequest(msg)
+
+        args = {
+            'stack_name': stack_name,
+            'physical_resource_id': physical_resource_id,
+            'logical_resource_id': req.params.get('LogicalResourceId'),
+        }
+
+        try:
+            resources = rpc.call(con, 'engine',
+                              {'method': 'describe_stack_resources',
+                               'args': args})
+
+        except rpc_common.RemoteError as ex:
+            return webob.exc.HTTPBadRequest(str(ex))
+
+        response = {
+            'DescribeStackResourcesResult': {
+                'StackResources': resources,
+            }
+        }
+        return response
+
+    def list_stack_resources(self, req):
+        """
+        Return summary of the resources belonging to the specified stack.
+
+        """
+        con = req.context
+
+        try:
+            resources = rpc.call(con, 'engine', {
+                'method': 'list_stack_resources',
+                'args': {'stack_name': req.params.get('StackName')}
+            })
+        except rpc_common.RemoteError as ex:
+            return webob.exc.HTTPBadRequest(str(ex))
+
+        return {
+            'ListStackResourcesResponse': {
+                'ListStackResourcesResult': {
+                    'StackResourceSummaries': resources,
+                },
+            },
+        }
+
 
 def create_resource(options):
     """
index d370a461cf1e8808da7a8e0d9f36f3bc85af3d4f..4a5dc1f03c01c17a53c4bdbc55d0f20b0139bdcf 100644 (file)
@@ -65,6 +65,15 @@ class V1Client(base_client.BaseClient):
     def list_stack_events(self, **kwargs):
         return self.stack_request("DescribeStackEvents", "GET", **kwargs)
 
+    def describe_stack_resource(self, **kwargs):
+        return self.stack_request("DescribeStackResource", "GET", **kwargs)
+
+    def describe_stack_resources(self, **kwargs):
+        return self.stack_request("DescribeStackResources", "GET", **kwargs)
+
+    def list_stack_resources(self, **kwargs):
+        return self.stack_request("ListStackResources", "GET", **kwargs)
+
     def validate_template(self, **kwargs):
         return self.stack_request("ValidateTemplate", "GET", **kwargs)
 
index c6ef2644558a9496ebc2e92c10d1edb47211e3c2..5ad1fb3a994d9ddae9aa1613f0a9efda885ac69c 100644 (file)
@@ -16,4 +16,6 @@
 SUPPORTED_PARAMS = ('StackName', 'TemplateBody', 'TemplateUrl',
                     'NotificationARNs', 'Parameters', 'Version',
                     'SignatureVersion', 'Timestamp', 'AWSAccessKeyId',
-                    'Signature', 'KeyStoneCreds', 'Timeout')
+                    'Signature', 'KeyStoneCreds', 'Timeout',
+                    'LogicalResourceId', 'PhysicalResourceId', 'NextToken',
+)
index 8dc2aa55e2ecd09a7d92f03fbc1bd9195aa614f4..d784e8c8a9f6c29d9386ba7c0514c0da7a2a4b7b 100644 (file)
@@ -95,6 +95,11 @@ def resource_get_by_name_and_stack(context, resource_name, stack_id):
                                                resource_name, stack_id)
 
 
+def resource_get_by_physical_resource_id(context, physical_resource_id):
+    return IMPL.resource_get_by_physical_resource_id(context,
+                                                     physical_resource_id)
+
+
 def stack_get(context, stack_id):
     return IMPL.stack_get(context, stack_id)
 
index 61366bbfc947707a0be6f0c99399956cf0af628a..f31b08d4817b0b5be37cd67fdaa60462e34fa096 100644 (file)
@@ -98,6 +98,13 @@ def resource_get_by_name_and_stack(context, resource_name, stack_id):
     return result
 
 
+def resource_get_by_physical_resource_id(context, physical_resource_id):
+    result = (model_query(context, models.Resource)
+              .filter_by(nova_instance=physical_resource_id)
+              .first())
+    return result
+
+
 def resource_get_all(context):
     results = model_query(context, models.Resource).all()
 
index 5de0ffe78d11a9093240c53bdf49fc92d79c7cbe..4dea7c572a8cac764e099545c491d2ed7f082bd4 100644 (file)
@@ -391,6 +391,67 @@ class EngineManager(manager.Manager):
             msg = 'Error creating event'
             return [msg, None]
 
+    def describe_stack_resource(self, context, stack_name, resource_name):
+        self._authenticate(context)
+
+        stack = db_api.stack_get(context, stack_name)
+        if not stack:
+            raise AttributeError('Unknown stack name')
+        resource = db_api.resource_get_by_name_and_stack(context,
+                                                         resource_name,
+                                                         stack.id)
+        if not resource:
+            raise AttributeError('Unknown resource name')
+        return format_resource_attributes(stack, resource)
+
+    def describe_stack_resources(self, context, stack_name,
+                                 physical_resource_id, logical_resource_id):
+        self._authenticate(context)
+
+        if stack_name:
+            stack = db_api.stack_get(context, stack_name)
+        else:
+            resource = db_api.resource_get_by_physical_resource_id(context,
+                    physical_resource_id)
+            if not resource:
+                msg = "The specified PhysicalResourceId doesn't exist"
+                raise AttributeError(msg)
+            stack = resource.stack
+
+        if not stack:
+            raise AttributeError("The specified stack doesn't exist")
+
+        resources = []
+        for r in stack.resources:
+            if logical_resource_id and r.name != logical_resource_id:
+                continue
+            formatted = format_resource_attributes(stack, r)
+            # this API call uses Timestamp instead of LastUpdatedTimestamp
+            formatted['Timestamp'] = formatted['LastUpdatedTimestamp']
+            del formatted['LastUpdatedTimestamp']
+            resources.append(formatted)
+
+        return resources
+
+    def list_stack_resources(self, context, stack_name):
+        self._authenticate(context)
+
+        stack = db_api.stack_get(context, stack_name)
+        if not stack:
+            raise AttributeError('Unknown stack name')
+
+        resources = []
+        response_keys = ('ResourceStatus', 'LogicalResourceId',
+                         'LastUpdatedTimestamp', 'PhysicalResourceId',
+                         'ResourceType')
+        for r in stack.resources:
+            formatted = format_resource_attributes(stack, r)
+            for key in formatted.keys():
+                if not key in response_keys:
+                    del formatted[key]
+            resources.append(formatted)
+        return resources
+
     def metadata_register_address(self, context, url):
         config.FLAGS.heat_metadata_server_url = url
 
@@ -520,3 +581,23 @@ class EngineManager(manager.Manager):
             self.run_rule(None, wr)
 
         return [None, wd.data]
+
+
+def format_resource_attributes(stack, resource):
+    """
+    Return a representation of the given resource that mathes the API output
+    expectations.
+    """
+    template = resource.parsed_template.template
+    template_resources = template.get('Resources', {})
+    resource_type = template_resources.get(resource.name, {}).get('Type', '')
+    last_updated_time = resource.updated_at or resource.created_at
+    return {
+        'StackId': stack.id,
+        'StackName': stack.name,
+        'LogicalResourceId': resource.name,
+        'PhysicalResourceId': resource.nova_instance or '',
+        'ResourceType': resource_type,
+        'LastUpdatedTimestamp': last_updated_time.isoformat(),
+        'ResourceStatus': resource.state,
+    }