Merge "Convergence: Allow creating lightweight stacks"
This commit is contained in:
commit
24bcc40c1c
|
@ -198,6 +198,10 @@ class GetAtt(function.Function):
|
||||||
r = self._resource()
|
r = self._resource()
|
||||||
if (r.action in (r.CREATE, r.ADOPT, r.SUSPEND, r.RESUME, r.UPDATE)):
|
if (r.action in (r.CREATE, r.ADOPT, r.SUSPEND, r.RESUME, r.UPDATE)):
|
||||||
return r.FnGetAtt(attribute)
|
return r.FnGetAtt(attribute)
|
||||||
|
# NOTE(sirushtim): Add r.INIT to states above once convergence
|
||||||
|
# is the default.
|
||||||
|
elif r.stack.has_cache_data() and r.action == r.INIT:
|
||||||
|
return r.FnGetAtt(attribute)
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
|
@ -184,9 +184,10 @@ class Resource(object):
|
||||||
self.replaced_by = None
|
self.replaced_by = None
|
||||||
self.current_template_id = None
|
self.current_template_id = None
|
||||||
|
|
||||||
resource = stack.db_resource_get(name)
|
if not stack.has_cache_data():
|
||||||
if resource:
|
resource = stack.db_resource_get(name)
|
||||||
self._load_data(resource)
|
if resource:
|
||||||
|
self._load_data(resource)
|
||||||
|
|
||||||
def rpc_client(self):
|
def rpc_client(self):
|
||||||
'''Return a client for making engine RPC calls.'''
|
'''Return a client for making engine RPC calls.'''
|
||||||
|
@ -1095,6 +1096,9 @@ class Resource(object):
|
||||||
|
|
||||||
:results: the id or name of the resource.
|
:results: the id or name of the resource.
|
||||||
'''
|
'''
|
||||||
|
if self.stack.has_cache_data():
|
||||||
|
return self.stack.cache_data_resource_id(self.name)
|
||||||
|
|
||||||
if self.resource_id is not None:
|
if self.resource_id is not None:
|
||||||
return six.text_type(self.resource_id)
|
return six.text_type(self.resource_id)
|
||||||
else:
|
else:
|
||||||
|
@ -1115,13 +1119,18 @@ class Resource(object):
|
||||||
:param path: a list of path components to select from the attribute.
|
:param path: a list of path components to select from the attribute.
|
||||||
:returns: the attribute value.
|
:returns: the attribute value.
|
||||||
'''
|
'''
|
||||||
try:
|
if self.stack.has_cache_data():
|
||||||
attribute = self.attributes[key]
|
# Load from cache for lightweight resources.
|
||||||
except KeyError:
|
attribute = self.stack.cache_data_resource_attribute(
|
||||||
raise exception.InvalidTemplateAttribute(resource=self.name,
|
self.name, key)
|
||||||
key=key)
|
|
||||||
else:
|
else:
|
||||||
return attributes.select_from_attribute(attribute, path)
|
try:
|
||||||
|
attribute = self.attributes[key]
|
||||||
|
except KeyError:
|
||||||
|
raise exception.InvalidTemplateAttribute(resource=self.name,
|
||||||
|
key=key)
|
||||||
|
|
||||||
|
return attributes.select_from_attribute(attribute, path)
|
||||||
|
|
||||||
def FnBase64(self, data):
|
def FnBase64(self, data):
|
||||||
'''
|
'''
|
||||||
|
|
|
@ -89,11 +89,16 @@ class Stack(collections.Mapping):
|
||||||
use_stored_context=False, username=None,
|
use_stored_context=False, username=None,
|
||||||
nested_depth=0, strict_validate=True, convergence=False,
|
nested_depth=0, strict_validate=True, convergence=False,
|
||||||
current_traversal=None, tags=None, prev_raw_template_id=None,
|
current_traversal=None, tags=None, prev_raw_template_id=None,
|
||||||
current_deps=None):
|
current_deps=None, cache_data=None):
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Initialise from a context, name, Template object and (optionally)
|
Initialise from a context, name, Template object and (optionally)
|
||||||
Environment object. The database ID may also be initialised, if the
|
Environment object. The database ID may also be initialised, if the
|
||||||
stack is already in the database.
|
stack is already in the database.
|
||||||
|
|
||||||
|
Creating a stack with cache_data creates a lightweight stack which
|
||||||
|
will not load any resources from the database and resolve the
|
||||||
|
functions from the cache_data specified.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def _validate_stack_name(name):
|
def _validate_stack_name(name):
|
||||||
|
@ -135,6 +140,7 @@ class Stack(collections.Mapping):
|
||||||
self.tags = tags
|
self.tags = tags
|
||||||
self.prev_raw_template_id = prev_raw_template_id
|
self.prev_raw_template_id = prev_raw_template_id
|
||||||
self.current_deps = current_deps
|
self.current_deps = current_deps
|
||||||
|
self.cache_data = cache_data
|
||||||
|
|
||||||
if use_stored_context:
|
if use_stored_context:
|
||||||
self.context = self.stored_context()
|
self.context = self.stored_context()
|
||||||
|
@ -339,8 +345,8 @@ class Stack(collections.Mapping):
|
||||||
return deps
|
return deps
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, context, stack_id=None, stack=None,
|
def load(cls, context, stack_id=None, stack=None, show_deleted=True,
|
||||||
show_deleted=True, use_stored_context=False, force_reload=False):
|
use_stored_context=False, force_reload=False, cache_data=None):
|
||||||
'''Retrieve a Stack from the database.'''
|
'''Retrieve a Stack from the database.'''
|
||||||
if stack is None:
|
if stack is None:
|
||||||
stack = stack_object.Stack.get_by_id(
|
stack = stack_object.Stack.get_by_id(
|
||||||
|
@ -356,7 +362,8 @@ class Stack(collections.Mapping):
|
||||||
stack.refresh()
|
stack.refresh()
|
||||||
|
|
||||||
return cls._from_db(context, stack,
|
return cls._from_db(context, stack,
|
||||||
use_stored_context=use_stored_context)
|
use_stored_context=use_stored_context,
|
||||||
|
cache_data=cache_data)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load_all(cls, context, limit=None, marker=None, sort_keys=None,
|
def load_all(cls, context, limit=None, marker=None, sort_keys=None,
|
||||||
|
@ -384,7 +391,7 @@ class Stack(collections.Mapping):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_db(cls, context, stack, resolve_data=True,
|
def _from_db(cls, context, stack, resolve_data=True,
|
||||||
use_stored_context=False):
|
use_stored_context=False, cache_data=None):
|
||||||
template = tmpl.Template.load(
|
template = tmpl.Template.load(
|
||||||
context, stack.raw_template_id, stack.raw_template)
|
context, stack.raw_template_id, stack.raw_template)
|
||||||
tags = None
|
tags = None
|
||||||
|
@ -407,7 +414,7 @@ class Stack(collections.Mapping):
|
||||||
username=stack.username, convergence=stack.convergence,
|
username=stack.username, convergence=stack.convergence,
|
||||||
current_traversal=stack.current_traversal, tags=tags,
|
current_traversal=stack.current_traversal, tags=tags,
|
||||||
prev_raw_template_id=stack.prev_raw_template_id,
|
prev_raw_template_id=stack.prev_raw_template_id,
|
||||||
current_deps=stack.current_deps)
|
current_deps=stack.current_deps, cache_data=cache_data)
|
||||||
|
|
||||||
def get_kwargs_for_cloning(self, keep_status=False, only_db=False):
|
def get_kwargs_for_cloning(self, keep_status=False, only_db=False):
|
||||||
"""Get common kwargs for calling Stack() for cloning.
|
"""Get common kwargs for calling Stack() for cloning.
|
||||||
|
@ -1568,3 +1575,16 @@ class Stack(collections.Mapping):
|
||||||
# of other resources, so ensure that attributes are re-calculated
|
# of other resources, so ensure that attributes are re-calculated
|
||||||
for res in six.itervalues(self.resources):
|
for res in six.itervalues(self.resources):
|
||||||
res.attributes.reset_resolved_values()
|
res.attributes.reset_resolved_values()
|
||||||
|
|
||||||
|
def has_cache_data(self):
|
||||||
|
if self.cache_data is not None:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def cache_data_resource_id(self, resource_name):
|
||||||
|
return self.cache_data.get(resource_name, {}).get('id')
|
||||||
|
|
||||||
|
def cache_data_resource_attribute(self, resource_name, attribute_key):
|
||||||
|
return self.cache_data.get(
|
||||||
|
resource_name, {}).get('attributes', {}).get(attribute_key)
|
||||||
|
|
|
@ -109,6 +109,7 @@ class NeutronTest(common.HeatTestCase):
|
||||||
|
|
||||||
tmpl = rsrc_defn.ResourceDefinition('test_res', 'Foo')
|
tmpl = rsrc_defn.ResourceDefinition('test_res', 'Foo')
|
||||||
stack = mock.MagicMock()
|
stack = mock.MagicMock()
|
||||||
|
stack.has_cache_data = mock.Mock(return_value=False)
|
||||||
res = SomeNeutronResource('aresource', tmpl, stack)
|
res = SomeNeutronResource('aresource', tmpl, stack)
|
||||||
|
|
||||||
mock_show_resource = mock.MagicMock()
|
mock_show_resource = mock.MagicMock()
|
||||||
|
|
|
@ -232,6 +232,37 @@ class StackTest(common.HeatTestCase):
|
||||||
all_resources = list(self.stack.iter_resources(1))
|
all_resources = list(self.stack.iter_resources(1))
|
||||||
self.assertEqual(5, len(all_resources))
|
self.assertEqual(5, len(all_resources))
|
||||||
|
|
||||||
|
@mock.patch.object(stack.Stack, 'db_resource_get')
|
||||||
|
def test_iter_resources_cached(self, mock_drg):
|
||||||
|
tpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
||||||
|
'Resources':
|
||||||
|
{'A': {'Type': 'GenericResourceType'},
|
||||||
|
'B': {'Type': 'GenericResourceType'}}}
|
||||||
|
self.stack = stack.Stack(self.ctx, 'test_stack',
|
||||||
|
template.Template(tpl),
|
||||||
|
status_reason='blarg',
|
||||||
|
cache_data={})
|
||||||
|
|
||||||
|
def get_more(nested_depth=0):
|
||||||
|
yield 'X'
|
||||||
|
yield 'Y'
|
||||||
|
yield 'Z'
|
||||||
|
|
||||||
|
self.stack['A'].nested = mock.MagicMock()
|
||||||
|
self.stack['A'].nested.return_value.iter_resources = mock.MagicMock(
|
||||||
|
side_effect=get_more)
|
||||||
|
|
||||||
|
resource_generator = self.stack.iter_resources()
|
||||||
|
self.assertIsNot(resource_generator, list)
|
||||||
|
|
||||||
|
first_level_resources = list(resource_generator)
|
||||||
|
self.assertEqual(2, len(first_level_resources))
|
||||||
|
all_resources = list(self.stack.iter_resources(1))
|
||||||
|
self.assertEqual(5, len(all_resources))
|
||||||
|
|
||||||
|
# A cache supplied means we should never query the database.
|
||||||
|
self.assertFalse(mock_drg.called)
|
||||||
|
|
||||||
def test_root_stack_no_parent(self):
|
def test_root_stack_no_parent(self):
|
||||||
tpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
tpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
||||||
'Resources':
|
'Resources':
|
||||||
|
@ -295,7 +326,7 @@ class StackTest(common.HeatTestCase):
|
||||||
current_traversal=None,
|
current_traversal=None,
|
||||||
tags=mox.IgnoreArg(),
|
tags=mox.IgnoreArg(),
|
||||||
prev_raw_template_id=None,
|
prev_raw_template_id=None,
|
||||||
current_deps=None)
|
current_deps=None, cache_data=None)
|
||||||
|
|
||||||
self.m.ReplayAll()
|
self.m.ReplayAll()
|
||||||
stack.Stack.load(self.ctx, stack_id=self.stack.id)
|
stack.Stack.load(self.ctx, stack_id=self.stack.id)
|
||||||
|
@ -1865,6 +1896,72 @@ class StackTest(common.HeatTestCase):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'foo', self.stack.resources['A'].properties['a_string'])
|
'foo', self.stack.resources['A'].properties['a_string'])
|
||||||
|
|
||||||
|
@mock.patch.object(stack.Stack, 'db_resource_get')
|
||||||
|
def test_lightweight_stack_getatt(self, mock_drg):
|
||||||
|
tmpl = template.Template({
|
||||||
|
'HeatTemplateFormatVersion': '2012-12-12',
|
||||||
|
'Resources': {
|
||||||
|
'foo': {'Type': 'GenericResourceType'},
|
||||||
|
'bar': {
|
||||||
|
'Type': 'ResourceWithPropsType',
|
||||||
|
'Properties': {
|
||||||
|
'Foo': {'Fn::GetAtt': ['foo', 'bar']},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
cache_data = {'foo': {'attributes': {'bar': 'baz'}}}
|
||||||
|
tmpl_stack = stack.Stack(self.ctx, 'test', tmpl)
|
||||||
|
tmpl_stack.store()
|
||||||
|
lightweight_stack = stack.Stack.load(self.ctx, stack_id=tmpl_stack.id,
|
||||||
|
cache_data=cache_data)
|
||||||
|
|
||||||
|
# Check if the property has the appropriate resolved value.
|
||||||
|
cached_property = lightweight_stack['bar'].properties['Foo']
|
||||||
|
self.assertEqual(cached_property, 'baz')
|
||||||
|
|
||||||
|
# Make sure FnGetAtt returns the cached value.
|
||||||
|
attr_value = lightweight_stack['foo'].FnGetAtt('bar')
|
||||||
|
self.assertEqual('baz', attr_value)
|
||||||
|
|
||||||
|
# Make sure calls are not made to the database to retrieve the
|
||||||
|
# resource state.
|
||||||
|
self.assertFalse(mock_drg.called)
|
||||||
|
|
||||||
|
@mock.patch.object(stack.Stack, 'db_resource_get')
|
||||||
|
def test_lightweight_stack_getrefid(self, mock_drg):
|
||||||
|
tmpl = template.Template({
|
||||||
|
'HeatTemplateFormatVersion': '2012-12-12',
|
||||||
|
'Resources': {
|
||||||
|
'foo': {'Type': 'GenericResourceType'},
|
||||||
|
'bar': {
|
||||||
|
'Type': 'ResourceWithPropsType',
|
||||||
|
'Properties': {
|
||||||
|
'Foo': {'Ref': 'foo'},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
cache_data = {'foo': {'id': 'physical-resource-id'}}
|
||||||
|
tmpl_stack = stack.Stack(self.ctx, 'test', tmpl)
|
||||||
|
tmpl_stack.store()
|
||||||
|
lightweight_stack = stack.Stack.load(self.ctx, stack_id=tmpl_stack.id,
|
||||||
|
cache_data=cache_data)
|
||||||
|
|
||||||
|
# Check if the property has the appropriate resolved value.
|
||||||
|
cached_property = lightweight_stack['bar'].properties['Foo']
|
||||||
|
self.assertEqual(cached_property, 'physical-resource-id')
|
||||||
|
|
||||||
|
# Make sure FnGetRefId returns the cached value.
|
||||||
|
resource_id = lightweight_stack['foo'].FnGetRefId()
|
||||||
|
self.assertEqual('physical-resource-id', resource_id)
|
||||||
|
|
||||||
|
# Make sure calls are not made to the database to retrieve the
|
||||||
|
# resource state.
|
||||||
|
self.assertFalse(mock_drg.called)
|
||||||
|
|
||||||
|
|
||||||
class StackKwargsForCloningTest(common.HeatTestCase):
|
class StackKwargsForCloningTest(common.HeatTestCase):
|
||||||
scenarios = [
|
scenarios = [
|
||||||
|
|
Loading…
Reference in New Issue