From 45e4c53f78a2211c615b1e37bd86dacc815fb388 Mon Sep 17 00:00:00 2001 From: Zane Bitter Date: Wed, 21 Jun 2017 20:04:01 -0400 Subject: [PATCH] Cache attributes with custom handling Previously, all caching of attribute values was done via the Attributes object. However, some resource types override Resource.get_attribute() to do custom handling of the trailing attribute path or dynamic attribute names, and in these cases the resulting values were not cached (since they don't go through the Attributes object). This patch adds a caching step for these resources: * OS::Senlin::Cluster * OS::Heat::ResourceChain * OS::Heat::ResourceGroup * OS::Heat::AutoscalingGroup * OS::Heat::SoftwareDeployment * OS::Heat::SoftwareDeploymentGroup * TemplateResource * AWS::CloudFormation::Stack Change-Id: I07ac22cc4370a79bd8712e2431fa3272115bc0eb Co-Authored-By: Crag Wolfe Partial-Bug: #1660831 --- heat/engine/attributes.py | 28 ++++++++++++++++------ heat/engine/resource.py | 25 ++++++++++++++++--- heat/engine/resources/template_resource.py | 6 +---- heat/engine/sync_point.py | 4 ++-- 4 files changed, 46 insertions(+), 17 deletions(-) diff --git a/heat/engine/attributes.py b/heat/engine/attributes.py index 150b5272d2..47e50be56a 100644 --- a/heat/engine/attributes.py +++ b/heat/engine/attributes.py @@ -146,7 +146,7 @@ class Attributes(collections.Mapping): def __init__(self, res_name, schema, resolver): self._resource_name = res_name self._resolver = resolver - self._attributes = Attributes._make_attributes(schema) + self.set_schema(schema) self.reset_resolved_values() assert ALL_ATTRIBUTES not in schema, \ @@ -159,6 +159,20 @@ class Attributes(collections.Mapping): self._has_new_resolved = False self._resolved_values = {} + def set_schema(self, schema): + self._attributes = self._make_attributes(schema) + + def get_cache_mode(self, attribute_name): + """Return the cache mode for the specified attribute. + + If the attribute is not defined in the schema, the default cache + mode (CACHE_LOCAL) is returned. + """ + try: + return self._attributes[attribute_name].schema.cache_mode + except KeyError: + return Schema.CACHE_LOCAL + @staticmethod def _make_attributes(schema): return dict((n, Attribute(n, d)) for n, d in schema.items()) @@ -229,10 +243,7 @@ class Attributes(collections.Mapping): @property def cached_attrs(self): - # do not return an empty dict - if self._resolved_values: - return self._resolved_values - return None + return self._resolved_values @cached_attrs.setter def cached_attrs(self, c_attrs): @@ -242,6 +253,10 @@ class Attributes(collections.Mapping): self._resolved_values = c_attrs self._has_new_resolved = False + def set_cached_attr(self, key, value): + self._resolved_values[key] = value + self._has_new_resolved = True + def has_new_cached_attrs(self): """Returns True if cached_attrs have changed @@ -269,8 +284,7 @@ class Attributes(collections.Mapping): self._validate_type(attrib, value) # only store if not None, it may resolve to an actual value # on subsequent calls - self._has_new_resolved = True - self._resolved_values[key] = value + self.set_cached_attr(key, value) return value def __len__(self): diff --git a/heat/engine/resource.py b/heat/engine/resource.py index c8340251f6..8084737f91 100644 --- a/heat/engine/resource.py +++ b/heat/engine/resource.py @@ -42,6 +42,7 @@ from heat.engine import rsrc_defn from heat.engine import scheduler from heat.engine import status from heat.engine import support +from heat.engine import sync_point from heat.objects import resource as resource_objects from heat.objects import resource_data as resource_data_objects from heat.objects import resource_properties_data as rpd_objects @@ -281,7 +282,7 @@ class Resource(status.ResourceStatus): self, resource.data) except exception.NotFound: self._data = {} - self.attributes.cached_attrs = resource.attr_data + self.attributes.cached_attrs = resource.attr_data or None self._attr_data_id = resource.attr_data_id self._rsrc_metadata = resource.rsrc_metadata self._stored_properties_data = resource.properties_data @@ -922,7 +923,7 @@ class Resource(status.ResourceStatus): for attr in attrs: path = (attr,) if isinstance(attr, six.string_types) else attr try: - yield attr, self.get_attribute(*path) + yield attr, self._get_attribute_caching(*path) except exception.InvalidTemplateAttribute as ita: LOG.info('%s', ita) @@ -2160,6 +2161,24 @@ class Resource(status.ResourceStatus): return attributes.select_from_attribute(attribute, path) + def _get_attribute_caching(self, key, *path): + cache_custom = ((self.attributes.get_cache_mode(key) != + attributes.Schema.CACHE_NONE) and + (type(self).get_attribute != Resource.get_attribute)) + if cache_custom: + if path: + full_key = sync_point.str_pack_tuple((key,) + path) + else: + full_key = key + if full_key in self.attributes.cached_attrs: + return self.attributes.cached_attrs[full_key] + + attr_val = self.get_attribute(key, *path) + + if cache_custom: + self.attributes.set_cached_attr(full_key, attr_val) + return attr_val + def FnGetAtt(self, key, *path): """For the intrinsic function Fn::GetAtt. @@ -2175,7 +2194,7 @@ class Resource(status.ResourceStatus): attribute = self.stack.cache_data_resource_attribute( self.name, complex_key) return attribute - return self.get_attribute(key, *path) + return self._get_attribute_caching(key, *path) def FnGetAtts(self): """For the intrinsic function get_attr which returns all attributes. diff --git a/heat/engine/resources/template_resource.py b/heat/engine/resources/template_resource.py index 1b5b41a9f1..05ec835f5b 100644 --- a/heat/engine/resources/template_resource.py +++ b/heat/engine/resources/template_resource.py @@ -11,8 +11,6 @@ # License for the specific language governing permissions and limitations # under the License. -import weakref - from oslo_serialization import jsonutils from requests import exceptions import six @@ -120,9 +118,7 @@ class TemplateResource(stack_resource.StackResource): tmpl, self.stack.env.param_defaults) self.attributes_schema.update(self.base_attributes_schema) - self.attributes = attributes.Attributes( - self.name, self.attributes_schema, - self._make_resolver(weakref.ref(self))) + self.attributes.set_schema(self.attributes_schema) def child_params(self): """Override method of child_params for the resource. diff --git a/heat/engine/sync_point.py b/heat/engine/sync_point.py index 56f08d614c..a8feea4af1 100644 --- a/heat/engine/sync_point.py +++ b/heat/engine/sync_point.py @@ -73,7 +73,7 @@ def update_input_data(context, entity_id, current_traversal, return rows_updated -def _str_pack_tuple(t): +def str_pack_tuple(t): return u'tuple:' + str(t) @@ -97,7 +97,7 @@ def _serialize(d): d2 = {} for k, v in d.items(): if isinstance(k, tuple): - k = _str_pack_tuple(k) + k = str_pack_tuple(k) if isinstance(v, dict): v = _serialize(v) d2[k] = v