def test_task_not_in_rerunable_status(self):
        wf_def = """
        version: 1.0

        tasks:
          task1:
            action: core.echo message="$RANDOM"
            next:
              - when: <% succeeded() %>
                do: task2
          task2:
            action: core.noop
            next:
              - when: <% succeeded() %>
                do: task3
          task3:
            action: core.noop
        """

        fast_forward_failure = [statuses.RUNNING, statuses.FAILED]
        fast_forward_success = [statuses.RUNNING, statuses.SUCCEEDED]

        spec = native_specs.WorkflowSpec(wf_def)
        self.assertDictEqual(spec.inspect(), {})

        conductor = conducting.WorkflowConductor(spec)
        conductor.request_workflow_status(statuses.RUNNING)

        # Succeed task1.
        next_tasks = conductor.get_next_tasks()
        self.forward_task_statuses(conductor, next_tasks[0]["id"],
                                   fast_forward_success)

        # Fail task2.
        next_tasks = conductor.get_next_tasks()
        self.forward_task_statuses(conductor, next_tasks[0]["id"],
                                   fast_forward_failure)

        # Assert workflow failed.
        self.assertEqual(conductor.get_workflow_status(), statuses.FAILED)

        # Assert rerun cannot happen because task3 has not happened yet.
        self.assertRaises(
            exc.InvalidTaskRerunRequest,
            conductor.request_workflow_rerun,
            task_requests=[requests.TaskRerunRequest("task3", 0)],
        )

        # Assert rerun cannot happen because task4 does not exists.
        self.assertRaises(
            exc.InvalidTaskRerunRequest,
            conductor.request_workflow_rerun,
            task_requests=[requests.TaskRerunRequest("task4", 0)],
        )
Example #2
0
    def test_load_task_rerun_request_dict_minimal(self):
        rerun_spec = {"task_id": "task1"}
        rerun_req = requests.TaskRerunRequest(rerun_spec)

        self.assertEqual(rerun_req.task_id, rerun_spec["task_id"])
        self.assertEqual(rerun_req.route, 0)
        self.assertEqual(rerun_req.reset_items, False)
        self.assertEqual(
            rerun_req.task_state_entry_id,
            constants.TASK_STATE_ROUTE_FORMAT % ("task1", "0"),
        )
Example #3
0
    def test_fail_single_branch(self):
        wf_name = "parallel"

        # Fail task2.
        expected_task_seq = ["task1", "task4", "task2", "task5"]

        mock_statuses = [
            statuses.SUCCEEDED,
            statuses.SUCCEEDED,
            statuses.FAILED,
            statuses.SUCCEEDED,
        ]

        expected_term_tasks = ["task2", "task5"]

        self.assert_spec_inspection(wf_name)

        conductor = self.assert_conducting_sequences(
            wf_name,
            expected_task_seq,
            mock_statuses=mock_statuses,
            expected_workflow_status=statuses.FAILED,
            expected_term_tasks=expected_term_tasks,
        )

        # Rerun entire workflow and fail task2 again.
        expected_task_seq = ["task1", "task4", "task2", "task5", "task2", "task6"]

        mock_statuses = [statuses.FAILED, statuses.SUCCEEDED]

        expected_term_tasks = ["task2", "task6"]

        conductor = self.assert_rerun_failed_tasks(
            conductor,
            expected_task_seq,
            mock_statuses=mock_statuses,
            expected_workflow_status=statuses.FAILED,
            expected_term_tasks=expected_term_tasks,
        )

        # Rerun workflow from task2 only and complete the workflow.
        expected_task_seq = ["task1", "task4", "task2", "task5", "task2", "task6", "task2", "task3"]

        expected_term_tasks = ["task3", "task6"]

        self.assert_rerun_failed_tasks(
            conductor,
            expected_task_seq,
            rerun_tasks=[requests.TaskRerunRequest("task2", 0)],
            expected_term_tasks=expected_term_tasks,
        )
