diff options
author | Jenkins <jenkins@review.openstack.org> | 2014-08-01 16:46:47 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2014-08-01 16:46:47 +0000 |
commit | 39a16683d32f91c2e28751213cf3f78f02d88846 (patch) | |
tree | af9fe30c97d84e3059adde17ffa5df6ee9bee848 | |
parent | ecc3d9f05bbeb087de86181f3b8f911c261de5c7 (diff) | |
parent | 2db68e5e923276b4882ad400b16e99c3cc065150 (diff) | |
download | heat-39a16683d32f91c2e28751213cf3f78f02d88846.tar.gz |
Merge "Pass the parent's registry into child stacks" into stable/icehouse
-rw-r--r-- | heat/engine/environment.py | 10 | ||||
-rw-r--r-- | heat/engine/resource.py | 30 | ||||
-rw-r--r-- | heat/engine/stack_resource.py | 23 | ||||
-rw-r--r-- | heat/tests/test_provider_template.py | 89 | ||||
-rw-r--r-- | heat/tests/test_stack_resource.py | 4 |
5 files changed, 144 insertions, 12 deletions
diff --git a/heat/engine/environment.py b/heat/engine/environment.py index a2162b9e4..f43baeb8e 100644 --- a/heat/engine/environment.py +++ b/heat/engine/environment.py @@ -249,7 +249,7 @@ class ResourceRegistry(object): yield self._registry[pattern] def get_resource_info(self, resource_type, resource_name=None, - registry_type=None): + registry_type=None, accept_fn=None): """Find possible matches to the resource type and name. chain the results from the global and user registry to find a match. @@ -280,10 +280,11 @@ class ResourceRegistry(object): for info in sorted(matches): match = info.get_resource_info(resource_type, resource_name) - if registry_type is None or isinstance(match, registry_type): + if ((registry_type is None or isinstance(match, registry_type)) and + (accept_fn is None or accept_fn(info))): return match - def get_class(self, resource_type, resource_name=None): + def get_class(self, resource_type, resource_name=None, accept_fn=None): if resource_type == "": msg = _('Resource "%s" has no type') % resource_name raise exception.StackValidationFailed(message=msg) @@ -296,7 +297,8 @@ class ResourceRegistry(object): raise exception.StackValidationFailed(message=msg) info = self.get_resource_info(resource_type, - resource_name=resource_name) + resource_name=resource_name, + accept_fn=accept_fn) if info is None: msg = _("Unknown resource Type : %s") % resource_type raise exception.StackValidationFailed(message=msg) diff --git a/heat/engine/resource.py b/heat/engine/resource.py index 2e3532b24..be094841e 100644 --- a/heat/engine/resource.py +++ b/heat/engine/resource.py @@ -27,6 +27,7 @@ from heat.engine import resources from heat.engine import support # import class to avoid name collisions and ugly aliasing from heat.engine.attributes import Attributes +from heat.engine import environment from heat.engine.properties import Properties from heat.openstack.common import log as logging @@ -132,9 +133,32 @@ class Resource(object): # Call is already for a subclass, so pass it through return super(Resource, cls).__new__(cls) - # Select the correct subclass to instantiate - ResourceClass = stack.env.get_class(json.get('Type'), - resource_name=name) + from heat.engine.resources import template_resource + # Select the correct subclass to instantiate. + + # Note: If the current stack is an implementation of + # a resource type (a TemplateResource mapped in the environment) + # then don't infinitely recurse by creating a child stack + # of the same type. Instead get the next match which will get + # us closer to a concrete class. + def get_ancestor_template_resources(): + """Return an ancestory list (TemplateResources only).""" + parent = stack.parent_resource + while parent is not None: + if isinstance(parent, template_resource.TemplateResource): + yield parent.template_name + parent = parent.stack.parent_resource + + ancestor_list = set(get_ancestor_template_resources()) + + def accept_class(res_info): + if not isinstance(res_info, environment.TemplateResourceInfo): + return True + return res_info.template_name not in ancestor_list + + ResourceClass = stack.env.registry.get_class(json.get('Type'), + resource_name=name, + accept_fn=accept_class) return ResourceClass(name, json, stack) def __init__(self, name, json_snippet, stack): diff --git a/heat/engine/stack_resource.py b/heat/engine/stack_resource.py index ce47dc9eb..11dd35c1d 100644 --- a/heat/engine/stack_resource.py +++ b/heat/engine/stack_resource.py @@ -113,7 +113,7 @@ class StackResource(resource.Resource): nested = parser.Stack(self.context, name, template, - environment.Environment(params), + self._nested_environment(params), disable_rollback=True, parent_resource=self, owner_id=self.stack.id) @@ -127,6 +127,23 @@ class StackResource(resource.Resource): message = exception.StackResourceLimitExceeded.msg_fmt raise exception.RequestLimitExceeded(message=message) + def _nested_environment(self, user_params): + """Build a sensible environment for the nested stack. + + This is built from the user_params and the parent stack's registry + so we can use user-defined resources within nested stacks. + """ + nested_env = environment.Environment() + nested_env.registry = self.stack.env.registry + user_env = {environment.PARAMETERS: {}} + if user_params is not None: + if environment.PARAMETERS not in user_params: + user_env[environment.PARAMETERS] = user_params + else: + user_env.update(user_params) + nested_env.load(user_env) + return nested_env + def create_with_template(self, child_template, user_params, timeout_mins=None, adopt_data=None): ''' @@ -148,7 +165,7 @@ class StackResource(resource.Resource): nested = parser.Stack(self.context, self.physical_resource_name(), template, - environment.Environment(user_params), + self._nested_environment(user_params), timeout_mins=timeout_mins, disable_rollback=True, parent_resource=self, @@ -205,7 +222,7 @@ class StackResource(resource.Resource): stack = parser.Stack(self.context, self.physical_resource_name(), template, - environment.Environment(user_params), + self._nested_environment(user_params), timeout_mins=timeout_mins, disable_rollback=True, parent_resource=self, diff --git a/heat/tests/test_provider_template.py b/heat/tests/test_provider_template.py index 122c7ef90..a8fbcbb93 100644 --- a/heat/tests/test_provider_template.py +++ b/heat/tests/test_provider_template.py @@ -14,6 +14,7 @@ import json import os import uuid +import yaml import testscenarios @@ -563,6 +564,94 @@ class ProviderTemplateTest(HeatTestCase): self.m.VerifyAll() +class NestedProvider(HeatTestCase): + """Prove that we can use the registry in a nested provider.""" + + def setUp(self): + super(NestedProvider, self).setUp() + utils.setup_dummy_db() + + def test_nested_env(self): + main_templ = ''' +heat_template_version: 2013-05-23 +resources: + secret2: + type: My::NestedSecret +outputs: + secret1: + value: { get_attr: [secret1, value] } +''' + + nested_templ = ''' +heat_template_version: 2013-05-23 +resources: + secret2: + type: My::Secret +outputs: + value: + value: { get_attr: [secret2, value] } +''' + + env_templ = ''' +resource_registry: + "My::Secret": "OS::Heat::RandomString" + "My::NestedSecret": nested.yaml +''' + + env = environment.Environment() + env.load(yaml.load(env_templ)) + templ = parser.Template(template_format.parse(main_templ), + files={'nested.yaml': nested_templ}) + stack = parser.Stack(utils.dummy_context(), + utils.random_name(), + templ, env=env) + stack.store() + stack.create() + self.assertEqual((stack.CREATE, stack.COMPLETE), stack.state) + + def test_no_infinite_recursion(self): + """Prove that we can override a python resource. + + And use that resource within the template resource. + """ + + main_templ = ''' +heat_template_version: 2013-05-23 +resources: + secret2: + type: OS::Heat::RandomString +outputs: + secret1: + value: { get_attr: [secret1, value] } +''' + + nested_templ = ''' +heat_template_version: 2013-05-23 +resources: + secret2: + type: OS::Heat::RandomString +outputs: + value: + value: { get_attr: [secret2, value] } +''' + + env_templ = ''' +resource_registry: + "OS::Heat::RandomString": nested.yaml +''' + + env = environment.Environment() + env.load(yaml.load(env_templ)) + templ = parser.Template(template_format.parse(main_templ), + files={'nested.yaml': nested_templ}) + stack = parser.Stack(utils.dummy_context(), + utils.random_name(), + templ, env=env) + stack.store() + stack.create() + self.assertEqual((stack.CREATE, stack.COMPLETE), stack.state) + + class ProviderTemplateUpdateTest(HeatTestCase): main_template = ''' HeatTemplateFormatVersion: '2012-12-12' diff --git a/heat/tests/test_stack_resource.py b/heat/tests/test_stack_resource.py index 48ed7b495..f3a1e0396 100644 --- a/heat/tests/test_stack_resource.py +++ b/heat/tests/test_stack_resource.py @@ -135,7 +135,7 @@ class StackResourceTest(HeatTestCase): self.stack = self.parent_resource.nested() self.assertEqual({"foo": "bar"}, self.stack.t.files) - @mock.patch.object(stack_resource.environment, 'Environment') + @mock.patch.object(stack_resource.StackResource, '_nested_environment') @mock.patch.object(stack_resource.parser, 'Template') @mock.patch.object(stack_resource.parser, 'Stack') def test_preview_with_implemented_child_resource(self, mock_stack_class, @@ -507,7 +507,7 @@ class StackResourceTest(HeatTestCase): parser.Template(self.templ, files={}).AndReturn(templ) self.m.StubOutWithMock(environment, 'Environment') - environment.Environment({"KeyName": "test"}).AndReturn(env) + environment.Environment().AndReturn(env) self.m.StubOutWithMock(parser, 'Stack') parser.Stack(ctx, phy_id, templ, env, timeout_mins=None, |