]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
Support lookup of stacks by name or ARN
authorZane Bitter <zbitter@redhat.com>
Wed, 5 Sep 2012 19:51:25 +0000 (21:51 +0200)
committerZane Bitter <zbitter@redhat.com>
Wed, 5 Sep 2012 19:53:24 +0000 (21:53 +0200)
Previously stacks could only be referenced by name. Now, use the canonical
stack identifier to act on them. The identifier can be obtained from the
RPC API by looking it up with identify_stack. In the AWS API, the user
can pass an ARN that is converted into an identifier.

Change-Id: I29309d12e522ed301c3f6269df5d1d14382b024b
Signed-off-by: Zane Bitter <zbitter@redhat.com>
heat/api/v1/stacks.py
heat/engine/manager.py
heat/engine/rpcapi.py
heat/tests/test_api_v1.py
heat/tests/test_engine_manager.py

index f692d742952a67dd440048d28c914d3aa1cb9252..e259df7eb7099222f52e2d277517d247b780e760 100644 (file)
@@ -77,6 +77,18 @@ class StackController(object):
                                             keyname='ParameterKey',
                                             valuename='ParameterValue')
 
+    def _get_identity(self, con, stack_name):
+        """
+        Generate a stack identifier from the given stack name or ARN.
+
+        In the case of a stack name, the identifier will be looked up in the
+        engine over RPC.
+        """
+        try:
+            return dict(identifier.HeatIdentifier.from_arn(stack_name))
+        except ValueError:
+            return self.engine_rpcapi.identify_stack(con, stack_name)
+
     def list(self, req):
         """
         Implements ListStacks API action
@@ -115,7 +127,7 @@ class StackController(object):
             # 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 = self.engine_rpcapi.show_stack(con,
-                                                       stack_name=None,
+                                                       stack_identity=None,
                                                        params=parms)
         except rpc_common.RemoteError as ex:
             return exception.map_remote_error(ex)
@@ -183,13 +195,14 @@ class StackController(object):
         # If no StackName parameter is passed, we pass None into the engine
         # this returns results for all stacks (visible to this user), which
         # is the behavior described in the AWS DescribeStacks API docs
-        stack_name = None
-        if 'StackName' in req.params:
-            stack_name = req.params['StackName']
-
         try:
+            if 'StackName' in req.params:
+                identity = self._get_identity(con, req.params['StackName'])
+            else:
+                identity = None
+
             stack_list = self.engine_rpcapi.show_stack(con,
-                                                       stack_name=stack_name,
+                                                       stack_identity=identity,
                                                        params=parms)
 
         except rpc_common.RemoteError as ex:
@@ -289,12 +302,17 @@ class StackController(object):
             msg = _("The Template must be a JSON document.")
             return exception.HeatInvalidParameterValueError(detail=msg)
 
+        args = {'template': stack,
+                'params': stack_parms,
+                'args': create_args}
         try:
-            res = engine_action[action](con,
-                                        stack_name=req.params['StackName'],
-                                        template=stack,
-                                        params=stack_parms,
-                                        args=create_args)
+            stack_name = req.params['StackName']
+            if action == self.CREATE_STACK:
+                args['stack_name'] = stack_name
+            else:
+                args['stack_identity'] = self._get_identity(con, stack_name)
+
+            res = engine_action[action](con, **args)
         except rpc_common.RemoteError as ex:
             return exception.map_remote_error(ex)
 
@@ -312,8 +330,9 @@ class StackController(object):
 
         logger.info('get_template')
         try:
+            identity = self._get_identity(con, req.params['StackName'])
             templ = self.engine_rpcapi.get_template(con,
-                                       stack_name=req.params['StackName'],
+                                       stack_identity=identity,
                                        params=parms)
         except rpc_common.RemoteError as ex:
             return exception.map_remote_error(ex)
@@ -374,8 +393,9 @@ class StackController(object):
         parms = dict(req.params)
 
         try:
+            identity = self._get_identity(con, req.params['StackName'])
             res = self.engine_rpcapi.delete_stack(con,
-                                     stack_name=req.params['StackName'],
+                                     stack_identity=identity,
                                      params=parms,
                                      cast=False)
 
@@ -418,8 +438,9 @@ class StackController(object):
 
         stack_name = req.params.get('StackName', None)
         try:
+            identity = stack_name and self._get_identity(con, stack_name)
             event_res = self.engine_rpcapi.list_events(con,
-                                                       stack_name=stack_name,
+                                                       stack_identity=identity,
                                                        params=parms)
         except rpc_common.RemoteError as ex:
             return exception.map_remote_error(ex)
@@ -461,8 +482,9 @@ class StackController(object):
         con = req.context
 
         try:
+            identity = self._get_identity(con, req.params['StackName'])
             resource_details = self.engine_rpcapi.describe_stack_resource(con,
-                        stack_name=req.params.get('StackName'),
+                        stack_identity=identity,
                         resource_name=req.params.get('LogicalResourceId'))
 
         except rpc_common.RemoteError as ex:
@@ -518,8 +540,9 @@ class StackController(object):
             return exception.HeatInvalidParameterCombinationError(detail=msg)
 
         try:
+            identity = self._get_identity(con, stack_name)
             resources = self.engine_rpcapi.describe_stack_resources(con,
-                stack_name=stack_name,
+                stack_identity=identity,
                 physical_resource_id=physical_resource_id,
                 logical_resource_id=req.params.get('LogicalResourceId'))
 
@@ -554,8 +577,9 @@ class StackController(object):
         con = req.context
 
         try:
+            identity = self._get_identity(con, req.params['StackName'])
             resources = self.engine_rpcapi.list_stack_resources(con,
-                             stack_name=req.params.get('StackName'))
+                    stack_identity=identity)
         except rpc_common.RemoteError as ex:
             return exception.map_remote_error(ex)
 
index 65a0d85ed7c80b8a2eac93ffd856b6cb368e6bbc..8816d6d8aec0cdb0ca0dc0cdc60f20c793e1437b 100644 (file)
@@ -28,6 +28,7 @@ from heat.common import config
 from heat.common import utils as heat_utils
 from heat.common import context as ctxtlib
 from heat.engine import api
+from heat.engine import identifier
 from heat.engine import parser
 from heat.engine import resources
 from heat.engine import watchrule
@@ -77,7 +78,23 @@ class EngineManager(manager.Manager):
         else:
             raise AttributeError('Unknown stack name')
 
-    def show_stack(self, context, stack_name, params):
+    def _get_stack(self, context, stack_identity):
+        identity = identifier.HeatIdentifier(**stack_identity)
+
+        if identity.tenant != context.tenant:
+            raise AttributeError('Invalid tenant')
+
+        s = db_api.stack_get(context, identity.stack_id)
+
+        if s is None:
+            raise AttributeError('Stack not found')
+
+        if identity.path or s.name != identity.stack_name:
+            raise AttributeError('Invalid stack ID')
+
+        return s
+
+    def show_stack(self, context, stack_identity, params):
         """
         The show_stack method returns the attributes of one stack.
         arg1 -> RPC context.
