Remove true/false return from action.execute()

In watcher/applier/workflow_engine/default.py, we are checking the
return value of action.execute(). As the "TODO" above indicates it
(line 118), we should get rid of this and only flag an action as
failed if an exception was raised during its execute(). We will
need to update the related unit tests.

Change-Id: Ia8ff7abd9994c3504e733ccd1d629cafe9d4b839
Closes-Bug: #1548383
This commit is contained in:
Tin Lam
2016-02-24 23:23:30 -06:00
parent 59427eb0d9
commit d7f4f42772
5 changed files with 105 additions and 76 deletions

View File

@@ -53,16 +53,15 @@ class DefaultActionPlanHandler(base.BaseActionPlanHandler):
event_types.EventTypes.LAUNCH_ACTION_PLAN, event_types.EventTypes.LAUNCH_ACTION_PLAN,
ap_objects.State.ONGOING) ap_objects.State.ONGOING)
applier = default.DefaultApplier(self.ctx, self.applier_manager) applier = default.DefaultApplier(self.ctx, self.applier_manager)
result = applier.execute(self.action_plan_uuid) applier.execute(self.action_plan_uuid)
state = ap_objects.State.SUCCEEDED
except Exception as e: except Exception as e:
LOG.exception(e) LOG.exception(e)
result = False state = ap_objects.State.FAILED
finally: finally:
if result is True:
status = ap_objects.State.SUCCEEDED
else:
status = ap_objects.State.FAILED
# update state # update state
self.notify(self.action_plan_uuid, self.notify(self.action_plan_uuid,
event_types.EventTypes.LAUNCH_ACTION_PLAN, event_types.EventTypes.LAUNCH_ACTION_PLAN,
status) state)

View File

