From f1961c734e81f1e39c16b04558e17e4048c78c06 Mon Sep 17 00:00:00 2001
From: Zane Bitter <zbitter@redhat.com>
Date: Mon, 25 Sep 2017 13:55:41 -0400
Subject: [PATCH] Use show_output in TemplateResource.get_reference_id()

TemplateResource is unique in that it can return a custom reference ID
defined as an output in the nested template. This was being fetched by the
StackResource.get_output() method, which also has the effect of retrieving
*all* of the outputs of the nested stack and caching them in memory in case
something else were to reference any of them.

Unfortunately when calculating the resource data for a stack (which we
must always do when e.g. showing the outputs), we always include the
reference IDs of all resources, regardless of whether they are referenced
by get_resource in the data we are looking to populate. (In fact, we have
no way from the Template API to distinguish where get_resource is used on
a particular resource, only where there are dependencies on it.) This is no
problem under the assumption that getting the reference ID is quick, but
that assumption does not hold for TemplateResource.

The show_output RPC call only retrieves a single output (as opposed to
show_stack, used in StackResource.get_output(), which calculates all of
them). Fall back to that call in TemplateResource.get_reference_id() if the
outputs are not already cached to avoid unnecessary overhead in the common
case.

Attribute values are now always fetched before the reference ID, so that we
won't end up making two RPC calls in the case where we also need to read
other outputs.

Change-Id: I66da13c0bb024749de4ae3f0c4b06ebb485cee37
Closes-Bug: #1719333
---
 heat/engine/resource.py                    |  7 +++++--
 heat/engine/resources/template_resource.py | 24 +++++++++++++++++++---
 2 files changed, 26 insertions(+), 5 deletions(-)

diff --git a/heat/engine/resource.py b/heat/engine/resource.py
index 4f5e73e096..61cc8f3bde 100644
--- a/heat/engine/resource.py
+++ b/heat/engine/resource.py
@@ -1052,9 +1052,12 @@ class Resource(status.ResourceStatus):
             for e in get_attrs(out_attrs - dep_attrs, cacheable_only=True):
                 pass
 
+        # Calculate attribute values *before* reference ID, to potentially
+        # save an extra RPC call in TemplateResource
+        attribute_values = dict(get_attrs(dep_attrs))
+
         return node_data.NodeData(self.id, self.name, self.uuid,
-                                  self.FnGetRefId(),
-                                  dict(get_attrs(dep_attrs)),
+                                  self.FnGetRefId(), attribute_values,
                                   self.action, self.status)
 
     def preview(self):
diff --git a/heat/engine/resources/template_resource.py b/heat/engine/resources/template_resource.py
index 4e87b5b826..7ab803f823 100644
--- a/heat/engine/resources/template_resource.py
+++ b/heat/engine/resources/template_resource.py
@@ -25,11 +25,14 @@ from heat.engine import environment
 from heat.engine import properties
 from heat.engine.resources import stack_resource
 from heat.engine import template
+from heat.rpc import api as rpc_api
 
 
 REMOTE_SCHEMES = ('http', 'https')
 LOCAL_SCHEMES = ('file',)
 
+STACK_ID_OUTPUT = 'OS::stack_id'
+
 
 def generate_class_from_template(name, data, param_defaults):
     tmpl = template.Template(template_format.parse(data))
@@ -300,10 +303,25 @@ class TemplateResource(stack_resource.StackResource):
         if self.resource_id is None:
             return six.text_type(self.name)
 
+        stack_identity = self.nested_identifier()
         try:
-            return self.get_output('OS::stack_id')
-        except exception.InvalidTemplateAttribute:
-            return self.nested_identifier().arn()
+            if self._outputs is not None:
+                return self.get_output(STACK_ID_OUTPUT)
+
+            output = self.rpc_client().show_output(self.context,
+                                                   dict(stack_identity),
+                                                   STACK_ID_OUTPUT)
+            if rpc_api.OUTPUT_ERROR in output:
+                raise exception.TemplateOutputError(
+                    resource=self.name,
+                    attribute=STACK_ID_OUTPUT,
+                    message=output[rpc_api.OUTPUT_ERROR])
+        except (exception.InvalidTemplateAttribute, exception.NotFound):
+            pass
+        else:
+            return output[rpc_api.OUTPUT_VALUE]
+
+        return stack_identity.arn()
 
     def get_attribute(self, key, *path):
         if self.resource_id is None: