Support attributes with dynamic scheme

Some resources (software deployment, template resources, etc)
doesn't have static attribute scheme. So when we are trying
to output attributes for such resources the output always
returns None or null. So the patch support this functionality
with the new type of attributes: dynamic scheme attributes.
They allow to resolve dynamic attributes for resource outputs
and fetch such info as nested stack attrs or
software deployment outputs.
The next patch will enable usage of dynamic scheme attributes
for heat resource.

Change-Id: I474addad0bbfe16aa5c562f2d6025c52f175dd4f
Partial-Bug: #1470675
This commit is contained in:
Angus Salkeld 2015-08-04 13:00:40 +10:00 committed by kairat_kushaev
parent 4d370367a8
commit 15e52ff5e9
5 changed files with 94 additions and 3 deletions

View File

@ -236,6 +236,34 @@ class Attributes(collections.Mapping):
'\n\t'.join(six.itervalues(self)))
class DynamicSchemeAttributes(Attributes):
"""The collection of attributes for resources without static attr scheme.
The class defines collection of attributes for such entities as Resource
Group, Software Deployment and so on that doesn't have static attribute
scheme. The attribute scheme for such kind of resources can contain
attribute from attribute scheme (like other resources) and dynamic
attributes (nested stack attrs or API response attrs).
"""
def __getitem__(self, key):
try:
# check if the value can be resolved with attributes
# in attributes schema (static attributes)
return super(DynamicSchemeAttributes, self).__getitem__(key)
except KeyError:
# ok, the attribute is not present in attribute scheme
# try to check the attributes dynamically
if key in self._resolved_values:
return self._resolved_values[key]
value = self._resolver(key)
if value is not None:
self._resolved_values[key] = value
return value
def select_from_attribute(attribute_value, path):
'''
Select an element from an attribute value.

View File

@ -182,6 +182,18 @@ class Resource(object):
return super(Resource, cls).__new__(ResourceClass)
def _init_attributes(self):
"""The method that defines attribute initialization for a resource.
Some resource requires different initialization of resource attributes.
So they must override this method and return the initialized
attributes to the resource.
:return: resource attributes
"""
return attributes.Attributes(self.name,
self.attributes_schema,
self._resolve_all_attributes)
def __init__(self, name, definition, stack):
def _validate_name(res_name):
@ -196,9 +208,7 @@ class Resource(object):
self.t = definition
self.reparse()
self.attributes_schema.update(self.base_attributes_schema)
self.attributes = attributes.Attributes(self.name,
self.attributes_schema,
self._resolve_all_attributes)
self.attributes = self._init_attributes()
self.abandon_in_progress = False

View File

@ -158,6 +158,8 @@ class HeatTestCase(testscenarios.WithScenarios,
generic_rsrc.StackResourceType)
resource._register_class('ResourceWithRestoreType',
generic_rsrc.ResourceWithRestoreType)
resource._register_class('DynamicSchemaResource',
generic_rsrc.DynamicSchemaResource)
def patchobject(self, obj, attr, **kwargs):
mockfixture = self.useFixture(mockpatch.PatchObject(obj, attr,

View File

@ -257,3 +257,27 @@ class ResourceWithRestoreType(ResWithComplexPropsAndAttrs):
value = data['resource_data']['a_string']
props['a_string'] = value
return defn.freeze(properties=props)
class DynamicSchemaResource(resource.Resource):
"""Resource with an attribute not registered in the attribute schema."""
properties_schema = {}
attributes_schema = {
'stat_attr': attributes.Schema('A generic static attribute',
type=attributes.Schema.STRING),
}
def _init_attributes(self):
# software deployment scheme is not static
# so return dynamic attributes for it
return attributes.DynamicSchemeAttributes(
self.name, self.attributes_schema, self._resolve_attribute)
def _resolve_attribute(self, name):
if name == 'stat_attr':
return "static_attribute"
elif name == 'dynamic_attr':
return "dynamic_attribute"
else:
raise KeyError()

View File

@ -16,8 +16,13 @@ import six
from heat.engine import attributes
from heat.engine import resources
from heat.engine import rsrc_defn
from heat.engine import stack
from heat.engine import support
from heat.engine import template
from heat.tests import common
from heat.tests import generic_resource
from heat.tests import utils
class AttributeSchemaTest(common.HeatTestCase):
@ -243,3 +248,25 @@ class AttributesTypeTest(common.HeatTestCase):
self.assertNotIn(msg, self.LOG.output)
attribs._validate_type(attr, self.invalid_value)
self.assertIn(msg, self.LOG.output)
class DynamicSchemeAttributeTest(common.HeatTestCase):
def setUp(self):
super(DynamicSchemeAttributeTest, self).setUp()
test_stack = stack.Stack(
utils.dummy_context(), 'test_stack',
template.Template.create_empty_template())
snippet = rsrc_defn.ResourceDefinition('test_resource',
'DynamicSchemaResource')
test_res = generic_resource.DynamicSchemaResource(
'aresource', snippet, test_stack)
self.attrs = test_res.attributes
def test_get_static_attribute(self):
self.assertEqual("static_attribute", self.attrs["stat_attr"])
def test_get_dynamic_attribute(self):
self.assertEqual("dynamic_attribute", self.attrs["dynamic_attr"])
def test_get_non_existing_attribute(self):
self.assertRaises(KeyError, self.attrs.__getitem__, "non_existing")