summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAngus Salkeld <angus.salkeld@rackspace.com>2014-05-29 10:03:40 +1000
committerSteve Baker <sbaker@redhat.com>2014-06-25 13:48:12 +1200
commit2db68e5e923276b4882ad400b16e99c3cc065150 (patch)
tree4f97b4c0a4ae209cc1219ef35570f7b24226da5e
parent9ab75b4a70a338bd88553c6003eae5157214ae61 (diff)
downloadheat-2db68e5e923276b4882ad400b16e99c3cc065150.tar.gz
Pass the parent's registry into child stacks
We make sure that there is no infinite recursion by not repeating a TemplateResource within a resources inheritance. Change-Id: I1ffa66886923b15b52a8075089dd4042f0a55862 Closes-Bug: #1297396
-rw-r--r--heat/engine/environment.py10
-rw-r--r--heat/engine/resource.py30
-rw-r--r--heat/engine/stack_resource.py23
-rw-r--r--heat/tests/test_provider_template.py89
-rw-r--r--heat/tests/test_stack_resource.py4
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,