Merge "Add a functional test for nested get_attr functions"
diff --git a/functional/test_nested_get_attr.py b/functional/test_nested_get_attr.py
new file mode 100644
index 0000000..fff89a4
--- /dev/null
+++ b/functional/test_nested_get_attr.py
@@ -0,0 +1,165 @@
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+# Using nested get_attr functions isn't a good idea - in particular, this
+# actually working depends on correct dependencies between the two resources
+# whose attributes are being fetched, and these dependencies are non-local to
+# where the get_attr calls are used. Nevertheless, it did sort-of work, and
+# this test will help keep it that way.
+
+from heat_integrationtests.functional import functional_base
+
+
+initial_template = '''
+heat_template_version: ocata
+resources:
+  dict_resource:
+    type: OS::Heat::Value
+    properties:
+      value:
+        blarg: wibble
+        foo: bar
+        baz: quux
+        fred: barney
+    # These dependencies are required because we only want to read the
+    # attribute values for a given resource once, and therefore we do so in
+    # dependency order. This is necessarily true for a convergence traversal,
+    # but also happens when we're fetching the resource attributes e.g. to show
+    # the output values. The key1/key2 attribute values must be stored before
+    # we attempt to calculate the dep_attrs for dict_resource in order to
+    # correctly determine which attributes of dict_resource are used.
+    depends_on:
+      - key1
+      - key2
+      - indirect_key3_dep
+  key1:
+    type: OS::Heat::Value
+    properties:
+      value: blarg
+  key2:
+    type: OS::Heat::Value
+    properties:
+      value: foo
+  key3:
+    type: OS::Heat::Value
+    properties:
+      value: fred
+  value1:
+    type: OS::Heat::Value
+    properties:
+      value:
+        get_attr:
+          - dict_resource
+          - value
+          - {get_attr: [key1, value]}
+  indirect_key3_dep:
+    type: OS::Heat::Value
+    properties:
+      value: ignored
+    depends_on: key3
+outputs:
+  value1:
+    value: {get_attr: [value1, value]}
+  value2:
+    value: {get_attr: [dict_resource, value, {get_attr: [key2, value]}]}
+  value3:
+    value: {get_attr: [dict_resource, value, {get_attr: [key3, value]}]}
+'''
+
+update_template = '''
+heat_template_version: ocata
+resources:
+  dict_resource:
+    type: OS::Heat::Value
+    properties:
+      value:
+        blarg: wibble
+        foo: bar
+        baz: quux
+        fred: barney
+    depends_on:
+      - key1
+      - key2
+      - indirect_key3_dep
+      - key4
+  key1:
+    type: OS::Heat::Value
+    properties:
+      value: foo
+  key2:
+    type: OS::Heat::Value
+    properties:
+      value: fred
+  key3:
+    type: OS::Heat::Value
+    properties:
+      value: blarg
+  key4:
+    type: OS::Heat::Value
+    properties:
+      value: baz
+  value1:
+    type: OS::Heat::Value
+    properties:
+      value:
+        get_attr:
+          - dict_resource
+          - value
+          - {get_attr: [key1, value]}
+  value4:
+    type: OS::Heat::Value
+    properties:
+      value:
+        get_attr:
+          - dict_resource
+          - value
+          - {get_attr: [key4, value]}
+  indirect_key3_dep:
+    type: OS::Heat::Value
+    properties:
+      value: ignored
+    depends_on: key3
+outputs:
+  value1:
+    value: {get_attr: [value1, value]}
+  value2:
+    value: {get_attr: [dict_resource, value, {get_attr: [key2, value]}]}
+  value3:
+    value: {get_attr: [dict_resource, value, {get_attr: [key3, value]}]}
+  value4:
+    value: {get_attr: [value4, value]}
+'''
+
+
+class NestedGetAttrTest(functional_base.FunctionalTestsBase):
+    def assertOutput(self, value, stack_identifier, key):
+        op = self.client.stacks.output_show(stack_identifier, key)['output']
+        self.assertEqual(key, op['output_key'])
+        if 'output_error' in op:
+            raise Exception(op['output_error'])
+        self.assertEqual(value, op['output_value'])
+
+    def test_nested_get_attr_create(self):
+        stack_identifier = self.stack_create(template=initial_template)
+
+        self.assertOutput('wibble', stack_identifier, 'value1')
+        self.assertOutput('bar', stack_identifier, 'value2')
+        self.assertOutput('barney', stack_identifier, 'value3')
+
+    def test_nested_get_attr_update(self):
+        stack_identifier = self.stack_create(template=initial_template)
+        self.update_stack(stack_identifier, template=update_template)
+
+        self.assertOutput('bar', stack_identifier, 'value1')
+        self.assertOutput('barney', stack_identifier, 'value2')
+        self.assertOutput('wibble', stack_identifier, 'value3')
+        self.assertOutput('quux', stack_identifier, 'value4')