@@ -86,12 +103,8 @@ class EngineManager(manager.Manager):
         """
         auth.authenticate(context)
 
-        if stack_name is not None:
-            s = db_api.stack_get_by_name(context, stack_name)
-            if s:
-                stacks = [s]
-            else:
-                raise AttributeError('Unknown stack name')
+        if stack_identity is not None:
+            stacks = [self._get_stack(context, stack_identity)]
         else:
             stacks = db_api.stack_get_by_tenant(context) or []
 
@@ -138,7 +151,7 @@ class EngineManager(manager.Manager):
 
         return dict(stack.identifier())
 
-    def update_stack(self, context, stack_name, template, params, args):
+    def update_stack(self, context, stack_identity, template, params, args):
         """
         The update_stack method updates an existing stack based on the
         provided template and parameters.
@@ -155,9 +168,7 @@ class EngineManager(manager.Manager):
         auth.authenticate(context)
 
         # Get the database representation of the existing stack
-        db_stack = db_api.stack_get_by_name(None, stack_name)
-        if not db_stack:
-            raise AttributeError('No stack exists with that name')
+        db_stack = self._get_stack(context, stack_identity)
 
         current_stack = parser.Stack.load(context, db_stack.id)
 
@@ -219,7 +230,7 @@ class EngineManager(manager.Manager):
         }
         return {'ValidateTemplateResult': result}
 
-    def get_template(self, context, stack_name, params):
+    def get_template(self, context, stack_identity, params):
         """
         Get the template.
         arg1 -> RPC context.
@@ -227,12 +238,12 @@ class EngineManager(manager.Manager):
         arg3 -> Dict of http request parameters passed in from API side.
         """
         auth.authenticate(context)
-        s = db_api.stack_get_by_name(context, stack_name)
+        s = self._get_stack(context, stack_identity)
         if s:
             return s.raw_template.template
         return None
 
-    def delete_stack(self, context, stack_name, params):
+    def delete_stack(self, context, stack_identity, params):
         """
         The delete_stack method deletes a given stack.
         arg1 -> RPC context.
@@ -242,17 +253,15 @@ class EngineManager(manager.Manager):
 
         auth.authenticate(context)
 
-        st = db_api.stack_get_by_name(context, stack_name)
-        if not st:
-            raise AttributeError('Unknown stack name')
+        st = self._get_stack(context, stack_identity)
 
-        logger.info('deleting stack %s' % stack_name)
+        logger.info('deleting stack %s' % st.name)
 
         stack = parser.Stack.load(context, st.id)
         greenpool.spawn_n(stack.delete)
         return None
 
-    def list_events(self, context, stack_name, params):
+    def list_events(self, context, stack_identity, params):
         """
         The list_events method lists all events associated with a given stack.
         arg1 -> RPC context.
@@ -262,10 +271,8 @@ class EngineManager(manager.Manager):
 
         auth.authenticate(context)
 
-        if stack_name is not None:
-            st = db_api.stack_get_by_name(context, stack_name)
-            if not st:
-                raise AttributeError('Unknown stack name')
+        if stack_identity is not None:
+            st = self._get_stack(context, stack_identity)
 
             events = db_api.event_get_all_by_stack(context, st.id)
         else:
@@ -303,12 +310,10 @@ class EngineManager(manager.Manager):
             msg = 'Error creating event'
             return [msg, None]
 
-    def describe_stack_resource(self, context, stack_name, resource_name):
+    def describe_stack_resource(self, context, stack_identity, resource_name):
         auth.authenticate(context)
 
-        s = db_api.stack_get_by_name(context, stack_name)
-        if not s:
-            raise AttributeError('Unknown stack name')
+        s = self._get_stack(context, stack_identity)
 
         stack = parser.Stack.load(context, s.id)
         if resource_name not in stack:
@@ -320,12 +325,12 @@ class EngineManager(manager.Manager):
 
         return api.format_stack_resource(stack[resource_name])
 
-    def describe_stack_resources(self, context, stack_name,
+    def describe_stack_resources(self, context, stack_identity,
                                  physical_resource_id, logical_resource_id):
         auth.authenticate(context)
 
-        if stack_name is not None:
-            s = db_api.stack_get_by_name(context, stack_name)
+        if stack_identity is not None:
+            s = self._get_stack(context, stack_identity)
         else:
             rs = db_api.resource_get_by_physical_resource_id(context,
                     physical_resource_id)
@@ -348,12 +353,10 @@ class EngineManager(manager.Manager):
                 for resource in stack if resource.id is not None and
                                          name_match(resource)]
 
-    def list_stack_resources(self, context, stack_name):
+    def list_stack_resources(self, context, stack_identity):
         auth.authenticate(context)
 
-        s = db_api.stack_get_by_name(context, stack_name)
-        if not s:
-            raise AttributeError('Unknown stack name')
+        s = self._get_stack(context, stack_identity)
 
         stack = parser.Stack.load(context, s.id)
 
