Remove the template mapping from the env before creating child stacks
Instead of going back through the stack linage, just remove the template resource "mapping" from the environment when we go to create the child environment. This is a lot more efficient when creating stacks with RPC. We don't want to reload every stack in each stack. part of blueprint decouple-nested Change-Id: Ic93d08e41d2214e51c836be306a500e5c3939f0f
This commit is contained in:
parent
a63187a9da
commit
8bc93e286b
|
@ -106,8 +106,7 @@ class ClassResourceInfo(ResourceInfo):
|
||||||
|
|
||||||
|
|
||||||
class TemplateResourceInfo(ResourceInfo):
|
class TemplateResourceInfo(ResourceInfo):
|
||||||
"""Store the info needed to start a TemplateResource.
|
"""Store the info needed to start a TemplateResource."""
|
||||||
"""
|
|
||||||
description = 'Template'
|
description = 'Template'
|
||||||
|
|
||||||
def __init__(self, registry, path, value):
|
def __init__(self, registry, path, value):
|
||||||
|
@ -128,6 +127,7 @@ class TemplateResourceInfo(ResourceInfo):
|
||||||
|
|
||||||
class MapResourceInfo(ResourceInfo):
|
class MapResourceInfo(ResourceInfo):
|
||||||
"""Store the mapping of one resource type to another.
|
"""Store the mapping of one resource type to another.
|
||||||
|
|
||||||
like: OS::Networking::FloatingIp -> OS::Neutron::FloatingIp
|
like: OS::Networking::FloatingIp -> OS::Neutron::FloatingIp
|
||||||
"""
|
"""
|
||||||
description = 'Mapping'
|
description = 'Mapping'
|
||||||
|
@ -141,6 +141,7 @@ class MapResourceInfo(ResourceInfo):
|
||||||
|
|
||||||
class GlobResourceInfo(MapResourceInfo):
|
class GlobResourceInfo(MapResourceInfo):
|
||||||
"""Store the mapping (with wild cards) of one resource type to another.
|
"""Store the mapping (with wild cards) of one resource type to another.
|
||||||
|
|
||||||
like: OS::Networking::* -> OS::Neutron::*
|
like: OS::Networking::* -> OS::Neutron::*
|
||||||
"""
|
"""
|
||||||
description = 'Wildcard Mapping'
|
description = 'Wildcard Mapping'
|
||||||
|
@ -231,6 +232,16 @@ class ResourceRegistry(object):
|
||||||
info.user_resource = (self.global_registry is not None)
|
info.user_resource = (self.global_registry is not None)
|
||||||
registry[name] = info
|
registry[name] = info
|
||||||
|
|
||||||
|
def remove_item(self, info):
|
||||||
|
if not isinstance(info, TemplateResourceInfo):
|
||||||
|
return
|
||||||
|
|
||||||
|
registry = self._registry
|
||||||
|
for key in info.path[:-1]:
|
||||||
|
registry = registry[key]
|
||||||
|
if info.path[-1] in registry:
|
||||||
|
registry.pop(info.path[-1])
|
||||||
|
|
||||||
def iterable_by(self, resource_type, resource_name=None):
|
def iterable_by(self, resource_type, resource_name=None):
|
||||||
is_templ_type = resource_type.endswith(('.yaml', '.template'))
|
is_templ_type = resource_type.endswith(('.yaml', '.template'))
|
||||||
if self.global_registry is not None and is_templ_type:
|
if self.global_registry is not None and is_templ_type:
|
||||||
|
@ -263,7 +274,7 @@ class ResourceRegistry(object):
|
||||||
yield self._registry[pattern]
|
yield self._registry[pattern]
|
||||||
|
|
||||||
def get_resource_info(self, resource_type, resource_name=None,
|
def get_resource_info(self, resource_type, resource_name=None,
|
||||||
registry_type=None, accept_fn=None):
|
registry_type=None):
|
||||||
"""Find possible matches to the resource type and name.
|
"""Find possible matches to the resource type and name.
|
||||||
chain the results from the global and user registry to find
|
chain the results from the global and user registry to find
|
||||||
a match.
|
a match.
|
||||||
|
@ -294,11 +305,10 @@ class ResourceRegistry(object):
|
||||||
for info in sorted(matches):
|
for info in sorted(matches):
|
||||||
match = info.get_resource_info(resource_type,
|
match = info.get_resource_info(resource_type,
|
||||||
resource_name)
|
resource_name)
|
||||||
if ((registry_type is None or isinstance(match, registry_type)) and
|
if ((registry_type is None or isinstance(match, registry_type))):
|
||||||
(accept_fn is None or accept_fn(info))):
|
|
||||||
return match
|
return match
|
||||||
|
|
||||||
def get_class(self, resource_type, resource_name=None, accept_fn=None):
|
def get_class(self, resource_type, resource_name=None):
|
||||||
if resource_type == "":
|
if resource_type == "":
|
||||||
msg = _('Resource "%s" has no type') % resource_name
|
msg = _('Resource "%s" has no type') % resource_name
|
||||||
raise exception.StackValidationFailed(message=msg)
|
raise exception.StackValidationFailed(message=msg)
|
||||||
|
@ -311,8 +321,7 @@ class ResourceRegistry(object):
|
||||||
raise exception.StackValidationFailed(message=msg)
|
raise exception.StackValidationFailed(message=msg)
|
||||||
|
|
||||||
info = self.get_resource_info(resource_type,
|
info = self.get_resource_info(resource_type,
|
||||||
resource_name=resource_name,
|
resource_name=resource_name)
|
||||||
accept_fn=accept_fn)
|
|
||||||
if info is None:
|
if info is None:
|
||||||
msg = _("Unknown resource Type : %s") % resource_type
|
msg = _("Unknown resource Type : %s") % resource_type
|
||||||
raise exception.StackValidationFailed(message=msg)
|
raise exception.StackValidationFailed(message=msg)
|
||||||
|
@ -436,25 +445,39 @@ class Environment(object):
|
||||||
return self.stack_lifecycle_plugins
|
return self.stack_lifecycle_plugins
|
||||||
|
|
||||||
|
|
||||||
def get_child_environment(parent_env, child_params):
|
def get_child_environment(parent_env, child_params, item_to_remove=None):
|
||||||
"""Build a child environment using the parent environment and params.
|
"""Build a child environment using the parent environment and params.
|
||||||
|
|
||||||
This is built from the child_params and the parent env so some
|
This is built from the child_params and the parent env so some
|
||||||
resources can use user-provided parameters as if they come from an
|
resources can use user-provided parameters as if they come from an
|
||||||
environment.
|
environment.
|
||||||
|
|
||||||
|
1. resource_registry must be merged (child env should be loaded after the
|
||||||
|
parent env to take presdence).
|
||||||
|
2. child parameters must overwrite the parent's as they won't be relevant
|
||||||
|
in the child template.
|
||||||
"""
|
"""
|
||||||
|
def is_flat_params(env_or_param):
|
||||||
|
if env_or_param is None:
|
||||||
|
return False
|
||||||
|
for sect in env_fmt.SECTIONS:
|
||||||
|
if sect in env_or_param:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
child_env = parent_env.user_env_as_dict()
|
||||||
|
child_env[env_fmt.PARAMETERS] = {}
|
||||||
|
flat_params = is_flat_params(child_params)
|
||||||
new_env = Environment()
|
new_env = Environment()
|
||||||
new_env.registry = copy.copy(parent_env.registry)
|
if flat_params and child_params is not None:
|
||||||
new_env.environment = new_env
|
|
||||||
child_env = {
|
|
||||||
env_fmt.PARAMETERS: {},
|
|
||||||
env_fmt.PARAMETER_DEFAULTS: parent_env.param_defaults}
|
|
||||||
if child_params is not None:
|
|
||||||
if env_fmt.PARAMETERS not in child_params:
|
|
||||||
child_env[env_fmt.PARAMETERS] = child_params
|
child_env[env_fmt.PARAMETERS] = child_params
|
||||||
else:
|
|
||||||
child_env.update(child_params)
|
|
||||||
|
|
||||||
new_env.load(child_env)
|
new_env.load(child_env)
|
||||||
|
if not flat_params and child_params is not None:
|
||||||
|
new_env.load(child_params)
|
||||||
|
|
||||||
|
if item_to_remove is not None:
|
||||||
|
new_env.registry.remove_item(item_to_remove)
|
||||||
return new_env
|
return new_env
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,6 @@ from heat.common import short_id
|
||||||
from heat.common import timeutils
|
from heat.common import timeutils
|
||||||
from heat.db import api as db_api
|
from heat.db import api as db_api
|
||||||
from heat.engine import attributes
|
from heat.engine import attributes
|
||||||
from heat.engine import environment
|
|
||||||
from heat.engine import event
|
from heat.engine import event
|
||||||
from heat.engine import function
|
from heat.engine import function
|
||||||
from heat.engine import properties
|
from heat.engine import properties
|
||||||
|
@ -125,33 +124,11 @@ class Resource(object):
|
||||||
ResourceClass = cls
|
ResourceClass = cls
|
||||||
else:
|
else:
|
||||||
from heat.engine.resources import template_resource
|
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 ancestry 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
|
|
||||||
|
|
||||||
registry = stack.env.registry
|
registry = stack.env.registry
|
||||||
try:
|
try:
|
||||||
ResourceClass = registry.get_class(definition.resource_type,
|
ResourceClass = registry.get_class(definition.resource_type,
|
||||||
resource_name=name,
|
resource_name=name)
|
||||||
accept_fn=accept_class)
|
|
||||||
except exception.NotFound:
|
except exception.NotFound:
|
||||||
ResourceClass = template_resource.TemplateResource
|
ResourceClass = template_resource.TemplateResource
|
||||||
assert issubclass(ResourceClass, Resource)
|
assert issubclass(ResourceClass, Resource)
|
||||||
|
|
|
@ -70,6 +70,7 @@ class TemplateResource(stack_resource.StackResource):
|
||||||
self.properties_schema = {}
|
self.properties_schema = {}
|
||||||
self.attributes_schema = {}
|
self.attributes_schema = {}
|
||||||
super(TemplateResource, self).__init__(name, json_snippet, stack)
|
super(TemplateResource, self).__init__(name, json_snippet, stack)
|
||||||
|
self.resource_info = tri
|
||||||
if self.validation_exception is None:
|
if self.validation_exception is None:
|
||||||
self._generate_schema(self.t)
|
self._generate_schema(self.t)
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,7 @@ class StackResource(resource.Resource):
|
||||||
def __init__(self, name, json_snippet, stack):
|
def __init__(self, name, json_snippet, stack):
|
||||||
super(StackResource, self).__init__(name, json_snippet, stack)
|
super(StackResource, self).__init__(name, json_snippet, stack)
|
||||||
self._nested = None
|
self._nested = None
|
||||||
|
self.resource_info = None
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
super(StackResource, self).validate()
|
super(StackResource, self).validate()
|
||||||
|
@ -177,8 +178,10 @@ class StackResource(resource.Resource):
|
||||||
|
|
||||||
if child_params is None:
|
if child_params is None:
|
||||||
child_params = self.child_params()
|
child_params = self.child_params()
|
||||||
|
|
||||||
child_env = environment.get_child_environment(
|
child_env = environment.get_child_environment(
|
||||||
self.stack.env, child_params)
|
self.stack.env, child_params,
|
||||||
|
item_to_remove=self.resource_info)
|
||||||
|
|
||||||
# Note we disable rollback for nested stacks, since they
|
# Note we disable rollback for nested stacks, since they
|
||||||
# should be rolled back by the parent stack on failure
|
# should be rolled back by the parent stack on failure
|
||||||
|
|
|
@ -457,3 +457,94 @@ class GlobalEnvLoadingTest(common.HeatTestCase):
|
||||||
call_list.sort()
|
call_list.sort()
|
||||||
|
|
||||||
self.assertEqual(expected, call_list)
|
self.assertEqual(expected, call_list)
|
||||||
|
|
||||||
|
|
||||||
|
class ChildEnvTest(common.HeatTestCase):
|
||||||
|
|
||||||
|
def test_params_flat(self):
|
||||||
|
new_params = {'foo': 'bar', 'tester': 'Yes'}
|
||||||
|
penv = environment.Environment()
|
||||||
|
expected = {'parameters': new_params,
|
||||||
|
'parameter_defaults': {},
|
||||||
|
'resource_registry': {'resources': {}}}
|
||||||
|
cenv = environment.get_child_environment(penv, new_params)
|
||||||
|
self.assertEqual(expected, cenv.user_env_as_dict())
|
||||||
|
|
||||||
|
def test_params_normal(self):
|
||||||
|
new_params = {'parameters': {'foo': 'bar', 'tester': 'Yes'}}
|
||||||
|
penv = environment.Environment()
|
||||||
|
expected = {'parameter_defaults': {},
|
||||||
|
'resource_registry': {'resources': {}}}
|
||||||
|
expected.update(new_params)
|
||||||
|
cenv = environment.get_child_environment(penv, new_params)
|
||||||
|
self.assertEqual(expected, cenv.user_env_as_dict())
|
||||||
|
|
||||||
|
def test_params_parent_overwritten(self):
|
||||||
|
new_params = {'parameters': {'foo': 'bar', 'tester': 'Yes'}}
|
||||||
|
parent_params = {'parameters': {'gone': 'hopefully'}}
|
||||||
|
penv = environment.Environment(env=parent_params)
|
||||||
|
expected = {'parameter_defaults': {},
|
||||||
|
'resource_registry': {'resources': {}}}
|
||||||
|
expected.update(new_params)
|
||||||
|
cenv = environment.get_child_environment(penv, new_params)
|
||||||
|
self.assertEqual(expected, cenv.user_env_as_dict())
|
||||||
|
|
||||||
|
def test_registry_merge_simple(self):
|
||||||
|
env1 = {u'resource_registry': {u'OS::Food': u'fruity.yaml'}}
|
||||||
|
env2 = {u'resource_registry': {u'OS::Fruit': u'apples.yaml'}}
|
||||||
|
penv = environment.Environment(env=env1)
|
||||||
|
cenv = environment.get_child_environment(penv, env2)
|
||||||
|
rr = cenv.user_env_as_dict()['resource_registry']
|
||||||
|
self.assertIn('OS::Food', rr)
|
||||||
|
self.assertIn('OS::Fruit', rr)
|
||||||
|
|
||||||
|
def test_registry_merge_favor_child(self):
|
||||||
|
env1 = {u'resource_registry': {u'OS::Food': u'carrots.yaml'}}
|
||||||
|
env2 = {u'resource_registry': {u'OS::Food': u'apples.yaml'}}
|
||||||
|
penv = environment.Environment(env=env1)
|
||||||
|
cenv = environment.get_child_environment(penv, env2)
|
||||||
|
res = cenv.get_resource_info('OS::Food')
|
||||||
|
self.assertEqual('apples.yaml', res.value)
|
||||||
|
|
||||||
|
def test_item_to_remove_simple(self):
|
||||||
|
env = {u'resource_registry': {u'OS::Food': u'fruity.yaml'}}
|
||||||
|
penv = environment.Environment(env)
|
||||||
|
victim = penv.get_resource_info('OS::Food', resource_name='abc')
|
||||||
|
self.assertIsNotNone(victim)
|
||||||
|
cenv = environment.get_child_environment(penv, None,
|
||||||
|
item_to_remove=victim)
|
||||||
|
res = cenv.get_resource_info('OS::Food', resource_name='abc')
|
||||||
|
self.assertIsNone(res)
|
||||||
|
self.assertNotIn('OS::Food',
|
||||||
|
cenv.user_env_as_dict()['resource_registry'])
|
||||||
|
# make sure the parent env is uneffected
|
||||||
|
innocent = penv.get_resource_info('OS::Food', resource_name='abc')
|
||||||
|
self.assertIsNotNone(innocent)
|
||||||
|
|
||||||
|
def test_item_to_remove_complex(self):
|
||||||
|
env = {u'resource_registry': {u'OS::Food': u'fruity.yaml',
|
||||||
|
u'resources': {u'abc': {
|
||||||
|
u'OS::Food': u'nutty.yaml'}}}}
|
||||||
|
penv = environment.Environment(env)
|
||||||
|
# the victim we want is the most specific one.
|
||||||
|
victim = penv.get_resource_info('OS::Food', resource_name='abc')
|
||||||
|
self.assertEqual(['resources', 'abc', 'OS::Food'], victim.path)
|
||||||
|
cenv = environment.get_child_environment(penv, None,
|
||||||
|
item_to_remove=victim)
|
||||||
|
res = cenv.get_resource_info('OS::Food', resource_name='abc')
|
||||||
|
self.assertEqual(['OS::Food'], res.path)
|
||||||
|
rr = cenv.user_env_as_dict()['resource_registry']
|
||||||
|
self.assertIn('OS::Food', rr)
|
||||||
|
self.assertNotIn('OS::Food', rr['resources']['abc'])
|
||||||
|
# make sure the parent env is uneffected
|
||||||
|
innocent2 = penv.get_resource_info('OS::Food', resource_name='abc')
|
||||||
|
self.assertEqual(['resources', 'abc', 'OS::Food'], innocent2.path)
|
||||||
|
|
||||||
|
def test_item_to_remove_none(self):
|
||||||
|
env = {u'resource_registry': {u'OS::Food': u'fruity.yaml'}}
|
||||||
|
penv = environment.Environment(env)
|
||||||
|
victim = penv.get_resource_info('OS::Food', resource_name='abc')
|
||||||
|
self.assertIsNotNone(victim)
|
||||||
|
cenv = environment.get_child_environment(penv, None)
|
||||||
|
res = cenv.get_resource_info('OS::Food', resource_name='abc')
|
||||||
|
self.assertIsNotNone(res)
|
||||||
|
|
|
@ -239,7 +239,7 @@ class StackResourceTest(common.HeatTestCase):
|
||||||
|
|
||||||
result = parent_resource.preview()
|
result = parent_resource.preview()
|
||||||
mock_env_class.assert_called_once_with(self.parent_stack.env,
|
mock_env_class.assert_called_once_with(self.parent_stack.env,
|
||||||
params)
|
params, item_to_remove=None)
|
||||||
self.assertEqual('preview_nested_stack', result)
|
self.assertEqual('preview_nested_stack', result)
|
||||||
mock_stack_class.assert_called_once_with(
|
mock_stack_class.assert_called_once_with(
|
||||||
mock.ANY,
|
mock.ANY,
|
||||||
|
@ -279,7 +279,7 @@ class StackResourceTest(common.HeatTestCase):
|
||||||
|
|
||||||
result = parent_resource.preview()
|
result = parent_resource.preview()
|
||||||
mock_env_class.assert_called_once_with(self.parent_stack.env,
|
mock_env_class.assert_called_once_with(self.parent_stack.env,
|
||||||
params)
|
params, item_to_remove=None)
|
||||||
self.assertEqual('preview_nested_stack', result)
|
self.assertEqual('preview_nested_stack', result)
|
||||||
mock_stack_class.assert_called_once_with(
|
mock_stack_class.assert_called_once_with(
|
||||||
mock.ANY,
|
mock.ANY,
|
||||||
|
|
Loading…
Reference in New Issue