Merge "Pre-empt in-progress nested stack updates on new update"
This commit is contained in:
@@ -163,6 +163,8 @@ class CheckResource(object):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
except exception.UpdateInProgress:
|
except exception.UpdateInProgress:
|
||||||
|
LOG.debug('Waiting for existing update to unlock resource %s',
|
||||||
|
rsrc.id)
|
||||||
if self._stale_resource_needs_retry(cnxt, rsrc, prev_template_id):
|
if self._stale_resource_needs_retry(cnxt, rsrc, prev_template_id):
|
||||||
rpc_data = sync_point.serialize_input_data(self.input_data)
|
rpc_data = sync_point.serialize_input_data(self.input_data)
|
||||||
self._rpc_client.check_resource(cnxt,
|
self._rpc_client.check_resource(cnxt,
|
||||||
@@ -170,6 +172,8 @@ class CheckResource(object):
|
|||||||
current_traversal,
|
current_traversal,
|
||||||
rpc_data, is_update,
|
rpc_data, is_update,
|
||||||
adopt_stack_data)
|
adopt_stack_data)
|
||||||
|
else:
|
||||||
|
rsrc.handle_preempt()
|
||||||
except exception.ResourceFailure as ex:
|
except exception.ResourceFailure as ex:
|
||||||
action = ex.action or rsrc.action
|
action = ex.action or rsrc.action
|
||||||
reason = 'Resource %s failed: %s' % (action,
|
reason = 'Resource %s failed: %s' % (action,
|
||||||
|
|||||||
@@ -1456,6 +1456,23 @@ class Resource(status.ResourceStatus):
|
|||||||
new_requires=new_requires)
|
new_requires=new_requires)
|
||||||
runner(timeout=timeout, progress_callback=progress_callback)
|
runner(timeout=timeout, progress_callback=progress_callback)
|
||||||
|
|
||||||
|
def handle_preempt(self):
|
||||||
|
"""Pre-empt an in-progress update when a new update is available.
|
||||||
|
|
||||||
|
This method is called when a previous convergence update is in
|
||||||
|
progress but a new update for the resource is available. By default
|
||||||
|
it does nothing, but subclasses may override it to cancel the
|
||||||
|
in-progress update if it is safe to do so.
|
||||||
|
|
||||||
|
Note that this method does not run in the context of the in-progress
|
||||||
|
update and has no access to runtime information about it; nor is it
|
||||||
|
safe to make changes to the Resource in the database. If implemented,
|
||||||
|
this method should cause the existing update to complete by external
|
||||||
|
means. If this leaves the resource in a FAILED state, that should be
|
||||||
|
taken into account in needs_replace_failed().
|
||||||
|
"""
|
||||||
|
return
|
||||||
|
|
||||||
def preview_update(self, after, before, after_props, before_props,
|
def preview_update(self, after, before, after_props, before_props,
|
||||||
prev_resource, check_init_complete=False):
|
prev_resource, check_init_complete=False):
|
||||||
"""Simulates update without actually updating the resource.
|
"""Simulates update without actually updating the resource.
|
||||||
|
|||||||
@@ -561,9 +561,10 @@ class StackResource(resource.Resource):
|
|||||||
return self._check_status_complete(target_action,
|
return self._check_status_complete(target_action,
|
||||||
cookie=cookie)
|
cookie=cookie)
|
||||||
|
|
||||||
def handle_update_cancel(self, cookie):
|
def _handle_cancel(self):
|
||||||
stack_identity = self.nested_identifier()
|
stack_identity = self.nested_identifier()
|
||||||
if stack_identity is not None:
|
if stack_identity is not None:
|
||||||
|
LOG.debug('Cancelling %s of %s' % (self.action, self))
|
||||||
try:
|
try:
|
||||||
self.rpc_client().stack_cancel_update(
|
self.rpc_client().stack_cancel_update(
|
||||||
self.context,
|
self.context,
|
||||||
@@ -573,6 +574,12 @@ class StackResource(resource.Resource):
|
|||||||
LOG.debug('Nested stack %s not in cancellable state',
|
LOG.debug('Nested stack %s not in cancellable state',
|
||||||
stack_identity.stack_name)
|
stack_identity.stack_name)
|
||||||
|
|
||||||
|
def handle_preempt(self):
|
||||||
|
self._handle_cancel()
|
||||||
|
|
||||||
|
def handle_update_cancel(self, cookie):
|
||||||
|
self._handle_cancel()
|
||||||
|
|
||||||
def handle_create_cancel(self, cookie):
|
def handle_create_cancel(self, cookie):
|
||||||
return self.handle_update_cancel(cookie)
|
return self.handle_update_cancel(cookie)
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
import json
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from heat_integrationtests.common import test
|
from heat_integrationtests.common import test
|
||||||
@@ -91,3 +92,85 @@ class SimultaneousUpdateStackTest(functional_base.FunctionalTestsBase):
|
|||||||
time.sleep(50)
|
time.sleep(50)
|
||||||
|
|
||||||
self.update_stack(stack_id, after)
|
self.update_stack(stack_id, after)
|
||||||
|
|
||||||
|
|
||||||
|
input_param = 'input'
|
||||||
|
preempt_nested_stack_type = 'preempt.yaml'
|
||||||
|
preempt_root_rsrcs = {
|
||||||
|
'nested_stack': {
|
||||||
|
'type': preempt_nested_stack_type,
|
||||||
|
'properties': {
|
||||||
|
'input': {'get_param': input_param},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
preempt_root_out = {'get_attr': ['nested_stack', 'delay_stack']}
|
||||||
|
preempt_delay_stack_type = 'delay.yaml'
|
||||||
|
preempt_nested_rsrcs = {
|
||||||
|
'delay_stack': {
|
||||||
|
'type': preempt_delay_stack_type,
|
||||||
|
'properties': {
|
||||||
|
'input': {'get_param': input_param},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
preempt_nested_out = {'get_resource': 'delay_stack'}
|
||||||
|
preempt_delay_rsrcs = {
|
||||||
|
'delay_resource': {
|
||||||
|
'type': 'OS::Heat::TestResource',
|
||||||
|
'properties': {
|
||||||
|
'action_wait_secs': {
|
||||||
|
'update': 6000,
|
||||||
|
},
|
||||||
|
'value': {'get_param': input_param},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _tmpl_with_rsrcs(rsrcs, output_value=None):
|
||||||
|
tmpl = {
|
||||||
|
'heat_template_version': 'queens',
|
||||||
|
'parameters': {
|
||||||
|
input_param: {
|
||||||
|
'type': 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'resources': rsrcs,
|
||||||
|
}
|
||||||
|
if output_value is not None:
|
||||||
|
outputs = {'delay_stack': {'value': output_value}}
|
||||||
|
tmpl['outputs'] = outputs
|
||||||
|
return json.dumps(tmpl)
|
||||||
|
|
||||||
|
|
||||||
|
class SimultaneousUpdateNestedStackTest(functional_base.FunctionalTestsBase):
|
||||||
|
@test.requires_convergence
|
||||||
|
def test_nested_preemption(self):
|
||||||
|
root_tmpl = _tmpl_with_rsrcs(preempt_root_rsrcs,
|
||||||
|
preempt_root_out)
|
||||||
|
files = {
|
||||||
|
preempt_nested_stack_type: _tmpl_with_rsrcs(preempt_nested_rsrcs,
|
||||||
|
preempt_nested_out),
|
||||||
|
preempt_delay_stack_type: _tmpl_with_rsrcs(preempt_delay_rsrcs),
|
||||||
|
}
|
||||||
|
stack_id = self.stack_create(template=root_tmpl, files=files,
|
||||||
|
parameters={input_param: 'foo'})
|
||||||
|
delay_stack_uuid = self.get_stack_output(stack_id, 'delay_stack')
|
||||||
|
|
||||||
|
# Start an update that includes a long delay in the second nested stack
|
||||||
|
self.update_stack(stack_id, template=root_tmpl, files=files,
|
||||||
|
parameters={input_param: 'bar'},
|
||||||
|
expected_status='UPDATE_IN_PROGRESS')
|
||||||
|
self._wait_for_resource_status(delay_stack_uuid, 'delay_resource',
|
||||||
|
'UPDATE_IN_PROGRESS')
|
||||||
|
|
||||||
|
# Update again to check that we preempt update of the first nested
|
||||||
|
# stack. This will delete the second nested stack, after preempting the
|
||||||
|
# update of that stack as well, which will cause the delay resource
|
||||||
|
# within to be cancelled.
|
||||||
|
empty_nest_files = {
|
||||||
|
preempt_nested_stack_type: _tmpl_with_rsrcs({}),
|
||||||
|
}
|
||||||
|
self.update_stack(stack_id, template=root_tmpl, files=empty_nest_files,
|
||||||
|
parameters={input_param: 'baz'})
|
||||||
|
|||||||
Reference in New Issue
Block a user