def test_wait_after_fail(self): """When a task fails, the already-running tasks are waited for""" class FailedTask(tasks.WorkflowTask): """Task that fails 1 second after starting""" name = 'failtask' def apply_async(self): self.set_state(tasks.TASK_FAILED) self.async_result.result = tasks.HandlerResult.fail() return self.async_result def handle_task_terminated(self): rv = super(FailedTask, self).handle_task_terminated() task2.set_state(tasks.TASK_SUCCEEDED) task2.async_result.result = None return rv class DelayedTask(tasks.WorkflowTask): """Task that succeeds 3 seconds after starting""" name = 'delayedtask' handle_task_terminated = mock.Mock() task1 = FailedTask(mock.Mock(), total_retries=0) task2 = DelayedTask(mock.Mock()) g = TaskDependencyGraph(MockWorkflowContext()) g.add_task(task1) g.add_task(task2) self.assertRaisesRegex(WorkflowFailed, 'failtask', g.execute) # even though the workflow failed 1 second in, the other task was # still waited for and completed task2.handle_task_terminated.assert_called()
def test_task_failed(self): """Execution is stopped when a task failed. The next task is not executed""" class FailedTask(tasks.WorkflowTask): name = 'failtask' def apply_async(self): self.set_state(tasks.TASK_FAILED) task1 = FailedTask(mock.Mock()) task2 = mock.Mock() g = TaskDependencyGraph(MockWorkflowContext()) seq = g.sequence() seq.add(task1, task2) with limited_sleep_mock(): try: g.execute() except RuntimeError as e: self.assertIn('Workflow failed', e.message) else: self.fail('Expected task to fail') self.assertTrue(task1.is_terminated) self.assertFalse(task2.apply_async.called)
def test_task_sequence(self): """Tasks in a sequence are called in order""" class Task(tasks.WorkflowTask): name = 'task' def apply_async(self): record.append(self.i) self.set_state(tasks.TASK_SUCCEEDED) task_count = 10 # prepare the task seuqence seq_tasks = [] for i in range(task_count): t = Task(None) seq_tasks.append(t) t.i = i g = TaskDependencyGraph(MockWorkflowContext()) seq = g.sequence() seq.add(*seq_tasks) record = [] with limited_sleep_mock(): g.execute() expected = list(range(task_count)) self.assertEqual(expected, record)
def test_cancel(self): """When execution is cancelled, an error is thrown and tasks are not executed. """ g = TaskDependencyGraph(MockWorkflowContext()) task = mock.Mock() g.add_task(task) with mock.patch('cloudify.workflows.api.cancel_request', True): self.assertRaises(api.ExecutionCancelled, g.execute) self.assertFalse(task.apply_async.called) self.assertFalse(task.cancel.called)
def test_task_on_success(self): """If a task has a success callback, dependent tasks still run""" wctx = MockWorkflowContext() g = TaskDependencyGraph(wctx) record = [] class Task(tasks.WorkflowTask): name = 'task' def apply_async(self): record.append(self.i) self.set_state(tasks.TASK_SUCCEEDED) self.async_result.result = None return self.async_result def on_success(task): record.append('success') return tasks.HandlerResult.cont() t1 = Task(wctx) t1.i = 1 t2 = Task(wctx) t1.on_success = on_success t2.i = 2 g.add_task(t1) g.add_task(t2) g.add_dependency(t2, t1) g.execute() assert record == [1, 'success', 2]
def _restore_graph(self, operations): mock_wf_ctx = mock.Mock() mock_wf_ctx.get_operations.return_value = [ Operation(op) for op in operations ] mock_retrieved_graph = mock.Mock(id=0) return TaskDependencyGraph.restore(mock_wf_ctx, mock_retrieved_graph)
def test_wait_after_fail(self): """When a task fails, the already-running tasks are waited for""" class FailedTask(tasks.WorkflowTask): """Task that fails 1 second after starting""" name = 'failtask' def get_state(self): if time.time() > start_time + 1: return tasks.TASK_FAILED else: return tasks.TASK_SENT class DelayedTask(tasks.WorkflowTask): """Task that succeeds 3 seconds after starting""" name = 'delayedtask' def get_state(self): if time.time() > start_time + 3: return tasks.TASK_SUCCEEDED else: return tasks.TASK_SENT handle_task_terminated = mock.Mock() task1 = FailedTask(mock.Mock()) task2 = DelayedTask(mock.Mock()) g = TaskDependencyGraph(MockWorkflowContext()) seq = g.sequence() seq.add(task1, task2) with limited_sleep_mock(): start_time = time.time() try: g.execute() except RuntimeError as e: self.assertIn('Workflow failed', e.message) else: self.fail('Expected task to fail') # even though the workflow failed 1 second in, the other task was # still waited for and completed task2.handle_task_terminated.assert_called()
def test_task_failed(self): """Execution is stopped when a task failed. The next task is not executed""" class FailedTask(tasks.WorkflowTask): name = 'failtask' def apply_async(self): self.set_state(tasks.TASK_FAILED) task1 = FailedTask(mock.Mock()) task2 = mock.Mock(execute_after=0) g = TaskDependencyGraph(MockWorkflowContext()) seq = g.sequence() seq.add(task1, task2) with limited_sleep_mock(): self.assertRaisesRegex(RuntimeError, 'Workflow failed', g.execute) self.assertTrue(task1.is_terminated) self.assertFalse(task2.apply_async.called)
def test_executes_single_task(self): """A single NOP task is executed within a single iteration of the tasks graph loop""" g = TaskDependencyGraph(MockWorkflowContext()) task = tasks.NOPLocalWorkflowTask(None) g.add_task(task) with limited_sleep_mock(limit=1): g.execute() self.assertTrue(task.is_terminated)
def __init__(self, workflow_context, handler): self.workflow_context = workflow_context self.handler = handler self._bootstrap_context = None self._graph_mode = False # the graph is always created internally for events to work properly # when graph mode is turned on this instance is returned to the user. self._task_graph = TaskDependencyGraph(workflow_context) # events related self._event_monitor = None self._event_monitor_thread = None # local task processing thread_pool_size = self.workflow_context._local_task_thread_pool_size self.local_tasks_processor = LocalTasksProcessing( thread_pool_size=thread_pool_size)
def test_executes_multiple_concurrent(self): """Independent tasks will be executed concurrently within the same iteration of the graph loop. """ g = TaskDependencyGraph(MockWorkflowContext()) task1 = tasks.NOPLocalWorkflowTask(None) task2 = tasks.NOPLocalWorkflowTask(None) g.add_task(task1) g.add_task(task2) with limited_sleep_mock(limit=1): g.execute() self.assertTrue(task1.is_terminated) self.assertTrue(task2.is_terminated)
def test_cancel(self): """When execution is cancelled, an error is thrown and tasks are not executed. """ g = TaskDependencyGraph(MockWorkflowContext()) task = mock.Mock() g.add_task(task) with mock.patch('cloudify.workflows.api.cancel_request', True): try: g.execute() except api.ExecutionCancelled: pass else: self.fail('Execution should have been cancelled') self.assertFalse(task.apply_async.called) self.assertFalse(task.cancel.called)
+ 'dependencies': [], + 'parameters': { + 'info': {}, + 'current_retries': 0, + 'send_task_events': False, + 'containing_subgraph': None, + 'task_kwargs': {} + } + } + + def _restore_graph(self, operations): + mock_wf_ctx = mock.Mock() + mock_wf_ctx.get_operations.return_value = [ + Operation(op) for op in operations] + mock_retrieved_graph = mock.Mock(id=0) + return TaskDependencyGraph.restore(mock_wf_ctx, mock_retrieved_graph) + + def test_restore_empty(self): + """Restoring an empty list of operations results in an empty graph""" + graph = self._restore_graph([]) + operations = list(graph.tasks_iter()) + assert operations == [] + + def test_restore_single(self): + """A single operation is restored into the graph""" + graph = self._restore_graph([self._remote_task()]) + operations = list(graph.tasks_iter()) + assert len(operations) == 1 + assert isinstance(operations[0], tasks.RemoteWorkflowTask) + + def test_restore_finished(self):