From 3433be542642c359486a30dcd5bd744e103196ba Mon Sep 17 00:00:00 2001 From: kairat_kushaev Date: Thu, 21 May 2015 15:26:59 +0300 Subject: [PATCH] Save snapshot to db before stack snapshot complete When executing stack snapshot heat do not release stack lock some undefined amount of time because it needs to prepare some data for stack snapshot. It leads to situation when stack snapshot is complete(from user perspective) but nobody can do any operations with stack. So the fix executes snapshot saving to DB right before stack becomes complete. Change-Id: Id011142498fee49fee9ec1437fc0816b25780e48 Closes-bug: #1456672 --- heat/engine/service.py | 19 ++++++++++++------- heat/engine/stack.py | 25 ++++++++++++++++++++----- heat/tests/openstack/test_volume.py | 2 +- heat/tests/test_server.py | 2 +- heat/tests/test_stack.py | 15 +++++++++++++++ 5 files changed, 49 insertions(+), 14 deletions(-) diff --git a/heat/engine/service.py b/heat/engine/service.py index cb78aa17ca..4dba6e68af 100644 --- a/heat/engine/service.py +++ b/heat/engine/service.py @@ -1277,13 +1277,18 @@ class EngineService(service.Service): @context.request_context def stack_snapshot(self, cnxt, stack_identity, name): def _stack_snapshot(stack, snapshot): - LOG.debug("snapshotting stack %s" % stack.name) - stack.snapshot() - data = stack.prepare_abandon() - snapshot_object.Snapshot.update( - cnxt, snapshot.id, - {'data': data, 'status': stack.status, - 'status_reason': stack.status_reason}) + + def save_snapshot(stack, action, status, reason): + """Function that saves snapshot before snapshot complete.""" + data = stack.prepare_abandon() + data["status"] = status + snapshot_object.Snapshot.update( + cnxt, snapshot.id, + {'data': data, 'status': status, + 'status_reason': reason}) + + LOG.debug("Snapshotting stack %s" % stack.name) + stack.snapshot(save_snapshot_func=save_snapshot) s = self._get_stack(cnxt, stack_identity) diff --git a/heat/engine/stack.py b/heat/engine/stack.py index b566bca04b..64951a6615 100755 --- a/heat/engine/stack.py +++ b/heat/engine/stack.py @@ -777,10 +777,22 @@ class Stack(collections.Mapping): @scheduler.wrappertask def stack_task(self, action, reverse=False, post_func=None, error_wait_time=None, - aggregate_exceptions=False): + aggregate_exceptions=False, pre_completion_func=None): ''' A task to perform an action on the stack and all of the resources in forward or reverse dependency order as specified by reverse + + :param action action that should be executed with stack resources + :param reverse defines if action on the resources need to be executed + in reverse order (resources - first and then res dependencies ) + :param post_func function that need to be executed after + action complete on the stack + :param error_wait_time time to wait before cancelling all execution + threads when an error occurred + :param aggregate_exceptions defines if exceptions should be aggregated + :param pre_completion_func function that need to be executed right + before action completion. Uses stack ,action, status and reason as + input parameters ''' try: lifecycle_plugin_utils.do_pre_ops(self.context, self, @@ -829,6 +841,9 @@ class Stack(collections.Mapping): stack_status = self.FAILED reason = 'Resource %s failed: %s' % (action, six.text_type(ex)) + if pre_completion_func: + pre_completion_func(self, action, stack_status, reason) + self.state_set(action, stack_status, reason) if callable(post_func): @@ -1431,12 +1446,12 @@ class Stack(collections.Mapping): sus_task(timeout=self.timeout_secs()) @profiler.trace('Stack.snapshot', hide_args=False) - def snapshot(self): + def snapshot(self, save_snapshot_func): '''Snapshot the stack, invoking handle_snapshot on all resources.''' self.updated_time = datetime.datetime.utcnow() - sus_task = scheduler.TaskRunner(self.stack_task, - action=self.SNAPSHOT, - reverse=False) + sus_task = scheduler.TaskRunner(self.stack_task, action=self.SNAPSHOT, + reverse=False, + pre_completion_func=save_snapshot_func) sus_task(timeout=self.timeout_secs()) @profiler.trace('Stack.delete_snapshot', hide_args=False) diff --git a/heat/tests/openstack/test_volume.py b/heat/tests/openstack/test_volume.py index b905faf7c0..ebb5e441aa 100644 --- a/heat/tests/openstack/test_volume.py +++ b/heat/tests/openstack/test_volume.py @@ -1034,7 +1034,7 @@ class CinderVolumeTest(vt_base.BaseVolumeTest): self.assertEqual((stack.CREATE, stack.COMPLETE), stack.state) - scheduler.TaskRunner(stack.snapshot)() + scheduler.TaskRunner(stack.snapshot, None)() self.assertEqual((stack.SNAPSHOT, stack.COMPLETE), stack.state) diff --git a/heat/tests/test_server.py b/heat/tests/test_server.py index 1104881082..484cffb1a3 100644 --- a/heat/tests/test_server.py +++ b/heat/tests/test_server.py @@ -3280,7 +3280,7 @@ class ServersTest(common.HeatTestCase): self.assertEqual((stack.CREATE, stack.COMPLETE), stack.state) - scheduler.TaskRunner(stack.snapshot)() + scheduler.TaskRunner(stack.snapshot, None)() self.assertEqual((stack.SNAPSHOT, stack.COMPLETE), stack.state) diff --git a/heat/tests/test_stack.py b/heat/tests/test_stack.py index 6e2dd34eb3..69e6d41092 100644 --- a/heat/tests/test_stack.py +++ b/heat/tests/test_stack.py @@ -1796,6 +1796,21 @@ class StackTest(common.HeatTestCase): '(AResource Bar) is incorrect.', six.text_type(ex)) + def test_snapshot_save_called_first(self): + def snapshotting_called_first(stack, action, status, reason): + self.assertEqual(stack.status, stack.IN_PROGRESS) + self.assertEqual(stack.action, stack.SNAPSHOT) + + tmpl = {'HeatTemplateFormatVersion': '2012-12-12', + 'Resources': { + 'A': {'Type': 'GenericResourceType'}, + 'B': {'Type': 'GenericResourceType'}}} + self.stack = stack.Stack(self.ctx, 'stack_details_test', + template.Template(tmpl)) + self.stack.store() + self.stack.create() + self.stack.snapshot(save_snapshot_func=snapshotting_called_first) + def test_restore(self): tmpl = {'HeatTemplateFormatVersion': '2012-12-12', 'Resources': {