Add cancelled state to executions
Allow workflow executions to be cancelled and propagate state up for subworkflows and with-items task. Cancelled workflow executions are subject to expiration. Change-Id: I74925105420e84c0b164f83b76edaa9b1612b5d5 Implements: blueprint mistral-cancel-state
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
# Copyright 2013 - Mirantis, Inc.
|
# Copyright 2013 - Mirantis, Inc.
|
||||||
# Copyright 2015 - StackStorm, Inc.
|
# Copyright 2015 - StackStorm, Inc.
|
||||||
# Copyright 2015 Huawei Technologies Co., Ltd.
|
# Copyright 2015 Huawei Technologies Co., Ltd.
|
||||||
|
# Copyright 2016 - Brocade Communications Systems, Inc.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -33,8 +34,17 @@ from mistral.workflow import states
|
|||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
STATE_TYPES = wtypes.Enum(str, states.IDLE, states.RUNNING, states.SUCCESS,
|
|
||||||
states.ERROR, states.PAUSED)
|
STATE_TYPES = wtypes.Enum(
|
||||||
|
str,
|
||||||
|
states.IDLE,
|
||||||
|
states.RUNNING,
|
||||||
|
states.SUCCESS,
|
||||||
|
states.ERROR,
|
||||||
|
states.PAUSED,
|
||||||
|
states.CANCELLED
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# TODO(rakhmerov): Make sure to make all needed renaming on public API.
|
# TODO(rakhmerov): Make sure to make all needed renaming on public API.
|
||||||
|
|
||||||
@@ -118,14 +128,14 @@ class ExecutionsController(rest.RestController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if delta.get('state'):
|
if delta.get('state'):
|
||||||
if delta.get('state') == states.PAUSED:
|
if states.is_paused(delta.get('state')):
|
||||||
wf_ex = rpc.get_engine_client().pause_workflow(id)
|
wf_ex = rpc.get_engine_client().pause_workflow(id)
|
||||||
elif delta.get('state') == states.RUNNING:
|
elif delta.get('state') == states.RUNNING:
|
||||||
wf_ex = rpc.get_engine_client().resume_workflow(
|
wf_ex = rpc.get_engine_client().resume_workflow(
|
||||||
id,
|
id,
|
||||||
env=delta.get('env')
|
env=delta.get('env')
|
||||||
)
|
)
|
||||||
elif delta.get('state') in [states.SUCCESS, states.ERROR]:
|
elif states.is_completed(delta.get('state')):
|
||||||
msg = wf_ex.state_info if wf_ex.state_info else None
|
msg = wf_ex.state_info if wf_ex.state_info else None
|
||||||
wf_ex = rpc.get_engine_client().stop_workflow(
|
wf_ex = rpc.get_engine_client().stop_workflow(
|
||||||
id,
|
id,
|
||||||
@@ -141,7 +151,8 @@ class ExecutionsController(rest.RestController):
|
|||||||
states.RUNNING,
|
states.RUNNING,
|
||||||
states.PAUSED,
|
states.PAUSED,
|
||||||
states.SUCCESS,
|
states.SUCCESS,
|
||||||
states.ERROR
|
states.ERROR,
|
||||||
|
states.CANCELLED
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# Copyright 2015 - Mirantis, Inc.
|
# Copyright 2015 - Mirantis, Inc.
|
||||||
# Copyright 2015 - StackStorm, Inc.
|
# Copyright 2015 - StackStorm, Inc.
|
||||||
|
# Copyright 2016 - Brocade Communications Systems, Inc.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -940,7 +941,8 @@ def get_expired_executions(time, session=None):
|
|||||||
query = query.filter(
|
query = query.filter(
|
||||||
sa.or_(
|
sa.or_(
|
||||||
models.WorkflowExecution.state == "SUCCESS",
|
models.WorkflowExecution.state == "SUCCESS",
|
||||||
models.WorkflowExecution.state == "ERROR"
|
models.WorkflowExecution.state == "ERROR",
|
||||||
|
models.WorkflowExecution.state == "CANCELLED"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -193,8 +193,13 @@ class PythonAction(Action):
|
|||||||
|
|
||||||
prev_state = self.action_ex.state
|
prev_state = self.action_ex.state
|
||||||
|
|
||||||
self.action_ex.state = (states.SUCCESS if result.is_success()
|
if result.is_success():
|
||||||
else states.ERROR)
|
self.action_ex.state = states.SUCCESS
|
||||||
|
elif result.is_cancel():
|
||||||
|
self.action_ex.state = states.CANCELLED
|
||||||
|
else:
|
||||||
|
self.action_ex.state = states.ERROR
|
||||||
|
|
||||||
self.action_ex.output = self._prepare_output(result)
|
self.action_ex.output = self._prepare_output(result)
|
||||||
self.action_ex.accepted = True
|
self.action_ex.accepted = True
|
||||||
|
|
||||||
|
|||||||
@@ -370,20 +370,24 @@ class WithItemsTask(RegularTask):
|
|||||||
assert self.task_ex
|
assert self.task_ex
|
||||||
|
|
||||||
state = action_ex.state
|
state = action_ex.state
|
||||||
|
|
||||||
# TODO(rakhmerov): Here we can define more informative messages
|
# TODO(rakhmerov): Here we can define more informative messages
|
||||||
# cases when action is successful and when it's not. For example,
|
# cases when action is successful and when it's not. For example,
|
||||||
# in state_info we can specify the cause action.
|
# in state_info we can specify the cause action.
|
||||||
state_info = (None if state == states.SUCCESS
|
# The use of action_ex.output.get('result') for state_info is not
|
||||||
else action_ex.output.get('result'))
|
# accurate because there could be action executions that had
|
||||||
|
# failed or was cancelled prior to this action execution.
|
||||||
|
state_info = {
|
||||||
|
states.SUCCESS: None,
|
||||||
|
states.ERROR: 'One or more action executions had failed.',
|
||||||
|
states.CANCELLED: 'One or more action executions was cancelled.'
|
||||||
|
}
|
||||||
|
|
||||||
with_items.increase_capacity(self.task_ex)
|
with_items.increase_capacity(self.task_ex)
|
||||||
|
|
||||||
if with_items.is_completed(self.task_ex):
|
if with_items.is_completed(self.task_ex):
|
||||||
self.complete(
|
state = with_items.get_final_state(self.task_ex)
|
||||||
with_items.get_final_state(self.task_ex),
|
self.complete(state, state_info[state])
|
||||||
state_info
|
|
||||||
)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if (with_items.has_more_iterations(self.task_ex)
|
if (with_items.has_more_iterations(self.task_ex)
|
||||||
|
|||||||
@@ -50,11 +50,26 @@ def stop_workflow(wf_ex, state, msg=None):
|
|||||||
# with ERROR state.
|
# with ERROR state.
|
||||||
wf.stop(state, msg)
|
wf.stop(state, msg)
|
||||||
|
|
||||||
|
# Cancels subworkflows.
|
||||||
|
if state == states.CANCELLED:
|
||||||
|
for task_ex in wf_ex.task_executions:
|
||||||
|
sub_wf_exs = db_api.get_workflow_executions(
|
||||||
|
task_execution_id=task_ex.id
|
||||||
|
)
|
||||||
|
|
||||||
|
for sub_wf_ex in sub_wf_exs:
|
||||||
|
if not states.is_completed(sub_wf_ex.state):
|
||||||
|
stop_workflow(sub_wf_ex, state, msg=msg)
|
||||||
|
|
||||||
|
|
||||||
def fail_workflow(wf_ex, msg=None):
|
def fail_workflow(wf_ex, msg=None):
|
||||||
stop_workflow(wf_ex, states.ERROR, msg)
|
stop_workflow(wf_ex, states.ERROR, msg)
|
||||||
|
|
||||||
|
|
||||||
|
def cancel_workflow(wf_ex, msg=None):
|
||||||
|
stop_workflow(wf_ex, states.CANCELLED, msg)
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace('workflow-handler-on-task-complete')
|
@profiler.trace('workflow-handler-on-task-complete')
|
||||||
def on_task_complete(task_ex):
|
def on_task_complete(task_ex):
|
||||||
wf_ex = task_ex.workflow_execution
|
wf_ex = task_ex.workflow_execution
|
||||||
|
|||||||
@@ -109,6 +109,8 @@ class Workflow(object):
|
|||||||
return self._succeed_workflow(final_context, msg)
|
return self._succeed_workflow(final_context, msg)
|
||||||
elif state == states.ERROR:
|
elif state == states.ERROR:
|
||||||
return self._fail_workflow(msg)
|
return self._fail_workflow(msg)
|
||||||
|
elif state == states.CANCELLED:
|
||||||
|
return self._cancel_workflow(msg)
|
||||||
|
|
||||||
@profiler.trace('workflow-on-task-complete')
|
@profiler.trace('workflow-on-task-complete')
|
||||||
def on_task_complete(self, task_ex):
|
def on_task_complete(self, task_ex):
|
||||||
@@ -212,7 +214,7 @@ class Workflow(object):
|
|||||||
|
|
||||||
# Workflow result should be accepted by parent workflows (if any)
|
# Workflow result should be accepted by parent workflows (if any)
|
||||||
# only if it completed successfully or failed.
|
# only if it completed successfully or failed.
|
||||||
self.wf_ex.accepted = state in (states.SUCCESS, states.ERROR)
|
self.wf_ex.accepted = states.is_completed(state)
|
||||||
|
|
||||||
if recursive and self.wf_ex.task_execution_id:
|
if recursive and self.wf_ex.task_execution_id:
|
||||||
parent_task_ex = db_api.get_task_execution(
|
parent_task_ex = db_api.get_task_execution(
|
||||||
@@ -275,7 +277,11 @@ class Workflow(object):
|
|||||||
|
|
||||||
wf_ctrl = wf_base.get_controller(self.wf_ex, self.wf_spec)
|
wf_ctrl = wf_base.get_controller(self.wf_ex, self.wf_spec)
|
||||||
|
|
||||||
if wf_ctrl.all_errors_handled():
|
if wf_ctrl.any_cancels():
|
||||||
|
self._cancel_workflow(
|
||||||
|
_build_cancel_info_message(wf_ctrl, self.wf_ex)
|
||||||
|
)
|
||||||
|
elif wf_ctrl.all_errors_handled():
|
||||||
self._succeed_workflow(wf_ctrl.evaluate_workflow_final_context())
|
self._succeed_workflow(wf_ctrl.evaluate_workflow_final_context())
|
||||||
else:
|
else:
|
||||||
self._fail_workflow(_build_fail_info_message(wf_ctrl, self.wf_ex))
|
self._fail_workflow(_build_fail_info_message(wf_ctrl, self.wf_ex))
|
||||||
@@ -310,6 +316,24 @@ class Workflow(object):
|
|||||||
if self.wf_ex.task_execution_id:
|
if self.wf_ex.task_execution_id:
|
||||||
self._schedule_send_result_to_parent_workflow()
|
self._schedule_send_result_to_parent_workflow()
|
||||||
|
|
||||||
|
def _cancel_workflow(self, msg):
|
||||||
|
if states.is_completed(self.wf_ex.state):
|
||||||
|
return
|
||||||
|
|
||||||
|
self.set_state(states.CANCELLED, state_info=msg)
|
||||||
|
|
||||||
|
# When we set an ERROR state we should safely set output value getting
|
||||||
|
# w/o exceptions due to field size limitations.
|
||||||
|
msg = utils.cut_by_kb(
|
||||||
|
msg,
|
||||||
|
cfg.CONF.engine.execution_field_size_limit_kb
|
||||||
|
)
|
||||||
|
|
||||||
|
self.wf_ex.output = {'result': msg}
|
||||||
|
|
||||||
|
if self.wf_ex.task_execution_id:
|
||||||
|
self._schedule_send_result_to_parent_workflow()
|
||||||
|
|
||||||
def _schedule_send_result_to_parent_workflow(self):
|
def _schedule_send_result_to_parent_workflow(self):
|
||||||
scheduler.schedule_call(
|
scheduler.schedule_call(
|
||||||
None,
|
None,
|
||||||
@@ -359,6 +383,16 @@ def _send_result_to_parent_workflow(wf_ex_id):
|
|||||||
wf_ex.id,
|
wf_ex.id,
|
||||||
wf_utils.Result(error=err_msg)
|
wf_utils.Result(error=err_msg)
|
||||||
)
|
)
|
||||||
|
elif wf_ex.state == states.CANCELLED:
|
||||||
|
err_msg = (
|
||||||
|
wf_ex.state_info or
|
||||||
|
'Cancelled subworkflow [execution_id=%s]' % wf_ex.id
|
||||||
|
)
|
||||||
|
|
||||||
|
rpc.get_engine_client().on_action_complete(
|
||||||
|
wf_ex.id,
|
||||||
|
wf_utils.Result(error=err_msg, cancel=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _build_fail_info_message(wf_ctrl, wf_ex):
|
def _build_fail_info_message(wf_ctrl, wf_ex):
|
||||||
@@ -389,3 +423,14 @@ def _build_fail_info_message(wf_ctrl, wf_ex):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|
||||||
|
def _build_cancel_info_message(wf_ctrl, wf_ex):
|
||||||
|
# Try to find where cancel is exactly.
|
||||||
|
cancelled_tasks = sorted(
|
||||||
|
wf_utils.find_cancelled_task_executions(wf_ex),
|
||||||
|
key=lambda t: t.name
|
||||||
|
)
|
||||||
|
|
||||||
|
return ('Cancelled tasks: %s' %
|
||||||
|
', '.join([t.name for t in cancelled_tasks]))
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Copyright 2013 - Mirantis, Inc.
|
# Copyright 2013 - Mirantis, Inc.
|
||||||
# Copyright 2015 - StackStorm, Inc.
|
# Copyright 2015 - StackStorm, Inc.
|
||||||
# Copyright 2015 Huawei Technologies Co., Ltd.
|
# Copyright 2015 Huawei Technologies Co., Ltd.
|
||||||
|
# Copyright 2016 - Brocade Communications Systems, Inc.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -201,6 +202,39 @@ class TestExecutionsController(base.APITest):
|
|||||||
self.assertDictEqual(expected_exec, resp.json)
|
self.assertDictEqual(expected_exec, resp.json)
|
||||||
mock_stop_wf.assert_called_once_with('123', 'ERROR', 'Force')
|
mock_stop_wf.assert_called_once_with('123', 'ERROR', 'Force')
|
||||||
|
|
||||||
|
@mock.patch.object(
|
||||||
|
db_api,
|
||||||
|
'ensure_workflow_execution_exists',
|
||||||
|
mock.MagicMock(return_value=None)
|
||||||
|
)
|
||||||
|
@mock.patch.object(rpc.EngineClient, 'stop_workflow')
|
||||||
|
def test_put_state_cancelled(self, mock_stop_wf):
|
||||||
|
update_exec = {
|
||||||
|
'id': WF_EX['id'],
|
||||||
|
'state': states.CANCELLED,
|
||||||
|
'state_info': 'Cancelled by user.'
|
||||||
|
}
|
||||||
|
|
||||||
|
wf_ex = copy.deepcopy(WF_EX)
|
||||||
|
wf_ex['state'] = states.CANCELLED
|
||||||
|
wf_ex['state_info'] = 'Cancelled by user.'
|
||||||
|
mock_stop_wf.return_value = wf_ex
|
||||||
|
|
||||||
|
resp = self.app.put_json('/v2/executions/123', update_exec)
|
||||||
|
|
||||||
|
expected_exec = copy.deepcopy(WF_EX_JSON_WITH_DESC)
|
||||||
|
expected_exec['state'] = states.CANCELLED
|
||||||
|
expected_exec['state_info'] = 'Cancelled by user.'
|
||||||
|
|
||||||
|
self.assertEqual(200, resp.status_int)
|
||||||
|
self.assertDictEqual(expected_exec, resp.json)
|
||||||
|
|
||||||
|
mock_stop_wf.assert_called_once_with(
|
||||||
|
'123',
|
||||||
|
'CANCELLED',
|
||||||
|
'Cancelled by user.'
|
||||||
|
)
|
||||||
|
|
||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
db_api,
|
db_api,
|
||||||
'ensure_workflow_execution_exists',
|
'ensure_workflow_execution_exists',
|
||||||
@@ -228,6 +262,33 @@ class TestExecutionsController(base.APITest):
|
|||||||
self.assertDictEqual(expected_exec, resp.json)
|
self.assertDictEqual(expected_exec, resp.json)
|
||||||
mock_resume_wf.assert_called_once_with('123', env=None)
|
mock_resume_wf.assert_called_once_with('123', env=None)
|
||||||
|
|
||||||
|
@mock.patch.object(
|
||||||
|
db_api,
|
||||||
|
'ensure_workflow_execution_exists',
|
||||||
|
mock.MagicMock(return_value=None)
|
||||||
|
)
|
||||||
|
def test_put_invalid_state(self):
|
||||||
|
invalid_states = [states.IDLE, states.WAITING, states.RUNNING_DELAYED]
|
||||||
|
|
||||||
|
for state in invalid_states:
|
||||||
|
update_exec = {
|
||||||
|
'id': WF_EX['id'],
|
||||||
|
'state': state
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = self.app.put_json(
|
||||||
|
'/v2/executions/123',
|
||||||
|
update_exec,
|
||||||
|
expect_errors=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(400, resp.status_int)
|
||||||
|
|
||||||
|
self.assertIn(
|
||||||
|
'Cannot change state to %s.' % state,
|
||||||
|
resp.json['faultstring']
|
||||||
|
)
|
||||||
|
|
||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
db_api,
|
db_api,
|
||||||
'ensure_workflow_execution_exists',
|
'ensure_workflow_execution_exists',
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# Copyright 2014 - Mirantis, Inc.
|
# Copyright 2014 - Mirantis, Inc.
|
||||||
# Copyright 2015 - StackStorm, Inc.
|
# Copyright 2015 - StackStorm, Inc.
|
||||||
|
# Copyright 2016 - Brocade Communications Systems, Inc.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -219,6 +220,10 @@ class EngineTestCase(base.DbTestCase):
|
|||||||
timeout=DEFAULT_TIMEOUT):
|
timeout=DEFAULT_TIMEOUT):
|
||||||
self.await_execution_state(ex_id, states.PAUSED, delay, timeout)
|
self.await_execution_state(ex_id, states.PAUSED, delay, timeout)
|
||||||
|
|
||||||
|
def await_execution_cancelled(self, ex_id, delay=DEFAULT_DELAY,
|
||||||
|
timeout=DEFAULT_TIMEOUT):
|
||||||
|
self.await_execution_state(ex_id, states.CANCELLED, delay, timeout)
|
||||||
|
|
||||||
# Various methods for action execution objects.
|
# Various methods for action execution objects.
|
||||||
|
|
||||||
def is_action_success(self, a_ex_id):
|
def is_action_success(self, a_ex_id):
|
||||||
|
|||||||
516
mistral/tests/unit/engine/test_workflow_cancel.py
Normal file
516
mistral/tests/unit/engine/test_workflow_cancel.py
Normal file
@@ -0,0 +1,516 @@
|
|||||||
|
# Copyright 2015 - StackStorm, Inc.
|
||||||
|
# Copyright 2016 - Brocade Communications Systems, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from mistral.db.v2 import api as db_api
|
||||||
|
from mistral.services import workbooks as wb_service
|
||||||
|
from mistral.services import workflows as wf_service
|
||||||
|
from mistral.tests.unit.engine import base
|
||||||
|
from mistral.workflow import states
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowCancelTest(base.EngineTestCase):
|
||||||
|
|
||||||
|
def test_cancel_workflow(self):
|
||||||
|
workflow = """
|
||||||
|
version: '2.0'
|
||||||
|
|
||||||
|
wf:
|
||||||
|
type: direct
|
||||||
|
tasks:
|
||||||
|
task1:
|
||||||
|
action: std.echo output="Echo"
|
||||||
|
on-complete:
|
||||||
|
- task2
|
||||||
|
|
||||||
|
task2:
|
||||||
|
action: std.echo output="foo"
|
||||||
|
wait-before: 3
|
||||||
|
"""
|
||||||
|
|
||||||
|
wf_service.create_workflows(workflow)
|
||||||
|
wf_ex = self.engine.start_workflow('wf', {})
|
||||||
|
|
||||||
|
self.engine.stop_workflow(
|
||||||
|
wf_ex.id,
|
||||||
|
states.CANCELLED,
|
||||||
|
"Cancelled by user."
|
||||||
|
)
|
||||||
|
|
||||||
|
self.await_execution_cancelled(wf_ex.id)
|
||||||
|
|
||||||
|
wf_ex = db_api.get_execution(wf_ex.id)
|
||||||
|
|
||||||
|
task_1_ex = self._assert_single_item(
|
||||||
|
wf_ex.task_executions,
|
||||||
|
name='task1'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.await_execution_success(task_1_ex.id)
|
||||||
|
|
||||||
|
wf_ex = db_api.get_execution(wf_ex.id)
|
||||||
|
|
||||||
|
task_1_ex = self._assert_single_item(
|
||||||
|
wf_ex.task_executions,
|
||||||
|
name='task1'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(states.CANCELLED, wf_ex.state)
|
||||||
|
self.assertEqual("Cancelled by user.", wf_ex.state_info)
|
||||||
|
self.assertEqual(1, len(wf_ex.task_executions))
|
||||||
|
self.assertEqual(states.SUCCESS, task_1_ex.state)
|
||||||
|
|
||||||
|
def test_cancel_paused_workflow(self):
|
||||||
|
workflow = """
|
||||||
|
version: '2.0'
|
||||||
|
|
||||||
|
wf:
|
||||||
|
type: direct
|
||||||
|
tasks:
|
||||||
|
task1:
|
||||||
|
action: std.echo output="Echo"
|
||||||
|
on-complete:
|
||||||
|
- task2
|
||||||
|
|
||||||
|
task2:
|
||||||
|
action: std.echo output="foo"
|
||||||
|
wait-before: 3
|
||||||
|
"""
|
||||||
|
|
||||||
|
wf_service.create_workflows(workflow)
|
||||||
|
wf_ex = self.engine.start_workflow('wf', {})
|
||||||
|
|
||||||
|
self.engine.pause_workflow(wf_ex.id)
|
||||||
|
|
||||||
|
self.await_execution_paused(wf_ex.id)
|
||||||
|
|
||||||
|
self.engine.stop_workflow(
|
||||||
|
wf_ex.id,
|
||||||
|
states.CANCELLED,
|
||||||
|
"Cancelled by user."
|
||||||
|
)
|
||||||
|
|
||||||
|
self.await_execution_cancelled(wf_ex.id)
|
||||||
|
|
||||||
|
wf_ex = db_api.get_execution(wf_ex.id)
|
||||||
|
|
||||||
|
task_1_ex = self._assert_single_item(
|
||||||
|
wf_ex.task_executions,
|
||||||
|
name='task1'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.await_execution_success(task_1_ex.id)
|
||||||
|
|
||||||
|
wf_ex = db_api.get_execution(wf_ex.id)
|
||||||
|
|
||||||
|
task_1_ex = self._assert_single_item(
|
||||||
|
wf_ex.task_executions,
|
||||||
|
name='task1'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(states.CANCELLED, wf_ex.state)
|
||||||
|
self.assertEqual("Cancelled by user.", wf_ex.state_info)
|
||||||
|
self.assertEqual(1, len(wf_ex.task_executions))
|
||||||
|
self.assertEqual(states.SUCCESS, task_1_ex.state)
|
||||||
|
|
||||||
|
def test_cancel_completed_workflow(self):
|
||||||
|
workflow = """
|
||||||
|
version: '2.0'
|
||||||
|
|
||||||
|
wf:
|
||||||
|
type: direct
|
||||||
|
tasks:
|
||||||
|
task1:
|
||||||
|
action: std.echo output="Echo"
|
||||||
|
"""
|
||||||
|
|
||||||
|
wf_service.create_workflows(workflow)
|
||||||
|
wf_ex = self.engine.start_workflow('wf', {})
|
||||||
|
|
||||||
|
self.await_execution_success(wf_ex.id)
|
||||||
|
|
||||||
|
self.engine.stop_workflow(
|
||||||
|
wf_ex.id,
|
||||||
|
states.CANCELLED,
|
||||||
|
"Cancelled by user."
|
||||||
|
)
|
||||||
|
|
||||||
|
wf_ex = db_api.get_execution(wf_ex.id)
|
||||||
|
|
||||||
|
task_1_ex = self._assert_single_item(
|
||||||
|
wf_ex.task_executions,
|
||||||
|
name='task1'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(states.SUCCESS, wf_ex.state)
|
||||||
|
self.assertIsNone(wf_ex.state_info)
|
||||||
|
self.assertEqual(1, len(wf_ex.task_executions))
|
||||||
|
self.assertEqual(states.SUCCESS, task_1_ex.state)
|
||||||
|
|
||||||
|
def test_cancel_parent_workflow(self):
|
||||||
|
workbook = """
|
||||||
|
version: '2.0'
|
||||||
|
|
||||||
|
name: wb
|
||||||
|
|
||||||
|
workflows:
|
||||||
|
wf:
|
||||||
|
type: direct
|
||||||
|
tasks:
|
||||||
|
taskx:
|
||||||
|
workflow: subwf
|
||||||
|
|
||||||
|
subwf:
|
||||||
|
type: direct
|
||||||
|
tasks:
|
||||||
|
task1:
|
||||||
|
action: std.echo output="Echo"
|
||||||
|
on-complete:
|
||||||
|
- task2
|
||||||
|
|
||||||
|
task2:
|
||||||
|
action: std.echo output="foo"
|
||||||
|
wait-before: 2
|
||||||
|
"""
|
||||||
|
|
||||||
|
wb_service.create_workbook_v2(workbook)
|
||||||
|
wf_ex = self.engine.start_workflow('wb.wf', {})
|
||||||
|
|
||||||
|
self.engine.stop_workflow(
|
||||||
|
wf_ex.id,
|
||||||
|
states.CANCELLED,
|
||||||
|
"Cancelled by user."
|
||||||
|
)
|
||||||
|
|
||||||
|
self.await_execution_cancelled(wf_ex.id)
|
||||||
|
|
||||||
|
wf_ex = db_api.get_execution(wf_ex.id)
|
||||||
|
task_ex = self._assert_single_item(wf_ex.task_executions, name='taskx')
|
||||||
|
|
||||||
|
self.await_execution_cancelled(task_ex.id)
|
||||||
|
|
||||||
|
wf_ex = db_api.get_execution(wf_ex.id)
|
||||||
|
task_ex = self._assert_single_item(wf_ex.task_executions, name='taskx')
|
||||||
|
action_exs = db_api.get_action_executions(task_execution_id=task_ex.id)
|
||||||
|
|
||||||
|
self.assertEqual(states.CANCELLED, wf_ex.state)
|
||||||
|
self.assertEqual("Cancelled by user.", wf_ex.state_info)
|
||||||
|
self.assertEqual(states.CANCELLED, task_ex.state)
|
||||||
|
self.assertEqual("Cancelled by user.", task_ex.state_info)
|
||||||
|
self.assertEqual(1, len(action_exs))
|
||||||
|
self.assertEqual(states.CANCELLED, action_exs[0].state)
|
||||||
|
self.assertEqual("Cancelled by user.", action_exs[0].state_info)
|
||||||
|
|
||||||
|
def test_cancel_child_workflow(self):
|
||||||
|
workbook = """
|
||||||
|
version: '2.0'
|
||||||
|
|
||||||
|
name: wb
|
||||||
|
|
||||||
|
workflows:
|
||||||
|
wf:
|
||||||
|
type: direct
|
||||||
|
tasks:
|
||||||
|
taskx:
|
||||||
|
workflow: subwf
|
||||||
|
|
||||||
|
subwf:
|
||||||
|
type: direct
|
||||||
|
tasks:
|
||||||
|
task1:
|
||||||
|
action: std.echo output="Echo"
|
||||||
|
on-complete:
|
||||||
|
- task2
|
||||||
|
|
||||||
|
task2:
|
||||||
|
action: std.echo output="foo"
|
||||||
|
wait-before: 3
|
||||||
|
"""
|
||||||
|
|
||||||
|
wb_service.create_workbook_v2(workbook)
|
||||||
|
wf_ex = self.engine.start_workflow('wb.wf', {})
|
||||||
|
|
||||||
|
wf_execs = db_api.get_workflow_executions()
|
||||||
|
wf_ex = self._assert_single_item(wf_execs, name='wb.wf')
|
||||||
|
task_ex = self._assert_single_item(wf_ex.task_executions, name='taskx')
|
||||||
|
subwf_ex = self._assert_single_item(wf_execs, name='wb.subwf')
|
||||||
|
|
||||||
|
self.engine.stop_workflow(
|
||||||
|
subwf_ex.id,
|
||||||
|
states.CANCELLED,
|
||||||
|
"Cancelled by user."
|
||||||
|
)
|
||||||
|
|
||||||
|
self.await_execution_cancelled(subwf_ex.id)
|
||||||
|
self.await_execution_cancelled(task_ex.id)
|
||||||
|
self.await_execution_cancelled(wf_ex.id)
|
||||||
|
|
||||||
|
wf_execs = db_api.get_workflow_executions()
|
||||||
|
wf_ex = self._assert_single_item(wf_execs, name='wb.wf')
|
||||||
|
task_ex = self._assert_single_item(wf_ex.task_executions, name='taskx')
|
||||||
|
subwf_ex = self._assert_single_item(wf_execs, name='wb.subwf')
|
||||||
|
|
||||||
|
self.assertEqual(states.CANCELLED, subwf_ex.state)
|
||||||
|
self.assertEqual("Cancelled by user.", subwf_ex.state_info)
|
||||||
|
self.assertEqual(states.CANCELLED, task_ex.state)
|
||||||
|
self.assertIn("Cancelled by user.", task_ex.state_info)
|
||||||
|
self.assertEqual(states.CANCELLED, wf_ex.state)
|
||||||
|
self.assertEqual("Cancelled tasks: taskx", wf_ex.state_info)
|
||||||
|
|
||||||
|
def test_cancel_with_items_parent_workflow(self):
|
||||||
|
workbook = """
|
||||||
|
version: '2.0'
|
||||||
|
|
||||||
|
name: wb
|
||||||
|
|
||||||
|
workflows:
|
||||||
|
wf:
|
||||||
|
type: direct
|
||||||
|
tasks:
|
||||||
|
taskx:
|
||||||
|
with-items: i in [1, 2]
|
||||||
|
workflow: subwf
|
||||||
|
|
||||||
|
subwf:
|
||||||
|
type: direct
|
||||||
|
tasks:
|
||||||
|
task1:
|
||||||
|
action: std.echo output="Echo"
|
||||||
|
on-complete:
|
||||||
|
- task2
|
||||||
|
|
||||||
|
task2:
|
||||||
|
action: std.echo output="foo"
|
||||||
|
wait-before: 1
|
||||||
|
"""
|
||||||
|
wb_service.create_workbook_v2(workbook)
|
||||||
|
wf_ex = self.engine.start_workflow('wb.wf', {})
|
||||||
|
|
||||||
|
self.engine.stop_workflow(
|
||||||
|
wf_ex.id,
|
||||||
|
states.CANCELLED,
|
||||||
|
"Cancelled by user."
|
||||||
|
)
|
||||||
|
|
||||||
|
wf_ex = db_api.get_execution(wf_ex.id)
|
||||||
|
task_ex = self._assert_single_item(wf_ex.task_executions, name='taskx')
|
||||||
|
|
||||||
|
self.await_execution_cancelled(wf_ex.id)
|
||||||
|
self.await_execution_cancelled(task_ex.id)
|
||||||
|
|
||||||
|
wf_execs = db_api.get_workflow_executions()
|
||||||
|
wf_ex = self._assert_single_item(wf_execs, name='wb.wf')
|
||||||
|
task_ex = self._assert_single_item(wf_ex.task_executions, name='taskx')
|
||||||
|
subwf_exs = self._assert_multiple_items(wf_execs, 2, name='wb.subwf')
|
||||||
|
|
||||||
|
self.assertEqual(states.CANCELLED, subwf_exs[0].state)
|
||||||
|
self.assertEqual("Cancelled by user.", subwf_exs[0].state_info)
|
||||||
|
self.assertEqual(states.CANCELLED, subwf_exs[1].state)
|
||||||
|
self.assertEqual("Cancelled by user.", subwf_exs[1].state_info)
|
||||||
|
self.assertEqual(states.CANCELLED, task_ex.state)
|
||||||
|
self.assertIn("cancelled", task_ex.state_info)
|
||||||
|
self.assertEqual(states.CANCELLED, wf_ex.state)
|
||||||
|
self.assertEqual("Cancelled by user.", wf_ex.state_info)
|
||||||
|
|
||||||
|
def test_cancel_with_items_child_workflow(self):
|
||||||
|
workbook = """
|
||||||
|
version: '2.0'
|
||||||
|
|
||||||
|
name: wb
|
||||||
|
|
||||||
|
workflows:
|
||||||
|
wf:
|
||||||
|
type: direct
|
||||||
|
tasks:
|
||||||
|
taskx:
|
||||||
|
with-items: i in [1, 2]
|
||||||
|
workflow: subwf
|
||||||
|
|
||||||
|
subwf:
|
||||||
|
type: direct
|
||||||
|
tasks:
|
||||||
|
task1:
|
||||||
|
action: std.echo output="Echo"
|
||||||
|
on-complete:
|
||||||
|
- task2
|
||||||
|
|
||||||
|
task2:
|
||||||
|
action: std.echo output="foo"
|
||||||
|
wait-before: 1
|
||||||
|
"""
|
||||||
|
|
||||||
|
wb_service.create_workbook_v2(workbook)
|
||||||
|
wf_ex = self.engine.start_workflow('wb.wf', {})
|
||||||
|
|
||||||
|
wf_execs = db_api.get_workflow_executions()
|
||||||
|
wf_ex = self._assert_single_item(wf_execs, name='wb.wf')
|
||||||
|
task_ex = self._assert_single_item(wf_ex.task_executions, name='taskx')
|
||||||
|
subwf_exs = self._assert_multiple_items(wf_execs, 2, name='wb.subwf')
|
||||||
|
|
||||||
|
self.engine.stop_workflow(
|
||||||
|
subwf_exs[0].id,
|
||||||
|
states.CANCELLED,
|
||||||
|
"Cancelled by user."
|
||||||
|
)
|
||||||
|
|
||||||
|
self.await_execution_cancelled(subwf_exs[0].id)
|
||||||
|
self.await_execution_success(subwf_exs[1].id)
|
||||||
|
self.await_execution_cancelled(task_ex.id)
|
||||||
|
self.await_execution_cancelled(wf_ex.id)
|
||||||
|
|
||||||
|
wf_execs = db_api.get_workflow_executions()
|
||||||
|
wf_ex = self._assert_single_item(wf_execs, name='wb.wf')
|
||||||
|
task_ex = self._assert_single_item(wf_ex.task_executions, name='taskx')
|
||||||
|
subwf_exs = self._assert_multiple_items(wf_execs, 2, name='wb.subwf')
|
||||||
|
|
||||||
|
self.assertEqual(states.CANCELLED, subwf_exs[0].state)
|
||||||
|
self.assertEqual("Cancelled by user.", subwf_exs[0].state_info)
|
||||||
|
self.assertEqual(states.SUCCESS, subwf_exs[1].state)
|
||||||
|
self.assertIsNone(subwf_exs[1].state_info)
|
||||||
|
self.assertEqual(states.CANCELLED, task_ex.state)
|
||||||
|
self.assertIn("cancelled", task_ex.state_info)
|
||||||
|
self.assertEqual(states.CANCELLED, wf_ex.state)
|
||||||
|
self.assertEqual("Cancelled tasks: taskx", wf_ex.state_info)
|
||||||
|
|
||||||
|
def test_cancel_then_fail_with_items_child_workflow(self):
|
||||||
|
workbook = """
|
||||||
|
version: '2.0'
|
||||||
|
|
||||||
|
name: wb
|
||||||
|
|
||||||
|
workflows:
|
||||||
|
wf:
|
||||||
|
type: direct
|
||||||
|
tasks:
|
||||||
|
taskx:
|
||||||
|
with-items: i in [1, 2]
|
||||||
|
workflow: subwf
|
||||||
|
|
||||||
|
subwf:
|
||||||
|
type: direct
|
||||||
|
tasks:
|
||||||
|
task1:
|
||||||
|
action: std.echo output="Echo"
|
||||||
|
on-complete:
|
||||||
|
- task2
|
||||||
|
|
||||||
|
task2:
|
||||||
|
action: std.echo output="foo"
|
||||||
|
wait-before: 1
|
||||||
|
"""
|
||||||
|
|
||||||
|
wb_service.create_workbook_v2(workbook)
|
||||||
|
wf_ex = self.engine.start_workflow('wb.wf', {})
|
||||||
|
|
||||||
|
wf_execs = db_api.get_workflow_executions()
|
||||||
|
wf_ex = self._assert_single_item(wf_execs, name='wb.wf')
|
||||||
|
task_ex = self._assert_single_item(wf_ex.task_executions, name='taskx')
|
||||||
|
subwf_exs = self._assert_multiple_items(wf_execs, 2, name='wb.subwf')
|
||||||
|
|
||||||
|
self.engine.stop_workflow(
|
||||||
|
subwf_exs[0].id,
|
||||||
|
states.CANCELLED,
|
||||||
|
"Cancelled by user."
|
||||||
|
)
|
||||||
|
|
||||||
|
self.engine.stop_workflow(
|
||||||
|
subwf_exs[1].id,
|
||||||
|
states.ERROR,
|
||||||
|
"Failed by user."
|
||||||
|
)
|
||||||
|
|
||||||
|
self.await_execution_cancelled(subwf_exs[0].id)
|
||||||
|
self.await_execution_error(subwf_exs[1].id)
|
||||||
|
self.await_execution_error(task_ex.id)
|
||||||
|
self.await_execution_error(wf_ex.id)
|
||||||
|
|
||||||
|
wf_execs = db_api.get_workflow_executions()
|
||||||
|
wf_ex = self._assert_single_item(wf_execs, name='wb.wf')
|
||||||
|
task_ex = self._assert_single_item(wf_ex.task_executions, name='taskx')
|
||||||
|
subwf_exs = self._assert_multiple_items(wf_execs, 2, name='wb.subwf')
|
||||||
|
|
||||||
|
self.assertEqual(states.CANCELLED, subwf_exs[0].state)
|
||||||
|
self.assertEqual("Cancelled by user.", subwf_exs[0].state_info)
|
||||||
|
self.assertEqual(states.ERROR, subwf_exs[1].state)
|
||||||
|
self.assertEqual("Failed by user.", subwf_exs[1].state_info)
|
||||||
|
self.assertEqual(states.ERROR, task_ex.state)
|
||||||
|
self.assertIn("failed", task_ex.state_info)
|
||||||
|
self.assertEqual(states.ERROR, wf_ex.state)
|
||||||
|
self.assertIn("Failed by user.", wf_ex.state_info)
|
||||||
|
|
||||||
|
def test_fail_then_cancel_with_items_child_workflow(self):
|
||||||
|
workbook = """
|
||||||
|
version: '2.0'
|
||||||
|
|
||||||
|
name: wb
|
||||||
|
|
||||||
|
workflows:
|
||||||
|
wf:
|
||||||
|
type: direct
|
||||||
|
tasks:
|
||||||
|
taskx:
|
||||||
|
with-items: i in [1, 2]
|
||||||
|
workflow: subwf
|
||||||
|
|
||||||
|
subwf:
|
||||||
|
type: direct
|
||||||
|
tasks:
|
||||||
|
task1:
|
||||||
|
action: std.echo output="Echo"
|
||||||
|
on-complete:
|
||||||
|
- task2
|
||||||
|
|
||||||
|
task2:
|
||||||
|
action: std.echo output="foo"
|
||||||
|
wait-before: 1
|
||||||
|
"""
|
||||||
|
|
||||||
|
wb_service.create_workbook_v2(workbook)
|
||||||
|
wf_ex = self.engine.start_workflow('wb.wf', {})
|
||||||
|
|
||||||
|
wf_execs = db_api.get_workflow_executions()
|
||||||
|
wf_ex = self._assert_single_item(wf_execs, name='wb.wf')
|
||||||
|
task_ex = self._assert_single_item(wf_ex.task_executions, name='taskx')
|
||||||
|
subwf_exs = self._assert_multiple_items(wf_execs, 2, name='wb.subwf')
|
||||||
|
|
||||||
|
self.engine.stop_workflow(
|
||||||
|
subwf_exs[1].id,
|
||||||
|
states.ERROR,
|
||||||
|
"Failed by user."
|
||||||
|
)
|
||||||
|
|
||||||
|
self.engine.stop_workflow(
|
||||||
|
subwf_exs[0].id,
|
||||||
|
states.CANCELLED,
|
||||||
|
"Cancelled by user."
|
||||||
|
)
|
||||||
|
|
||||||
|
self.await_execution_cancelled(subwf_exs[0].id)
|
||||||
|
self.await_execution_error(subwf_exs[1].id)
|
||||||
|
self.await_execution_error(task_ex.id)
|
||||||
|
self.await_execution_error(wf_ex.id)
|
||||||
|
|
||||||
|
wf_execs = db_api.get_workflow_executions()
|
||||||
|
wf_ex = self._assert_single_item(wf_execs, name='wb.wf')
|
||||||
|
task_ex = self._assert_single_item(wf_ex.task_executions, name='taskx')
|
||||||
|
subwf_exs = self._assert_multiple_items(wf_execs, 2, name='wb.subwf')
|
||||||
|
|
||||||
|
self.assertEqual(states.CANCELLED, subwf_exs[0].state)
|
||||||
|
self.assertEqual("Cancelled by user.", subwf_exs[0].state_info)
|
||||||
|
self.assertEqual(states.ERROR, subwf_exs[1].state)
|
||||||
|
self.assertEqual("Failed by user.", subwf_exs[1].state_info)
|
||||||
|
self.assertEqual(states.ERROR, task_ex.state)
|
||||||
|
self.assertIn("failed", task_ex.state_info)
|
||||||
|
self.assertEqual(states.ERROR, wf_ex.state)
|
||||||
|
self.assertIn("Failed by user.", wf_ex.state_info)
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
# Copyright 2015 - Alcatel-lucent, Inc.
|
# Copyright 2015 - Alcatel-lucent, Inc.
|
||||||
# Copyright 2015 - StackStorm, Inc.
|
# Copyright 2015 - StackStorm, Inc.
|
||||||
|
# Copyright 2016 - Brocade Communications Systems, Inc.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -65,8 +66,25 @@ def _load_executions():
|
|||||||
'workflow_name': 'test_exec',
|
'workflow_name': 'test_exec',
|
||||||
'state': "SUCCESS",
|
'state': "SUCCESS",
|
||||||
'task_execution_id': '789'
|
'task_execution_id': '789'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'abc',
|
||||||
|
'name': 'cancelled_expired',
|
||||||
|
'created_at': time_now - datetime.timedelta(minutes=60),
|
||||||
|
'updated_at': time_now - datetime.timedelta(minutes=59),
|
||||||
|
'workflow_name': 'test_exec',
|
||||||
|
'state': "CANCELLED",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'def',
|
||||||
|
'name': 'cancelled_not_expired',
|
||||||
|
'created_at': time_now - datetime.timedelta(minutes=15),
|
||||||
|
'updated_at': time_now - datetime.timedelta(minutes=5),
|
||||||
|
'workflow_name': 'test_exec',
|
||||||
|
'state': "CANCELLED",
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
for wf_exec in wf_execs:
|
for wf_exec in wf_execs:
|
||||||
db_api.create_workflow_execution(wf_exec)
|
db_api.create_workflow_execution(wf_exec)
|
||||||
|
|
||||||
@@ -105,9 +123,9 @@ class ExpirationPolicyTest(base.DbTestCase):
|
|||||||
# Call for all expired wfs execs.
|
# Call for all expired wfs execs.
|
||||||
execs = db_api.get_expired_executions(now)
|
execs = db_api.get_expired_executions(now)
|
||||||
|
|
||||||
# Should be only 3, the RUNNING execution shouldn't return,
|
# Should be only 5, the RUNNING execution shouldn't return,
|
||||||
# so the child wf (that has parent task id).
|
# so the child wf (that has parent task id).
|
||||||
self.assertEqual(3, len(execs))
|
self.assertEqual(5, len(execs))
|
||||||
|
|
||||||
# Switch context to Admin since expiration policy running as Admin.
|
# Switch context to Admin since expiration policy running as Admin.
|
||||||
_switch_context(None, True)
|
_switch_context(None, True)
|
||||||
@@ -118,8 +136,8 @@ class ExpirationPolicyTest(base.DbTestCase):
|
|||||||
# Only non_expired available (update_at < older_than).
|
# Only non_expired available (update_at < older_than).
|
||||||
execs = db_api.get_expired_executions(now)
|
execs = db_api.get_expired_executions(now)
|
||||||
|
|
||||||
self.assertEqual(1, len(execs))
|
self.assertEqual(2, len(execs))
|
||||||
self.assertEqual('987', execs[0].id)
|
self.assertListEqual(['987', 'def'], sorted([ex.id for ex in execs]))
|
||||||
|
|
||||||
_set_expiration_policy_config(1, 5)
|
_set_expiration_policy_config(1, 5)
|
||||||
expiration_policy.run_execution_expiration_policy(self, ctx)
|
expiration_policy.run_execution_expiration_policy(self, ctx)
|
||||||
|
|||||||
@@ -140,6 +140,13 @@ class WorkflowController(object):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def any_cancels(self):
|
||||||
|
"""Determines if there are any task cancellations.
|
||||||
|
|
||||||
|
:return: True if there is one or more tasks in cancelled state.
|
||||||
|
"""
|
||||||
|
return len(wf_utils.find_cancelled_task_executions(self.wf_ex)) > 0
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def evaluate_workflow_final_context(self):
|
def evaluate_workflow_final_context(self):
|
||||||
"""Evaluates final workflow context assuming that workflow has finished.
|
"""Evaluates final workflow context assuming that workflow has finished.
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright 2013 - Mirantis, Inc.
|
# Copyright 2013 - Mirantis, Inc.
|
||||||
|
# Copyright 2016 - Brocade Communications Systems, Inc.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -22,17 +21,28 @@ RUNNING = 'RUNNING'
|
|||||||
RUNNING_DELAYED = 'DELAYED'
|
RUNNING_DELAYED = 'DELAYED'
|
||||||
PAUSED = 'PAUSED'
|
PAUSED = 'PAUSED'
|
||||||
SUCCESS = 'SUCCESS'
|
SUCCESS = 'SUCCESS'
|
||||||
|
CANCELLED = 'CANCELLED'
|
||||||
ERROR = 'ERROR'
|
ERROR = 'ERROR'
|
||||||
|
|
||||||
_ALL = [IDLE, WAITING, RUNNING, SUCCESS, ERROR, PAUSED, RUNNING_DELAYED]
|
_ALL = [
|
||||||
|
IDLE,
|
||||||
|
WAITING,
|
||||||
|
RUNNING,
|
||||||
|
RUNNING_DELAYED,
|
||||||
|
PAUSED,
|
||||||
|
SUCCESS,
|
||||||
|
CANCELLED,
|
||||||
|
ERROR
|
||||||
|
]
|
||||||
|
|
||||||
_VALID_TRANSITIONS = {
|
_VALID_TRANSITIONS = {
|
||||||
IDLE: [RUNNING, ERROR],
|
IDLE: [RUNNING, ERROR, CANCELLED],
|
||||||
WAITING: [RUNNING],
|
WAITING: [RUNNING],
|
||||||
RUNNING: [PAUSED, RUNNING_DELAYED, SUCCESS, ERROR],
|
RUNNING: [PAUSED, RUNNING_DELAYED, SUCCESS, ERROR, CANCELLED],
|
||||||
RUNNING_DELAYED: [RUNNING, ERROR],
|
RUNNING_DELAYED: [RUNNING, ERROR, CANCELLED],
|
||||||
PAUSED: [RUNNING, ERROR],
|
PAUSED: [RUNNING, ERROR, CANCELLED],
|
||||||
SUCCESS: [],
|
SUCCESS: [],
|
||||||
|
CANCELLED: [],
|
||||||
ERROR: [RUNNING]
|
ERROR: [RUNNING]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +56,7 @@ def is_invalid(state):
|
|||||||
|
|
||||||
|
|
||||||
def is_completed(state):
|
def is_completed(state):
|
||||||
return state in [SUCCESS, ERROR]
|
return state in [SUCCESS, ERROR, CANCELLED]
|
||||||
|
|
||||||
|
|
||||||
def is_running(state):
|
def is_running(state):
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# Copyright 2014 - Mirantis, Inc.
|
# Copyright 2014 - Mirantis, Inc.
|
||||||
# Copyright 2015 - StackStorm, Inc.
|
# Copyright 2015 - StackStorm, Inc.
|
||||||
|
# Copyright 2016 - Brocade Communications Systems, Inc.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -20,30 +21,47 @@ from mistral.workflow import states
|
|||||||
class Result(object):
|
class Result(object):
|
||||||
"""Explicit data structure containing a result of task execution."""
|
"""Explicit data structure containing a result of task execution."""
|
||||||
|
|
||||||
def __init__(self, data=None, error=None):
|
def __init__(self, data=None, error=None, cancel=False):
|
||||||
self.data = data
|
self.data = data
|
||||||
self.error = error
|
self.error = error
|
||||||
|
self.cancel = cancel
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'Result [data=%s, error=%s]' % (
|
return 'Result [data=%s, error=%s, cancel=%s]' % (
|
||||||
repr(self.data), repr(self.error))
|
repr(self.data), repr(self.error), str(self.cancel)
|
||||||
|
)
|
||||||
|
|
||||||
|
def is_cancel(self):
|
||||||
|
return self.cancel
|
||||||
|
|
||||||
def is_error(self):
|
def is_error(self):
|
||||||
return self.error is not None
|
return self.error is not None and not self.is_cancel()
|
||||||
|
|
||||||
def is_success(self):
|
def is_success(self):
|
||||||
return not self.is_error()
|
return not self.is_error() and not self.is_cancel()
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return self.data == other.data and self.error == other.error
|
return (
|
||||||
|
self.data == other.data and
|
||||||
|
self.error == other.error and
|
||||||
|
self.cancel == other.cancel
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ResultSerializer(serializers.Serializer):
|
class ResultSerializer(serializers.Serializer):
|
||||||
def serialize(self, entity):
|
def serialize(self, entity):
|
||||||
return {'data': entity.data, 'error': entity.error}
|
return {
|
||||||
|
'data': entity.data,
|
||||||
|
'error': entity.error,
|
||||||
|
'cancel': entity.cancel
|
||||||
|
}
|
||||||
|
|
||||||
def deserialize(self, entity):
|
def deserialize(self, entity):
|
||||||
return Result(entity['data'], entity['error'])
|
return Result(
|
||||||
|
entity['data'],
|
||||||
|
entity['error'],
|
||||||
|
entity.get('cancel', False)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def find_task_execution_not_state(wf_ex, task_spec, state):
|
def find_task_execution_not_state(wf_ex, task_spec, state):
|
||||||
@@ -106,3 +124,7 @@ def find_incomplete_task_executions(wf_ex):
|
|||||||
|
|
||||||
def find_error_task_executions(wf_ex):
|
def find_error_task_executions(wf_ex):
|
||||||
return find_task_executions_with_state(wf_ex, states.ERROR)
|
return find_task_executions_with_state(wf_ex, states.ERROR)
|
||||||
|
|
||||||
|
|
||||||
|
def find_cancelled_task_executions(wf_ex):
|
||||||
|
return find_task_executions_with_state(wf_ex, states.CANCELLED)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Copyright 2014 - Mirantis, Inc.
|
# Copyright 2014 - Mirantis, Inc.
|
||||||
|
# Copyright 2016 - Brocade Communications Systems, Inc.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
# You may obtain a copy of the License at
|
# You may obtain a copy of the License at
|
||||||
#
|
#
|
||||||
@@ -69,9 +70,12 @@ def get_concurrency(task_ex):
|
|||||||
|
|
||||||
def get_final_state(task_ex):
|
def get_final_state(task_ex):
|
||||||
find_error = lambda x: x.accepted and x.state == states.ERROR
|
find_error = lambda x: x.accepted and x.state == states.ERROR
|
||||||
|
find_cancel = lambda x: x.accepted and x.state == states.CANCELLED
|
||||||
|
|
||||||
if list(filter(find_error, task_ex.executions)):
|
if list(filter(find_error, task_ex.executions)):
|
||||||
return states.ERROR
|
return states.ERROR
|
||||||
|
elif list(filter(find_cancel, task_ex.executions)):
|
||||||
|
return states.CANCELLED
|
||||||
else:
|
else:
|
||||||
return states.SUCCESS
|
return states.SUCCESS
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user