Example #4
0
    def test_rerun_with_duplicate_task_requests(self):
        wf_def = """
        version: 1.0

        tasks:
          task1:
            action: core.echo message="$RANDOM"
            next:
              - when: <% succeeded() %>
                publish: foobar="foobar"
                do: task2
          task2:
            action: core.noop
            next:
              - when: <% succeeded() %>
                publish: foobar="fubar"

        output:
          - foobar: <% ctx().foobar %>
        """

        fast_forward_failure = [statuses.RUNNING, statuses.FAILED]
        fast_forward_success = [statuses.RUNNING, statuses.SUCCEEDED]

        spec = native_specs.WorkflowSpec(wf_def)
        self.assertDictEqual(spec.inspect(), {})

        conductor = conducting.WorkflowConductor(spec)
        conductor.request_workflow_status(statuses.RUNNING)

        # Succeed task1.
        next_tasks = conductor.get_next_tasks()
        self.forward_task_statuses(conductor, next_tasks[0]['id'], fast_forward_success)

        # Fail task2.
        next_tasks = conductor.get_next_tasks()
        self.forward_task_statuses(conductor, next_tasks[0]['id'], fast_forward_failure)

        # Render workflow output and assert workflow status, error, and output.
        conductor.render_workflow_output()
        self.assertEqual(conductor.get_workflow_status(), statuses.FAILED)
        self.assertDictEqual(conductor.get_workflow_output(), {'foobar': 'foobar'})
        self.assertIn('task2', [e['task_id'] for e in conductor.errors])

        # Request workflow rerun from the succeeded task1. Request task1 twice.
        tk1_rerun_req = requests.TaskRerunRequest('task1', 0)
        tk2_rerun_req = requests.TaskRerunRequest('task1', 0)
        conductor.request_workflow_rerun(task_requests=[tk1_rerun_req, tk2_rerun_req])

        # Assert workflow status is resuming and state is reset. We expect only
        # a single instance of task1 is setup.
        self.assertEqual(conductor.get_workflow_status(), statuses.RESUMING)
        self.assertIsNone(conductor.get_workflow_output())
        next_tasks = conductor.get_next_tasks()
        self.assertEqual(len(next_tasks), 1)
        self.assertEqual(next_tasks[0]['id'], 'task1')

        # Assert sequence of tasks is correct.
        self.assertIsNotNone(conductor.workflow_state.get_staged_task('task1', 0))
        self.assertIsNone(conductor.workflow_state.get_staged_task('task2', 0))

        task_states = [
            t for t in list(enumerate(conductor.workflow_state.sequence))
            if t[1]['id'] == 'task1' and t[1]['route'] == 0
        ]

        self.assertEqual(len(task_states), 2)
        self.assertListEqual([t[0] for t in task_states], [0, 2])

        # Succeed task1.
        next_tasks = conductor.get_next_tasks()
        self.assertEqual(next_tasks[0]['id'], 'task1')
        self.forward_task_statuses(conductor, next_tasks[0]['id'], fast_forward_success)

        # Succeed task2.
        next_tasks = conductor.get_next_tasks()
        self.assertEqual(next_tasks[0]['id'], 'task2')
        self.forward_task_statuses(conductor, next_tasks[0]['id'], fast_forward_success)

        # Assert workflow is completed.
        conductor.render_workflow_output()
        self.assertEqual(conductor.get_workflow_status(), statuses.SUCCEEDED)
        self.assertDictEqual(conductor.get_workflow_output(), {'foobar': 'fubar'})
