]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
heat engine/API : Internal API rework
authorSteven Hardy <shardy@redhat.com>
Tue, 10 Jul 2012 17:13:23 +0000 (18:13 +0100)
committerSteven Hardy <shardy@redhat.com>
Mon, 16 Jul 2012 16:21:11 +0000 (17:21 +0100)
Refactor engine-api code so that AWS specific
details like key names are handled in the API
Fixes #172

Change-Id: I4c5b153557216c03e5a98193e54cf75e3c7b97dd
Signed-off-by: Steven Hardy <shardy@redhat.com>
heat/api/v1/stacks.py
heat/engine/api.py
heat/engine/manager.py
heat/tests/test_api_v1.py [new file with mode: 0644]
heat/tests/test_engine_api_utils.py
heat/tests/test_stacks.py

index 1f9010533747f75a78012b2aae538280eee30e6b..84c0a60b932afb36f7f7056cf00d556f10365496 100644 (file)
@@ -31,6 +31,7 @@ from heat.common import context
 from heat import utils
 from heat import rpc
 import heat.rpc.common as rpc_common
+import heat.engine.api as engine_api
 
 from heat.openstack.common import log as logging
 
@@ -79,22 +80,103 @@ class StackController(object):
             # FIXME : further investigation into engine errors required
             return exception.HeatInternalFailureError(detail=ex.value)
 
+    @staticmethod
+    def _extract_user_params(params):
+        """
+        Extract a dictionary of user input parameters for the stack
+
+        In the AWS API parameters, each user parameter appears as two key-value
+        pairs with keys of the form below:
+
+        Parameters.member.1.ParameterKey
+        Parameters.member.1.ParameterValue
+
+        We reformat this into a normal dict here to match the heat
+        engine API expected format
+
+        Note this implemented outside of "create" as it will also be
+        used by update (and EstimateTemplateCost if appropriate..)
+        """
+        # Define the AWS key format to extract
+        PARAM_KEYS = (
+        PARAM_USER_KEY_re,
+        PARAM_USER_VALUE_fmt,
+        ) = (
+        re.compile(r'Parameters\.member\.(.*?)\.ParameterKey$'),
+        'Parameters.member.%s.ParameterValue',
+        )
+
+        def get_param_pairs():
+            for k in params:
+                keymatch = PARAM_USER_KEY_re.match(k)
+                if keymatch:
+                    key = params[k]
+                    v = PARAM_USER_VALUE_fmt % keymatch.group(1)
+                    try:
+                        value = params[v]
+                    except KeyError:
+                        logger.error('Could not apply parameter %s' % key)
+
+                    yield (key, value)
+
+        return dict(get_param_pairs())
+
+    @staticmethod
+    def _reformat_dict_keys(keymap={}, inputdict={}):
+        '''
+        Utility function for mapping one dict format to another
+        '''
+        result = {}
+        for key in keymap:
+            result[keymap[key]] = inputdict[key]
+        return result
+
     def list(self, req):
         """
         Implements ListStacks API action
         Lists summary information for all stacks
         """
+
+        def format_stack_summary(s):
+            """
+            Reformat engine output into the AWS "StackSummary" format
+            """
+            # Map the engine-api format to the AWS StackSummary datatype
+            keymap = {
+                engine_api.STACK_CREATION_TIME: 'CreationTime',
+                engine_api.STACK_UPDATED_TIME: 'LastUpdatedTime',
+                engine_api.STACK_ID: 'StackId',
+                engine_api.STACK_NAME: 'StackName',
+                engine_api.STACK_STATUS: 'StackStatus',
+                engine_api.STACK_STATUS_DATA: 'StackStatusReason',
+                engine_api.STACK_TMPL_DESCRIPTION: 'TemplateDescription',
+            }
+
+            result = self._reformat_dict_keys(keymap, s)
+
+            # AWS docs indicate DeletionTime is ommitted for current stacks
+            # This is still TODO in the engine, we don't keep data for
+            # stacks after they are deleted
+            if engine_api.STACK_DELETION_TIME in s:
+                result['DeletionTime'] = s[engine_api.STACK_DELETION_TIME]
+
+            return self._stackid_addprefix(result)
+
         con = req.context
         parms = dict(req.params)
 
-        stack_list = rpc.call(con, 'engine',
-                            {'method': 'list_stacks',
-                            'args': {'params': parms}})
+        try:
+            # Note show_stack returns details for all stacks when called with
+            # no stack_name, we only use a subset of the result here though
+            stack_list = rpc.call(con, 'engine',
+                              {'method': 'show_stack',
+                               'args': {'stack_name': None,
+                                'params': parms}})
+        except rpc_common.RemoteError as ex:
+            return self._remote_error(ex)
 
-        res = {'StackSummaries': []}
-        if stack_list is not None:
-            for s in stack_list['stacks']:
-                res['StackSummaries'].append(self._stackid_addprefix(s))
+        res = {'StackSummaries': [format_stack_summary(s)
+                                   for s in stack_list['stacks']]}
 
         return self._format_response('ListStacks', res)
 
