From 19adfda3b911cbb697e4fd6c07854d5f61ecbb99 Mon Sep 17 00:00:00 2001 From: suzhengwei Date: Thu, 2 Nov 2017 14:46:10 +0800 Subject: [PATCH] option to rollback action_plan when it fails It has costs when rollback action_plan. So give users an option whether to rollback it when the action_plan fails. Change-Id: I20c0afded795eda7fb1b57ffdd2ae1ca36c45301 --- watcher/applier/workflow_engine/default.py | 17 +++++-- watcher/conf/applier.py | 11 +++- .../test_taskflow_action_container.py | 51 +++++++++++++++++++ 3 files changed, 75 insertions(+), 4 deletions(-) diff --git a/watcher/applier/workflow_engine/default.py b/watcher/applier/workflow_engine/default.py index 66f717e9b..0fb8c4e4f 100644 --- a/watcher/applier/workflow_engine/default.py +++ b/watcher/applier/workflow_engine/default.py @@ -25,8 +25,11 @@ from taskflow import task as flow_task from watcher.applier.workflow_engine import base from watcher.common import exception +from watcher import conf from watcher import objects +CONF = conf.CONF + LOG = log.getLogger(__name__) @@ -127,9 +130,11 @@ class DefaultWorkFlowEngine(base.BaseWorkFlowEngine): class TaskFlowActionContainer(base.BaseTaskFlowActionContainer): def __init__(self, db_action, engine): - name = "action_type:{0} uuid:{1}".format(db_action.action_type, - db_action.uuid) - super(TaskFlowActionContainer, self).__init__(name, db_action, engine) + self.name = "action_type:{0} uuid:{1}".format(db_action.action_type, + db_action.uuid) + super(TaskFlowActionContainer, self).__init__(self.name, + db_action, + engine) def do_pre_execute(self): db_action = self.engine.notify(self._db_action, @@ -158,6 +163,12 @@ class TaskFlowActionContainer(base.BaseTaskFlowActionContainer): self.action.post_condition() def do_revert(self, *args, **kwargs): + # NOTE: Not rollback action plan + if not CONF.watcher_applier.rollback_when_actionplan_failed: + LOG.info("Failed actionplan rollback option is turned off, and " + "the following action will be skipped: %s", self.name) + return + LOG.warning("Revert action: %s", self.name) try: # TODO(jed): do we need to update the states in case of failure? diff --git a/watcher/conf/applier.py b/watcher/conf/applier.py index 21b70306c..34ee80d96 100644 --- a/watcher/conf/applier.py +++ b/watcher/conf/applier.py @@ -43,11 +43,20 @@ APPLIER_MANAGER_OPTS = [ help='Select the engine to use to execute the workflow'), ] +APPLIER_OPTS = [ + cfg.BoolOpt('rollback_when_actionplan_failed', + default=False, + help='If set True, the failed actionplan will rollback ' + 'when executing. Defaule value is False.'), +] + def register_opts(conf): conf.register_group(watcher_applier) conf.register_opts(APPLIER_MANAGER_OPTS, group=watcher_applier) + conf.register_opts(APPLIER_OPTS, group=watcher_applier) def list_opts(): - return [(watcher_applier, APPLIER_MANAGER_OPTS)] + return [(watcher_applier, APPLIER_MANAGER_OPTS), + (watcher_applier, APPLIER_OPTS)] diff --git a/watcher/tests/applier/workflow_engine/test_taskflow_action_container.py b/watcher/tests/applier/workflow_engine/test_taskflow_action_container.py index b8f43636c..f699fa305 100644 --- a/watcher/tests/applier/workflow_engine/test_taskflow_action_container.py +++ b/watcher/tests/applier/workflow_engine/test_taskflow_action_container.py @@ -19,6 +19,8 @@ import eventlet from unittest import mock +from oslo_config import cfg + from watcher.applier.workflow_engine import default as tflow from watcher.common import clients from watcher.common import nova_helper @@ -111,3 +113,52 @@ class TestTaskFlowActionContainer(base.DbTestCase): mock_eventlet_spawn.return_value = et action_container.execute() et.kill.assert_called_with() + + @mock.patch('watcher.applier.workflow_engine.default.LOG') + def test_execute_without_rollback(self, mock_log): + action_plan = obj_utils.create_test_action_plan( + self.context, audit_id=self.audit.id, + strategy_id=self.strategy.id, + state=objects.action_plan.State.ONGOING) + + action = obj_utils.create_test_action( + self.context, action_plan_id=action_plan.id, + state=objects.action.State.FAILED, + action_type='nop', + input_parameters={'message': 'hello World'}) + action_container = tflow.TaskFlowActionContainer( + db_action=action, + engine=self.engine) + + cfg.CONF.set_override("rollback_when_actionplan_failed", False, + group="watcher_applier") + action_name = "action_type:{0} uuid:{1}".format(action.action_type, + action.uuid) + expected_log = ('Failed actionplan rollback option is turned off, ' + 'and the following action will be skipped: %s') + action_container.revert() + mock_log.info.assert_called_once_with(expected_log, action_name) + + @mock.patch('watcher.applier.workflow_engine.default.LOG') + def test_execute_with_rollback(self, mock_log): + action_plan = obj_utils.create_test_action_plan( + self.context, audit_id=self.audit.id, + strategy_id=self.strategy.id, + state=objects.action_plan.State.ONGOING) + + action = obj_utils.create_test_action( + self.context, action_plan_id=action_plan.id, + state=objects.action.State.FAILED, + action_type='nop', + input_parameters={'message': 'hello World'}) + action_container = tflow.TaskFlowActionContainer( + db_action=action, + engine=self.engine) + + cfg.CONF.set_override("rollback_when_actionplan_failed", True, + group="watcher_applier") + action_name = "action_type:{0} uuid:{1}".format(action.action_type, + action.uuid) + expected_log = 'Revert action: %s' + action_container.revert() + mock_log.warning.assert_called_once_with(expected_log, action_name)