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 Fixes-bug: #1297396
This commit is contained in:
parent
0262a29325
commit
37bb332f35
|
@ -248,7 +248,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.
|
||||
|
@ -279,10 +279,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)
|
||||
|
@ -295,7 +296,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)
|
||||
|
|
|
@ -22,6 +22,7 @@ from heat.common import identifier
|
|||
from heat.common import short_id
|
||||
from heat.db import api as db_api
|
||||
from heat.engine.attributes import Attributes
|
||||
from heat.engine import environment
|
||||
from heat.engine import event
|
||||
from heat.engine import function
|
||||
from heat.engine.properties import Properties
|
||||
|
@ -101,9 +102,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):
|
||||
|
|
|
@ -111,7 +111,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)
|
||||
|
@ -125,6 +125,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):
|
||||
'''
|
||||
|
@ -146,7 +163,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,
|
||||
|
@ -203,7 +220,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,
|
||||
|
|
|
@ -575,6 +575,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'
|
||||
|
|
|
@ -136,7 +136,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,
|
||||
|
@ -488,7 +488,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,
|
||||
|
|
Loading…
Reference in New Issue