@@ -103,6 +185,53 @@ class StackController(object):
         Implements DescribeStacks API action
         Gets detailed information for a stack (or all stacks)
         """
+        def format_stack_outputs(o):
+            keymap = {
+                engine_api.OUTPUT_DESCRIPTION: 'Description',
+                engine_api.OUTPUT_KEY: 'OutputKey',
+                engine_api.OUTPUT_VALUE: 'OutputValue',
+            }
+
+            return self._reformat_dict_keys(keymap, o)
+
+        def format_stack(s):
+            """
+            Reformat engine output into the AWS "StackSummary" format
+            """
+            keymap = {
+                engine_api.STACK_CAPABILITIES: 'Capabilities',
+                engine_api.STACK_CREATION_TIME: 'CreationTime',
+                engine_api.STACK_DESCRIPTION: 'Description',
+                engine_api.STACK_DISABLE_ROLLBACK: 'DisableRollback',
+                engine_api.STACK_UPDATED_TIME: 'LastUpdatedTime',
+                engine_api.STACK_NOTIFICATION_TOPICS: 'NotificationARNs',
+                engine_api.STACK_PARAMETERS: 'Parameters',
+                engine_api.STACK_ID: 'StackId',
+                engine_api.STACK_NAME: 'StackName',
+                engine_api.STACK_STATUS: 'StackStatus',
+                engine_api.STACK_STATUS_DATA: 'StackStatusReason',
+                engine_api.STACK_TIMEOUT: 'TimeoutInMinutes',
+            }
+
+            result = self._reformat_dict_keys(keymap, s)
+
+            # Reformat outputs, these are handled separately as they are
+            # only present in the engine output for a completely created
+            # stack
+            result['Outputs'] = []
+            if engine_api.STACK_OUTPUTS in s:
+                for o in s[engine_api.STACK_OUTPUTS]:
+                    result['Outputs'].append(format_stack_outputs(o))
+
+            # Reformat Parameters dict-of-dict into AWS API format
+            # This is a list-of-dict with nasty "ParameterKey" : key
+            # "ParameterValue" : value format.
+            result['Parameters'] = [{'ParameterKey':k,
+                'ParameterValue':v.get('Default')}
+                for (k, v) in result['Parameters'].items()]
+
+            return self._stackid_addprefix(result)
+
         con = req.context
         parms = dict(req.params)
 
@@ -122,15 +251,7 @@ class StackController(object):
         except rpc_common.RemoteError as ex:
             return self._remote_error(ex)
 
-        res = {'Stacks': []}
-        for s in stack_list['stacks']:
-            # Reformat Parameters dict-of-dict into AWS API format
-            # This is a list-of-dict with nasty "ParameterKey" : key
-            # "ParameterValue" : value format.
-            s['Parameters'] = [{'ParameterKey':k,
-                'ParameterValue':v.get('Default')}
-                for (k, v) in s['Parameters'].items()]
-            res['Stacks'].append(self._stackid_addprefix(s))
+        res = {'Stacks': [format_stack(s) for s in stack_list['stacks']]}
 
         return self._format_response('DescribeStacks', res)
 
@@ -165,8 +286,29 @@ class StackController(object):
         Implements CreateStack API action
         Create stack as defined in template file
         """
+        def extract_args(params):
+            """
+            Extract request parameters/arguments and reformat them to match
+            the engine API.  FIXME: we currently only support a subset of
+            the AWS defined parameters (both here and in the engine)
+            """
+            # TODO : Capabilities, DisableRollback, NotificationARNs
+            keymap = {'TimeoutInMinutes': engine_api.PARAM_TIMEOUT, }
+
+            result = {}
+            for k in keymap:
+                if k in req.params:
+                    result[keymap[k]] = params[k]
+
+            return result
+
         con = req.context
-        parms = dict(req.params)
+
+        # Extract the stack input parameters
+        stack_parms = self._extract_user_params(req.params)
+
+        # Extract any additional arguments ("Request Parameters")
+        create_args = extract_args(req.params)
 
         try:
             templ = self._get_template(req)
@@ -189,7 +331,8 @@ class StackController(object):
                             {'method': 'create_stack',
                              'args': {'stack_name': req.params['StackName'],
                                       'template': stack,
-                                      'params': parms}})
+                                      'params': stack_parms,
+                                      'args': create_args}})
         except rpc_common.RemoteError as ex:
             return self._remote_error(ex)
 
@@ -288,6 +431,27 @@ class StackController(object):
         Implements the DescribeStackEvents API action
         Returns events related to a specified stack (or all stacks)
         """
+        def format_stack_event(e):
+            """
+            Reformat engine output into the AWS "StackEvent" format
+            """
+            keymap = {
+                engine_api.EVENT_ID: 'EventId',
+                engine_api.EVENT_RES_NAME: 'LogicalResourceId',
+                engine_api.EVENT_RES_PHYSICAL_ID: 'PhysicalResourceId',
+                engine_api.EVENT_RES_PROPERTIES: 'ResourceProperties',
+                engine_api.EVENT_RES_STATUS: 'ResourceStatus',
+                engine_api.EVENT_RES_STATUS_DATA: 'ResourceStatusData',
+                engine_api.EVENT_RES_TYPE: 'ResourceType',
+                engine_api.EVENT_STACK_ID: 'StackId',
+                engine_api.EVENT_STACK_NAME: 'StackName',
+                engine_api.EVENT_TIMESTAMP: 'Timestamp',
+            }
+
+            result = self._reformat_dict_keys(keymap, e)
+
+            return self._stackid_addprefix(result)
+
         con = req.context
         parms = dict(req.params)
 
@@ -302,9 +466,7 @@ class StackController(object):
 
         events = 'Error' not in event_res and event_res['events'] or []
 
-        result = []
-        for e in events:
-            result.append(self._stackid_addprefix(e))
+        result = [format_stack_event(e) for e in events]
 
         return self._format_response('DescribeStackEvents',
             {'StackEvents': result})
@@ -314,6 +476,28 @@ class StackController(object):
         Implements the DescribeStackResource API action
         Return the details of the given resource belonging to the given stack.
         """