Example #5
0
    def test_rerun_failed_task_with_items_and_reset(self):
        wf_def = """
        version: 1.0

        vars:
          - items: []
          - xs:
              - fee
              - fi
              - fo
              - fum

        tasks:
          task1:
            with: <% ctx(xs) %>
            action: core.echo message=<% item() %>
            next:
              - when: <% succeeded() %>
                publish:
                  - items: <% result() %>
                do: task2
          task2:
            action: core.noop

        output:
          - items: <% ctx(items) %>
        """

        fast_forward_success = [statuses.RUNNING, statuses.SUCCEEDED]

        spec = native_specs.WorkflowSpec(wf_def)
        self.assertDictEqual(spec.inspect(), {})

        conductor = conducting.WorkflowConductor(spec)
        conductor.request_workflow_status(statuses.RUNNING)

        next_tasks = conductor.get_next_tasks()
        self.assertEqual(next_tasks[0]['id'], 'task1')

        # Fail some item(s) to fail the workflow.
        task_items = ['fee', 'fi', 'fo', 'fum']
        task_items_status = [statuses.SUCCEEDED] * 3 + [statuses.FAILED]

        for idx, task_item in enumerate(task_items):
            fast_forward_statuses = [statuses.RUNNING, task_items_status[idx]]
            self.forward_task_item_statuses(
                conductor,
                next_tasks[0]['id'],
                idx,
                fast_forward_statuses,
                result=task_item,
                accumulated_result=task_items[0:idx + 1]
            )

        # Render workflow output and assert workflow status, error, and output.
        conductor.render_workflow_output()
        self.assertEqual(conductor.get_workflow_status(), statuses.FAILED)
        task1 = conductor.get_task_state_entry('task1', 0)
        self.assertEqual(task1['status'], statuses.FAILED)
        self.assertIn('task1', [e['task_id'] for e in conductor.errors if e.get('task_id')])
        self.assertDictEqual(conductor.get_workflow_output(), {'items': []})

        # Request workflow rerun.
        task_rerun_req = requests.TaskRerunRequest('task1', 0, reset_items=True)
        conductor.request_workflow_rerun(task_requests=[task_rerun_req])

        # Assert items are preserved in get next tasks.
        next_tasks = conductor.get_next_tasks()
        self.assertEqual(next_tasks[0]['id'], 'task1')
        self.assertEqual(len(next_tasks[0]['actions']), 4)

        # Succeed task1.
        task_items = ['fee', 'fi', 'fo', 'fum']
        task_items_status = [statuses.SUCCEEDED] * 4

        for idx, task_item in enumerate(task_items):
            fast_forward_statuses = [statuses.RUNNING, task_items_status[idx]]
            self.forward_task_item_statuses(
                conductor,
                next_tasks[0]['id'],
                idx,
                fast_forward_statuses,
                result=task_item,
                accumulated_result=task_items[0:idx + 1]
            )

        # Succeed task2.
        next_tasks = conductor.get_next_tasks()
        self.assertEqual(next_tasks[0]['id'], 'task2')
        self.forward_task_statuses(conductor, next_tasks[0]['id'], fast_forward_success)

        # Render workflow output and assert workflow status, error, and output.
        self.assertEqual(len(conductor.get_next_tasks()), 0)
        conductor.render_workflow_output()
        self.assertEqual(conductor.get_workflow_status(), statuses.SUCCEEDED)
        task1 = conductor.get_task_state_entry('task1', 0)
        self.assertEqual(task1['status'], statuses.SUCCEEDED)
        self.assertNotIn('task1', [e['task_id'] for e in conductor.errors])
        task2 = conductor.get_task_state_entry('task2', 0)
        self.assertEqual(task2['status'], statuses.SUCCEEDED)
        self.assertNotIn('task2', [e['task_id'] for e in conductor.errors])
        expected_workflow_output = {'items': ['fee', 'fi', 'fo', 'fum']}
        self.assertDictEqual(conductor.get_workflow_output(), expected_workflow_output)