index b731e7b0141fefc922f44e9e4155583c4e6ab2f3..6e0b672a61c11e871e3ea9dac0c13006480a7169 100644 (file)
@@ -67,12 +67,10 @@ class EngineAPI(heat.openstack.common.rpc.proxy.RpcProxy):
                            or None to see all
         """
         return self.call(ctxt, self.make_msg('identify_stack',
-                                             stack_name=stack_name,
-                                             topic=_engine_topic(self.topic,
-                                                                 ctxt,
-                                                                 None)))
+                                             stack_name=stack_name),
+                         topic=_engine_topic(self.topic, ctxt, None))
 
-    def show_stack(self, ctxt, stack_name, params):
+    def show_stack(self, ctxt, stack_identity, params):
         """
         The show_stack method returns the attributes of one stack.
 
@@ -82,7 +80,7 @@ class EngineAPI(heat.openstack.common.rpc.proxy.RpcProxy):
         :param params: Dict of http request parameters passed in from API side.
         """
         return self.call(ctxt, self.make_msg('show_stack',
-                         stack_name=stack_name, params=params),
+                         stack_identity=stack_identity, params=params),
                          topic=_engine_topic(self.topic, ctxt, None))
 
     def create_stack(self, ctxt, stack_name, template, params, args):
@@ -103,7 +101,7 @@ class EngineAPI(heat.openstack.common.rpc.proxy.RpcProxy):
                          template=template, params=params, args=args),
                          topic=_engine_topic(self.topic, ctxt, None))
 
-    def update_stack(self, ctxt, stack_name, template, params, args):
+    def update_stack(self, ctxt, stack_identity, template, params, args):
         """
         The update_stack method updates an existing stack based on the
         provided template and parameters.
@@ -117,7 +115,7 @@ class EngineAPI(heat.openstack.common.rpc.proxy.RpcProxy):
         :param args: Request parameters/args passed from API
         """
         return self.call(ctxt, self.make_msg('update_stack',
-                         stack_name=stack_name,
+                         stack_identity=stack_identity,
                          template=template, params=params, args=args),
                          topic=_engine_topic(self.topic, ctxt, None))
 
@@ -134,7 +132,7 @@ class EngineAPI(heat.openstack.common.rpc.proxy.RpcProxy):
                          template=template, params=params),
                          topic=_engine_topic(self.topic, ctxt, None))
 
-    def get_template(self, ctxt, stack_name, params):
+    def get_template(self, ctxt, stack_identity, params):
         """
         Get the template.
 
@@ -143,10 +141,10 @@ class EngineAPI(heat.openstack.common.rpc.proxy.RpcProxy):
         :param params: Dict of http request parameters passed in from API side.
         """
         return self.call(ctxt, self.make_msg('get_template',
-                         stack_name=stack_name, params=params),
+                         stack_identity=stack_identity, params=params),
                          topic=_engine_topic(self.topic, ctxt, None))
 
-    def delete_stack(self, ctxt, stack_name, params, cast=True):
+    def delete_stack(self, ctxt, stack_identity, params, cast=True):
         """
         The delete_stack method deletes a given stack.
 
@@ -156,10 +154,10 @@ class EngineAPI(heat.openstack.common.rpc.proxy.RpcProxy):
         """
         rpc_method = self.cast if cast else self.call
         return rpc_method(ctxt, self.make_msg('delete_stack',
-                  stack_name=stack_name, params=params),
+                  stack_identity=stack_identity, params=params),
                   topic=_engine_topic(self.topic, ctxt, None))
 
-    def list_events(self, ctxt, stack_name, params):
+    def list_events(self, ctxt, stack_identity, params):
         """
         The list_events method lists all events associated with a given stack.
 
