HOT templates get_attr allows extra attributes
The HOT templates now allows the use of extra attributes for the get_attr function. Wich this change is possible to traverse complex resource attributes. For example, getting an IP address for a server on a given network: when the network name is stable, and always known: get_attr: - my_server - networks - public - 0 or if the network name is something other than public: get_attr: - my_server - networks - get_param: my_network_name - 0 If one of the extra arguments does not match with a key or a valid index, an empty string is returned. As a bonus track the Fn::GetAttr support was removed from the HOT templates. Change-Id: Ia7361fbd10611cc796bd90e781f4ae387c970dbd Implements: blueprint hot-select
This commit is contained in:
parent
5261b5beff
commit
d246508752
@ -472,23 +472,31 @@ resource definition is shown below.
|
||||
|
||||
get_attr
|
||||
--------
|
||||
The *get_attr* function allows for referencing an attribute of a resource. At
|
||||
The *get_attr* function allows referencing an attribute of a resource. At
|
||||
runtime, it will be resolved to the value of an attribute of a resource instance
|
||||
created from the respective resource definition of the template.
|
||||
The syntax of the get_attr function is as follows:
|
||||
|
||||
::
|
||||
|
||||
get_attr: [ <resource ID>, <attribute name> ]
|
||||
get_attr:
|
||||
- <resource ID>
|
||||
- <attribute name>
|
||||
- <key/index 1> (optional)
|
||||
- <key/index 2> (optional)
|
||||
- ...
|
||||
|
||||
resource ID
|
||||
This parameter specifies the resource the attribute of which shall be
|
||||
This parameter specifies the resource for which the attributes shall be
|
||||
resolved. This resource must be defined within the *resources* section of
|
||||
the template (see also :ref:`hot_spec_resources`).
|
||||
attribute name
|
||||
This parameter specifies the attribute to be resolved.
|
||||
The attribute name is required as it specifies the attribute
|
||||
to be resolved. If the attribute returns a complex data structure
|
||||
such as a list or a map, then subsequent keys or indexes can be specified
|
||||
which navigate the data structure to return the desired value.
|
||||
|
||||
An example of using the get_attr function is shown below:
|
||||
Some examples of how to use the get_attr function are shown below:
|
||||
|
||||
::
|
||||
|
||||
@ -501,7 +509,18 @@ An example of using the get_attr function is shown below:
|
||||
instance_ip:
|
||||
description: IP address of the deployed compute instance
|
||||
value: { get_attr: [my_instance, first_address] }
|
||||
instance_private_ip:
|
||||
description: Private IP address of the deployed compute instance
|
||||
value: { get_attr: [my_instance, networks, private, 0] }
|
||||
|
||||
In this example, if the networks attribute contained the following data:
|
||||
|
||||
::
|
||||
|
||||
{"public": ["2001:0db8:0000:0000:0000:ff00:0042:8329", "1.2.3.4"],
|
||||
"private": ["10.0.0.1"]}
|
||||
|
||||
then the value of the get_attr function would resolve to "10.0.0.1".
|
||||
|
||||
get_resource
|
||||
------------
|
||||
|
@ -165,14 +165,14 @@ class HOTemplate(template.Template):
|
||||
Resolve constructs of the form { get_attr: [my_resource, my_attr] }
|
||||
"""
|
||||
def match_get_attr(key, value):
|
||||
return (key in ['get_attr', 'Fn::GetAtt'] and
|
||||
return (key in ['get_attr'] and
|
||||
isinstance(value, list) and
|
||||
len(value) == 2 and
|
||||
len(value) >= 2 and
|
||||
None not in value and
|
||||
value[0] in resources)
|
||||
|
||||
def handle_get_attr(args):
|
||||
resource, att = args
|
||||
resource = args[0]
|
||||
try:
|
||||
r = resources[resource]
|
||||
if r.state in (
|
||||
@ -182,10 +182,20 @@ class HOTemplate(template.Template):
|
||||
(r.RESUME, r.COMPLETE),
|
||||
(r.UPDATE, r.IN_PROGRESS),
|
||||
(r.UPDATE, r.COMPLETE)):
|
||||
return r.FnGetAtt(att)
|
||||
except KeyError:
|
||||
rsrc_attr = args[1]
|
||||
attr = r.FnGetAtt(rsrc_attr)
|
||||
try:
|
||||
for inner_attr in args[2:]:
|
||||
if hasattr(attr, str(inner_attr)):
|
||||
attr = getattr(attr, inner_attr)
|
||||
else:
|
||||
attr = attr[inner_attr]
|
||||
return attr
|
||||
except (KeyError, IndexError, TypeError):
|
||||
return ''
|
||||
except (KeyError, IndexError):
|
||||
raise exception.InvalidTemplateAttribute(resource=resource,
|
||||
key=att)
|
||||
key=rsrc_attr)
|
||||
|
||||
return template._resolve(match_get_attr, handle_get_attr, s,
|
||||
transform)
|
||||
|
@ -54,12 +54,51 @@ class GenericResource(resource.Resource):
|
||||
|
||||
|
||||
class ResourceWithProps(GenericResource):
|
||||
properties_schema = {'Foo': {'Type': 'String'}}
|
||||
properties_schema = {'Foo': {'Type': 'String'}}
|
||||
|
||||
|
||||
class ResourceWithComplexAttributes(GenericResource):
|
||||
attributes_schema = {'list': 'A list',
|
||||
'flat_dict': 'A flat dictionary',
|
||||
'nested_dict': 'A nested dictionary',
|
||||
'simple_object': 'An object',
|
||||
'complex_object': 'A really complex object',
|
||||
'none': 'A None'
|
||||
}
|
||||
|
||||
list = ['foo', 'bar']
|
||||
flat_dict = {'key1': 'val1', 'key2': 'val2', 'key3': 'val3'}
|
||||
nested_dict = {'list': [1, 2, 3],
|
||||
'string': 'abc',
|
||||
'dict': {'a': 1, 'b': 2, 'c': 3}}
|
||||
|
||||
class AnObject(object):
|
||||
def __init__(self, first, second, third):
|
||||
self.first = first
|
||||
self.second = second
|
||||
self.third = third
|
||||
|
||||
simple_object = AnObject('a', 'b', 'c')
|
||||
complex_object = AnObject('a', flat_dict, simple_object)
|
||||
|
||||
def _resolve_attribute(self, name):
|
||||
if name == 'list':
|
||||
return self.list
|
||||
if name == 'flat_dict':
|
||||
return self.flat_dict
|
||||
if name == 'nested_dict':
|
||||
return self.nested_dict
|
||||
if name == 'simple_object':
|
||||
return self.simple_object
|
||||
if name == 'complex_object':
|
||||
return self.complex_object
|
||||
if name == 'none':
|
||||
return None
|
||||
|
||||
|
||||
class ResourceWithRequiredProps(GenericResource):
|
||||
properties_schema = {'Foo': {'Type': 'String',
|
||||
'Required': True}}
|
||||
properties_schema = {'Foo': {'Type': 'String',
|
||||
'Required': True}}
|
||||
|
||||
|
||||
class SignalResource(signal_responder.SignalResponder):
|
||||
|
@ -14,6 +14,7 @@
|
||||
from heat.common import template_format
|
||||
from heat.common import exception
|
||||
from heat.engine import parser
|
||||
from heat.engine import resource
|
||||
from heat.engine import hot
|
||||
from heat.engine import parameters
|
||||
from heat.engine import template
|
||||
@ -22,12 +23,27 @@ from heat.engine import constraints
|
||||
from heat.tests.common import HeatTestCase
|
||||
from heat.tests import test_parser
|
||||
from heat.tests import utils
|
||||
from heat.tests import generic_resource as generic_rsrc
|
||||
|
||||
|
||||
hot_tpl_empty = template_format.parse('''
|
||||
heat_template_version: 2013-05-23
|
||||
''')
|
||||
|
||||
hot_tpl_generic_resource = template_format.parse('''
|
||||
heat_template_version: 2013-05-23
|
||||
resources:
|
||||
resource1:
|
||||
type: GenericResourceType
|
||||
''')
|
||||
|
||||
hot_tpl_complex_attrs = template_format.parse('''
|
||||
heat_template_version: 2013-05-23
|
||||
resources:
|
||||
resource1:
|
||||
type: ResourceWithComplexAttributesType
|
||||
''')
|
||||
|
||||
|
||||
class HOTemplateTest(HeatTestCase):
|
||||
"""Test processing of HOT templates."""
|
||||
@ -195,16 +211,10 @@ class StackTest(test_parser.StackTest):
|
||||
"""Test stack function when stack was created from HOT template."""
|
||||
|
||||
@utils.stack_delete_after
|
||||
def test_get_attr(self):
|
||||
def test_get_attr_multiple_rsrc_status(self):
|
||||
"""Test resolution of get_attr occurrences in HOT template."""
|
||||
|
||||
hot_tpl = template_format.parse('''
|
||||
heat_template_version: 2013-05-23
|
||||
resources:
|
||||
resource1:
|
||||
type: GenericResourceType
|
||||
''')
|
||||
|
||||
hot_tpl = hot_tpl_generic_resource
|
||||
self.stack = parser.Stack(self.ctx, 'test_get_attr',
|
||||
template.Template(hot_tpl))
|
||||
self.stack.store()
|
||||
@ -227,27 +237,45 @@ class StackTest(test_parser.StackTest):
|
||||
# GenericResourceType has an attribute 'foo' which yields the
|
||||
# resource name.
|
||||
self.assertEqual({'Value': 'resource1'}, resolved)
|
||||
# test invalid reference
|
||||
|
||||
@utils.stack_delete_after
|
||||
def test_get_attr_invalid(self):
|
||||
"""Test resolution of get_attr occurrences in HOT template."""
|
||||
|
||||
hot_tpl = hot_tpl_generic_resource
|
||||
self.stack = parser.Stack(self.ctx, 'test_get_attr',
|
||||
template.Template(hot_tpl))
|
||||
self.stack.store()
|
||||
self.stack.create()
|
||||
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
|
||||
self.stack.state)
|
||||
self.assertRaises(exception.InvalidTemplateAttribute,
|
||||
hot.HOTemplate.resolve_attributes,
|
||||
{'Value': {'get_attr': ['resource1', 'NotThere']}},
|
||||
self.stack)
|
||||
|
||||
snippet = {'Value': {'Fn::GetAtt': ['resource1', 'foo']}}
|
||||
@utils.stack_delete_after
|
||||
def test_get_attr_invalid_resource(self):
|
||||
"""Test resolution of get_attr occurrences in HOT template."""
|
||||
|
||||
hot_tpl = hot_tpl_complex_attrs
|
||||
self.stack = parser.Stack(self.ctx,
|
||||
'test_get_attr_invalid_none',
|
||||
template.Template(hot_tpl))
|
||||
self.stack.store()
|
||||
self.stack.create()
|
||||
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
|
||||
self.stack.state)
|
||||
|
||||
snippet = {'Value': {'get_attr': ['resource2', 'who_cares']}}
|
||||
resolved = hot.HOTemplate.resolve_attributes(snippet, self.stack)
|
||||
self.assertEqual({'Value': 'resource1'}, resolved)
|
||||
self.assertEqual(snippet, resolved)
|
||||
|
||||
@utils.stack_delete_after
|
||||
def test_get_resource(self):
|
||||
"""Test resolution of get_resource occurrences in HOT template."""
|
||||
|
||||
hot_tpl = template_format.parse('''
|
||||
heat_template_version: 2013-05-23
|
||||
resources:
|
||||
resource1:
|
||||
type: GenericResourceType
|
||||
''')
|
||||
|
||||
hot_tpl = hot_tpl_generic_resource
|
||||
self.stack = parser.Stack(self.ctx, 'test_get_resource',
|
||||
template.Template(hot_tpl))
|
||||
self.stack.store()
|
||||
@ -260,6 +288,131 @@ class StackTest(test_parser.StackTest):
|
||||
self.assertEqual({'value': 'resource1'}, resolved)
|
||||
|
||||
|
||||
class StackAttributesTest(HeatTestCase):
|
||||
"""
|
||||
Test stack get_attr function when stack was created from HOT template.
|
||||
"""
|
||||
def setUp(self):
|
||||
super(StackAttributesTest, self).setUp()
|
||||
|
||||
utils.setup_dummy_db()
|
||||
self.ctx = utils.dummy_context()
|
||||
|
||||
resource._register_class('GenericResourceType',
|
||||
generic_rsrc.GenericResource)
|
||||
resource._register_class('ResourceWithComplexAttributesType',
|
||||
generic_rsrc.ResourceWithComplexAttributes)
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
scenarios = [
|
||||
('get_flat_attr',
|
||||
dict(hot_tpl=hot_tpl_generic_resource,
|
||||
snippet={'Value': {'get_attr': ['resource1', 'foo']}},
|
||||
resource_name='resource1',
|
||||
expected={'Value': 'resource1'})),
|
||||
('get_list_attr',
|
||||
dict(hot_tpl=hot_tpl_complex_attrs,
|
||||
snippet={'Value': {'get_attr': ['resource1', 'list', 0]}},
|
||||
resource_name='resource1',
|
||||
expected={
|
||||
'Value':
|
||||
generic_rsrc.ResourceWithComplexAttributes.list[0]})),
|
||||
('get_flat_dict_attr',
|
||||
dict(hot_tpl=hot_tpl_complex_attrs,
|
||||
snippet={'Value': {'get_attr': ['resource1',
|
||||
'flat_dict',
|
||||
'key2']}},
|
||||
resource_name='resource1',
|
||||
expected={
|
||||
'Value':
|
||||
generic_rsrc.ResourceWithComplexAttributes.
|
||||
flat_dict['key2']})),
|
||||
('get_nested_attr_list',
|
||||
dict(hot_tpl=hot_tpl_complex_attrs,
|
||||
snippet={'Value': {'get_attr': ['resource1',
|
||||
'nested_dict',
|
||||
'list',
|
||||
0]}},
|
||||
resource_name='resource1',
|
||||
expected={
|
||||
'Value':
|
||||
generic_rsrc.ResourceWithComplexAttributes.
|
||||
nested_dict['list'][0]})),
|
||||
('get_nested_attr_dict',
|
||||
dict(hot_tpl=hot_tpl_complex_attrs,
|
||||
snippet={'Value': {'get_attr': ['resource1',
|
||||
'nested_dict',
|
||||
'dict',
|
||||
'a']}},
|
||||
resource_name='resource1',
|
||||
expected={
|
||||
'Value':
|
||||
generic_rsrc.ResourceWithComplexAttributes.
|
||||
nested_dict['dict']['a']})),
|
||||
('get_simple_object',
|
||||
dict(hot_tpl=hot_tpl_complex_attrs,
|
||||
snippet={'Value': {'get_attr': ['resource1',
|
||||
'simple_object',
|
||||
'first']}},
|
||||
resource_name='resource1',
|
||||
expected={
|
||||
'Value':
|
||||
generic_rsrc.ResourceWithComplexAttributes.
|
||||
simple_object.first})),
|
||||
('get_complex_object',
|
||||
dict(hot_tpl=hot_tpl_complex_attrs,
|
||||
snippet={'Value': {'get_attr': ['resource1',
|
||||
'complex_object',
|
||||
'second',
|
||||
'key1']}},
|
||||
resource_name='resource1',
|
||||
expected={
|
||||
'Value':
|
||||
generic_rsrc.ResourceWithComplexAttributes.
|
||||
complex_object.second['key1']})),
|
||||
('get_complex_object_invalid_argument',
|
||||
dict(hot_tpl=hot_tpl_complex_attrs,
|
||||
snippet={'Value': {'get_attr': ['resource1',
|
||||
'complex_object',
|
||||
'not_there']}},
|
||||
resource_name='resource1',
|
||||
expected={'Value': ''})),
|
||||
('get_attr_none',
|
||||
dict(hot_tpl=hot_tpl_complex_attrs,
|
||||
snippet={'Value': {'get_attr': ['resource1',
|
||||
'none',
|
||||
'who_cares']}},
|
||||
resource_name='resource1',
|
||||
expected={'Value': ''}))
|
||||
]
|
||||
|
||||
@utils.stack_delete_after
|
||||
def test_get_attr(self):
|
||||
"""Test resolution of get_attr occurrences in HOT template."""
|
||||
|
||||
self.stack = parser.Stack(self.ctx, 'test_get_attr',
|
||||
template.Template(self.hot_tpl))
|
||||
self.stack.store()
|
||||
self.stack.create()
|
||||
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
|
||||
self.stack.state)
|
||||
|
||||
rsrc = self.stack[self.resource_name]
|
||||
for action, status in (
|
||||
(rsrc.CREATE, rsrc.IN_PROGRESS),
|
||||
(rsrc.CREATE, rsrc.COMPLETE),
|
||||
(rsrc.RESUME, rsrc.IN_PROGRESS),
|
||||
(rsrc.RESUME, rsrc.COMPLETE),
|
||||
(rsrc.UPDATE, rsrc.IN_PROGRESS),
|
||||
(rsrc.UPDATE, rsrc.COMPLETE)):
|
||||
rsrc.state_set(action, status)
|
||||
|
||||
resolved = hot.HOTemplate.resolve_attributes(self.snippet,
|
||||
self.stack)
|
||||
self.assertEqual(self.expected, resolved)
|
||||
|
||||
|
||||
class HOTParamValidatorTest(HeatTestCase):
|
||||
"""Test HOTParamValidator"""
|
||||
|
||||
|
@ -714,6 +714,8 @@ class StackTest(HeatTestCase):
|
||||
generic_rsrc.GenericResource)
|
||||
resource._register_class('ResourceWithPropsType',
|
||||
generic_rsrc.ResourceWithProps)
|
||||
resource._register_class('ResourceWithComplexAttributesType',
|
||||
generic_rsrc.ResourceWithComplexAttributes)
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user