Example #6
0
    def test_rerun_from_remediated_task(self):
        wf_def = """
        version: 1.0
        vars:
          - foobar: null
          - fubar: null
        tasks:
          task1:
            action: core.echo message="$RANDOM"
            next:
              - when: <% failed() %>
                publish: foobar="fubar"
                do: task3
              - when: <% succeeded() %>
                publish: foobar="foobar"
          task2:
            action: core.noop
            next:
              - when: <% succeeded() %>
                publish: fubar="fubar"
          task3:
            action: core.noop
        output:
          - foobar: <% ctx().foobar %>
          - fubar: <% ctx().fubar %>
        """

        fast_forward_failure = [statuses.RUNNING, statuses.FAILED]
        fast_forward_success = [statuses.RUNNING, statuses.SUCCEEDED]

        spec = native_specs.WorkflowSpec(wf_def)
        self.assertDictEqual(spec.inspect(), {})

        conductor = conducting.WorkflowConductor(spec)
        conductor.request_workflow_status(statuses.RUNNING)

        # Fail task1 to trigger the remediation task.
        next_tasks = conductor.get_next_tasks()
        self.assertEqual(next_tasks[0]['id'], 'task1')
        self.forward_task_statuses(conductor, next_tasks[0]['id'], fast_forward_failure)

        # Succeed the remediation task3.
        next_tasks = conductor.get_next_tasks()
        self.assertEqual(next_tasks[1]['id'], 'task3')
        self.forward_task_statuses(conductor, next_tasks[1]['id'], fast_forward_success)

        # Fail task2 to stop the workflow.
        next_tasks = conductor.get_next_tasks()
        self.assertEqual(next_tasks[0]['id'], 'task2')
        self.forward_task_statuses(conductor, next_tasks[0]['id'], fast_forward_failure)

        # Render workflow output and assert workflow status, error, and output.
        conductor.render_workflow_output()
        self.assertEqual(conductor.get_workflow_status(), statuses.FAILED)
        task1 = conductor.get_task_state_entry('task1', 0)
        self.assertEqual(task1['status'], statuses.FAILED)
        self.assertIn('task1', [e['task_id'] for e in conductor.errors])
        task2 = conductor.get_task_state_entry('task2', 0)
        self.assertEqual(task2['status'], statuses.FAILED)
        self.assertIn('task2', [e['task_id'] for e in conductor.errors])

        # Request workflow rerun and explicitly ask the remediated task1 to rerun.
        tk1_rerun_req = requests.TaskRerunRequest('task1', 0)
        tk2_rerun_req = requests.TaskRerunRequest('task2', 0)
        conductor.request_workflow_rerun(task_requests=[tk1_rerun_req, tk2_rerun_req])

        # Succeed task1 and task2.
        next_tasks = conductor.get_next_tasks()
        self.assertEqual(next_tasks[0]['id'], 'task1')
        self.forward_task_statuses(conductor, next_tasks[0]['id'], fast_forward_success)
        self.assertEqual(next_tasks[1]['id'], 'task2')
        self.forward_task_statuses(conductor, next_tasks[1]['id'], fast_forward_success)

        # Render workflow output and assert workflow status, error, and output.
        self.assertEqual(len(conductor.get_next_tasks()), 0)
        conductor.render_workflow_output()
        self.assertEqual(conductor.get_workflow_status(), statuses.SUCCEEDED)
        task1 = conductor.get_task_state_entry('task1', 0)
        self.assertEqual(task1['status'], statuses.SUCCEEDED)
        self.assertNotIn('task1', [e['task_id'] for e in conductor.errors])
        task2 = conductor.get_task_state_entry('task2', 0)
        self.assertEqual(task2['status'], statuses.SUCCEEDED)
        self.assertNotIn('task2', [e['task_id'] for e in conductor.errors])
        expected_workflow_output = {'foobar': 'foobar', 'fubar': 'fubar'}
        self.assertDictEqual(conductor.get_workflow_output(), expected_workflow_output)
