Allow in-place updates for all compatible types
We previously only allowed a resource to update in-place when the 'type'
field in the old and new templates matched. In an age of environment type
mappings, this is incorrect. Instead, allow a resource to be updated
in-place when the old and new resource plugins are of the same class, since
this is actually what determines whether handle_update is capable of
correctly updating the resource in-place.
There is a special-case for TemplateResource as we currently subclass it
for each template type, but all TemplateResource subclasses are in fact
capable of converting between each other via a stack update.
Change-Id: Iba4abf5efd9737ca6a17d8c91d5c54ab6d3f12e0
Co-Authored-By: Zane Bitter <zbitter@redhat.com>
Closes-Bug: #1508115
diff --git a/functional/test_create_update.py b/functional/test_create_update.py
index 153eb9f..1aef6b6 100644
--- a/functional/test_create_update.py
+++ b/functional/test_create_update.py
@@ -331,6 +331,105 @@
self.assertEqual(nested_resources,
self.list_resources(nested_identifier))
+ def test_stack_update_alias_type(self):
+ env = {'resource_registry':
+ {'My::TestResource': 'OS::Heat::RandomString',
+ 'My::TestResource2': 'OS::Heat::RandomString'}}
+ stack_identifier = self.stack_create(
+ template=self.provider_template,
+ environment=env
+ )
+ p_res = self.client.resources.get(stack_identifier, 'test1')
+ self.assertEqual('My::TestResource', p_res.resource_type)
+
+ initial_resources = {'test1': 'My::TestResource'}
+ self.assertEqual(initial_resources,
+ self.list_resources(stack_identifier))
+ res = self.client.resources.get(stack_identifier, 'test1')
+ # Modify the type of the resource alias to My::TestResource2
+ tmpl_update = copy.deepcopy(self.provider_template)
+ tmpl_update['resources']['test1']['type'] = 'My::TestResource2'
+ self.update_stack(stack_identifier, tmpl_update, environment=env)
+ res_a = self.client.resources.get(stack_identifier, 'test1')
+ self.assertEqual(res.physical_resource_id, res_a.physical_resource_id)
+ self.assertEqual(res.attributes['value'], res_a.attributes['value'])
+
+ def test_stack_update_alias_changes(self):
+ env = {'resource_registry':
+ {'My::TestResource': 'OS::Heat::RandomString'}}
+ stack_identifier = self.stack_create(
+ template=self.provider_template,
+ environment=env
+ )
+ p_res = self.client.resources.get(stack_identifier, 'test1')
+ self.assertEqual('My::TestResource', p_res.resource_type)
+
+ initial_resources = {'test1': 'My::TestResource'}
+ self.assertEqual(initial_resources,
+ self.list_resources(stack_identifier))
+ res = self.client.resources.get(stack_identifier, 'test1')
+ # Modify the resource alias to point to a different type
+ env = {'resource_registry':
+ {'My::TestResource': 'OS::Heat::TestResource'}}
+ self.update_stack(stack_identifier, template=self.provider_template,
+ environment=env)
+ res_a = self.client.resources.get(stack_identifier, 'test1')
+ self.assertNotEqual(res.physical_resource_id,
+ res_a.physical_resource_id)
+
+ def test_stack_update_provider_type(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',
+ 'My::TestResource2': 'provider.template'}}
+ stack_identifier = self.stack_create(
+ template=self.provider_template,
+ files=files,
+ environment=env
+ )
+ p_res = self.client.resources.get(stack_identifier, 'test1')
+ self.assertEqual('My::TestResource', p_res.resource_type)
+
+ 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))
+ n_res = self.client.resources.get(nested_identifier, 'test1')
+
+ # Modify the type of the provider resource to My::TestResource2
+ tmpl_update = copy.deepcopy(self.provider_template)
+ tmpl_update['resources']['test1']['type'] = 'My::TestResource2'
+ self.update_stack(stack_identifier, tmpl_update,
+ environment=env, files=files)
+ p_res = self.client.resources.get(stack_identifier, 'test1')
+ self.assertEqual('My::TestResource2', p_res.resource_type)
+
+ # Parent resources should be unchanged and the nested stack
+ # should have been updated in-place without replacement
+ self.assertEqual({u'test1': u'My::TestResource2'},
+ 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
+ self.assertEqual(nested_resources,
+ self.list_resources(nested_identifier))
+ n_res2 = self.client.resources.get(nested_identifier, 'test1')
+ self.assertEqual(n_res.physical_resource_id,
+ n_res2.physical_resource_id)
+
def test_stack_update_provider_group(self):
"""Test two-level nested update."""