Skip to content

Commit

Permalink
✨ : add retry in job workflow
Browse files Browse the repository at this point in the history
  • Loading branch information
cdubuisson committed Sep 2, 2019
1 parent 9a65016 commit 1b1686f
Show file tree
Hide file tree
Showing 25 changed files with 444 additions and 7 deletions.
11 changes: 11 additions & 0 deletions src/main/java/io/codeka/gaia/runner/StackRunner.java
Expand Up @@ -136,6 +136,17 @@ public void apply(JobWorkflow jobWorkflow, TerraformModule module, Stack stack)
);
}

@Async
public void retry(JobWorkflow jobWorkflow, TerraformModule module, Stack stack) {
stepRepository.deleteByJobId(jobWorkflow.getJob().getId());
treatJob(
jobWorkflow,
JobWorkflow::retry,
() -> managePlanScript(jobWorkflow.getJob().getType(), stack, module),
result -> managePlanResult(result, jobWorkflow, stack)
);
}

public Job getJob(String jobId) {
if (this.jobs.containsKey(jobId)) {
// try in memory
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/io/codeka/gaia/stacks/bo/Job.java
Expand Up @@ -47,6 +47,13 @@ public void end(JobStatus jobStatus) {
this.status = jobStatus;
}

public void reset() {
this.status = null;
this.startDateTime = null;
this.endDateTime = null;
this.steps.clear();
}

public String getId() {
return id;
}
Expand Down
Expand Up @@ -32,12 +32,12 @@ public JobRestController(JobRepository jobRepository, StackRepository stackRepos
}

@GetMapping(params = "stackId")
public List<Job> jobs(@RequestParam String stackId){
public List<Job> jobs(@RequestParam String stackId) {
return this.jobRepository.findAllByStackId(stackId);
}

@GetMapping("/{id}")
public Job job(@PathVariable String id){
public Job job(@PathVariable String id) {
return this.jobRepository.findById(id).orElseThrow(JobNotFoundException::new);
}

Expand All @@ -54,8 +54,17 @@ public void planOrApplyJob(@PathVariable String id, @PathVariable StepType stepT
}
}

@PostMapping("/{id}/retry")
public void retryJob(@PathVariable String id) {
var job = this.jobRepository.findById(id).orElseThrow(JobNotFoundException::new);
var stack = this.stackRepository.findById(job.getStackId()).orElseThrow();
var module = this.moduleRepository.findById(stack.getModuleId()).orElseThrow();

this.stackRunner.retry(new JobWorkflow(job), module, stack);
}

}

@ResponseStatus(HttpStatus.NOT_FOUND)
class JobNotFoundException extends RuntimeException{
class JobNotFoundException extends RuntimeException {
}
Expand Up @@ -9,4 +9,7 @@
*/
@Repository
public interface StepRepository extends MongoRepository<Step, String> {

void deleteByJobId(String jobId);

}
4 changes: 4 additions & 0 deletions src/main/java/io/codeka/gaia/stacks/workflow/JobWorkflow.java
Expand Up @@ -37,6 +37,10 @@ public void fail() {
this.state.fail(this);
}

public void retry() {
this.state.retry(this);
}

public Job getJob() {
return job;
}
Expand Down
Expand Up @@ -5,7 +5,7 @@
/**
* Describes a job which apply has been failed
*/
public class ApplyFailedState implements JobState {
public class ApplyFailedState extends RetryableState implements JobState {
@Override
public void plan(JobWorkflow jobWorkflow) {
throw new UnsupportedOperationException("Unable to start a plan after an apply failed");
Expand Down
Expand Up @@ -25,4 +25,9 @@ public void end(JobWorkflow jobWorkflow) {
public void fail(JobWorkflow jobWorkflow) {
throw new UnsupportedOperationException("Unable to fail an apply finished");
}

@Override
public void retry(JobWorkflow jobWorkflow) {
throw new UnsupportedOperationException("Unable to retry a job after an apply finished");
}
}
Expand Up @@ -30,4 +30,9 @@ public void fail(JobWorkflow jobWorkflow) {
jobWorkflow.getJob().end(JobStatus.APPLY_FAILED);
jobWorkflow.setState(new ApplyFailedState());
}

@Override
public void retry(JobWorkflow jobWorkflow) {
throw new UnsupportedOperationException("Unable to retry a job after an apply started");
}
}
Expand Up @@ -15,4 +15,5 @@ public interface JobState {

void fail(JobWorkflow jobWorkflow);

void retry(JobWorkflow jobWorkflow);
}
Expand Up @@ -35,4 +35,9 @@ public void end(JobWorkflow jobWorkflow) {
public void fail(JobWorkflow jobWorkflow) {
throw new UnsupportedOperationException("Unable to fail the step of a job not even started");
}

@Override
public void retry(JobWorkflow jobWorkflow) {
throw new UnsupportedOperationException("Unable to retry a job not even started");
}
}
Expand Up @@ -5,7 +5,7 @@
/**
* Describes a job which plan has been failed
*/
public class PlanFailedState implements JobState {
public class PlanFailedState extends RetryableState implements JobState {
@Override
public void plan(JobWorkflow jobWorkflow) {
throw new UnsupportedOperationException("Unable to start a plan after a plan failed");
Expand Down
Expand Up @@ -29,11 +29,16 @@ public void apply(JobWorkflow jobWorkflow) {

@Override
public void end(JobWorkflow jobWorkflow) {
throw new UnsupportedOperationException("Unable to end an plan finished");
throw new UnsupportedOperationException("Unable to end a plan finished");
}

@Override
public void fail(JobWorkflow jobWorkflow) {
throw new UnsupportedOperationException("Unable to fail an plan finished");
throw new UnsupportedOperationException("Unable to fail a plan finished");
}

@Override
public void retry(JobWorkflow jobWorkflow) {
throw new UnsupportedOperationException("Unable to retry a job after a plan finished");
}
}
Expand Up @@ -30,4 +30,9 @@ public void fail(JobWorkflow jobWorkflow) {
jobWorkflow.getJob().end(JobStatus.PLAN_FAILED);
jobWorkflow.setState(new PlanFailedState());
}

@Override
public void retry(JobWorkflow jobWorkflow) {
throw new UnsupportedOperationException("Unable to retry a job after a plan started");
}
}
@@ -0,0 +1,11 @@
package io.codeka.gaia.stacks.workflow.state;

import io.codeka.gaia.stacks.workflow.JobWorkflow;

abstract class RetryableState {
public final void retry(JobWorkflow jobWorkflow) {
jobWorkflow.getJob().reset();
jobWorkflow.setState(new NotStartedState());
jobWorkflow.plan();
}
}
126 changes: 126 additions & 0 deletions src/test/java/io/codeka/gaia/runner/StackRunnerTest.java
Expand Up @@ -267,6 +267,132 @@ void apply_shouldSaveJobAndSteps() {
verify(stepRepository, times(2)).saveAll(job.getSteps());
}

@Test
void retry_shouldDeletePreviousSteps() {
// when
when(dockerRunner.runContainerForJob(any(), any())).thenReturn(0);
stackRunner.retry(jobWorkflow, module, stack);

// then
verify(stepRepository).deleteByJobId(jobWorkflow.getJob().getId());
}

@Test
void retry_shouldExecuteRetryWorkflow() {
// when
when(dockerRunner.runContainerForJob(any(), any())).thenReturn(0);
stackRunner.retry(jobWorkflow, module, stack);

// then
verify(jobWorkflow).retry();
}

@Test
void retry_shouldUsePlanScript_WhenJobIsRun() {
// when
when(dockerRunner.runContainerForJob(any(), any())).thenReturn(0);
stackRunner.retry(jobWorkflow, module, stack);

// then
verify(stackCommandBuilder).buildPlanScript(stack, module);
}

@Test
void retry_shouldUsePlanDestroyScript_WhenJobIsStop() {
// given
job.setType(JobType.DESTROY);

// when
when(dockerRunner.runContainerForJob(any(), any())).thenReturn(0);
stackRunner.retry(jobWorkflow, module, stack);

// then
verify(stackCommandBuilder).buildPlanDestroyScript(stack, module);
}

@Test
void retry_shouldEndJob_WhenSuccessful() {
// when
when(dockerRunner.runContainerForJob(any(), any())).thenReturn(0);
stackRunner.retry(jobWorkflow, module, stack);

// then
verify(jobWorkflow).end();
}

@Test
void retry_shouldEndJob_WhenThereIsADiff() {
// when
when(dockerRunner.runContainerForJob(any(), any())).thenReturn(2);
stackRunner.retry(jobWorkflow, module, stack);

// then
verify(jobWorkflow).end();
}

@Test
void retry_shouldFailJob_WhenThereIsAError() {
// when
when(dockerRunner.runContainerForJob(any(), any())).thenReturn(99);
stackRunner.retry(jobWorkflow, module, stack);

// then
verify(jobWorkflow).fail();
}

@Test
void retry_shouldUpdateStack_WhenThereIsADiff() {
// given
stack.setState(StackState.RUNNING);

// when
when(dockerRunner.runContainerForJob(any(), any())).thenReturn(2);
stackRunner.retry(jobWorkflow, module, stack);

// then
assertEquals(StackState.TO_UPDATE, stack.getState());
verify(stackRepository).save(stack);
}

@Test
void retry_shouldNotUpdateStack_WhenThereIsADiffForNewStacks() {
// given
stack.setState(StackState.NEW);

// when
when(dockerRunner.runContainerForJob(any(), any())).thenReturn(2);
stackRunner.retry(jobWorkflow, module, stack);

// then
assertEquals(StackState.NEW, stack.getState());
verifyZeroInteractions(stackRepository);
}

@Test
void retry_shouldNotUpdateStack_WhenThereIsADiffAndJobIsStop() {
// given
stack.setState(StackState.RUNNING);
job.setType(JobType.DESTROY);

// when
when(dockerRunner.runContainerForJob(any(), any())).thenReturn(2);
stackRunner.retry(jobWorkflow, module, stack);

// then
assertEquals(StackState.RUNNING, stack.getState());
verifyZeroInteractions(stackRepository);
}

@Test
void retry_shouldSaveJobAndSteps() {
// when
stackRunner.retry(jobWorkflow, module, stack);

// then
verify(jobRepository, times(2)).save(job);
verify(stepRepository, times(2)).saveAll(job.getSteps());
}

@Test
void getJob_shouldReturnJob_whenNotInMemory() {
// when
Expand Down

0 comments on commit 1b1686f

Please sign in to comment.