Merge "test_resource for functional tests"
diff --git a/common/test.py b/common/test.py
index b88c72b..331c922 100644
--- a/common/test.py
+++ b/common/test.py
@@ -276,7 +276,8 @@
 
     def update_stack(self, stack_identifier, template, environment=None,
                      files=None, parameters=None,
-                     expected_status='UPDATE_COMPLETE'):
+                     expected_status='UPDATE_COMPLETE',
+                     disable_rollback=True):
         env = environment or {}
         env_files = files or {}
         parameters = parameters or {}
@@ -286,11 +287,19 @@
             stack_name=stack_name,
             template=template,
             files=env_files,
-            disable_rollback=True,
+            disable_rollback=disable_rollback,
             parameters=parameters,
             environment=env
         )
-        self._wait_for_stack_status(stack_identifier, expected_status)
+        kwargs = {'stack_identifier': stack_identifier,
+                  'status': expected_status}
+        if expected_status in ['ROLLBACK_COMPLETE']:
+            self.addCleanup(self.client.stacks.delete, stack_name)
+            # To trigger rollback you would intentionally fail the stack
+            # Hence check for rollback failures
+            kwargs['failure_pattern'] = '^ROLLBACK_FAILED$'
+
+        self._wait_for_stack_status(**kwargs)
 
     def assert_resource_is_a_stack(self, stack_identifier, res_name,
                                    wait=False):
@@ -334,7 +343,7 @@
 
     def stack_create(self, stack_name=None, template=None, files=None,
                      parameters=None, environment=None,
-                     expected_status='CREATE_COMPLETE'):
+                     expected_status='CREATE_COMPLETE', disable_rollback=True):
         name = stack_name or self._stack_rand_name()
         templ = template or self.template
         templ_files = files or {}
@@ -344,16 +353,23 @@
             stack_name=name,
             template=templ,
             files=templ_files,
-            disable_rollback=True,
+            disable_rollback=disable_rollback,
             parameters=params,
             environment=env
         )
-        self.addCleanup(self.client.stacks.delete, name)
+        if expected_status not in ['ROLLBACK_COMPLETE']:
+            self.addCleanup(self.client.stacks.delete, name)
 
         stack = self.client.stacks.get(name)
         stack_identifier = '%s/%s' % (name, stack.id)
+        kwargs = {'stack_identifier': stack_identifier,
+                  'status': expected_status}
         if expected_status:
-            self._wait_for_stack_status(stack_identifier, expected_status)
+            if expected_status in ['ROLLBACK_COMPLETE']:
+                # To trigger rollback you would intentionally fail the stack
+                # Hence check for rollback failures
+                kwargs['failure_pattern'] = '^ROLLBACK_FAILED$'
+            self._wait_for_stack_status(**kwargs)
         return stack_identifier
 
     def stack_adopt(self, stack_name=None, files=None,
diff --git a/common/test_resources/test_resource.py b/common/test_resources/test_resource.py
new file mode 100644
index 0000000..88f1745
--- /dev/null
+++ b/common/test_resources/test_resource.py
@@ -0,0 +1,134 @@
+#
+#    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 eventlet
+
+from heat.common.i18n import _
+from heat.engine import attributes
+from heat.engine import properties
+from heat.engine import resource
+from heat.engine import support
+from oslo_log import log as logging
+
+LOG = logging.getLogger(__name__)
+
+
+class TestResource(resource.Resource):
+    '''
+    A resource which stores the string value that was provided.
+
+    This resource is to be used only for testing.
+    It has control knobs such as 'update_replace', 'fail', 'wait_secs'
+
+    '''
+
+    support_status = support.SupportStatus(version='2014.1')
+
+    PROPERTIES = (
+        VALUE, UPDATE_REPLACE, FAIL, WAIT_SECS
+    ) = (
+        'value', 'update_replace', 'fail', 'wait_secs'
+    )
+
+    ATTRIBUTES = (
+        OUTPUT,
+    ) = (
+        'output',
+    )
+
+    properties_schema = {
+        VALUE: properties.Schema(
+            properties.Schema.STRING,
+            _('The input string to be stored.'),
+            default='test_string',
+            update_allowed=True
+        ),
+        FAIL: properties.Schema(
+            properties.Schema.BOOLEAN,
+            _('Value which can be set to fail the resource operation '
+              'to test failure scenarios.'),
+            update_allowed=True,
+            default=False
+        ),
+        UPDATE_REPLACE: properties.Schema(
+            properties.Schema.BOOLEAN,
+            _('Value which can be set to trigger update replace for '
+              'the particular resource'),
+            update_allowed=True,
+            default=False
+        ),
+        WAIT_SECS: properties.Schema(
+            properties.Schema.NUMBER,
+            _('Value which can be set for resource to wait after an action '
+              'is performed.'),
+            update_allowed=True,
+            default=0,
+        ),
+    }
+
+    attributes_schema = {
+        OUTPUT: attributes.Schema(
+            _('The string that was stored. This value is '
+              'also available by referencing the resource.'),
+            cache_mode=attributes.Schema.CACHE_NONE
+        ),
+    }
+
+    def handle_create(self):
+        value = self.properties.get(self.VALUE)
+        fail_prop = self.properties.get(self.FAIL)
+        sleep_secs = self.properties.get(self.WAIT_SECS)
+
+        self.data_set('value', value, redact=False)
+        self.resource_id_set(self.physical_resource_name())
+
+        # sleep for specified time
+        if sleep_secs:
+            LOG.debug("Resource %s sleeping for %s seconds",
+                      self.name, sleep_secs)
+            eventlet.sleep(sleep_secs)
+
+        # emulate failure
+        if fail_prop:
+            raise Exception("Test Resource failed %s", self.name)
+
+    def handle_update(self, json_snippet=None, tmpl_diff=None, prop_diff=None):
+        value = prop_diff.get(self.VALUE)
+        new_prop = json_snippet._properties
+        if value:
+            update_replace = new_prop.get(self.UPDATE_REPLACE, False)
+            if update_replace:
+                raise resource.UpdateReplace(self.name)
+            else:
+                fail_prop = new_prop.get(self.FAIL, False)
+                sleep_secs = new_prop.get(self.WAIT_SECS, 0)
+                # emulate failure
+                if fail_prop:
+                    raise Exception("Test Resource failed %s", self.name)
+                # update in place
+                self.data_set('value', value, redact=False)
+
+                if sleep_secs:
+                    LOG.debug("Update of Resource %s sleeping for %s seconds",
+                              self.name, sleep_secs)
+                    eventlet.sleep(sleep_secs)
+
+    def _resolve_attribute(self, name):
+        if name == self.OUTPUT:
+            return self.data().get('value')
+
+
+def resource_mapping():
+    return {
+        'OS::Heat::TestResource': TestResource,
+    }
diff --git a/functional/test_create_update.py b/functional/test_create_update.py
new file mode 100644
index 0000000..37b1790
--- /dev/null
+++ b/functional/test_create_update.py
@@ -0,0 +1,386 @@
+#    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 copy
+from heat_integrationtests.common import test
+import json
+
+test_template_one_resource = {
+    'heat_template_version': '2013-05-23',
+    'description': 'Test template to create one instance.',
+    'resources': {
+        'test1': {
+            'type': 'OS::Heat::TestResource',
+            'properties': {
+                'value': 'Test1',
+                'fail': False,
+                'update_replace': False,
+                'wait_secs': 0
+            }
+        }
+    }
+}
+
+test_template_two_resource = {
+    'heat_template_version': '2013-05-23',
+    'description': 'Test template to create two instance.',
+    'resources': {
+        'test1': {
+            'type': 'OS::Heat::TestResource',
+            'properties': {
+                'value': 'Test1',
+                'fail': False,
+                'update_replace': False,
+                'wait_secs': 0
+            }
+        },
+        'test2': {
+            'type': 'OS::Heat::TestResource',
+            'properties': {
+                'value': 'Test1',
+                'fail': False,
+                'update_replace': False,
+                'wait_secs': 0
+            }
+        }
+    }
+}
+
+
+def _change_rsrc_properties(template, rsrcs, values):
+        modified_template = copy.deepcopy(template)
+        for rsrc_name in rsrcs:
+            rsrc_prop = modified_template['resources'][
+                rsrc_name]['properties']
+            for prop in rsrc_prop:
+                if prop in values:
+                    rsrc_prop[prop] = values[prop]
+        return modified_template
+
+
+class CreateStackTest(test.HeatIntegrationTest):
+    def setUp(self):
+        super(CreateStackTest, self).setUp()
+        self.client = self.orchestration_client
+
+    def test_create_rollback(self):
+        values = {'fail': True, 'value': 'test_create_rollback'}
+        template = _change_rsrc_properties(test_template_one_resource,
+                                           ['test1'], values)
+
+        self.stack_create(
+            template=template,
+            expected_status='ROLLBACK_COMPLETE',
+            disable_rollback=False)
+
+
+class UpdateStackTest(test.HeatIntegrationTest):
+
+    provider_template = {
+        'heat_template_version': '2013-05-23',
+        'description': 'foo',
+        'resources': {
+            'test1': {
+                'type': 'My::TestResource'
+            }
+        }
+    }
+
+    provider_group_template = '''
+heat_template_version: 2013-05-23
+resources:
+  test_group:
+    type: OS::Heat::ResourceGroup
+    properties:
+      count: 2
+      resource_def:
+        type: My::TestResource
+'''
+
+    update_userdata_template = '''
+heat_template_version: 2014-10-16
+parameters:
+  flavor:
+    type: string
+  user_data:
+    type: string
+  image:
+    type: string
+
+resources:
+  server:
+    type: OS::Nova::Server
+    properties:
+      image: {get_param: image}
+      flavor: {get_param: flavor}
+      user_data_format: SOFTWARE_CONFIG
+      user_data: {get_param: user_data}
+'''
+
+    def setUp(self):
+        super(UpdateStackTest, self).setUp()
+        self.client = self.orchestration_client
+
+    def test_stack_update_nochange(self):
+        template = _change_rsrc_properties(test_template_one_resource,
+                                           ['test1'],
+                                           {'value': 'test_no_change'})
+        stack_identifier = self.stack_create(
+            template=template)
+        expected_resources = {'test1': 'OS::Heat::TestResource'}
+        self.assertEqual(expected_resources,
+                         self.list_resources(stack_identifier))
+
+        # Update with no changes, resources should be unchanged
+        self.update_stack(stack_identifier, template)
+        self.assertEqual(expected_resources,
+                         self.list_resources(stack_identifier))
+
+    def test_stack_in_place_update(self):
+        template = _change_rsrc_properties(test_template_one_resource,
+                                           ['test1'],
+                                           {'value': 'test_in_place'})
+        stack_identifier = self.stack_create(
+            template=template)
+        expected_resources = {'test1': 'OS::Heat::TestResource'}
+        self.assertEqual(expected_resources,
+                         self.list_resources(stack_identifier))
+        resource = self.client.resources.list(stack_identifier)
+        initial_phy_id = resource[0].physical_resource_id
+
+        tmpl_update = _change_rsrc_properties(
+            test_template_one_resource, ['test1'],
+            {'value': 'test_in_place_update'})
+        # Update the Value
+        self.update_stack(stack_identifier, tmpl_update)
+        resource = self.client.resources.list(stack_identifier)
+        # By default update_in_place
+        self.assertEqual(initial_phy_id,
+                         resource[0].physical_resource_id)
+
+    def test_stack_update_replace(self):
+        template = _change_rsrc_properties(test_template_one_resource,
+                                           ['test1'],
+                                           {'value': 'test_replace'})
+        stack_identifier = self.stack_create(
+            template=template)
+        expected_resources = {'test1': 'OS::Heat::TestResource'}
+        self.assertEqual(expected_resources,
+                         self.list_resources(stack_identifier))
+        resource = self.client.resources.list(stack_identifier)
+        initial_phy_id = resource[0].physical_resource_id
+
+        # Update the value and also set update_replace prop
+        tmpl_update = _change_rsrc_properties(
+            test_template_one_resource, ['test1'],
+            {'value': 'test_in_place_update', 'update_replace': True})
+        self.update_stack(stack_identifier, tmpl_update)
+        resource = self.client.resources.list(stack_identifier)
+        # update Replace
+        self.assertNotEqual(initial_phy_id,
+                            resource[0].physical_resource_id)
+
+    def test_stack_update_add_remove(self):
+        template = _change_rsrc_properties(test_template_one_resource,
+                                           ['test1'],
+                                           {'value': 'test_add_remove'})
+        stack_identifier = self.stack_create(
+            template=template)
+        initial_resources = {'test1': 'OS::Heat::TestResource'}
+        self.assertEqual(initial_resources,
+                         self.list_resources(stack_identifier))
+
+        tmpl_update = _change_rsrc_properties(
+            test_template_two_resource, ['test1', 'test2'],
+            {'value': 'test_add_remove_update'})
+        # Add one resource via a stack update
+        self.update_stack(stack_identifier, tmpl_update)
+        updated_resources = {'test1': 'OS::Heat::TestResource',
+                             'test2': 'OS::Heat::TestResource'}
+        self.assertEqual(updated_resources,
+                         self.list_resources(stack_identifier))
+
+        # Then remove it by updating with the original template
+        self.update_stack(stack_identifier, template)
+        self.assertEqual(initial_resources,
+                         self.list_resources(stack_identifier))
+
+    def test_stack_update_rollback(self):
+        template = _change_rsrc_properties(test_template_one_resource,
+                                           ['test1'],
+                                           {'value': 'test_update_rollback'})
+        stack_identifier = self.stack_create(
+            template=template)
+        initial_resources = {'test1': 'OS::Heat::TestResource'}
+        self.assertEqual(initial_resources,
+                         self.list_resources(stack_identifier))
+
+        tmpl_update = _change_rsrc_properties(
+            test_template_two_resource, ['test1', 'test2'],
+            {'value': 'test_update_rollback', 'fail': True})
+        # stack update, also set failure
+        self.update_stack(stack_identifier, tmpl_update,
+                          expected_status='ROLLBACK_COMPLETE',
+                          disable_rollback=False)
+        # since stack update failed only the original resource is present
+        updated_resources = {'test1': 'OS::Heat::TestResource'}
+        self.assertEqual(updated_resources,
+                         self.list_resources(stack_identifier))
+
+    def test_stack_update_provider(self):
+        template = _change_rsrc_properties(
+            test_template_one_resource, ['test1'],
+            {'value': 'test_provider_template'})
+        files = {'provider.template': json.dumps(template)}
+        env = {'resource_registry':
+               {'My::TestResource': 'provider.template'}}
+        stack_identifier = self.stack_create(
+            template=self.provider_template,
+            files=files,
+            environment=env
+        )
+
+        initial_resources = {'test1': 'My::TestResource'}
+        self.assertEqual(initial_resources,
+                         self.list_resources(stack_identifier))
+
+        # Prove the resource is backed by a nested stack, save the ID
+        nested_identifier = self.assert_resource_is_a_stack(stack_identifier,
+                                                            'test1')
+        nested_id = nested_identifier.split('/')[-1]
+
+        # Then check the expected resources are in the nested stack
+        nested_resources = {'test1': 'OS::Heat::TestResource'}
+        self.assertEqual(nested_resources,
+                         self.list_resources(nested_identifier))
+        tmpl_update = _change_rsrc_properties(
+            test_template_two_resource, ['test1', 'test2'],
+            {'value': 'test_provider_template'})
+        # Add one resource via a stack update by changing the nested stack
+        files['provider.template'] = json.dumps(tmpl_update)
+        self.update_stack(stack_identifier, self.provider_template,
+                          environment=env, files=files)
+
+        # Parent resources should be unchanged and the nested stack
+        # should have been updated in-place without replacement
+        self.assertEqual(initial_resources,
+                         self.list_resources(stack_identifier))
+        rsrc = self.client.resources.get(stack_identifier, 'test1')
+        self.assertEqual(rsrc.physical_resource_id, nested_id)
+
+        # Then check the expected resources are in the nested stack
+        nested_resources = {'test1': 'OS::Heat::TestResource',
+                            'test2': 'OS::Heat::TestResource'}
+        self.assertEqual(nested_resources,
+                         self.list_resources(nested_identifier))
+
+    def test_stack_update_provider_group(self):
+        '''Test two-level nested update.'''
+        # Create a ResourceGroup (which creates a nested stack),
+        # containing provider resources (which create a nested
+        # stack), thus excercising an update which traverses
+        # two levels of nesting.
+        template = _change_rsrc_properties(
+            test_template_one_resource, ['test1'],
+            {'value': 'test_provider_group_template'})
+        files = {'provider.template': json.dumps(template)}
+        env = {'resource_registry':
+               {'My::TestResource': 'provider.template'}}
+
+        stack_identifier = self.stack_create(
+            template=self.provider_group_template,
+            files=files,
+            environment=env
+        )
+
+        initial_resources = {'test_group': 'OS::Heat::ResourceGroup'}
+        self.assertEqual(initial_resources,
+                         self.list_resources(stack_identifier))
+
+        # Prove the resource is backed by a nested stack, save the ID
+        nested_identifier = self.assert_resource_is_a_stack(stack_identifier,
+                                                            'test_group')
+
+        # Then check the expected resources are in the nested stack
+        nested_resources = {'0': 'My::TestResource',
+                            '1': 'My::TestResource'}
+        self.assertEqual(nested_resources,
+                         self.list_resources(nested_identifier))
+
+        for n_rsrc in nested_resources:
+            rsrc = self.client.resources.get(nested_identifier, n_rsrc)
+            provider_stack = self.client.stacks.get(rsrc.physical_resource_id)
+            provider_identifier = '%s/%s' % (provider_stack.stack_name,
+                                             provider_stack.id)
+            provider_resources = {u'test1': u'OS::Heat::TestResource'}
+            self.assertEqual(provider_resources,
+                             self.list_resources(provider_identifier))
+
+        tmpl_update = _change_rsrc_properties(
+            test_template_two_resource, ['test1', 'test2'],
+            {'value': 'test_provider_group_template'})
+        # Add one resource via a stack update by changing the nested stack
+        files['provider.template'] = json.dumps(tmpl_update)
+        self.update_stack(stack_identifier, self.provider_group_template,
+                          environment=env, files=files)
+
+        # Parent resources should be unchanged and the nested stack
+        # should have been updated in-place without replacement
+        self.assertEqual(initial_resources,
+                         self.list_resources(stack_identifier))
+
+        # Resource group stack should also be unchanged (but updated)
+        nested_stack = self.client.stacks.get(nested_identifier)
+        self.assertEqual('UPDATE_COMPLETE', nested_stack.stack_status)
+        self.assertEqual(nested_resources,
+                         self.list_resources(nested_identifier))
+
+        for n_rsrc in nested_resources:
+            rsrc = self.client.resources.get(nested_identifier, n_rsrc)
+            provider_stack = self.client.stacks.get(rsrc.physical_resource_id)
+            provider_identifier = '%s/%s' % (provider_stack.stack_name,
+                                             provider_stack.id)
+            provider_resources = {'test1': 'OS::Heat::TestResource',
+                                  'test2': 'OS::Heat::TestResource'}
+            self.assertEqual(provider_resources,
+                             self.list_resources(provider_identifier))
+
+    def test_stack_update_with_replacing_userdata(self):
+        """Confirm that we can update userdata of instance during updating
+        stack by the user of member role.
+
+        Make sure that a resource that inherites from StackUser can be deleted
+        during updating stack.
+        """
+        if not self.conf.minimal_image_ref:
+            raise self.skipException("No minimal image configured to test")
+        if not self.conf.minimal_instance_type:
+            raise self.skipException("No flavor configured to test")
+
+        parms = {'flavor': self.conf.minimal_instance_type,
+                 'image': self.conf.minimal_image_ref,
+                 'user_data': ''}
+        name = self._stack_rand_name()
+
+        stack_identifier = self.stack_create(
+            stack_name=name,
+            template=self.update_userdata_template,
+            parameters=parms
+        )
+
+        parms_updated = parms
+        parms_updated['user_data'] = 'two'
+        self.update_stack(
+            stack_identifier,
+            template=self.update_userdata_template,
+            parameters=parms_updated)
diff --git a/functional/test_update.py b/functional/test_update.py
deleted file mode 100644
index b329ddc..0000000
--- a/functional/test_update.py
+++ /dev/null
@@ -1,240 +0,0 @@
-#    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.
-
-
-from heat_integrationtests.common import test
-
-
-class UpdateStackTest(test.HeatIntegrationTest):
-
-    template = '''
-heat_template_version: 2013-05-23
-resources:
-  random1:
-    type: OS::Heat::RandomString
-'''
-    update_template = '''
-heat_template_version: 2013-05-23
-resources:
-  random1:
-    type: OS::Heat::RandomString
-  random2:
-    type: OS::Heat::RandomString
-'''
-
-    provider_template = '''
-heat_template_version: 2013-05-23
-resources:
-  random1:
-    type: My::RandomString
-'''
-
-    provider_group_template = '''
-heat_template_version: 2013-05-23
-resources:
-  random_group:
-    type: OS::Heat::ResourceGroup
-    properties:
-      count: 2
-      resource_def:
-        type: My::RandomString
-'''
-
-    update_userdata_template = '''
-heat_template_version: 2014-10-16
-parameters:
-  flavor:
-    type: string
-  user_data:
-    type: string
-  image:
-    type: string
-
-resources:
-  server:
-    type: OS::Nova::Server
-    properties:
-      image: {get_param: image}
-      flavor: {get_param: flavor}
-      user_data_format: SOFTWARE_CONFIG
-      user_data: {get_param: user_data}
-'''
-
-    def setUp(self):
-        super(UpdateStackTest, self).setUp()
-        self.client = self.orchestration_client
-
-    def test_stack_update_nochange(self):
-        stack_identifier = self.stack_create()
-        expected_resources = {'random1': 'OS::Heat::RandomString'}
-        self.assertEqual(expected_resources,
-                         self.list_resources(stack_identifier))
-
-        # Update with no changes, resources should be unchanged
-        self.update_stack(stack_identifier, self.template)
-        self.assertEqual(expected_resources,
-                         self.list_resources(stack_identifier))
-
-    def test_stack_update_add_remove(self):
-        stack_identifier = self.stack_create()
-        initial_resources = {'random1': 'OS::Heat::RandomString'}
-        self.assertEqual(initial_resources,
-                         self.list_resources(stack_identifier))
-
-        # Add one resource via a stack update
-        self.update_stack(stack_identifier, self.update_template)
-        updated_resources = {'random1': 'OS::Heat::RandomString',
-                             'random2': 'OS::Heat::RandomString'}
-        self.assertEqual(updated_resources,
-                         self.list_resources(stack_identifier))
-
-        # Then remove it by updating with the original template
-        self.update_stack(stack_identifier, self.template)
-        self.assertEqual(initial_resources,
-                         self.list_resources(stack_identifier))
-
-    def test_stack_update_provider(self):
-        files = {'provider.yaml': self.template}
-        env = {'resource_registry':
-               {'My::RandomString': 'provider.yaml'}}
-        stack_identifier = self.stack_create(
-            template=self.provider_template,
-            files=files,
-            environment=env
-        )
-
-        initial_resources = {'random1': 'My::RandomString'}
-        self.assertEqual(initial_resources,
-                         self.list_resources(stack_identifier))
-
-        # Prove the resource is backed by a nested stack, save the 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'}
-        self.assertEqual(nested_resources,
-                         self.list_resources(nested_identifier))
-
-        # Add one resource via a stack update by changing the nested stack
-        files['provider.yaml'] = self.update_template
-        self.update_stack(stack_identifier, self.provider_template,
-                          environment=env, files=files)
-
-        # Parent resources should be unchanged and the nested stack
-        # should have been updated in-place without replacement
-        self.assertEqual(initial_resources,
-                         self.list_resources(stack_identifier))
-        rsrc = self.client.resources.get(stack_identifier, 'random1')
-        self.assertEqual(rsrc.physical_resource_id, nested_id)
-
-        # Then check the expected resources are in the nested stack
-        nested_resources = {'random1': 'OS::Heat::RandomString',
-                            'random2': 'OS::Heat::RandomString'}
-        self.assertEqual(nested_resources,
-                         self.list_resources(nested_identifier))
-
-    def test_stack_update_provider_group(self):
-        '''Test two-level nested update.'''
-        # Create a ResourceGroup (which creates a nested stack),
-        # containing provider resources (which create a nested
-        # stack), thus excercising an update which traverses
-        # two levels of nesting.
-        files = {'provider.yaml': self.template}
-        env = {'resource_registry':
-               {'My::RandomString': 'provider.yaml'}}
-
-        stack_identifier = self.stack_create(
-            template=self.provider_group_template,
-            files=files,
-            environment=env
-        )
-
-        initial_resources = {'random_group': 'OS::Heat::ResourceGroup'}
-        self.assertEqual(initial_resources,
-                         self.list_resources(stack_identifier))
-
-        # Prove the resource is backed by a nested stack, save the ID
-        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',
-                            '1': 'My::RandomString'}
-        self.assertEqual(nested_resources,
-                         self.list_resources(nested_identifier))
-
-        for n_rsrc in nested_resources:
-            rsrc = self.client.resources.get(nested_identifier, n_rsrc)
-            provider_stack = self.client.stacks.get(rsrc.physical_resource_id)
-            provider_identifier = '%s/%s' % (provider_stack.stack_name,
-                                             provider_stack.id)
-            provider_resources = {u'random1': u'OS::Heat::RandomString'}
-            self.assertEqual(provider_resources,
-                             self.list_resources(provider_identifier))
-
-        # Add one resource via a stack update by changing the nested stack
-        files['provider.yaml'] = self.update_template
-        self.update_stack(stack_identifier, self.provider_group_template,
-                          environment=env, files=files)
-
-        # Parent resources should be unchanged and the nested stack
-        # should have been updated in-place without replacement
-        self.assertEqual(initial_resources,
-                         self.list_resources(stack_identifier))
-
-        # Resource group stack should also be unchanged (but updated)
-        nested_stack = self.client.stacks.get(nested_identifier)
-        self.assertEqual('UPDATE_COMPLETE', nested_stack.stack_status)
-        self.assertEqual(nested_resources,
-                         self.list_resources(nested_identifier))
-
-        for n_rsrc in nested_resources:
-            rsrc = self.client.resources.get(nested_identifier, n_rsrc)
-            provider_stack = self.client.stacks.get(rsrc.physical_resource_id)
-            provider_identifier = '%s/%s' % (provider_stack.stack_name,
-                                             provider_stack.id)
-            provider_resources = {'random1': 'OS::Heat::RandomString',
-                                  'random2': 'OS::Heat::RandomString'}
-            self.assertEqual(provider_resources,
-                             self.list_resources(provider_identifier))
-
-    def test_stack_update_with_replacing_userdata(self):
-        """Confirm that we can update userdata of instance during updating
-        stack by the user of member role.
-
-        Make sure that a resource that inherites from StackUser can be deleted
-        during updating stack.
-        """
-        if not self.conf.minimal_image_ref:
-            raise self.skipException("No minimal image configured to test")
-        if not self.conf.minimal_instance_type:
-            raise self.skipException("No flavor configured to test")
-
-        parms = {'flavor': self.conf.minimal_instance_type,
-                 'image': self.conf.minimal_image_ref,
-                 'user_data': ''}
-        name = self._stack_rand_name()
-
-        stack_identifier = self.stack_create(
-            stack_name=name,
-            template=self.update_userdata_template,
-            parameters=parms
-        )
-
-        parms_updated = parms
-        parms_updated['user_data'] = 'two'
-        self.update_stack(
-            stack_identifier,
-            template=self.update_userdata_template,
-            parameters=parms_updated)