Example #7
0
    def test_rerun_succeeded_workflow(self):
        wf_def = """
        version: 1.0

        tasks:
          task1:
            action: core.echo message="$RANDOM"
            next:
              - when: <% succeeded() %>
                publish: foobar="foobar"
                do: task2
          task2:
            action: core.noop
            next:
              - when: <% succeeded() %>
                publish: foobar="fubar"

        output:
          - foobar: <% ctx().foobar %>
        """

        fast_forward_failure = [statuses.RUNNING, statuses.FAILED]
        fast_forward_success = [statuses.RUNNING, statuses.SUCCEEDED]

        spec = native_specs.WorkflowSpec(wf_def)
        self.assertDictEqual(spec.inspect(), {})

        conductor = conducting.WorkflowConductor(spec)
        conductor.request_workflow_status(statuses.RUNNING)

        # Succeed task1.
        next_tasks = conductor.get_next_tasks()
        self.forward_task_statuses(conductor, next_tasks[0]['id'], fast_forward_success)

        # Succeed task2.
        next_tasks = conductor.get_next_tasks()
        self.forward_task_statuses(conductor, next_tasks[0]['id'], fast_forward_success)

        # Assert workflow is completed.
        conductor.render_workflow_output()
        self.assertEqual(conductor.get_workflow_status(), statuses.SUCCEEDED)
        self.assertDictEqual(conductor.get_workflow_output(), {'foobar': 'fubar'})

        # Request workflow rerun from the succeeded task1.
        task_rerun_req = requests.TaskRerunRequest('task1', 0)
        conductor.request_workflow_rerun(task_requests=[task_rerun_req])

        # Succeed task1.
        next_tasks = conductor.get_next_tasks()
        self.forward_task_statuses(conductor, next_tasks[0]['id'], fast_forward_success)

        # Fail task2.
        next_tasks = conductor.get_next_tasks()
        self.forward_task_statuses(conductor, next_tasks[0]['id'], fast_forward_failure)

        # Render workflow output and assert workflow status, error, and output.
        conductor.render_workflow_output()
        self.assertEqual(conductor.get_workflow_status(), statuses.FAILED)
        self.assertDictEqual(conductor.get_workflow_output(), {'foobar': 'foobar'})
        task2 = conductor.get_task_state_entry('task2', 0)
        self.assertEqual(task2['status'], statuses.FAILED)
        self.assertIn('task2', [e['task_id'] for e in conductor.errors])

        # Request workflow rerun.
        conductor.request_workflow_rerun()

        # Succeed task2.
        next_tasks = conductor.get_next_tasks()
        self.forward_task_statuses(conductor, next_tasks[0]['id'], fast_forward_success)

        # Assert workflow is completed.
        conductor.render_workflow_output()
        self.assertEqual(conductor.get_workflow_status(), statuses.SUCCEEDED)
        self.assertDictEqual(conductor.get_workflow_output(), {'foobar': 'fubar'})