+
+        def format_resource_detail(r):
+            """
+            Reformat engine output into the AWS "StackResourceDetail" format
+            """
+            keymap = {
+                engine_api.RES_DESCRIPTION: 'Description',
+                engine_api.RES_UPDATED_TIME: 'LastUpdatedTimestamp',
+                engine_api.RES_NAME: 'LogicalResourceId',
+                engine_api.RES_METADATA: 'Metadata',
+                engine_api.RES_PHYSICAL_ID: 'PhysicalResourceId',
+                engine_api.RES_STATUS: 'ResourceStatus',
+                engine_api.RES_STATUS_DATA: 'ResourceStatusReason',
+                engine_api.RES_TYPE: 'ResourceType',
+                engine_api.RES_STACK_ID: 'StackId',
+                engine_api.RES_STACK_NAME: 'StackName',
+            }
+
+            result = self._reformat_dict_keys(keymap, r)
+
+            return self._stackid_addprefix(result)
+
         con = req.context
         args = {
             'stack_name': req.params.get('StackName'),
@@ -328,8 +512,10 @@ class StackController(object):
         except rpc_common.RemoteError as ex:
             return self._remote_error(ex)
 
+        result = format_resource_detail(resource_details)
+
         return self._format_response('DescribeStackResource',
-            {'StackResourceDetail': resource_details})
+            {'StackResourceDetail': result})
 
     def describe_stack_resources(self, req):
         """
@@ -347,6 +533,27 @@ class StackController(object):
         `LogicalResourceId`: filter the resources list by the logical resource
         id.
         """
+
+        def format_stack_resource(r):
+            """
+            Reformat engine output into the AWS "StackResource" format
+            """
+            keymap = {
+                engine_api.RES_DESCRIPTION: 'Description',
+                engine_api.RES_NAME: 'LogicalResourceId',
+                engine_api.RES_PHYSICAL_ID: 'PhysicalResourceId',
+                engine_api.RES_STATUS: 'ResourceStatus',
+                engine_api.RES_STATUS_DATA: 'ResourceStatusReason',
+                engine_api.RES_TYPE: 'ResourceType',
+                engine_api.RES_STACK_ID: 'StackId',
+                engine_api.RES_STACK_NAME: 'StackName',
+                engine_api.RES_UPDATED_TIME: 'Timestamp',
+            }
+
+            result = self._reformat_dict_keys(keymap, r)
+
+            return self._stackid_addprefix(result)
+
         con = req.context
         stack_name = req.params.get('StackName')
         physical_resource_id = req.params.get('PhysicalResourceId')
@@ -368,9 +575,7 @@ class StackController(object):
         except rpc_common.RemoteError as ex:
             return self._remote_error(ex)
 
-        result = []
-        for r in resources:
-            result.append(self._stackid_addprefix(r))
+        result = [format_stack_resource(r) for r in resources]
 
         return self._format_response('DescribeStackResources',
             {'StackResources': result})
