# 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 network: type: string resources: server: type: OS::Nova::Server properties: image: {get_param: image} flavor: {get_param: flavor} networks: [{network: {get_param: network} }] 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, 'network': self.conf.fixed_network_name, '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)