@@ -22,6 +22,7 @@ from taskflow import task
from watcher._i18n import _LE, _LW, _LC from watcher._i18n import _LE, _LW, _LC
from watcher.applier.workflow_engine import base from watcher.applier.workflow_engine import base
from watcher.common import exception
from watcher.objects import action as obj_action from watcher.objects import action as obj_action
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@@ -77,10 +78,9 @@ class DefaultWorkFlowEngine(base.BaseWorkFlowEngine):
e = engines.load(flow) e = engines.load(flow)
e.run() e.run()
return True
except Exception as e: except Exception as e:
LOG.exception(e) raise exception.WorkflowExecutionException(error=e)
return False
class TaskFlowActionContainer(task.Task): class TaskFlowActionContainer(task.Task):
@@ -121,14 +121,9 @@ class TaskFlowActionContainer(task.Task):
try: try:
LOG.debug("Running action %s", self.name) LOG.debug("Running action %s", self.name)
# todo(jed) remove return (true or false) raise an Exception self.action.execute()
result = self.action.execute() self.engine.notify(self._db_action,
if result is not True: obj_action.State.SUCCEEDED)
self.engine.notify(self._db_action,
obj_action.State.FAILED)
else:
self.engine.notify(self._db_action,
obj_action.State.SUCCEEDED)
except Exception as e: except Exception as e:
LOG.exception(e) LOG.exception(e)
LOG.error(_LE('The WorkFlow Engine has failed ' LOG.error(_LE('The WorkFlow Engine has failed '

View File

@@ -234,6 +234,9 @@ class PatchError(Invalid):
# decision engine # decision engine
class WorkflowExecutionException(WatcherException):
msg_fmt = _('Workflow execution error: %(error)s')
class IllegalArgumentException(WatcherException): class IllegalArgumentException(WatcherException):
msg_fmt = _('Illegal argument') msg_fmt = _('Illegal argument')

View File

@@ -7,9 +7,9 @@
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: python-watcher 0.24.1.dev4\n" "Project-Id-Version: python-watcher 0.24.1.dev12\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2016-03-14 15:29+0100\n" "POT-Creation-Date: 2016-03-16 18:18-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -109,17 +109,17 @@ msgstr ""
msgid "Migration of type %(migration_type)s is not supported." msgid "Migration of type %(migration_type)s is not supported."
msgstr "" msgstr ""
#: watcher/applier/workflow_engine/default.py:134 #: watcher/applier/workflow_engine/default.py:129
#, python-format #, python-format
msgid "The WorkFlow Engine has failed to execute the action %s" msgid "The WorkFlow Engine has failed to execute the action %s"
msgstr "" msgstr ""
#: watcher/applier/workflow_engine/default.py:152 #: watcher/applier/workflow_engine/default.py:147
#, python-format #, python-format
msgid "Revert action %s" msgid "Revert action %s"
msgstr "" msgstr ""
#: watcher/applier/workflow_engine/default.py:158 #: watcher/applier/workflow_engine/default.py:153
msgid "Oops! We need disaster recover plan" msgid "Oops! We need disaster recover plan"
msgstr "" msgstr ""
@@ -292,64 +292,69 @@ msgstr ""
msgid "Couldn't apply patch '%(patch)s'. Reason: %(reason)s" msgid "Couldn't apply patch '%(patch)s'. Reason: %(reason)s"
msgstr "" msgstr ""
#: watcher/common/exception.py:239 #: watcher/common/exception.py:238
#, python-format
msgid "Workflow execution error: %(error)s"
msgstr ""
#: watcher/common/exception.py:242
msgid "Illegal argument" msgid "Illegal argument"
msgstr "" msgstr ""
#: watcher/common/exception.py:243 #: watcher/common/exception.py:246
msgid "No such metric" msgid "No such metric"
msgstr "" msgstr ""
#: watcher/common/exception.py:247 #: watcher/common/exception.py:250
msgid "No rows were returned" msgid "No rows were returned"
msgstr "" msgstr ""
#: watcher/common/exception.py:251 #: watcher/common/exception.py:254
#, python-format #, python-format
msgid "%(client)s connection failed. Reason: %(reason)s" msgid "%(client)s connection failed. Reason: %(reason)s"
msgstr "" msgstr ""
#: watcher/common/exception.py:255 #: watcher/common/exception.py:258
msgid "'Keystone API endpoint is missing''" msgid "'Keystone API endpoint is missing''"
msgstr "" msgstr ""
#: watcher/common/exception.py:259 #: watcher/common/exception.py:262
msgid "The list of hypervisor(s) in the cluster is empty" msgid "The list of hypervisor(s) in the cluster is empty"
msgstr "" msgstr ""
#: watcher/common/exception.py:263 #: watcher/common/exception.py:266
msgid "The metrics resource collector is not defined" msgid "The metrics resource collector is not defined"
msgstr "" msgstr ""
#: watcher/common/exception.py:267 #: watcher/common/exception.py:270
msgid "the cluster state is not defined" msgid "the cluster state is not defined"
msgstr "" msgstr ""
#: watcher/common/exception.py:273 #: watcher/common/exception.py:276
#, python-format #, python-format
msgid "The instance '%(name)s' is not found" msgid "The instance '%(name)s' is not found"
msgstr "" msgstr ""
#: watcher/common/exception.py:277 #: watcher/common/exception.py:280
msgid "The hypervisor is not found" msgid "The hypervisor is not found"
msgstr "" msgstr ""
#: watcher/common/exception.py:281 #: watcher/common/exception.py:284
#, python-format #, python-format
msgid "Error loading plugin '%(name)s'" msgid "Error loading plugin '%(name)s'"
msgstr "" msgstr ""
#: watcher/common/exception.py:285 #: watcher/common/exception.py:288
#, python-format #, python-format
msgid "The identifier '%(name)s' is a reserved word" msgid "The identifier '%(name)s' is a reserved word"
msgstr "" msgstr ""
#: watcher/common/exception.py:289 #: watcher/common/exception.py:292
#, python-format #, python-format
msgid "The %(name)s resource %(id)s is not soft deleted" msgid "The %(name)s resource %(id)s is not soft deleted"
msgstr "" msgstr ""
#: watcher/common/exception.py:293 #: watcher/common/exception.py:296
msgid "Limit should be positive" msgid "Limit should be positive"
msgstr "" msgstr ""

View File

@@ -25,6 +25,7 @@ from stevedore import extension
from watcher.applier.actions import base as abase from watcher.applier.actions import base as abase
from watcher.applier.workflow_engine import default as tflow from watcher.applier.workflow_engine import default as tflow
from watcher.common import exception
from watcher.common import utils from watcher.common import utils
from watcher import objects from watcher import objects
from watcher.tests.db import base from watcher.tests.db import base
@@ -63,10 +64,15 @@ class TestDefaultWorkFlowEngine(base.DbTestCase):
context=self.context, context=self.context,
applier_manager=mock.MagicMock()) applier_manager=mock.MagicMock())
def test_execute(self): @mock.patch('taskflow.engines.load')
@mock.patch('taskflow.patterns.graph_flow.Flow.link')
def test_execute(self, graph_flow, engines):
actions = mock.MagicMock() actions = mock.MagicMock()
result = self.engine.execute(actions) try:
self.assertEqual(True, result) self.engine.execute(actions)
self.assertTrue(engines.called)
except Exception as exc:
self.fail(exc)
def create_action(self, action_type, parameters, next): def create_action(self, action_type, parameters, next):
action = { action = {
@@ -91,64 +97,84 @@ class TestDefaultWorkFlowEngine(base.DbTestCase):
for a in actions: for a in actions:
self.check_action_state(a, expected_state) self.check_action_state(a, expected_state)
def test_execute_with_no_actions(self): @mock.patch('taskflow.engines.load')
@mock.patch('taskflow.patterns.graph_flow.Flow.link')
def test_execute_with_no_actions(self, graph_flow, engines):
actions = [] actions = []
result = self.engine.execute(actions) try:
self.assertEqual(True, result) self.engine.execute(actions)
self.assertFalse(graph_flow.called)
self.assertTrue(engines.called)
except Exception as exc:
self.fail(exc)
def test_execute_with_one_action(self): def test_execute_with_one_action(self):
actions = [self.create_action("nop", {'message': 'test'}, None)] actions = [self.create_action("nop", {'message': 'test'}, None)]
result = self.engine.execute(actions) try:
self.assertEqual(True, result) self.engine.execute(actions)
self.check_actions_state(actions, objects.action.State.SUCCEEDED) self.check_actions_state(actions, objects.action.State.SUCCEEDED)
except Exception as exc:
self.fail(exc)
def test_execute_with_two_actions(self): def test_execute_with_two_actions(self):
actions = [] actions = []
next = self.create_action("sleep", {'duration': 0.0}, None) second = self.create_action("sleep", {'duration': 0.0}, None)
first = self.create_action("nop", {'message': 'test'}, next.id) first = self.create_action("nop", {'message': 'test'}, second.id)
actions.append(first) actions.append(first)
actions.append(next) actions.append(second)
result = self.engine.execute(actions) try:
self.assertEqual(True, result) self.engine.execute(actions)
self.check_actions_state(actions, objects.action.State.SUCCEEDED) self.check_actions_state(actions, objects.action.State.SUCCEEDED)
except Exception as exc:
self.fail(exc)
def test_execute_with_three_actions(self): def test_execute_with_three_actions(self):
actions = [] actions = []
next2 = self.create_action("nop", {'message': 'next'}, None)
next = self.create_action("sleep", {'duration': 0.0}, next2.id) third = self.create_action("nop", {'message': 'next'}, None)
first = self.create_action("nop", {'message': 'hello'}, next.id) second = self.create_action("sleep", {'duration': 0.0}, third.id)
first = self.create_action("nop", {'message': 'hello'}, second.id)
self.check_action_state(first, objects.action.State.PENDING) self.check_action_state(first, objects.action.State.PENDING)
self.check_action_state(next, objects.action.State.PENDING) self.check_action_state(second, objects.action.State.PENDING)
self.check_action_state(next2, objects.action.State.PENDING) self.check_action_state(third, objects.action.State.PENDING)
actions.append(first) actions.append(first)
actions.append(next) actions.append(second)
actions.append(next2) actions.append(third)
result = self.engine.execute(actions) try:
self.assertEqual(True, result) self.engine.execute(actions)
self.check_actions_state(actions, objects.action.State.SUCCEEDED) self.check_actions_state(actions, objects.action.State.SUCCEEDED)
except Exception as exc:
self.fail(exc)
def test_execute_with_exception(self): def test_execute_with_exception(self):
actions = [] actions = []
next2 = self.create_action("no_exist", {'message': 'next'}, None)
next = self.create_action("sleep", {'duration': 0.0}, next2.id) third = self.create_action("no_exist", {'message': 'next'}, None)
first = self.create_action("nop", {'message': 'hello'}, next.id) second = self.create_action("sleep", {'duration': 0.0}, third.id)
first = self.create_action("nop", {'message': 'hello'}, second.id)
self.check_action_state(first, objects.action.State.PENDING) self.check_action_state(first, objects.action.State.PENDING)
self.check_action_state(next, objects.action.State.PENDING) self.check_action_state(second, objects.action.State.PENDING)
self.check_action_state(next2, objects.action.State.PENDING) self.check_action_state(third, objects.action.State.PENDING)
actions.append(first)
actions.append(next) actions.append(first)
actions.append(next2) actions.append(second)
actions.append(third)
self.assertRaises(exception.WorkflowExecutionException,
self.engine.execute, actions)
result = self.engine.execute(actions)
self.assertEqual(False, result)
self.check_action_state(first, objects.action.State.SUCCEEDED) self.check_action_state(first, objects.action.State.SUCCEEDED)
self.check_action_state(next, objects.action.State.SUCCEEDED) self.check_action_state(second, objects.action.State.SUCCEEDED)
self.check_action_state(next2, objects.action.State.FAILED) self.check_action_state(third, objects.action.State.FAILED)
@mock.patch("watcher.common.loader.default.DriverManager") @mock.patch("watcher.common.loader.default.DriverManager")
def test_execute_with_action_exception(self, m_driver): def test_execute_with_action_exception(self, m_driver):
@@ -161,6 +187,7 @@ class TestDefaultWorkFlowEngine(base.DbTestCase):
obj=None), obj=None),
namespace=FakeAction.namespace()) namespace=FakeAction.namespace())
actions = [self.create_action("dontcare", {}, None)] actions = [self.create_action("dontcare", {}, None)]
result = self.engine.execute(actions)
self.assertEqual(False, result) self.assertRaises(exception.WorkflowExecutionException,
self.engine.execute, actions)
self.check_action_state(actions[0], objects.action.State.FAILED) self.check_action_state(actions[0], objects.action.State.FAILED)