Support lookup of stacks by name or ARN

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>
This commit is contained in:
Zane Bitter 2012-09-05 21:51:25 +02:00
parent 052a6a4311
commit 38f886947c
5 changed files with 417 additions and 111 deletions

View 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)

View 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)

View 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):

View 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()

View 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,