From d52903d823fbb1f2d287ac2dbd8dd02997df77a4 Mon Sep 17 00:00:00 2001 From: Zane Bitter Date: Mon, 10 Jul 2017 13:48:01 -0400 Subject: [PATCH] Add a functional test for nested get_attr functions Nesting get_attr functions (i.e. using a get_attr in the arguments to another get_attr) is unwise, and we probably should have prohibited it somehow. Nonetheless, we didn't and some users (including but not necessarily limited to TripleO) depend on this working. Since this would be easy to break by accident, add a functional test to make sure we can only break it on purpose. Change-Id: I234a0762a00815af86b5711e7494366c16e717c2 --- functional/test_nested_get_attr.py | 165 +++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 functional/test_nested_get_attr.py 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')