summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSteven Hardy <shardy@redhat.com>2015-10-21 18:56:01 +0100
committerZane Bitter <zbitter@redhat.com>2015-11-17 16:40:15 -0500
commit67e9b5bf1131019ed0746fde7e56c60462327c53 (patch)
treec440b77b7d15f7dc53a27c234beb5bce9df21cb1
parent9bf939178a8c263a7e236e14979dc52d8d526d4c (diff)
downloadheat-67e9b5bf1131019ed0746fde7e56c60462327c53.tar.gz
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 (cherry picked from commit 98044dbd17c703e6062752f043e1cac6563e3589)
-rw-r--r--heat/engine/resource.py13
-rw-r--r--heat/engine/resources/template_resource.py5
-rw-r--r--heat/engine/update.py4
-rw-r--r--heat_integrationtests/functional/test_create_update.py99
4 files changed, 119 insertions, 2 deletions
diff --git a/heat/engine/resource.py b/heat/engine/resource.py
index ca969510d..312b82166 100644
--- a/heat/engine/resource.py
+++ b/heat/engine/resource.py
@@ -309,6 +309,19 @@ class Resource(object):
# common resources have not nested, StackResource overrides it
return False
+ def resource_class(self):
+ """Return the resource class.
+
+ This is used to compare old and new resources when updating, to ensure
+ that in-place updates are possible. This method shold return the
+ highest common class in the hierarchy whose subclasses are all capable
+ of converting to each other's types via handle_update().
+
+ This mechanism may disappear again in future, so third-party resource
+ types should not rely on it.
+ """
+ return type(self)
+
def has_hook(self, hook):
# Clear the cache to make sure the data is up to date:
self._data = None
diff --git a/heat/engine/resources/template_resource.py b/heat/engine/resources/template_resource.py
index 84f68d25f..0b0c61ba5 100644
--- a/heat/engine/resources/template_resource.py
+++ b/heat/engine/resources/template_resource.py
@@ -61,6 +61,11 @@ class TemplateResource(stack_resource.StackResource):
if self.validation_exception is None:
self._generate_schema(self.t)
+ def resource_class(self):
+ # All TemplateResource subclasses can be converted to each other with
+ # a stack update, so allow them to cross-update in place.
+ return TemplateResource
+
def _get_resource_info(self, rsrc_defn):
tri = self.stack.env.get_resource_info(
rsrc_defn.resource_type,
diff --git a/heat/engine/update.py b/heat/engine/update.py
index db2c78d56..5b62bd4b9 100644
--- a/heat/engine/update.py
+++ b/heat/engine/update.py
@@ -137,10 +137,10 @@ class StackUpdate(object):
@scheduler.wrappertask
def _process_new_resource_update(self, new_res):
res_name = new_res.name
- res_type = new_res.type()
+ res_class = new_res.resource_class()
if (res_name in self.existing_stack and
- res_type == self.existing_stack[res_name].type()):
+ self.existing_stack[res_name].resource_class() is res_class):
existing_res = self.existing_stack[res_name]
try:
yield self._update_in_place(existing_res,
diff --git a/heat_integrationtests/functional/test_create_update.py b/heat_integrationtests/functional/test_create_update.py
index 37b179084..821aeff76 100644
--- a/heat_integrationtests/functional/test_create_update.py
+++ b/heat_integrationtests/functional/test_create_update.py
@@ -284,6 +284,105 @@ resources:
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.'''
# Create a ResourceGroup (which creates a nested stack),