@@ -168,25 +166,26 @@ class EngineAPI(heat.openstack.common.rpc.proxy.RpcProxy):
         :param params: Params passed from API.
         """
         return self.call(ctxt, self.make_msg('list_events',
-                         stack_name=stack_name, params=params),
+                         stack_identity=stack_identity, params=params),
                          topic=_engine_topic(self.topic, ctxt, None))
 
-    def describe_stack_resource(self, ctxt, stack_name, resource_name):
+    def describe_stack_resource(self, ctxt, stack_identity, resource_name):
         return self.call(ctxt, self.make_msg('describe_stack_resource',
-                         stack_name=stack_name, resource_name=resource_name),
+                         stack_identity=stack_identity,
+                         resource_name=resource_name),
                          topic=_engine_topic(self.topic, ctxt, None))
 
-    def describe_stack_resources(self, ctxt, stack_name,
+    def describe_stack_resources(self, ctxt, stack_identity,
                                  physical_resource_id, logical_resource_id):
         return self.call(ctxt, self.make_msg('describe_stack_resources',
-                         stack_name=stack_name,
+                         stack_identity=stack_identity,
                          physical_resource_id=physical_resource_id,
                          logical_resource_id=logical_resource_id),
                          topic=_engine_topic(self.topic, ctxt, None))
 
-    def list_stack_resources(self, ctxt, stack_name):
+    def list_stack_resources(self, ctxt, stack_identity):
         return self.call(ctxt, self.make_msg('list_stack_resources',
-                         stack_name=stack_name),
+                         stack_identity=stack_identity),
                          topic=_engine_topic(self.topic, ctxt, None))
 
     def metadata_list_stacks(self, ctxt):
index a0b90e4ba9dcffd913c8cbe395b0702910780b93..0b28c1d3a08353c6593656997c50f3e0eb963ad7 100644 (file)
@@ -28,6 +28,7 @@ import urlparse
 from heat.common import config
 from heat.common import context
 from heat.engine import auth
+from heat.engine import identifier
 from heat.openstack.common import cfg
 from heat.openstack.common import rpc
 import heat.openstack.common.rpc.common as rpc_common
@@ -95,7 +96,7 @@ class StackControllerTest(unittest.TestCase):
                         u'stack_status': u'CREATE_COMPLETE'}]}
         self.m.StubOutWithMock(rpc, 'call')
         rpc.call(dummy_req.context, self.topic, {'method': 'show_stack',
-                                    'args': {'stack_name': None,
+                                    'args': {'stack_identity': None,
                                     'params': dict(dummy_req.params)},
                                     'version': self.api_version}, None
                 ).AndReturn(engine_resp)
@@ -122,7 +123,7 @@ class StackControllerTest(unittest.TestCase):
         # heat exception type
         self.m.StubOutWithMock(rpc, 'call')
         rpc.call(dummy_req.context, self.topic, {'method': 'show_stack',
-                                    'args': {'stack_name': None,
+                                    'args': {'stack_identity': None,
                                     'params': dict(dummy_req.params)},
                                     'version': self.api_version}, None
                 ).AndRaise(rpc_common.RemoteError("AttributeError"))
@@ -142,7 +143,7 @@ class StackControllerTest(unittest.TestCase):
         # heat exception type
         self.m.StubOutWithMock(rpc, 'call')
         rpc.call(dummy_req.context, self.topic, {'method': 'show_stack',
-                                    'args': {'stack_name': None,
+                                    'args': {'stack_identity': None,
                                     'params': dict(dummy_req.params)},
                                     'version': self.api_version}, None
                 ).AndRaise(rpc_common.RemoteError("Exception"))
@@ -156,6 +157,7 @@ class StackControllerTest(unittest.TestCase):
     def test_describe(self):
         # Format a dummy GET request to pass into the WSGI handler
         stack_name = u"wordpress"
+        identity = dict(identifier.HeatIdentifier('t', stack_name, '6'))
         params = {'Action': 'DescribeStacks', 'StackName': stack_name}
         dummy_req = self._dummy_GET_request(params)
 
@@ -190,8 +192,94 @@ class StackControllerTest(unittest.TestCase):
             u'capabilities':[]}]}
 
         self.m.StubOutWithMock(rpc, 'call')
+        rpc.call(dummy_req.context, self.topic, {'method': 'identify_stack',
+            'args': {'stack_name': stack_name},
+            'version': self.api_version}, None).AndReturn(identity)
         rpc.call(dummy_req.context, self.topic, {'method': 'show_stack',
-            'args': {'stack_name': stack_name,
+            'args': {'stack_identity': identity,
+                     'params': dict(dummy_req.params)},
+            'version': self.api_version}, None).AndReturn(engine_resp)
+
+        self.m.ReplayAll()
+
+        # Call the list controller function and compare the response
+        response = self.controller.describe(dummy_req)
+
+        expected = {'DescribeStacksResponse': {'DescribeStacksResult':
+                {'Stacks':
+                [{'StackId': u'arn:openstack:heat::t:stacks/wordpress/6',
+                'StackStatusReason': u'Stack successfully created',
+                'Description': u'blah',
+                'Parameters':
+                    [{'ParameterValue': u'admin',
+                    'ParameterKey': u'DBUsername'},
+                    {'ParameterValue': u'F16',
+                    'ParameterKey': u'LinuxDistribution'},
+                    {'ParameterValue': u'm1.large',
+                    'ParameterKey': u'InstanceType'},
+                    {'ParameterValue': u'admin',
+                    'ParameterKey': u'DBRootPassword'},
+                    {'ParameterValue': u'admin',
+                    'ParameterKey': u'DBPassword'},
+                    {'ParameterValue': u'wordpress',
+                    'ParameterKey': u'DBName'}],
+                'Outputs':
+                    [{'OutputKey': u'WebsiteURL',
+                    'OutputValue': u'http://10.0.0.8/wordpress',
+                    'Description': u'URL for Wordpress wiki'}],
+                'TimeoutInMinutes': 60,
+                'CreationTime': u'2012-07-09T09:12:45Z',
+                'Capabilities': [],
+                'StackName': u'wordpress',
+                'NotificationARNs': [],
+                'StackStatus': u'CREATE_COMPLETE',
+                'DisableRollback': True,
+                'LastUpdatedTime': u'2012-07-09T09:13:11Z'}]}}}
+
+        self.assertEqual(response, expected)
+
+    def test_describe_arn(self):
+        # Format a dummy GET request to pass into the WSGI handler
+        stack_name = u"wordpress"
+        stack_identifier = identifier.HeatIdentifier('t', stack_name, '6')
+        identity = dict(stack_identifier)
+        params = {'Action': 'DescribeStacks',
+                  'StackName': stack_identifier.arn()}
+        dummy_req = self._dummy_GET_request(params)
+
+        # Stub out the RPC call to the engine with a pre-canned response
+        # Note the engine returns a load of keys we don't actually use
+        # so this is a subset of the real response format
+        engine_resp = {u'stacks': [
+            {u'stack_identity': {u'tenant': u't',
+                                 u'stack_name': u'wordpress',
+                                 u'stack_id': u'6',
+                                 u'path': u''},
+            u'updated_time': u'2012-07-09T09:13:11Z',
+            u'parameters':{
+            u'DBUsername': {u'Default': u'admin'},
+            u'LinuxDistribution': {u'Default': u'F16'},
+            u'InstanceType': {u'Default': u'm1.large'},
+            u'DBRootPassword': {u'Default': u'admin'},
+            u'DBPassword': {u'Default': u'admin'},
+            u'DBName': {u'Default': u'wordpress'}},
+            u'outputs':
+                [{u'output_key': u'WebsiteURL',
+                u'description': u'URL for Wordpress wiki',
+                u'output_value': u'http://10.0.0.8/wordpress'}],
+            u'stack_status_reason': u'Stack successfully created',
+            u'creation_time': u'2012-07-09T09:12:45Z',
+            u'stack_name': u'wordpress',
+            u'notification_topics': [],
+            u'stack_status': u'CREATE_COMPLETE',
+            u'description': u'blah',
+            u'disable_rollback': True,
+            u'timeout_mins':60,
+            u'capabilities':[]}]}
+
+        self.m.StubOutWithMock(rpc, 'call')
+        rpc.call(dummy_req.context, self.topic, {'method': 'show_stack',
+            'args': {'stack_identity': identity,
                      'params': dict(dummy_req.params)},
             'version': self.api_version}, None).AndReturn(engine_resp)
 
@@ -235,14 +323,18 @@ class StackControllerTest(unittest.TestCase):
 
     def test_describe_aterr(self):
         stack_name = "wordpress"
+        identity = dict(identifier.HeatIdentifier('t', stack_name, '6'))
         params = {'Action': 'DescribeStacks', 'StackName': stack_name}
         dummy_req = self._dummy_GET_request(params)
 
         # Insert an engine RPC error and ensure we map correctly to the
         # heat exception type
         self.m.StubOutWithMock(rpc, 'call')
+        rpc.call(dummy_req.context, self.topic, {'method': 'identify_stack',
+            'args': {'stack_name': stack_name},
+            'version': self.api_version}, None).AndReturn(identity)
         rpc.call(dummy_req.context, self.topic, {'method': 'show_stack',
-            'args': {'stack_name': stack_name,
+            'args': {'stack_identity': identity,
             'params': dict(dummy_req.params)},
             'version': self.api_version}, None
             ).AndRaise(rpc_common.RemoteError("AttributeError"))
@@ -253,6 +345,25 @@ class StackControllerTest(unittest.TestCase):
         self.assertEqual(type(result),
                          exception.HeatInvalidParameterValueError)
 
+    def test_describe_bad_name(self):
+        stack_name = "wibble"
+        params = {'Action': 'DescribeStacks', 'StackName': stack_name}
+        dummy_req = self._dummy_GET_request(params)
+
+        # Insert an engine RPC error and ensure we map correctly to the
+        # heat exception type
+        self.m.StubOutWithMock(rpc, 'call')
+        rpc.call(dummy_req.context, self.topic, {'method': 'identify_stack',
+            'args': {'stack_name': stack_name},
+            'version': self.api_version}, None
+            ).AndRaise(rpc_common.RemoteError("AttributeError"))
+
+        self.m.ReplayAll()
+
+        result = self.controller.describe(dummy_req)
+        self.assertEqual(type(result),
+                         exception.HeatInvalidParameterValueError)
+
     def test_get_template_int_body(self):
         ''' Test the internal _get_template function '''
         params = {'TemplateBody': "abcdef"}
@@ -375,19 +486,20 @@ class StackControllerTest(unittest.TestCase):
         dummy_req = self._dummy_GET_request(params)
 
         # Stub out the RPC call to the engine with a pre-canned response
-        engine_resp = {u'tenant': u't',
-                       u'stack_name': u'wordpress',
-                       u'stack_id': u'1',
-                       u'path': u''}
+        identity = dict(identifier.HeatIdentifier('t', stack_name, '1'))
 
         self.m.StubOutWithMock(rpc, 'call')
+        rpc.call(dummy_req.context, self.topic, {'method': 'identify_stack',
+            'args': {'stack_name': stack_name},
+            'version': self.api_version}, None).AndReturn(identity)
+
         rpc.call(dummy_req.context, self.topic, {'method': 'update_stack',
             'args':
-            {'stack_name': stack_name,
+            {'stack_identity': identity,
             'template': template,
             'params': engine_parms,
             'args': engine_args},
-            'version': self.api_version}, None).AndReturn(engine_resp)
+            'version': self.api_version}, None).AndReturn(identity)
 
         self.m.ReplayAll()
 
@@ -403,6 +515,30 @@ class StackControllerTest(unittest.TestCase):
 
         self.assertEqual(response, expected)
 
+    def test_update_bad_name(self):
+        stack_name = "wibble"
+        template = {u'Foo': u'bar'}
+        json_template = json.dumps(template)
+        params = {'Action': 'UpdateStack', 'StackName': stack_name,
+                  'TemplateBody': '%s' % json_template,
+                  'Parameters.member.1.ParameterKey': 'InstanceType',
+                  'Parameters.member.1.ParameterValue': 'm1.xlarge'}
+        dummy_req = self._dummy_GET_request(params)
+
+        # Insert an engine RPC error and ensure we map correctly to the
+        # heat exception type
+        self.m.StubOutWithMock(rpc, 'call')
+        rpc.call(dummy_req.context, self.topic, {'method': 'identify_stack',
+            'args': {'stack_name': stack_name},
+            'version': self.api_version}, None
+            ).AndRaise(rpc_common.RemoteError("AttributeError"))
+
+        self.m.ReplayAll()
+
+        result = self.controller.update(dummy_req)
+        self.assertEqual(type(result),
+                         exception.HeatInvalidParameterValueError)
+
     def test_create_or_update_err(self):
         result = self.controller.create_or_update(req={}, action="dsdgfdf")
         self.assertEqual(type(result), exception.HeatInternalFailureError)
@@ -410,6 +546,7 @@ class StackControllerTest(unittest.TestCase):
     def test_get_template(self):
         # Format a dummy request
         stack_name = "wordpress"
+        identity = dict(identifier.HeatIdentifier('t', stack_name, '6'))
         template = {u'Foo': u'bar'}
         params = {'Action': 'GetTemplate', 'StackName': stack_name}
         dummy_req = self._dummy_GET_request(params)
@@ -418,9 +555,12 @@ class StackControllerTest(unittest.TestCase):
         engine_resp = template
 
         self.m.StubOutWithMock(rpc, 'call')
+        rpc.call(dummy_req.context, self.topic, {'method': 'identify_stack',
+            'args': {'stack_name': stack_name},
+            'version': self.api_version}, None).AndReturn(identity)
         rpc.call(dummy_req.context, self.topic, {'method': 'get_template',
             'args':
-            {'stack_name': stack_name,
+            {'stack_identity': identity,
             'params': dict(dummy_req.params)},
             'version': self.api_version}, None).AndReturn(engine_resp)
 
@@ -435,6 +575,7 @@ class StackControllerTest(unittest.TestCase):
 
     def test_get_template_err_rpcerr(self):
         stack_name = "wordpress"
+        identity = dict(identifier.HeatIdentifier('t', stack_name, '6'))
         template = {u'Foo': u'bar'}
         params = {'Action': 'GetTemplate', 'StackName': stack_name}
         dummy_req = self._dummy_GET_request(params)
@@ -442,9 +583,12 @@ class StackControllerTest(unittest.TestCase):
         # Insert an engine RPC error and ensure we map correctly to the
         # heat exception type
         self.m.StubOutWithMock(rpc, 'call')
+        rpc.call(dummy_req.context, self.topic, {'method': 'identify_stack',
+            'args': {'stack_name': stack_name},
+            'version': self.api_version}, None).AndReturn(identity)
         rpc.call(dummy_req.context, self.topic, {'method': 'get_template',
             'args':
-            {'stack_name': stack_name,
+            {'stack_identity': identity,
             'params': dict(dummy_req.params)},
             'version': self.api_version}, None
             ).AndRaise(rpc_common.RemoteError("AttributeError"))
@@ -456,8 +600,28 @@ class StackControllerTest(unittest.TestCase):
         self.assertEqual(type(result),
                          exception.HeatInvalidParameterValueError)
 
+    def test_get_template_bad_name(self):
+        stack_name = "wibble"
+        params = {'Action': 'GetTemplate', 'StackName': stack_name}
+        dummy_req = self._dummy_GET_request(params)
+
+        # Insert an engine RPC error and ensure we map correctly to the
+        # heat exception type
+        self.m.StubOutWithMock(rpc, 'call')
+        rpc.call(dummy_req.context, self.topic, {'method': 'identify_stack',
+            'args': {'stack_name': stack_name},
+            'version': self.api_version}, None
+            ).AndRaise(rpc_common.RemoteError("AttributeError"))
+
+        self.m.ReplayAll()
+
+        result = self.controller.get_template(dummy_req)
+        self.assertEqual(type(result),
+                         exception.HeatInvalidParameterValueError)
+
     def test_get_template_err_none(self):
         stack_name = "wordpress"
+        identity = dict(identifier.HeatIdentifier('t', stack_name, '6'))
         template = {u'Foo': u'bar'}
         params = {'Action': 'GetTemplate', 'StackName': stack_name}
         dummy_req = self._dummy_GET_request(params)
@@ -467,9 +631,12 @@ class StackControllerTest(unittest.TestCase):
         engine_resp = None
 
         self.m.StubOutWithMock(rpc, 'call')
+        rpc.call(dummy_req.context, self.topic, {'method': 'identify_stack',
+            'args': {'stack_name': stack_name},
+            'version': self.api_version}, None).AndReturn(identity)
         rpc.call(dummy_req.context, self.topic, {'method': 'get_template',
             'args':
-            {'stack_name': stack_name,
+            {'stack_identity': identity,
             'params': dict(dummy_req.params)},
             'version': self.api_version}, None).AndReturn(engine_resp)
 
@@ -503,19 +670,21 @@ class StackControllerTest(unittest.TestCase):
     def test_delete(self):
         # Format a dummy request
         stack_name = "wordpress"
+        identity = dict(identifier.HeatIdentifier('t', stack_name, '1'))
         params = {'Action': 'DeleteStack', 'StackName': stack_name}
         dummy_req = self._dummy_GET_request(params)
 
         # Stub out the RPC call to the engine with a pre-canned response
-        # Engine returns None when delete successful
-        engine_resp = None
-
         self.m.StubOutWithMock(rpc, 'call')
+        rpc.call(dummy_req.context, self.topic, {'method': 'identify_stack',
+            'args': {'stack_name': stack_name},
+            'version': self.api_version}, None).AndReturn(identity)
+        # Engine returns None when delete successful
         rpc.call(dummy_req.context, self.topic, {'method': 'delete_stack',
             'args':
-            {'stack_name': stack_name,
+            {'stack_identity': identity,
             'params': dict(dummy_req.params)},
-            'version': self.api_version}, None).AndReturn(engine_resp)
+            'version': self.api_version}, None).AndReturn(None)
 
         self.m.ReplayAll()
 
@@ -527,15 +696,21 @@ class StackControllerTest(unittest.TestCase):
 
     def test_delete_err_rpcerr(self):
         stack_name = "wordpress"
+        identity = dict(identifier.HeatIdentifier('t', stack_name, '1'))
         params = {'Action': 'DeleteStack', 'StackName': stack_name}
         dummy_req = self._dummy_GET_request(params)
 
+        # Stub out the RPC call to the engine with a pre-canned response
+        self.m.StubOutWithMock(rpc, 'call')
+        rpc.call(dummy_req.context, self.topic, {'method': 'identify_stack',
+            'args': {'stack_name': stack_name},
+            'version': self.api_version}, None).AndReturn(identity)
+
         # Insert an engine RPC error and ensure we map correctly to the
         # heat exception type
-        self.m.StubOutWithMock(rpc, 'call')
         rpc.call(dummy_req.context, self.topic, {'method': 'delete_stack',
             'args':
-            {'stack_name': stack_name,
+            {'stack_identity': identity,
             'params': dict(dummy_req.params)},
             'version': self.api_version}, None
             ).AndRaise(rpc_common.RemoteError("AttributeError"))
@@ -547,9 +722,29 @@ class StackControllerTest(unittest.TestCase):
         self.assertEqual(type(result),
                          exception.HeatInvalidParameterValueError)
 
+    def test_delete_bad_name(self):
+        stack_name = "wibble"
+        params = {'Action': 'DeleteStack', 'StackName': stack_name}
+        dummy_req = self._dummy_GET_request(params)
+
+        # Insert an engine RPC error and ensure we map correctly to the
+        # heat exception type
+        self.m.StubOutWithMock(rpc, 'call')
+        rpc.call(dummy_req.context, self.topic, {'method': 'identify_stack',
+            'args': {'stack_name': stack_name},
+            'version': self.api_version}, None
+            ).AndRaise(rpc_common.RemoteError("AttributeError"))
+
+        self.m.ReplayAll()
+
+        result = self.controller.delete(dummy_req)
+        self.assertEqual(type(result),
+                         exception.HeatInvalidParameterValueError)
+
     def test_events_list(self):
         # Format a dummy request
         stack_name = "wordpress"
+        identity = dict(identifier.HeatIdentifier('t', stack_name, '6'))
         params = {'Action': 'DescribeStackEvents', 'StackName': stack_name}
         dummy_req = self._dummy_GET_request(params)
 
@@ -570,9 +765,12 @@ class StackControllerTest(unittest.TestCase):
                         u'resource_type': u'AWS::EC2::Instance'}]}
 
         self.m.StubOutWithMock(rpc, 'call')
+        rpc.call(dummy_req.context, self.topic, {'method': 'identify_stack',
+            'args': {'stack_name': stack_name},
+            'version': self.api_version}, None).AndReturn(identity)
         rpc.call(dummy_req.context, self.topic, {'method': 'list_events',
             'args':
-            {'stack_name': stack_name,
+            {'stack_identity': identity,
             'params': dict(dummy_req.params)},
             'version': self.api_version}, None).AndReturn(engine_resp)
 
@@ -598,15 +796,19 @@ class StackControllerTest(unittest.TestCase):
 
     def test_events_list_err_rpcerr(self):
         stack_name = "wordpress"
+        identity = dict(identifier.HeatIdentifier('t', stack_name, '6'))
         params = {'Action': 'DescribeStackEvents', 'StackName': stack_name}
         dummy_req = self._dummy_GET_request(params)
 
         # Insert an engine RPC error and ensure we map correctly to the
         # heat exception type
         self.m.StubOutWithMock(rpc, 'call')
+        rpc.call(dummy_req.context, self.topic, {'method': 'identify_stack',
+            'args': {'stack_name': stack_name},
+            'version': self.api_version}, None).AndReturn(identity)
         rpc.call(dummy_req.context, self.topic, {'method': 'list_events',
             'args':
-            {'stack_name': stack_name,
+            {'stack_identity': identity,
             'params': dict(dummy_req.params)},
             'version': self.api_version}, None
             ).AndRaise(rpc_common.RemoteError("Exception"))
@@ -617,9 +819,29 @@ class StackControllerTest(unittest.TestCase):
 
         self.assertEqual(type(result), exception.HeatInternalFailureError)
 
+    def test_events_list_bad_name(self):
+        stack_name = "wibble"
+        params = {'Action': 'DescribeStackEvents', 'StackName': stack_name}
+        dummy_req = self._dummy_GET_request(params)
+
+        # Insert an engine RPC error and ensure we map correctly to the
+        # heat exception type
+        self.m.StubOutWithMock(rpc, 'call')
+        rpc.call(dummy_req.context, self.topic, {'method': 'identify_stack',
+            'args': {'stack_name': stack_name},
+            'version': self.api_version}, None
+            ).AndRaise(rpc_common.RemoteError("AttributeError"))
+
+        self.m.ReplayAll()
+
+        result = self.controller.events_list(dummy_req)
+        self.assertEqual(type(result),
+                         exception.HeatInvalidParameterValueError)
+
     def test_describe_stack_resource(self):
         # Format a dummy request
         stack_name = "wordpress"
+        identity = dict(identifier.HeatIdentifier('t', stack_name, '6'))
         params = {'Action': 'DescribeStackResource',
                   'StackName': stack_name,
                   'LogicalResourceId': "WikiDatabase"}
@@ -642,8 +864,11 @@ class StackControllerTest(unittest.TestCase):
                        u'metadata': {u'wordpress': []}}
 
         self.m.StubOutWithMock(rpc, 'call')
+        rpc.call(dummy_req.context, self.topic, {'method': 'identify_stack',
+            'args': {'stack_name': stack_name},
+            'version': self.api_version}, None).AndReturn(identity)
         args = {
-            'stack_name': dummy_req.params.get('StackName'),
+            'stack_identity': identity,
             'resource_name': dummy_req.params.get('LogicalResourceId'),
         }
         rpc.call(dummy_req.context, self.topic,
@@ -675,6 +900,7 @@ class StackControllerTest(unittest.TestCase):
     def test_describe_stack_resources(self):
         # Format a dummy request
         stack_name = "wordpress"
+        identity = dict(identifier.HeatIdentifier('t', stack_name, '6'))
         params = {'Action': 'DescribeStackResources',
                   'StackName': stack_name,
                   'LogicalResourceId': "WikiDatabase"}
@@ -697,8 +923,11 @@ class StackControllerTest(unittest.TestCase):
                         u'metadata': {u'ensureRunning': u'true''true'}}]
 
         self.m.StubOutWithMock(rpc, 'call')
+        rpc.call(dummy_req.context, self.topic, {'method': 'identify_stack',
+            'args': {'stack_name': stack_name},
+            'version': self.api_version}, None).AndReturn(identity)
         args = {
-            'stack_name': dummy_req.params.get('StackName'),
+            'stack_identity': identity,
             'physical_resource_id': None,
             'logical_resource_id': dummy_req.params.get('LogicalResourceId'),
         }
@@ -727,6 +956,27 @@ class StackControllerTest(unittest.TestCase):
 
         self.assertEqual(response, expected)
 
+    def test_describe_stack_resources_bad_name(self):
+        stack_name = "wibble"
+        params = {'Action': 'DescribeStackResources',
+                  'StackName': stack_name,
+                  'LogicalResourceId': "WikiDatabase"}
+        dummy_req = self._dummy_GET_request(params)
+
+        # Insert an engine RPC error and ensure we map correctly to the
+        # heat exception type
+        self.m.StubOutWithMock(rpc, 'call')
+        rpc.call(dummy_req.context, self.topic, {'method': 'identify_stack',
+            'args': {'stack_name': stack_name},
+            'version': self.api_version}, None
+            ).AndRaise(rpc_common.RemoteError("AttributeError"))
+
+        self.m.ReplayAll()
+
+        result = self.controller.describe_stack_resources(dummy_req)
+        self.assertEqual(type(result),
+                         exception.HeatInvalidParameterValueError)
+
     def test_describe_stack_resources_err_inval(self):
         # Format a dummy request containing both StackName and
         # PhysicalResourceId, which is invalid and should throw a
@@ -743,6 +993,7 @@ class StackControllerTest(unittest.TestCase):
     def test_list_stack_resources(self):
         # Format a dummy request
         stack_name = "wordpress"
+        identity = dict(identifier.HeatIdentifier('t', stack_name, '6'))
         params = {'Action': 'ListStackResources',
                   'StackName': stack_name}
         dummy_req = self._dummy_GET_request(params)
@@ -764,12 +1015,12 @@ class StackControllerTest(unittest.TestCase):
                         u'metadata': {}}]
 
         self.m.StubOutWithMock(rpc, 'call')
-        args = {
-            'stack_name': dummy_req.params.get('StackName'),
-        }
+        rpc.call(dummy_req.context, self.topic, {'method': 'identify_stack',
+            'args': {'stack_name': stack_name},
+            'version': self.api_version}, None).AndReturn(identity)
         rpc.call(dummy_req.context, self.topic,
             {'method': 'list_stack_resources',
-            'args': args,
+            'args': {'stack_identity': identity},
             'version': self.api_version}, None).AndReturn(engine_resp)
 
         self.m.ReplayAll()
@@ -788,6 +1039,26 @@ class StackControllerTest(unittest.TestCase):
 
         self.assertEqual(response, expected)
 
+    def test_list_stack_resources_bad_name(self):
+        stack_name = "wibble"
+        params = {'Action': 'ListStackResources',
+                  'StackName': stack_name}
+        dummy_req = self._dummy_GET_request(params)
+
+        # Insert an engine RPC error and ensure we map correctly to the
+        # heat exception type
+        self.m.StubOutWithMock(rpc, 'call')
+        rpc.call(dummy_req.context, self.topic, {'method': 'identify_stack',
+            'args': {'stack_name': stack_name},
+            'version': self.api_version}, None
+            ).AndRaise(rpc_common.RemoteError("AttributeError"))
+
+        self.m.ReplayAll()
+
+        result = self.controller.list_stack_resources(dummy_req)
+        self.assertEqual(type(result),
+                         exception.HeatInvalidParameterValueError)
+
     def setUp(self):
         self.maxDiff = None
         self.m = mox.Mox()
index 59e827916a1659dccb37f9e1376c251f08821704..2e9f8b5dfeda7896ce53fd9752dec0789f5e0d59 100644 (file)
@@ -179,7 +179,7 @@ class stackManagerTest(unittest.TestCase):
                           self.ctx, 'wibble')
 
     def test_stack_event_list(self):
-        el = self.man.list_events(self.ctx, self.stack_name, {})
+        el = self.man.list_events(self.ctx, self.stack_identity, {})
 
         self.assertTrue('events' in el)
         events = el['events']
@@ -239,12 +239,14 @@ class stackManagerTest(unittest.TestCase):
         self.assertEqual(len(sl['stacks']), 0)
 
     def test_stack_describe_nonexistent(self):
+        nonexist = dict(self.stack_identity)
+        nonexist['stack_name'] = 'wibble'
         self.assertRaises(AttributeError,
                           self.man.show_stack,
-                          self.ctx, 'wibble', {})
+                          self.ctx, nonexist, {})
 
     def test_stack_describe(self):
-        sl = self.man.show_stack(self.ctx, self.stack_name, {})
+        sl = self.man.show_stack(self.ctx, self.stack_identity, {})
 
         self.assertEqual(len(sl['stacks']), 1)
 
@@ -262,7 +264,7 @@ class stackManagerTest(unittest.TestCase):
         self.assertTrue('parameters' in s)
 
     def test_stack_resource_describe(self):
-        r = self.man.describe_stack_resource(self.ctx, self.stack_name,
+        r = self.man.describe_stack_resource(self.ctx, self.stack_identity,
                                              'WebServer')
 
         self.assertTrue('description' in r)
@@ -280,18 +282,20 @@ class stackManagerTest(unittest.TestCase):
         self.assertEqual(r['logical_resource_id'], 'WebServer')
 
     def test_stack_resource_describe_nonexist_stack(self):
+        nonexist = dict(self.stack_identity)
+        nonexist['stack_name'] = 'foo'
         self.assertRaises(AttributeError,
                           self.man.describe_stack_resource,
-                          self.ctx, 'foo', 'WebServer')
+                          self.ctx, nonexist, 'WebServer')
 
     def test_stack_resource_describe_nonexist_resource(self):
         self.assertRaises(AttributeError,
                           self.man.describe_stack_resource,
-                          self.ctx, self.stack_name, 'foo')
+                          self.ctx, self.stack_identity, 'foo')
 
     def test_stack_resources_describe(self):
         resources = self.man.describe_stack_resources(self.ctx,
-                                                      self.stack_name,
+                                                      self.stack_identity,
                                                       None, 'WebServer')
 
         self.assertEqual(len(resources), 1)
@@ -311,7 +315,7 @@ class stackManagerTest(unittest.TestCase):
 
     def test_stack_resources_describe_no_filter(self):
         resources = self.man.describe_stack_resources(self.ctx,
-                                                 self.stack_name,
+                                                 self.stack_identity,
                                                  None, None)
 
         self.assertEqual(len(resources), 1)
@@ -325,9 +329,11 @@ class stackManagerTest(unittest.TestCase):
                           self.ctx, None, None, 'WebServer')
 
     def test_stack_resources_describe_nonexist_stack(self):
+        nonexist = dict(self.stack_identity)
+        nonexist['stack_name'] = 'foo'
         self.assertRaises(AttributeError,
                           self.man.describe_stack_resources,
-                          self.ctx, 'foo', None, 'WebServer')
+                          self.ctx, nonexist, None, 'WebServer')
 
     def test_stack_resources_describe_nonexist_physid(self):
         self.assertRaises(AttributeError,
@@ -335,7 +341,8 @@ class stackManagerTest(unittest.TestCase):
                           self.ctx, None, 'foo', 'WebServer')
 
     def test_stack_resources_list(self):
-        resources = self.man.list_stack_resources(self.ctx, self.stack_name)
+        resources = self.man.list_stack_resources(self.ctx,
+                                                  self.stack_identity)
 
         self.assertEqual(len(resources), 1)
         r = resources[0]
@@ -348,9 +355,11 @@ class stackManagerTest(unittest.TestCase):
         self.assertTrue('resource_type' in r)
 
     def test_stack_resources_list_nonexist_stack(self):
+        nonexist = dict(self.stack_identity)
+        nonexist['stack_name'] = 'foo'
         self.assertRaises(AttributeError,
                           self.man.list_stack_resources,
-                          self.ctx, 'foo')
+                          self.ctx, nonexist)
 
     def test_metadata(self):
         err, metadata = self.man.metadata_get_resource(None,