Move template resource tests to functional
Note: this enables adopt and abandon in the gate
so we can test this feature.
Part of blueprint decouple-nested
Change-Id: Id1e63fc4b4e609f699d718b8569c25d246e83faa
diff --git a/common/test.py b/common/test.py
index 3de44f5..25703f2 100644
--- a/common/test.py
+++ b/common/test.py
@@ -326,6 +326,22 @@
)
self._wait_for_stack_status(stack_identifier, 'UPDATE_COMPLETE')
+ def assert_resource_is_a_stack(self, stack_identifier, res_name):
+ rsrc = self.client.resources.get(stack_identifier, res_name)
+ nested_link = [l for l in rsrc.links if l['rel'] == 'nested']
+ nested_href = nested_link[0]['href']
+ nested_id = nested_href.split('/')[-1]
+ nested_identifier = '/'.join(nested_href.split('/')[-2:])
+ self.assertEqual(rsrc.physical_resource_id, nested_id)
+
+ nested_stack = self.client.stacks.get(nested_id)
+ nested_identifier2 = '%s/%s' % (nested_stack.stack_name,
+ nested_stack.id)
+ self.assertEqual(nested_identifier, nested_identifier2)
+ parent_id = stack_identifier.split("/")[-1]
+ self.assertEqual(parent_id, nested_stack.parent)
+ return nested_identifier
+
def list_resources(self, stack_identifier):
resources = self.client.resources.list(stack_identifier)
return dict((r.resource_name, r.resource_type) for r in resources)
@@ -351,3 +367,25 @@
stack_identifier = '%s/%s' % (name, stack.id)
self._wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
return stack_identifier
+
+ def stack_adopt(self, stack_name=None, files=None,
+ parameters=None, environment=None, adopt_data=None,
+ wait_for_status='ADOPT_COMPLETE'):
+ name = stack_name or self._stack_rand_name()
+ templ_files = files or {}
+ params = parameters or {}
+ env = environment or {}
+ self.client.stacks.create(
+ stack_name=name,
+ files=templ_files,
+ disable_rollback=True,
+ parameters=params,
+ environment=env,
+ adopt_stack_data=adopt_data,
+ )
+ self.addCleanup(self.client.stacks.delete, name)
+
+ stack = self.client.stacks.get(name)
+ stack_identifier = '%s/%s' % (name, stack.id)
+ self._wait_for_stack_status(stack_identifier, wait_for_status)
+ return stack_identifier
diff --git a/functional/test_template_resource.py b/functional/test_template_resource.py
new file mode 100644
index 0000000..828ff74
--- /dev/null
+++ b/functional/test_template_resource.py
@@ -0,0 +1,421 @@
+# 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.
+
+import json
+import logging
+import yaml
+
+from heat_integrationtests.common import test
+
+
+LOG = logging.getLogger(__name__)
+
+
+class TemplateResourceTest(test.HeatIntegrationTest):
+ """Prove that we can use the registry in a nested provider."""
+ def setUp(self):
+ super(TemplateResourceTest, self).setUp()
+ self.client = self.orchestration_client
+
+ def test_nested_env(self):
+ main_templ = '''
+heat_template_version: 2013-05-23
+resources:
+ secret1:
+ type: My::NestedSecret
+outputs:
+ secret-out:
+ value: { get_attr: [secret1, value] }
+'''
+
+ nested_templ = '''
+heat_template_version: 2013-05-23
+resources:
+ secret2:
+ type: My::Secret
+outputs:
+ value:
+ value: { get_attr: [secret2, value] }
+'''
+
+ env_templ = '''
+resource_registry:
+ "My::Secret": "OS::Heat::RandomString"
+ "My::NestedSecret": nested.yaml
+'''
+
+ stack_identifier = self.stack_create(
+ template=main_templ,
+ files={'nested.yaml': nested_templ},
+ environment=env_templ)
+ self.assert_resource_is_a_stack(stack_identifier, 'secret1')
+
+ def test_no_infinite_recursion(self):
+ """Prove that we can override a python resource.
+
+ And use that resource within the template resource.
+ """
+
+ main_templ = '''
+heat_template_version: 2013-05-23
+resources:
+ secret1:
+ type: OS::Heat::RandomString
+outputs:
+ secret-out:
+ value: { get_attr: [secret1, value] }
+'''
+
+ nested_templ = '''
+heat_template_version: 2013-05-23
+resources:
+ secret2:
+ type: OS::Heat::RandomString
+outputs:
+ value:
+ value: { get_attr: [secret2, value] }
+'''
+
+ env_templ = '''
+resource_registry:
+ "OS::Heat::RandomString": nested.yaml
+'''
+
+ stack_identifier = self.stack_create(
+ template=main_templ,
+ files={'nested.yaml': nested_templ},
+ environment=env_templ)
+ self.assert_resource_is_a_stack(stack_identifier, 'secret1')
+
+
+class NestedAttributesTest(test.HeatIntegrationTest):
+ """Prove that we can use the template resource references."""
+
+ main_templ = '''
+heat_template_version: 2014-10-16
+resources:
+ secret2:
+ type: My::NestedSecret
+outputs:
+ old_way:
+ value: { get_attr: [secret2, nested_str]}
+ test_attr1:
+ value: { get_attr: [secret2, resource.secret1, value]}
+ test_attr2:
+ value: { get_attr: [secret2, resource.secret1.value]}
+ test_ref:
+ value: { get_resource: secret2 }
+'''
+
+ env_templ = '''
+resource_registry:
+ "My::NestedSecret": nested.yaml
+'''
+
+ def setUp(self):
+ super(NestedAttributesTest, self).setUp()
+ self.client = self.orchestration_client
+
+ def test_stack_ref(self):
+ nested_templ = '''
+heat_template_version: 2014-10-16
+resources:
+ secret1:
+ type: OS::Heat::RandomString
+'''
+ stack_identifier = self.stack_create(
+ template=self.main_templ,
+ files={'nested.yaml': nested_templ},
+ environment=self.env_templ)
+ self.assert_resource_is_a_stack(stack_identifier, 'secret2')
+ stack = self.client.stacks.get(stack_identifier)
+ test_ref = self._stack_output(stack, 'test_ref')
+ self.assertIn('arn:openstack:heat:', test_ref)
+
+ def test_transparent_ref(self):
+ """With the addition of OS::stack_id we can now use the nested resource
+ more transparently.
+ """
+ nested_templ = '''
+heat_template_version: 2014-10-16
+resources:
+ secret1:
+ type: OS::Heat::RandomString
+outputs:
+ OS::stack_id:
+ value: {get_resource: secret1}
+ nested_str:
+ value: {get_attr: [secret1, value]}
+'''
+ stack_identifier = self.stack_create(
+ template=self.main_templ,
+ files={'nested.yaml': nested_templ},
+ environment=self.env_templ)
+ self.assert_resource_is_a_stack(stack_identifier, 'secret2')
+ stack = self.client.stacks.get(stack_identifier)
+ test_ref = self._stack_output(stack, 'test_ref')
+ test_attr = self._stack_output(stack, 'old_way')
+
+ self.assertNotIn('arn:openstack:heat', test_ref)
+ self.assertEqual(test_attr, test_ref)
+
+ def test_nested_attributes(self):
+ nested_templ = '''
+heat_template_version: 2014-10-16
+resources:
+ secret1:
+ type: OS::Heat::RandomString
+outputs:
+ nested_str:
+ value: {get_attr: [secret1, value]}
+'''
+ stack_identifier = self.stack_create(
+ template=self.main_templ,
+ files={'nested.yaml': nested_templ},
+ environment=self.env_templ)
+ self.assert_resource_is_a_stack(stack_identifier, 'secret2')
+ stack = self.client.stacks.get(stack_identifier)
+ old_way = self._stack_output(stack, 'old_way')
+ test_attr1 = self._stack_output(stack, 'test_attr1')
+ test_attr2 = self._stack_output(stack, 'test_attr2')
+
+ self.assertEqual(old_way, test_attr1)
+ self.assertEqual(old_way, test_attr2)
+
+
+class TemplateResourceUpdateTest(test.HeatIntegrationTest):
+ """Prove that we can do template resource updates."""
+
+ main_template = '''
+HeatTemplateFormatVersion: '2012-12-12'
+Resources:
+ the_nested:
+ Type: the.yaml
+ Properties:
+ one: my_name
+
+Outputs:
+ identifier:
+ Value: {Ref: the_nested}
+ value:
+ Value: {'Fn::GetAtt': [the_nested, the_str]}
+'''
+
+ main_template_2 = '''
+HeatTemplateFormatVersion: '2012-12-12'
+Resources:
+ the_nested:
+ Type: the.yaml
+ Properties:
+ one: updated_name
+
+Outputs:
+ identifier:
+ Value: {Ref: the_nested}
+ value:
+ Value: {'Fn::GetAtt': [the_nested, the_str]}
+'''
+
+ initial_tmpl = '''
+HeatTemplateFormatVersion: '2012-12-12'
+Parameters:
+ one:
+ Default: foo
+ Type: String
+Resources:
+ NestedResource:
+ Type: OS::Heat::RandomString
+ Properties:
+ salt: {Ref: one}
+Outputs:
+ the_str:
+ Value: {'Fn::GetAtt': [NestedResource, value]}
+'''
+ prop_change_tmpl = '''
+HeatTemplateFormatVersion: '2012-12-12'
+Parameters:
+ one:
+ Default: yikes
+ Type: String
+ two:
+ Default: foo
+ Type: String
+Resources:
+ NestedResource:
+ Type: OS::Heat::RandomString
+ Properties:
+ salt: {Ref: one}
+Outputs:
+ the_str:
+ Value: {'Fn::GetAtt': [NestedResource, value]}
+'''
+ attr_change_tmpl = '''
+HeatTemplateFormatVersion: '2012-12-12'
+Parameters:
+ one:
+ Default: foo
+ Type: String
+Resources:
+ NestedResource:
+ Type: OS::Heat::RandomString
+ Properties:
+ salt: {Ref: one}
+Outputs:
+ the_str:
+ Value: {'Fn::GetAtt': [NestedResource, value]}
+ something_else:
+ Value: just_a_string
+'''
+ content_change_tmpl = '''
+HeatTemplateFormatVersion: '2012-12-12'
+Parameters:
+ one:
+ Default: foo
+ Type: String
+Resources:
+ NestedResource:
+ Type: OS::Heat::RandomString
+ Properties:
+ salt: yum
+Outputs:
+ the_str:
+ Value: {'Fn::GetAtt': [NestedResource, value]}
+'''
+
+ EXPECTED = (UPDATE, NOCHANGE) = ('update', 'nochange')
+ scenarios = [
+ ('no_changes', dict(template=main_template,
+ provider=initial_tmpl,
+ expect=NOCHANGE)),
+ ('main_tmpl_change', dict(template=main_template_2,
+ provider=initial_tmpl,
+ expect=UPDATE)),
+ ('provider_change', dict(template=main_template,
+ provider=content_change_tmpl,
+ expect=UPDATE)),
+ ('provider_props_change', dict(template=main_template,
+ provider=prop_change_tmpl,
+ expect=NOCHANGE)),
+ ('provider_attr_change', dict(template=main_template,
+ provider=attr_change_tmpl,
+ expect=NOCHANGE)),
+ ]
+
+ def setUp(self):
+ super(TemplateResourceUpdateTest, self).setUp()
+ self.client = self.orchestration_client
+
+ def test_template_resource_update_template_schema(self):
+ stack_identifier = self.stack_create(
+ template=self.main_template,
+ files={'the.yaml': self.initial_tmpl})
+ stack = self.client.stacks.get(stack_identifier)
+ initial_id = self._stack_output(stack, 'identifier')
+ initial_val = self._stack_output(stack, 'value')
+
+ self.update_stack(stack_identifier,
+ self.template,
+ files={'the.yaml': self.provider})
+ stack = self.client.stacks.get(stack_identifier)
+ self.assertEqual(initial_id,
+ self._stack_output(stack, 'identifier'))
+ if self.expect == self.NOCHANGE:
+ self.assertEqual(initial_val,
+ self._stack_output(stack, 'value'))
+ else:
+ self.assertNotEqual(initial_val,
+ self._stack_output(stack, 'value'))
+
+
+class TemplateResourceAdoptTest(test.HeatIntegrationTest):
+ """Prove that we can do template resource adopt/abandon."""
+
+ main_template = '''
+HeatTemplateFormatVersion: '2012-12-12'
+Resources:
+ the_nested:
+ Type: the.yaml
+ Properties:
+ one: my_name
+Outputs:
+ identifier:
+ Value: {Ref: the_nested}
+ value:
+ Value: {'Fn::GetAtt': [the_nested, the_str]}
+'''
+
+ nested_templ = '''
+HeatTemplateFormatVersion: '2012-12-12'
+Parameters:
+ one:
+ Default: foo
+ Type: String
+Resources:
+ RealRandom:
+ Type: OS::Heat::RandomString
+ Properties:
+ salt: {Ref: one}
+Outputs:
+ the_str:
+ Value: {'Fn::GetAtt': [RealRandom, value]}
+'''
+
+ def setUp(self):
+ super(TemplateResourceAdoptTest, self).setUp()
+ self.client = self.orchestration_client
+
+ def _yaml_to_json(self, yaml_templ):
+ return yaml.load(yaml_templ)
+
+ def test_abandon(self):
+ stack_name = self._stack_rand_name()
+ self.client.stacks.create(
+ stack_name=stack_name,
+ template=self.main_template,
+ files={'the.yaml': self.nested_templ},
+ disable_rollback=True,
+ )
+ stack = self.client.stacks.get(stack_name)
+ stack_identifier = '%s/%s' % (stack_name, stack.id)
+ self._wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
+
+ info = self.client.stacks.abandon(stack_id=stack_identifier)
+ self.assertEqual(self._yaml_to_json(self.main_template),
+ info['template'])
+ self.assertEqual(self._yaml_to_json(self.nested_templ),
+ info['resources']['the_nested']['template'])
+
+ def test_adopt(self):
+ data = {
+ 'resources': {
+ 'the_nested': {
+ "type": "the.yaml",
+ "resources": {
+ "RealRandom": {
+ "type": "OS::Heat::RandomString",
+ 'resource_data': {'value': 'goopie'},
+ 'resource_id': 'froggy'
+ }
+ }
+ }
+ },
+ "environment": {"parameters": {}},
+ "template": yaml.load(self.main_template)
+ }
+
+ stack_identifier = self.stack_adopt(
+ adopt_data=json.dumps(data),
+ files={'the.yaml': self.nested_templ})
+
+ self.assert_resource_is_a_stack(stack_identifier, 'the_nested')
+ stack = self.client.stacks.get(stack_identifier)
+ self.assertEqual('goopie', self._stack_output(stack, 'value'))
diff --git a/functional/test_update.py b/functional/test_update.py
index 610fe10..ea436ad 100644
--- a/functional/test_update.py
+++ b/functional/test_update.py
@@ -101,13 +101,9 @@
self.list_resources(stack_identifier))
# Prove the resource is backed by a nested stack, save the ID
- rsrc = self.client.resources.get(stack_identifier, 'random1')
- nested_link = [l for l in rsrc.links if l['rel'] == 'nested']
- nested_href = nested_link[0]['href']
- nested_id = nested_href.split('/')[-1]
- nested_identifier = '/'.join(nested_href.split('/')[-2:])
- physical_resource_id = rsrc.physical_resource_id
- self.assertEqual(physical_resource_id, nested_id)
+ nested_identifier = self.assert_resource_is_a_stack(stack_identifier,
+ 'random1')
+ nested_id = nested_identifier.split('/')[-1]
# Then check the expected resources are in the nested stack
nested_resources = {'random1': 'OS::Heat::RandomString'}
@@ -153,14 +149,8 @@
self.list_resources(stack_identifier))
# Prove the resource is backed by a nested stack, save the ID
- rsrc = self.client.resources.get(stack_identifier, 'random_group')
- physical_resource_id = rsrc.physical_resource_id
-
- nested_stack = self.client.stacks.get(physical_resource_id)
- nested_identifier = '%s/%s' % (nested_stack.stack_name,
- nested_stack.id)
- parent_id = stack_identifier.split("/")[-1]
- self.assertEqual(parent_id, nested_stack.parent)
+ nested_identifier = self.assert_resource_is_a_stack(stack_identifier,
+ 'random_group')
# Then check the expected resources are in the nested stack
nested_resources = {'0': 'My::RandomString',