@@ -274,6 +274,97 @@ def test_retries_exhausted(self):
274274 actual_task_sequence = [item ['id' ] for item in conductor .workflow_state .sequence ]
275275 self .assertListEqual (expected_task_sequence , actual_task_sequence )
276276
277+ def test_retries_exhausted_and_task_remediated (self ):
278+ wf_def = """
279+ version: 1.0
280+
281+ tasks:
282+ task1:
283+ action: core.echo message="$RANDOM"
284+ retry:
285+ count: 3
286+ next:
287+ - when: <% succeeded() %>
288+ do: task2
289+ - when: <% failed() %>
290+ do: task3
291+ task2:
292+ action: core.noop
293+ task3:
294+ action: core.echo message="BOOM!"
295+ next:
296+ - do: fail
297+ """
298+
299+ expected_tk1_action_spec = {'action' : 'core.echo' , 'input' : {'message' : '$RANDOM' }}
300+ expected_tk3_action_spec = {'action' : 'core.echo' , 'input' : {'message' : 'BOOM!' }}
301+
302+ spec = native_specs .WorkflowSpec (wf_def )
303+ self .assertDictEqual (spec .inspect (), {})
304+
305+ conductor = conducting .WorkflowConductor (spec )
306+ conductor .request_workflow_status (statuses .RUNNING )
307+
308+ # Failed execution for task1.
309+ next_tasks = conductor .get_next_tasks ()
310+ self .assertEqual (len (next_tasks ), 1 )
311+ self .assertEqual (next_tasks [0 ]['id' ], 'task1' )
312+ self .assertDictEqual (next_tasks [0 ]['actions' ][0 ], expected_tk1_action_spec )
313+ self .forward_task_statuses (conductor , 'task1' , [statuses .RUNNING , statuses .FAILED ])
314+
315+ # Failed retry #1 for task1.
316+ tk1_state = conductor .get_task_state_entry ('task1' , 0 )
317+ self .assertEqual (tk1_state ['status' ], statuses .RETRYING )
318+ self .assertEqual (tk1_state ['retry' ]['count' ], 3 )
319+ self .assertEqual (tk1_state ['retry' ]['tally' ], 1 )
320+ next_tasks = conductor .get_next_tasks ()
321+ self .assertEqual (len (next_tasks ), 1 )
322+ self .assertEqual (next_tasks [0 ]['id' ], 'task1' )
323+ self .assertDictEqual (next_tasks [0 ]['actions' ][0 ], expected_tk1_action_spec )
324+ self .forward_task_statuses (conductor , 'task1' , [statuses .RUNNING , statuses .FAILED ])
325+
326+ # Failed retry #2 for task1.
327+ tk1_state = conductor .get_task_state_entry ('task1' , 0 )
328+ self .assertEqual (tk1_state ['status' ], statuses .RETRYING )
329+ self .assertEqual (tk1_state ['retry' ]['tally' ], 2 )
330+ next_tasks = conductor .get_next_tasks ()
331+ self .assertEqual (len (next_tasks ), 1 )
332+ self .assertEqual (next_tasks [0 ]['id' ], 'task1' )
333+ self .assertDictEqual (next_tasks [0 ]['actions' ][0 ], expected_tk1_action_spec )
334+ self .forward_task_statuses (conductor , 'task1' , [statuses .RUNNING , statuses .FAILED ])
335+
336+ # Failed retry #3 for task1.
337+ tk1_state = conductor .get_task_state_entry ('task1' , 0 )
338+ self .assertEqual (tk1_state ['status' ], statuses .RETRYING )
339+ self .assertEqual (tk1_state ['retry' ]['tally' ], 3 )
340+ next_tasks = conductor .get_next_tasks ()
341+ self .assertEqual (len (next_tasks ), 1 )
342+ self .assertEqual (next_tasks [0 ]['id' ], 'task1' )
343+ self .assertDictEqual (next_tasks [0 ]['actions' ][0 ], expected_tk1_action_spec )
344+ self .forward_task_statuses (conductor , 'task1' , [statuses .RUNNING , statuses .FAILED ])
345+
346+ # Assert task1 failed and the workflow execution progresses to task3.
347+ tk1_state = conductor .get_task_state_entry ('task1' , 0 )
348+ self .assertEqual (tk1_state ['status' ], statuses .FAILED )
349+ self .assertEqual (tk1_state ['retry' ]['tally' ], 3 )
350+ next_tasks = conductor .get_next_tasks ()
351+ self .assertEqual (len (next_tasks ), 1 )
352+ self .assertEqual (next_tasks [0 ]['id' ], 'task3' )
353+ self .assertDictEqual (next_tasks [0 ]['actions' ][0 ], expected_tk3_action_spec )
354+
355+ # Successful execution for task3.
356+ self .forward_task_statuses (conductor , 'task3' , [statuses .RUNNING , statuses .SUCCEEDED ])
357+ tk3_state = conductor .get_task_state_entry ('task3' , 0 )
358+ self .assertEqual (tk3_state ['status' ], statuses .SUCCEEDED )
359+
360+ # Assert workflow failed (manual under task3).
361+ self .assertEqual (conductor .get_workflow_status (), statuses .FAILED )
362+
363+ # Assert there is only a single task1 and a single task3 in the task sequences.
364+ expected_task_sequence = ['task1' , 'task3' , 'fail' ]
365+ actual_task_sequence = [item ['id' ] for item in conductor .workflow_state .sequence ]
366+ self .assertListEqual (expected_task_sequence , actual_task_sequence )
367+
277368 def test_retry_delay_with_task_delay_defined (self ):
278369 wf_def = """
279370 version: 1.0
0 commit comments