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',