From 15e52ff5e96f1521d7c06a8d55f62f5fb6ce33d3 Mon Sep 17 00:00:00 2001 From: Angus Salkeld Date: Tue, 4 Aug 2015 13:00:40 +1000 Subject: [PATCH] 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 --- heat/engine/attributes.py | 28 ++++++++++++++++++++++++++++ heat/engine/resource.py | 16 +++++++++++++--- heat/tests/common.py | 2 ++ heat/tests/generic_resource.py | 24 ++++++++++++++++++++++++ heat/tests/test_attributes.py | 27 +++++++++++++++++++++++++++ 5 files changed, 94 insertions(+), 3 deletions(-) diff --git a/heat/engine/attributes.py b/heat/engine/attributes.py index fae19a80b3..1bbb39dd88 100644 --- a/heat/engine/attributes.py +++ b/heat/engine/attributes.py @@ -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. diff --git a/heat/engine/resource.py b/heat/engine/resource.py index 413dd834d9..2a04d4b3b2 100644 --- a/heat/engine/resource.py +++ b/heat/engine/resource.py @@ -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 diff --git a/heat/tests/common.py b/heat/tests/common.py index 3d2b8b730c..a7ad40adc3 100644 --- a/heat/tests/common.py +++ b/heat/tests/common.py @@ -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, diff --git a/heat/tests/generic_resource.py b/heat/tests/generic_resource.py index 85d086ef5d..53699fc2e1 100644 --- a/heat/tests/generic_resource.py +++ b/heat/tests/generic_resource.py @@ -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() diff --git a/heat/tests/test_attributes.py b/heat/tests/test_attributes.py index c32526016b..bf4d76a58a 100644 --- a/heat/tests/test_attributes.py +++ b/heat/tests/test_attributes.py @@ -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")