@@ -380,6 +585,21 @@ class StackController(object):
         Implements the ListStackResources API action
         Return summary of the resources belonging to the specified stack.
         """
+        def format_resource_summary(r):
+            """
+            Reformat engine output into the AWS "StackResourceSummary" format
+            """
+            keymap = {
+                engine_api.RES_UPDATED_TIME: 'LastUpdatedTimestamp',
+                engine_api.RES_NAME: 'LogicalResourceId',
+                engine_api.RES_PHYSICAL_ID: 'PhysicalResourceId',
+                engine_api.RES_STATUS: 'ResourceStatus',
+                engine_api.RES_STATUS_DATA: 'ResourceStatusReason',
+                engine_api.RES_TYPE: 'ResourceType',
+            }
+
+            return self._reformat_dict_keys(keymap, r)
+
         con = req.context
 
         try:
@@ -390,8 +610,10 @@ class StackController(object):
         except rpc_common.RemoteError as ex:
             return self._remote_error(ex)
 
+        summaries = [format_resource_summary(r) for r in resources]
+
         return self._format_response('ListStackResources',
-            {'StackResourceSummaries': resources})
+            {'StackResourceSummaries': summaries})
 
 
 def create_resource(options):
index a87a3fb805bcbd2d13b0b23ad96ac46d37ef1072..447363eadcd4283f3d35ab275f6f8845297a0b64 100644 (file)
@@ -20,48 +20,14 @@ from heat.openstack.common import log as logging
 
 logger = logging.getLogger('heat.engine.manager')
 
-PARAM_KEYS = (
-    PARAM_TIMEOUT,
-    PARAM_USER_KEY_re,
-    PARAM_USER_VALUE_fmt,
-) = (
-    'TimeoutInMinutes',
-    re.compile(r'Parameters\.member\.(.*?)\.ParameterKey$'),
-    'Parameters.member.%s.ParameterValue',
-)
-
-
-def extract_user_params(params):
-    '''
-    Extract a dictionary of user parameters (to e.g. a stack create command)
-    from the parameter dictionary passed through the API.
-
-    In the API parameters, each user parameter appears as two key-value pairs
-    with keys of the form:
-
-        Parameters.member.1.ParameterKey
-        Parameters.member.1.ParameterValue
-    '''
-    def get_param_pairs():
-        for k in params:
-            keymatch = PARAM_USER_KEY_re.match(k)
-            if keymatch:
-                key = params[k]
-                v = PARAM_USER_VALUE_fmt % keymatch.group(1)
-                try:
-                    value = params[v]
-                except KeyError:
-                    logger.error('Could not apply parameter %s' % key)
-
-                yield (key, value)
-
-    return dict(get_param_pairs())
+PARAM_KEYS = (PARAM_TIMEOUT, ) = ('timeout_mins', )
 
 
 def extract_args(params):
     '''
     Extract any arguments passed as parameters through the API and return them
-    as a dictionary.
+    as a dictionary. This allows us to filter the passed args and do type
+    conversion where appropriate
     '''
     kwargs = {}
     try:
@@ -70,63 +36,34 @@ def extract_args(params):
         logger.exception('create timeout conversion')
     else:
         if timeout_mins > 0:
-            kwargs['timeout_mins'] = timeout_mins
+            kwargs[PARAM_TIMEOUT] = timeout_mins
     return kwargs
 
 
-def _filter_keys(data, keys):
-    '''
-    Filter the provided data so that only the dictionary keys specified are
-    present. If keys is None, return all of the data.
-    '''
-    if keys is not None:
-        data = dict((k, v) for (k, v) in data.iteritems() if k in keys)
-
-    return data
-
-
 STACK_KEYS = (
     STACK_NAME, STACK_ID,
     STACK_CREATION_TIME, STACK_UPDATED_TIME, STACK_DELETION_TIME,
     STACK_NOTIFICATION_TOPICS,
     STACK_DESCRIPTION, STACK_TMPL_DESCRIPTION,
     STACK_PARAMETERS, STACK_OUTPUTS,
-    STACK_STATUS, STACK_STATUS_DATA,
-    STACK_TIMEOUT,
+    STACK_STATUS, STACK_STATUS_DATA, STACK_CAPABILITIES,
+    STACK_DISABLE_ROLLBACK, STACK_TIMEOUT,
 ) = (
-    'StackName', 'StackId',
-    'CreationTime', 'LastUpdatedTime', 'DeletionTime',
-    'NotificationARNs',
-    'Description', 'TemplateDescription',
-    'Parameters', 'Outputs',
-    'StackStatus', 'StackStatusReason',
-    PARAM_TIMEOUT,
+    'stack_name', 'stack_id',
+    'creation_time', 'updated_time', 'deletion_time',
+    'notification_topics',
+    'description', 'template_description',
+    'parameters', 'outputs',
+    'stack_status', 'stack_status_reason', 'capabilities',
+    'disable_rollback', 'timeout_mins'
 )
 
-KEYS_STACK = (
-    STACK_NAME, STACK_ID,
-    STACK_CREATION_TIME, STACK_UPDATED_TIME,
-    STACK_NOTIFICATION_TOPICS,
-    STACK_DESCRIPTION,
-    STACK_PARAMETERS, STACK_DESCRIPTION, STACK_OUTPUTS,
-    STACK_STATUS, STACK_STATUS_DATA,
-    STACK_TIMEOUT,
-)
-KEYS_STACK_SUMMARY = (
-    STACK_CREATION_TIME, STACK_DELETION_TIME,
-    STACK_UPDATED_TIME,
-    STACK_ID, STACK_NAME,
-    STACK_TMPL_DESCRIPTION,
-    STACK_STATUS, STACK_STATUS_DATA,
-)
-
-
 STACK_OUTPUT_KEYS = (
     OUTPUT_DESCRIPTION,
     OUTPUT_KEY, OUTPUT_VALUE,
 ) = (
-    'Description',
-    'OutputKey', 'OutputValue',
+    'description',
+    'output_key', 'output_value',
 )
 
 
@@ -144,7 +81,7 @@ def format_stack_outputs(stack, outputs):
     return [format_stack_output(key) for key in outputs]
 
 
-def format_stack(stack, keys=None):
+def format_stack(stack):
     '''
     Return a representation of the given stack that matches the API output
     expectations.
@@ -160,6 +97,8 @@ def format_stack(stack, keys=None):
         STACK_TMPL_DESCRIPTION: stack.t[parser.DESCRIPTION],
         STACK_STATUS: stack.state,
         STACK_STATUS_DATA: stack.state_description,
+        STACK_CAPABILITIES: [],   # TODO Not implemented yet
+        STACK_DISABLE_ROLLBACK: True,   # TODO Not implemented yet
         STACK_TIMEOUT: stack.timeout_mins,
     }
 
@@ -167,7 +106,7 @@ def format_stack(stack, keys=None):
     if stack.state == stack.CREATE_COMPLETE:
         info[STACK_OUTPUTS] = format_stack_outputs(stack, stack.outputs)
 
-    return _filter_keys(info, keys)
+    return info
 
 
 RES_KEYS = (
@@ -175,42 +114,21 @@ RES_KEYS = (
     RES_NAME, RES_PHYSICAL_ID, RES_METADATA,
     RES_STATUS, RES_STATUS_DATA, RES_TYPE,
     RES_STACK_ID, RES_STACK_NAME,
-    RES_TIMESTAMP,
 ) = (
-    'Description', 'LastUpdatedTimestamp',
-    'LogicalResourceId', 'PhysicalResourceId', 'Metadata',
-    'ResourceStatus', 'ResourceStatusReason', 'ResourceType',
+    'description', 'updated_time',
+    'logical_resource_id', 'physical_resource_id', 'metadata',
+    'resource_status', 'resource_status_reason', 'resource_type',
     STACK_ID, STACK_NAME,
-    'Timestamp',
-)
-
-KEYS_RESOURCE_DETAIL = (
-    RES_DESCRIPTION, RES_UPDATED_TIME,
-    RES_NAME, RES_PHYSICAL_ID, RES_METADATA,
-    RES_STATUS, RES_STATUS_DATA, RES_TYPE,
-    RES_STACK_ID, RES_STACK_NAME,
-)
-KEYS_RESOURCE = (
-    RES_DESCRIPTION,
-    RES_NAME, RES_PHYSICAL_ID,
-    RES_STATUS, RES_STATUS_DATA, RES_TYPE,
-    RES_STACK_ID, RES_STACK_NAME,
-    RES_TIMESTAMP,
-)
-KEYS_RESOURCE_SUMMARY = (
-    RES_UPDATED_TIME,
-    RES_NAME, RES_PHYSICAL_ID,
-    RES_STATUS, RES_STATUS_DATA, RES_TYPE,
 )
 
 
-def format_stack_resource(resource, keys=None):
+def format_stack_resource(resource):
     '''
     Return a representation of the given resource that matches the API output
     expectations.
     '''
     last_updated_time = resource.updated_time or resource.created_time
-    attrs = {
+    res = {
         RES_DESCRIPTION: resource.parsed_template().get('Description', ''),
         RES_UPDATED_TIME: heat_utils.strtime(last_updated_time),
         RES_NAME: resource.name,
@@ -221,10 +139,9 @@ def format_stack_resource(resource, keys=None):
         RES_TYPE: resource.t['Type'],
         RES_STACK_ID: resource.stack.id,
         RES_STACK_NAME: resource.stack.name,
-        RES_TIMESTAMP: heat_utils.strtime(last_updated_time),
     }
 
-    return _filter_keys(attrs, keys)
+    return res
 
 
 EVENT_KEYS = (
@@ -235,18 +152,18 @@ EVENT_KEYS = (
     EVENT_RES_STATUS, EVENT_RES_STATUS_DATA, EVENT_RES_TYPE,
     EVENT_RES_PROPERTIES,
 ) = (
-    'EventId',
+    'event_id',
     STACK_ID, STACK_NAME,
-    RES_TIMESTAMP,
+    "event_time",
     RES_NAME, RES_PHYSICAL_ID,
     RES_STATUS, RES_STATUS_DATA, RES_TYPE,
-    'ResourceProperties',
+    'resource_properties',
 )
 
 
-def format_event(event, keys=None):
+def format_event(event):
     s = event.stack
-    attrs = {
+    event = {
         EVENT_ID: event.id,
         EVENT_STACK_ID: s.id,
         EVENT_STACK_NAME: s.name,
@@ -259,4 +176,4 @@ def format_event(event, keys=None):
         EVENT_RES_PROPERTIES: event.resource_properties,
     }
 
-    return _filter_keys(attrs, keys)
+    return event
index d0eb2b5cd63bed39e843aa1b59365f773acdf7ca..dcc3bca94538cef20a449d7bc11a1958012d31ce 100644 (file)
@@ -60,26 +60,6 @@ class EngineManager(manager.Manager):
         """Load configuration options and connect to the hypervisor."""
         pass
 
-    def list_stacks(self, context, params):
-        """
-        The list_stacks method is the end point that actually implements
-        the 'list' command of the heat API.
-        arg1 -> RPC context.
-        arg2 -> Dict of http request parameters passed in from API side.
-        """
-
-        auth.authenticate(context)
-
-        stacks = db_api.stack_get_by_user(context)
-        if stacks is None:
-            stacks = []
-
-        def format_stack_summary(s):
-            stack = parser.Stack.load(context, s.id)
-            return api.format_stack(stack, api.KEYS_STACK_SUMMARY)
-
-        return {'stacks': [format_stack_summary(s) for s in stacks]}
-
     def show_stack(self, context, stack_name, params):
         """
         The show_stack method returns the attributes of one stack.
