| # 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) |