def __init__(self, context, stack_name, tmpl, env=None,
stack_id=None, action=None, status=None,
status_reason='', timeout_mins=60, resolve_data=True,
- disable_rollback=True):
+ disable_rollback=True, parent_resource=None):
'''
Initialise from a context, name, Template object and (optionally)
Environment object. The database ID may also be initialised, if the
self.status_reason = status_reason
self.timeout_mins = timeout_mins
self.disable_rollback = disable_rollback
+ self.parent_resource = parent_resource
resources.initialise()
return deps
@classmethod
- def load(cls, context, stack_id=None, stack=None, resolve_data=True):
+ def load(cls, context, stack_id=None, stack=None, resolve_data=True,
+ parent_resource=None):
'''Retrieve a Stack from the database.'''
if stack is None:
stack = db_api.stack_get(context, stack_id)
env = environment.Environment(stack.parameters)
stack = cls(context, stack.name, template, env,
stack.id, stack.action, stack.status, stack.status_reason,
- stack.timeout, resolve_data, stack.disable_rollback)
+ stack.timeout, resolve_data, stack.disable_rollback,
+ parent_resource)
return stack
parameters=parameters),
functools.partial(template.resolve_availability_zones,
stack=stack),
+ functools.partial(template.resolve_resource_facade,
+ stack=stack),
template.resolve_find_in_map,
template.reduce_joins])
'''
if self._nested is None and self.resource_id is not None:
self._nested = parser.Stack.load(self.context,
- self.resource_id)
+ self.resource_id,
+ parent_resource=self)
if self._nested is None:
raise exception.NotFound('Nested stack not found in DB')
template,
environment.Environment(user_params),
timeout_mins=timeout_mins,
- disable_rollback=True)
+ disable_rollback=True,
+ parent_resource=self)
nested_id = self._nested.store(self.stack)
self.resource_id_set(nested_id)
return _resolve(lambda k, v: k == 'Fn::Base64', handle_base64, s)
+ @staticmethod
+ def resolve_resource_facade(s, stack):
+ '''
+ Resolve constructs of the form {'Fn::ResourceFacade': 'Metadata'}
+ '''
+ resource_attributes = ('Metadata', 'DeletionPolicy', 'UpdatePolicy')
+
+ def handle_resource_facade(arg):
+ if arg not in resource_attributes:
+ raise ValueError(
+ 'Incorrect arguments to "Fn::ResourceFacade" %s: %s' %
+ ('should be one of', str(resource_attributes)))
+ try:
+ if arg == 'Metadata':
+ return stack.parent_resource.metadata
+ return stack.parent_resource.t[arg]
+ except KeyError:
+ raise KeyError('"%s" is not specified in parent resource' %
+ arg)
+
+ return _resolve(lambda k, v: k == 'Fn::ResourceFacade',
+ handle_resource_facade,
+ s)
+
def _resolve(match, handle, snippet):
'''
import uuid
from heat.common import context
+from heat.engine import environment
from heat.common import exception
from heat.common import template_format
from heat.engine import clients
parser.Template.resolve_replace(snippet),
'"foo" is "${var3}"')
+ def test_resource_facade(self):
+ metadata_snippet = {'Fn::ResourceFacade': 'Metadata'}
+ deletion_policy_snippet = {'Fn::ResourceFacade': 'DeletionPolicy'}
+ update_policy_snippet = {'Fn::ResourceFacade': 'UpdatePolicy'}
+
+ class DummyClass:
+ pass
+ parent_resource = DummyClass()
+ parent_resource.metadata = '{"foo": "bar"}'
+ parent_resource.t = {'DeletionPolicy': 'Retain',
+ 'UpdatePolicy': '{"foo": "bar"}'}
+ stack = parser.Stack(None, 'test_stack',
+ parser.Template({}),
+ parent_resource=parent_resource)
+ self.assertEqual(
+ parser.Template.resolve_resource_facade(metadata_snippet, stack),
+ '{"foo": "bar"}')
+ self.assertEqual(
+ parser.Template.resolve_resource_facade(deletion_policy_snippet,
+ stack), 'Retain')
+ self.assertEqual(
+ parser.Template.resolve_resource_facade(update_policy_snippet,
+ stack), '{"foo": "bar"}')
+
+ def test_resource_facade_invalid_arg(self):
+ snippet = {'Fn::ResourceFacade': 'wibble'}
+ stack = parser.Stack(None, 'test_stack', parser.Template({}))
+ self.assertRaises(ValueError,
+ parser.Template.resolve_resource_facade,
+ snippet,
+ stack)
+
+ def test_resource_facade_missing_key(self):
+ snippet = {'Fn::ResourceFacade': 'DeletionPolicy'}
+
+ class DummyClass:
+ pass
+ parent_resource = DummyClass()
+ parent_resource.metadata = '{"foo": "bar"}'
+ parent_resource.t = {}
+ stack = parser.Stack(None, 'test_stack',
+ parser.Template({}),
+ parent_resource=parent_resource)
+ self.assertRaises(KeyError,
+ parser.Template.resolve_resource_facade,
+ snippet,
+ stack)
+
class StackTest(HeatTestCase):
def setUp(self):
self.assertRaises(exception.NotFound, parser.Stack.load,
None, -1)
+ @stack_delete_after
+ def test_load_parent_resource(self):
+ self.stack = parser.Stack(self.ctx, 'load_parent_resource',
+ parser.Template({}))
+ self.stack.store()
+ stack = db_api.stack_get(self.ctx, self.stack.id)
+
+ t = template.Template.load(self.ctx, stack.raw_template_id)
+ self.m.StubOutWithMock(template.Template, 'load')
+ template.Template.load(self.ctx, stack.raw_template_id).AndReturn(t)
+
+ env = environment.Environment(stack.parameters)
+ self.m.StubOutWithMock(environment, 'Environment')
+ environment.Environment(stack.parameters).AndReturn(env)
+
+ self.m.StubOutWithMock(parser.Stack, '__init__')
+ parser.Stack.__init__(self.ctx, stack.name, t, env, stack.id,
+ stack.action, stack.status, stack.status_reason,
+ stack.timeout, True, stack.disable_rollback,
+ 'parent')
+
+ self.m.ReplayAll()
+ parser.Stack.load(self.ctx, stack_id=self.stack.id,
+ parent_resource='parent')
+
+ self.m.VerifyAll()
+
# Note tests creating a stack should be decorated with @stack_delete_after
# to ensure the self.stack is properly cleaned up
@stack_delete_after
--- /dev/null
+# 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.
+
+
+from heat.common import template_format
+from heat.common import context
+from heat.common import exception
+from heat.engine import parser
+from heat.engine import resource
+from heat.engine import stack_resource
+from heat.engine import template
+from heat.openstack.common import uuidutils
+from heat.tests.common import HeatTestCase
+from heat.tests import generic_resource as generic_rsrc
+from heat.tests.utils import setup_dummy_db
+from heat.tests.utils import stack_delete_after
+
+ws_res_snippet = {"Type": "some_magic_type",
+ "metadata": {
+ "key": "value",
+ "some": "more stuff"}}
+
+wp_template = '''
+{
+ "AWSTemplateFormatVersion" : "2010-09-09",
+ "Description" : "WordPress",
+ "Parameters" : {
+ "KeyName" : {
+ "Description" : "KeyName",
+ "Type" : "String",
+ "Default" : "test"
+ }
+ },
+ "Resources" : {
+ "WebServer": {
+ "Type": "AWS::EC2::Instance",
+ "metadata": {"Fn::ResourceFacade": "Metadata"},
+ "Properties": {
+ "ImageId" : "F17-x86_64-gold",
+ "InstanceType" : "m1.large",
+ "KeyName" : "test",
+ "UserData" : "wordpress"
+ }
+ }
+ }
+}
+'''
+
+
+class MyStackResource(stack_resource.StackResource,
+ generic_rsrc.GenericResource):
+ def physical_resource_name(self):
+ return "cb2f2b28-a663-4683-802c-4b40c916e1ff"
+
+
+class StackResourceTest(HeatTestCase):
+
+ def setUp(self):
+ super(StackResourceTest, self).setUp()
+ setup_dummy_db()
+ resource._register_class('some_magic_type',
+ MyStackResource)
+ t = parser.Template({template.RESOURCES:
+ {"provider_resource": ws_res_snippet}})
+ self.parent_stack = parser.Stack(None, 'test_stack', t,
+ stack_id=uuidutils.generate_uuid())
+ self.parent_resource = MyStackResource('test',
+ ws_res_snippet,
+ self.parent_stack)
+ self.parent_resource.context = context.get_admin_context()
+ self.templ = template_format.parse(wp_template)
+
+ @stack_delete_after
+ def test_create_with_template_ok(self):
+ self.parent_resource.create_with_template(self.templ,
+ {"KeyName": "key"})
+ self.stack = self.parent_resource.nested()
+
+ self.assertEqual(self.parent_resource, self.stack.parent_resource)
+ self.assertEqual("cb2f2b28-a663-4683-802c-4b40c916e1ff",
+ self.stack.name)
+ self.assertEqual(self.templ, self.stack.t.t)
+ self.assertEqual(self.stack.id, self.parent_resource.resource_id)
+
+ @stack_delete_after
+ def test_load_nested_ok(self):
+ self.parent_resource.create_with_template(self.templ,
+ {"KeyName": "key"})
+ self.stack = self.parent_resource.nested()
+
+ self.parent_resource._nested = None
+ self.m.StubOutWithMock(parser.Stack, 'load')
+ parser.Stack.load(self.parent_resource.context,
+ self.parent_resource.resource_id,
+ parent_resource=self.parent_resource).AndReturn('s')
+ self.m.ReplayAll()
+
+ self.parent_resource.nested()
+ self.m.VerifyAll()
+
+ @stack_delete_after
+ def test_load_nested_non_exist(self):
+ self.parent_resource.create_with_template(self.templ,
+ {"KeyName": "key"})
+ self.stack = self.parent_resource.nested()
+
+ self.parent_resource._nested = None
+ self.m.StubOutWithMock(parser.Stack, 'load')
+ parser.Stack.load(self.parent_resource.context,
+ self.parent_resource.resource_id,
+ parent_resource=self.parent_resource)
+ self.m.ReplayAll()
+
+ self.assertRaises(exception.NotFound, self.parent_resource.nested)
+ self.m.VerifyAll()
+
+ def test_delete_nested_ok(self):
+ nested = self.m.CreateMockAnything()
+ self.m.StubOutWithMock(stack_resource.StackResource, 'nested')
+ stack_resource.StackResource.nested().AndReturn(nested)
+ nested.delete()
+ self.m.ReplayAll()
+
+ self.parent_resource.delete_nested()
+ self.m.VerifyAll()
+
+ def test_get_output_ok(self):
+ nested = self.m.CreateMockAnything()
+ self.m.StubOutWithMock(stack_resource.StackResource, 'nested')
+ stack_resource.StackResource.nested().AndReturn(nested)
+ nested.outputs = {"key": "value"}
+ nested.output('key').AndReturn("value")
+ self.m.ReplayAll()
+
+ self.assertEqual("value", self.parent_resource.get_output("key"))
+
+ self.m.VerifyAll()
+
+ def test_get_output_key_not_found(self):
+ nested = self.m.CreateMockAnything()
+ self.m.StubOutWithMock(stack_resource.StackResource, 'nested')
+ stack_resource.StackResource.nested().AndReturn(nested)
+ nested.outputs = {}
+ self.m.ReplayAll()
+
+ self.assertRaises(exception.InvalidTemplateAttribute,
+ self.parent_resource.get_output,
+ "key")
+
+ self.m.VerifyAll()