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)