@@ -100,11 +80,11 @@ class EngineManager(manager.Manager):
 
         def format_stack_detail(s):
             stack = parser.Stack.load(context, s.id)
-            return api.format_stack(stack, api.KEYS_STACK)
+            return api.format_stack(stack)
 
         return {'stacks': [format_stack_detail(s) for s in stacks]}
 
-    def create_stack(self, context, stack_name, template, params):
+    def create_stack(self, context, stack_name, template, params, args):
         """
         The create_stack method creates a new stack using the template
         provided.
@@ -113,7 +93,8 @@ class EngineManager(manager.Manager):
         arg1 -> RPC context.
         arg2 -> Name of the stack you want to create.
         arg3 -> Template of stack you want to create.
-        arg4 -> Params passed from API.
+        arg4 -> Stack Input Params
+        arg4 -> Request parameters/args passed from API
         """
         logger.info('template is %s' % template)
 
@@ -123,10 +104,11 @@ class EngineManager(manager.Manager):
             return {'Error': 'Stack already exists with that name.'}
 
         tmpl = parser.Template(template)
+
         # Extract the template parameters, and any common query parameters
-        template_params = parser.Parameters(stack_name, tmpl,
-                                            api.extract_user_params(params))
-        common_params = api.extract_args(params)
+        template_params = parser.Parameters(stack_name, tmpl, params)
+        common_params = api.extract_args(args)
+
         stack = parser.Stack(context, stack_name, tmpl, template_params,
                              **common_params)
 
@@ -159,8 +141,7 @@ class EngineManager(manager.Manager):
         stack_name = 'validate'
         try:
             tmpl = parser.Template(template)
