diff --git a/heat_integrationtests/common/test.py b/heat_integrationtests/common/test.py index b88c72bc90..331c922cec 100644 --- a/heat_integrationtests/common/test.py +++ b/heat_integrationtests/common/test.py @@ -276,7 +276,8 @@ class HeatIntegrationTest(testscenarios.WithScenarios, 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 @@ class HeatIntegrationTest(testscenarios.WithScenarios, 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 @@ class HeatIntegrationTest(testscenarios.WithScenarios, 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 @@ class HeatIntegrationTest(testscenarios.WithScenarios, 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/heat_integrationtests/common/test_resources/test_resource.py b/heat_integrationtests/common/test_resources/test_resource.py new file mode 100644 index 0000000000..88f1745fb8 --- /dev/null +++ b/heat_integrationtests/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/heat_integrationtests/functional/test_create_update.py b/heat_integrationtests/functional/test_create_update.py new file mode 100644 index 0000000000..37b179084c --- /dev/null +++ b/heat_integrationtests/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/heat_integrationtests/functional/test_update.py b/heat_integrationtests/functional/test_update.py deleted file mode 100644 index b329ddc049..0000000000 --- a/heat_integrationtests/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) diff --git a/heat_integrationtests/pre_test_hook.sh b/heat_integrationtests/pre_test_hook.sh index ba3eb8cc8a..fa4db09d22 100755 --- a/heat_integrationtests/pre_test_hook.sh +++ b/heat_integrationtests/pre_test_hook.sh @@ -20,3 +20,4 @@ echo "HEAT_ENABLE_ADOPT_ABANDON=True" >> $localrc_path echo -e '[[post-config|$HEAT_CONF]]\n[DEFAULT]\n' >> $localconf echo -e 'notification_driver=messagingv2\n' >> $localconf echo -e 'num_engine_workers=2\n' >> $localconf +echo -e 'plugin_dirs=$HEAT_DIR/heat_integrationtests/common/test_resources\n' >> $localconf