Example #8
0
    def test_rerun_from_failed_task(self):
        wf_def = """
        version: 1.0

        tasks:
          task1:
            action: core.echo message="$RANDOM"
            next:
              - when: <% succeeded() %>
                publish: foobar="foobar"
                do: task2
          task2:
            action: core.noop
            next:
              - when: <% succeeded() %>
                publish: foobar="fubar"

        output:
          - foobar: <% ctx().foobar %>
        """

        fast_forward_failure = [statuses.RUNNING, statuses.FAILED]
        fast_forward_success = [statuses.RUNNING, statuses.SUCCEEDED]

        spec = native_specs.WorkflowSpec(wf_def)
        self.assertDictEqual(spec.inspect(), {})

        conductor = conducting.WorkflowConductor(spec)
        conductor.request_workflow_status(statuses.RUNNING)

        # Succeed task1.
        next_tasks = conductor.get_next_tasks()
        self.forward_task_statuses(conductor, next_tasks[0]['id'], fast_forward_success)

        # Fail task2.
        next_tasks = conductor.get_next_tasks()
        self.forward_task_statuses(conductor, next_tasks[0]['id'], fast_forward_failure)

        # Render workflow output and assert workflow status, error, and output.
        conductor.render_workflow_output()
        self.assertEqual(conductor.get_workflow_status(), statuses.FAILED)
        self.assertDictEqual(conductor.get_workflow_output(), {'foobar': 'foobar'})
        actual_task2_errors = [e for e in conductor.errors if e.get('task_id', None) == 'task2']
        self.assertGreater(len(actual_task2_errors), 0)

        # Request workflow rerun.
        conductor.request_workflow_rerun()

        # Assert workflow status is resuming and state is reset.
        self.assertEqual(conductor.get_workflow_status(), statuses.RESUMING)
        self.assertIsNone(conductor.get_workflow_output())
        actual_task2_errors = [e for e in conductor.errors if e.get('task_id', None) == 'task2']
        self.assertEqual(len(actual_task2_errors), 0)
        next_tasks = conductor.get_next_tasks()
        self.assertEqual(len(next_tasks), 1)
        self.assertEqual(next_tasks[0]['id'], 'task2')

        # Assert sequence of tasks is correct.
        staged_task = conductor.workflow_state.get_staged_task('task2', 0)
        self.assertTrue(staged_task['ready'])
        self.assertDictEqual(staged_task['ctxs'], {'in': [0, 1]})
        self.assertDictEqual(staged_task['prev'], {'task1__t0': 0})

        task_state = conductor.workflow_state.get_task('task2', 0)
        self.assertDictEqual(task_state['ctxs'], {'in': [0, 1]})
        self.assertDictEqual(task_state['prev'], {'task1__t0': 0})
        self.assertDictEqual(task_state['next'], {})

        task_states = [
            t for t in list(enumerate(conductor.workflow_state.sequence))
            if t[1]['id'] == 'task2' and t[1]['route'] == 0
        ]

        self.assertEqual(len(task_states), 2)
        self.assertListEqual([t[0] for t in task_states], [1, 2])

        # Fail task2 again.
        next_tasks = conductor.get_next_tasks()
        self.forward_task_statuses(conductor, next_tasks[0]['id'], fast_forward_failure)

        # Render workflow output and assert workflow status, error, and output.
        conductor.render_workflow_output()
        self.assertEqual(conductor.get_workflow_status(), statuses.FAILED)
        self.assertDictEqual(conductor.get_workflow_output(), {'foobar': 'foobar'})
        actual_task2_errors = [e for e in conductor.errors if e.get('task_id', None) == 'task2']
        self.assertGreater(len(actual_task2_errors), 0)

        # Assert there is a rerun record.
        expected_rerun_records = [[1]]
        self.assertListEqual(conductor.workflow_state.reruns, expected_rerun_records)

        # Request workflow rerun from task.
        task_rerun_req = requests.TaskRerunRequest('task2', 0)
        conductor.request_workflow_rerun(task_requests=[task_rerun_req])

        # Assert workflow status is resuming and state is reset.
        self.assertEqual(conductor.get_workflow_status(), statuses.RESUMING)
        self.assertIsNone(conductor.get_workflow_output())
        actual_task2_errors = [e for e in conductor.errors if e.get('task_id', None) == 'task2']
        self.assertEqual(len(actual_task2_errors), 0)
        next_tasks = conductor.get_next_tasks()
        self.assertEqual(len(next_tasks), 1)
        self.assertEqual(next_tasks[0]['id'], 'task2')

        # Assert sequence of tasks is correct.
        staged_task = conductor.workflow_state.get_staged_task('task2', 0)
        self.assertTrue(staged_task['ready'])
        self.assertDictEqual(staged_task['ctxs'], {'in': [0, 1]})
        self.assertDictEqual(staged_task['prev'], {'task1__t0': 0})

        task_state = conductor.workflow_state.get_task('task2', 0)
        self.assertDictEqual(task_state['ctxs'], {'in': [0, 1]})
        self.assertDictEqual(task_state['prev'], {'task1__t0': 0})
        self.assertDictEqual(task_state['next'], {})

        task_states = [
            t for t in list(enumerate(conductor.workflow_state.sequence))
            if t[1]['id'] == 'task2' and t[1]['route'] == 0
        ]

        self.assertEqual(len(task_states), 3)
        self.assertListEqual([t[0] for t in task_states], [1, 2, 3])

        # Succeed task2.
        next_tasks = conductor.get_next_tasks()
        self.forward_task_statuses(conductor, next_tasks[0]['id'], fast_forward_success)

        # Assert workflow is completed.
        conductor.render_workflow_output()
        self.assertEqual(conductor.get_workflow_status(), statuses.SUCCEEDED)
        self.assertDictEqual(conductor.get_workflow_output(), {'foobar': 'fubar'})

        # Assert there are two separate rerun records.
        expected_rerun_records = [[1], [2]]
        self.assertListEqual(conductor.workflow_state.reruns, expected_rerun_records)
    def test_rerun_from_multiple_sequential_tasks(self):
        wf_def = """
        version: 1.0

        tasks:
          task1:
            action: core.echo message="$RANDOM"
            next:
              - when: <% succeeded() %>
                publish: foobar="foobar"
                do: task2
          task2:
            action: core.noop
            next:
              - when: <% succeeded() %>
                publish: foobar="fubar"

        output:
          - foobar: <% ctx().foobar %>
        """

        fast_forward_failure = [statuses.RUNNING, statuses.FAILED]
        fast_forward_success = [statuses.RUNNING, statuses.SUCCEEDED]

        spec = native_specs.WorkflowSpec(wf_def)
        self.assertDictEqual(spec.inspect(), {})

        conductor = conducting.WorkflowConductor(spec)
        conductor.request_workflow_status(statuses.RUNNING)

        # Succeed task1.
        next_tasks = conductor.get_next_tasks()
        self.forward_task_statuses(conductor, next_tasks[0]["id"],
                                   fast_forward_success)

        # Fail task2.
        next_tasks = conductor.get_next_tasks()
        self.forward_task_statuses(conductor, next_tasks[0]["id"],
                                   fast_forward_failure)

        # Render workflow output and assert workflow status, error, and output.
        conductor.render_workflow_output()
        self.assertEqual(conductor.get_workflow_status(), statuses.FAILED)
        self.assertDictEqual(conductor.get_workflow_output(),
                             {"foobar": "foobar"})
        self.assertIn("task2", [e["task_id"] for e in conductor.errors])

        # Request workflow rerun from both the succeeded task1 and the failed task2.
        tk1_rerun_req = requests.TaskRerunRequest("task1", 0)
        tk2_rerun_req = requests.TaskRerunRequest("task2", 0)
        conductor.request_workflow_rerun(
            task_requests=[tk1_rerun_req, tk2_rerun_req])

        # Assert workflow status is resuming and state is reset. We expect only task1
        # is setup to rerun since task2 is dependent on task1.
        self.assertEqual(conductor.get_workflow_status(), statuses.RESUMING)
        self.assertIsNone(conductor.get_workflow_output())
        next_tasks = conductor.get_next_tasks()
        self.assertEqual(len(next_tasks), 1)
        self.assertEqual(next_tasks[0]["id"], "task1")

        # Assert sequence of tasks is correct.
        self.assertIsNotNone(
            conductor.workflow_state.get_staged_task("task1", 0))
        self.assertIsNone(conductor.workflow_state.get_staged_task("task2", 0))

        task_states = [
            t for t in list(enumerate(conductor.workflow_state.sequence))
            if t[1]["id"] == "task1" and t[1]["route"] == 0
        ]

        self.assertEqual(len(task_states), 2)
        self.assertListEqual([t[0] for t in task_states], [0, 2])

        # Succeed task1.
        next_tasks = conductor.get_next_tasks()
        self.assertEqual(next_tasks[0]["id"], "task1")
        self.forward_task_statuses(conductor, next_tasks[0]["id"],
                                   fast_forward_success)

        # Succeed task2.
        next_tasks = conductor.get_next_tasks()
        self.assertEqual(next_tasks[0]["id"], "task2")
        self.forward_task_statuses(conductor, next_tasks[0]["id"],
                                   fast_forward_success)

        # Assert workflow is completed.
        conductor.render_workflow_output()
        self.assertEqual(conductor.get_workflow_status(), statuses.SUCCEEDED)
        self.assertDictEqual(conductor.get_workflow_output(),
                             {"foobar": "fubar"})