-            user_params = parser.Parameters(stack_name, tmpl,
-                                            api.extract_user_params(params))
+            user_params = parser.Parameters(stack_name, tmpl, params)
             s = parser.Stack(context, stack_name, tmpl, user_params)
         except KeyError as ex:
             res = ('A Fn::FindInMap operation referenced '
@@ -272,8 +253,7 @@ class EngineManager(manager.Manager):
         if resource.id is None:
             raise AttributeError('Resource not created')
 
-        return api.format_stack_resource(stack[resource_name],
-                                         api.KEYS_RESOURCE_DETAIL)
+        return api.format_stack_resource(stack[resource_name])
 
     def describe_stack_resources(self, context, stack_name,
                                  physical_resource_id, logical_resource_id):
@@ -299,7 +279,7 @@ class EngineManager(manager.Manager):
         else:
             name_match = lambda r: True
 
-        return [api.format_stack_resource(resource, api.KEYS_RESOURCE)
+        return [api.format_stack_resource(resource)
                 for resource in stack if resource.id is not None and
                                          name_match(resource)]
 
@@ -312,7 +292,7 @@ class EngineManager(manager.Manager):
 
         stack = parser.Stack.load(context, s.id)
 
-        return [api.format_stack_resource(resource, api.KEYS_RESOURCE_SUMMARY)
+        return [api.format_stack_resource(resource)
                 for resource in stack if resource.id is not None]
 
     def metadata_register_address(self, context, url):
diff --git a/heat/tests/test_api_v1.py b/heat/tests/test_api_v1.py
new file mode 100644 (file)
index 0000000..03ac9ab
--- /dev/null
@@ -0,0 +1,91 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+
+import nose
+import unittest
+from nose.plugins.attrib import attr
+
+from heat.common.config import HeatConfigOpts
+import heat.api.v1.stacks as stacks
+
+
+@attr(tag=['unit', 'api-v1-stacks', 'StackController'])
+@attr(speed='fast')
+class StackControllerTest(unittest.TestCase):
+    '''
+    Tests the API class which acts as the WSGI controller,
+    the endpoint processing API requests after they are routed
+    '''
+    def test_params_extract(self):
+        p = {'Parameters.member.Foo.ParameterKey': 'foo',
+             'Parameters.member.Foo.ParameterValue': 'bar',
+             'Parameters.member.Blarg.ParameterKey': 'blarg',
+             'Parameters.member.Blarg.ParameterValue': 'wibble'}
+        params = self.controller._extract_user_params(p)
+        self.assertEqual(len(params), 2)
+        self.assertTrue('foo' in params)
+        self.assertEqual(params['foo'], 'bar')
+        self.assertTrue('blarg' in params)
+        self.assertEqual(params['blarg'], 'wibble')
+
+    def test_params_extract_dots(self):
+        p = {'Parameters.member.Foo.Bar.ParameterKey': 'foo',
+             'Parameters.member.Foo.Bar.ParameterValue': 'bar',
+             'Parameters.member.Foo.Baz.ParameterKey': 'blarg',
+             'Parameters.member.Foo.Baz.ParameterValue': 'wibble'}
+        params = self.controller._extract_user_params(p)
+        self.assertEqual(len(params), 2)
+        self.assertTrue('foo' in params)
+        self.assertEqual(params['foo'], 'bar')
+        self.assertTrue('blarg' in params)
+        self.assertEqual(params['blarg'], 'wibble')
+
+    def test_params_extract_garbage(self):
+        p = {'Parameters.member.Foo.Bar.ParameterKey': 'foo',
+             'Parameters.member.Foo.Bar.ParameterValue': 'bar',
+             'Foo.Baz.ParameterKey': 'blarg',
+             'Foo.Baz.ParameterValue': 'wibble'}
+        params = self.controller._extract_user_params(p)
+        self.assertEqual(len(params), 1)
+        self.assertTrue('foo' in params)
+        self.assertEqual(params['foo'], 'bar')
+
+    def test_params_extract_garbage_prefix(self):
+        p = {'prefixParameters.member.Foo.Bar.ParameterKey': 'foo',
+             'Parameters.member.Foo.Bar.ParameterValue': 'bar'}
+        params = self.controller._extract_user_params(p)
+        self.assertFalse(params)
+
+    def test_params_extract_garbage_suffix(self):
+        p = {'Parameters.member.Foo.Bar.ParameterKeysuffix': 'foo',
+             'Parameters.member.Foo.Bar.ParameterValue': 'bar'}
+        params = self.controller._extract_user_params(p)
+        self.assertFalse(params)
+
+    # TODO : lots more StackController tests..
+
+    def setUp(self):
+        # Create WSGI controller instance
+        options = HeatConfigOpts()
+        self.controller = stacks.StackController(options)
+        print "setup complete"
+
+    def tearDown(self):
+        print "teardown complete"
+
+
+if __name__ == '__main__':
+    sys.argv.append(__file__)
+    nose.main()
index 9dc589cbf80d11311bc4c0a3a6dfb3e24028718f..8650a6bc395bb22a7e8299d41f545d34bda7e0a4 100644 (file)
@@ -23,69 +23,23 @@ import heat.engine.api as api
 @attr(tag=['unit', 'engine-api'])
 @attr(speed='fast')
 class EngineApiTest(unittest.TestCase):
-    def test_params_extract(self):
-        p = {'Parameters.member.Foo.ParameterKey': 'foo',
-             'Parameters.member.Foo.ParameterValue': 'bar',
-             'Parameters.member.Blarg.ParameterKey': 'blarg',
-             'Parameters.member.Blarg.ParameterValue': 'wibble'}
-        params = api.extract_user_params(p)
-        self.assertEqual(len(params), 2)
-        self.assertTrue('foo' in params)
-        self.assertEqual(params['foo'], 'bar')
-        self.assertTrue('blarg' in params)
-        self.assertEqual(params['blarg'], 'wibble')
-
-    def test_params_extract_dots(self):
-        p = {'Parameters.member.Foo.Bar.ParameterKey': 'foo',
-             'Parameters.member.Foo.Bar.ParameterValue': 'bar',
-             'Parameters.member.Foo.Baz.ParameterKey': 'blarg',
-             'Parameters.member.Foo.Baz.ParameterValue': 'wibble'}
-        params = api.extract_user_params(p)
-        self.assertEqual(len(params), 2)
-        self.assertTrue('foo' in params)
-        self.assertEqual(params['foo'], 'bar')
-        self.assertTrue('blarg' in params)
-        self.assertEqual(params['blarg'], 'wibble')
-
-    def test_params_extract_garbage(self):
-        p = {'Parameters.member.Foo.Bar.ParameterKey': 'foo',
-             'Parameters.member.Foo.Bar.ParameterValue': 'bar',
-             'Foo.Baz.ParameterKey': 'blarg',
-             'Foo.Baz.ParameterValue': 'wibble'}
-        params = api.extract_user_params(p)
-        self.assertEqual(len(params), 1)
-        self.assertTrue('foo' in params)
-        self.assertEqual(params['foo'], 'bar')
-
-    def test_params_extract_garbage_prefix(self):
-        p = {'prefixParameters.member.Foo.Bar.ParameterKey': 'foo',
-             'Parameters.member.Foo.Bar.ParameterValue': 'bar'}
-        params = api.extract_user_params(p)
-        self.assertFalse(params)
-
-    def test_params_extract_garbage_suffix(self):
-        p = {'Parameters.member.Foo.Bar.ParameterKeysuffix': 'foo',
-             'Parameters.member.Foo.Bar.ParameterValue': 'bar'}
-        params = api.extract_user_params(p)
-        self.assertFalse(params)
-
     def test_timeout_extract(self):
-        p = {'TimeoutInMinutes': '5'}
+        p = {'timeout_mins': '5'}
         args = api.extract_args(p)
         self.assertEqual(args['timeout_mins'], 5)
 
     def test_timeout_extract_zero(self):
-        p = {'TimeoutInMinutes': '0'}
+        p = {'timeout_mins': '0'}
         args = api.extract_args(p)
         self.assertTrue('timeout_mins' not in args)
 
     def test_timeout_extract_garbage(self):
-        p = {'TimeoutInMinutes': 'wibble'}
+        p = {'timeout_mins': 'wibble'}
         args = api.extract_args(p)
         self.assertTrue('timeout_mins' not in args)
 
     def test_timeout_extract_none(self):
-        p = {'TimeoutInMinutes': None}
+        p = {'timeout_mins': None}
         args = api.extract_args(p)
         self.assertTrue('timeout_mins' not in args)
 
index 40cbfb24ebf19441f83b01d030c1462ab67d0a54..ec813cb73739dc7e5ae76daae6ed0278743c8ea5 100644 (file)
@@ -172,63 +172,48 @@ class stackManagerTest(unittest.TestCase):
 
         self.assertEqual(len(events), 2)
         for ev in events:
-            self.assertTrue('EventId' in ev)
-            self.assertTrue(ev['EventId'] > 0)
+            self.assertTrue('event_id' in ev)
+            self.assertTrue(ev['event_id'] > 0)
 
-            self.assertTrue('LogicalResourceId' in ev)
-            self.assertEqual(ev['LogicalResourceId'], 'WebServer')
+            self.assertTrue('logical_resource_id' in ev)
+            self.assertEqual(ev['logical_resource_id'], 'WebServer')
 
-            self.assertTrue('PhysicalResourceId' in ev)
+            self.assertTrue('physical_resource_id' in ev)
 
-            self.assertTrue('ResourceProperties' in ev)
+            self.assertTrue('resource_properties' in ev)
             # Big long user data field.. it mentions 'wordpress'
             # a few times so this should work.
-            user_data = ev['ResourceProperties']['UserData']
+            user_data = ev['resource_properties']['UserData']
             self.assertNotEqual(user_data.find('wordpress'), -1)
-            self.assertEqual(ev['ResourceProperties']['ImageId'],
+            self.assertEqual(ev['resource_properties']['ImageId'],
                              'F16-x86_64-gold')
-            self.assertEqual(ev['ResourceProperties']['InstanceType'],
+            self.assertEqual(ev['resource_properties']['InstanceType'],
                              'm1.large')
 
-            self.assertTrue('ResourceStatus' in ev)
-            self.assertTrue(ev['ResourceStatus'] in ('IN_PROGRESS',
+            self.assertTrue('resource_status' in ev)
+            self.assertTrue(ev['resource_status'] in ('IN_PROGRESS',
                                                      'CREATE_COMPLETE'))
 
-            self.assertTrue('ResourceStatusReason' in ev)
-            self.assertEqual(ev['ResourceStatusReason'], 'state changed')
+            self.assertTrue('resource_status_reason' in ev)
+            self.assertEqual(ev['resource_status_reason'], 'state changed')
 
-            self.assertTrue('ResourceType' in ev)
-            self.assertEqual(ev['ResourceType'], 'AWS::EC2::Instance')
+            self.assertTrue('resource_type' in ev)
+            self.assertEqual(ev['resource_type'], 'AWS::EC2::Instance')
 
-            self.assertTrue('StackId' in ev)
+            self.assertTrue('stack_id' in ev)
 
-            self.assertTrue('StackName' in ev)
-            self.assertEqual(ev['StackName'], self.stack_name)
+            self.assertTrue('stack_name' in ev)
+            self.assertEqual(ev['stack_name'], self.stack_name)
 
-            self.assertTrue('Timestamp' in ev)
-
-    def test_stack_list(self):
-        sl = self.man.list_stacks(self.ctx, {})
-
-        self.assertTrue(len(sl['stacks']) > 0)
-        for s in sl['stacks']:
-            self.assertTrue('CreationTime' in s)
-            self.assertTrue('LastUpdatedTime' in s)
-            self.assertTrue('StackId' in s)
-            self.assertNotEqual(s['StackId'], None)
-            self.assertTrue('StackName' in s)
-            self.assertTrue('StackStatus' in s)
-            self.assertTrue('StackStatusReason' in s)
-            self.assertTrue('TemplateDescription' in s)
-            self.assertNotEqual(s['TemplateDescription'].find('WordPress'), -1)
+            self.assertTrue('event_time' in ev)
 
     def test_stack_describe_all(self):
         sl = self.man.show_stack(self.ctx, None, {})
 
         self.assertEqual(len(sl['stacks']), 1)
         for s in sl['stacks']:
-            self.assertNotEqual(s['StackId'], None)
-            self.assertNotEqual(s['Description'].find('WordPress'), -1)
+            self.assertNotEqual(s['stack_id'], None)
+            self.assertNotEqual(s['description'].find('WordPress'), -1)
 
     def test_stack_describe_all_empty(self):
         self.tearDown()
@@ -250,35 +235,35 @@ class stackManagerTest(unittest.TestCase):
         self.assertEqual(len(sl['stacks']), 1)
 
         s = sl['stacks'][0]
-        self.assertTrue('CreationTime' in s)
-        self.assertTrue('LastUpdatedTime' in s)
-        self.assertTrue('StackId' in s)
-        self.assertNotEqual(s['StackId'], None)
-        self.assertTrue('StackName' in s)
-        self.assertEqual(s['StackName'], self.stack_name)
-        self.assertTrue('StackStatus' in s)
-        self.assertTrue('StackStatusReason' in s)
-        self.assertTrue('Description' in s)
-        self.assertNotEqual(s['Description'].find('WordPress'), -1)
-        self.assertTrue('Parameters' in s)
+        self.assertTrue('creation_time' in s)
+        self.assertTrue('updated_time' in s)
+        self.assertTrue('stack_id' in s)
+        self.assertNotEqual(s['stack_id'], None)
+        self.assertTrue('stack_name' in s)
+        self.assertEqual(s['stack_name'], self.stack_name)
+        self.assertTrue('stack_status' in s)
+        self.assertTrue('stack_status_reason' in s)
+        self.assertTrue('description' in s)
+        self.assertNotEqual(s['description'].find('WordPress'), -1)
+        self.assertTrue('parameters' in s)
 
     def test_stack_resource_describe(self):
         r = self.man.describe_stack_resource(self.ctx, self.stack_name,
                                              'WebServer')
 
-        self.assertTrue('Description' in r)
-        self.assertTrue('LastUpdatedTimestamp' in r)
-        self.assertTrue('StackId' in r)
-        self.assertNotEqual(r['StackId'], None)
-        self.assertTrue('StackName' in r)
-        self.assertEqual(r['StackName'], self.stack_name)
-        self.assertTrue('Metadata' in r)
-        self.assertTrue('ResourceStatus' in r)
-        self.assertTrue('ResourceStatusReason' in r)
-        self.assertTrue('ResourceType' in r)
-        self.assertTrue('PhysicalResourceId' in r)
-        self.assertTrue('LogicalResourceId' in r)
-        self.assertEqual(r['LogicalResourceId'], 'WebServer')
+        self.assertTrue('description' in r)
+        self.assertTrue('updated_time' in r)
+        self.assertTrue('stack_id' in r)
+        self.assertNotEqual(r['stack_id'], None)
+        self.assertTrue('stack_name' in r)
+        self.assertEqual(r['stack_name'], self.stack_name)
+        self.assertTrue('metadata' in r)
+        self.assertTrue('resource_status' in r)
+        self.assertTrue('resource_status_reason' in r)
+        self.assertTrue('resource_type' in r)
+        self.assertTrue('physical_resource_id' in r)
+        self.assertTrue('logical_resource_id' in r)
+        self.assertEqual(r['logical_resource_id'], 'WebServer')
 
     def test_stack_resource_describe_nonexist_stack(self):
         self.assertRaises(AttributeError,
@@ -297,18 +282,18 @@ class stackManagerTest(unittest.TestCase):
 
         self.assertEqual(len(resources), 1)
         r = resources[0]
-        self.assertTrue('Description' in r)
-        self.assertTrue('Timestamp' in r)
-        self.assertTrue('StackId' in r)
-        self.assertNotEqual(r['StackId'], None)
-        self.assertTrue('StackName' in r)
-        self.assertEqual(r['StackName'], self.stack_name)
-        self.assertTrue('ResourceStatus' in r)
-        self.assertTrue('ResourceStatusReason' in r)
-        self.assertTrue('ResourceType' in r)
-        self.assertTrue('PhysicalResourceId' in r)
-        self.assertTrue('LogicalResourceId' in r)
-        self.assertEqual(r['LogicalResourceId'], 'WebServer')
+        self.assertTrue('description' in r)
+        self.assertTrue('updated_time' in r)
+        self.assertTrue('stack_id' in r)
+        self.assertNotEqual(r['stack_id'], None)
+        self.assertTrue('stack_name' in r)
+        self.assertEqual(r['stack_name'], self.stack_name)
+        self.assertTrue('resource_status' in r)
+        self.assertTrue('resource_status_reason' in r)
+        self.assertTrue('resource_type' in r)
+        self.assertTrue('physical_resource_id' in r)
+        self.assertTrue('logical_resource_id' in r)
+        self.assertEqual(r['logical_resource_id'], 'WebServer')
 
     def test_stack_resources_describe_no_filter(self):
         resources = self.man.describe_stack_resources(self.ctx,
@@ -317,8 +302,8 @@ class stackManagerTest(unittest.TestCase):
 
         self.assertEqual(len(resources), 1)
         r = resources[0]
-        self.assertTrue('LogicalResourceId' in r)
-        self.assertEqual(r['LogicalResourceId'], 'WebServer')
+        self.assertTrue('logical_resource_id' in r)
+        self.assertEqual(r['logical_resource_id'], 'WebServer')
 
     def test_stack_resources_describe_bad_lookup(self):
         self.assertRaises(AttributeError,
@@ -340,13 +325,13 @@ class stackManagerTest(unittest.TestCase):
 
         self.assertEqual(len(resources), 1)
         r = resources[0]
-        self.assertTrue('LastUpdatedTimestamp' in r)
-        self.assertTrue('PhysicalResourceId' in r)
-        self.assertTrue('LogicalResourceId' in r)
-        self.assertEqual(r['LogicalResourceId'], 'WebServer')
-        self.assertTrue('ResourceStatus' in r)
-        self.assertTrue('ResourceStatusReason' in r)
-        self.assertTrue('ResourceType' in r)
+        self.assertTrue('updated_time' in r)
+        self.assertTrue('physical_resource_id' in r)
+        self.assertTrue('logical_resource_id' in r)
+        self.assertEqual(r['logical_resource_id'], 'WebServer')
+        self.assertTrue('resource_status' in r)
+        self.assertTrue('resource_status_reason' in r)
+        self.assertTrue('resource_type' in r)
 
     def test_stack_resources_list_nonexist_stack(self):
         self.assertRaises(AttributeError,