Skip to content

Commit

Permalink
fix(runJob): cancel underlying job on cancel stage (#2966)
Browse files Browse the repository at this point in the history
  • Loading branch information
emjburns committed Jun 13, 2019
1 parent 50ecc92 commit 30a10c1
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,20 @@ package com.netflix.spinnaker.orca.clouddriver.pipeline.job
import com.netflix.spinnaker.orca.clouddriver.config.PreconfiguredJobStageProperties
import com.netflix.spinnaker.orca.clouddriver.exception.PreconfiguredJobNotFoundException
import com.netflix.spinnaker.orca.clouddriver.service.JobService
import com.netflix.spinnaker.orca.clouddriver.tasks.job.DestroyJobTask
import com.netflix.spinnaker.orca.pipeline.TaskNode
import com.netflix.spinnaker.orca.pipeline.model.Stage
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component

@Component
class PreconfiguredJobStage extends RunJobStage {

private JobService jobService

public PreconfiguredJobStage(Optional<JobService> optionalJobService) {
@Autowired
public PreconfiguredJobStage(DestroyJobTask destroyJobTask, Optional<JobService> optionalJobService) {
super(destroyJobTask)
this.jobService = optionalJobService.orElse(null)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,36 @@

package com.netflix.spinnaker.orca.clouddriver.pipeline.job;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.spinnaker.orca.CancellableStage;
import com.netflix.spinnaker.orca.clouddriver.tasks.artifacts.ConsumeArtifactTask;
import com.netflix.spinnaker.orca.clouddriver.tasks.job.DestroyJobTask;
import com.netflix.spinnaker.orca.clouddriver.tasks.job.MonitorJobTask;
import com.netflix.spinnaker.orca.clouddriver.tasks.job.RunJobTask;
import com.netflix.spinnaker.orca.clouddriver.tasks.job.WaitOnJobCompletion;
import com.netflix.spinnaker.orca.jackson.OrcaObjectMapper;
import com.netflix.spinnaker.orca.pipeline.StageDefinitionBuilder;
import com.netflix.spinnaker.orca.pipeline.TaskNode;
import com.netflix.spinnaker.orca.pipeline.model.Stage;
import com.netflix.spinnaker.orca.pipeline.tasks.artifacts.BindProducedArtifactsTask;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class RunJobStage implements StageDefinitionBuilder {
public class RunJobStage implements StageDefinitionBuilder, CancellableStage {

private final Logger log = LoggerFactory.getLogger(getClass());
private final DestroyJobTask destroyJobTask;
private final ObjectMapper objectMapper = OrcaObjectMapper.newInstance();

@Autowired
public RunJobStage(DestroyJobTask destroyJobTask) {
this.destroyJobTask = destroyJobTask;
}

@Override
public void taskGraph(Stage stage, TaskNode.Builder builder) {
Expand All @@ -56,6 +72,43 @@ public void taskGraph(Stage stage, TaskNode.Builder builder) {
}
}

@Override
public Result cancel(Stage stage) {
log.info(
"Canceling run job stage {} for executionId {}",
stage.getId(),
stage.getExecution().getId());
Map<String, Object> destroyContext = new HashMap<>();

try {
RunJobStageContext context =
objectMapper.convertValue(stage.getContext(), RunJobStageContext.class);
String jobId = context.getJobStatus().getId();

if (context.getCloudProvider().equalsIgnoreCase("titus")) {
destroyContext.put("jobId", jobId);
} else {
destroyContext.put("jobName", context.getJobStatus().getName());
}

destroyContext.put("cloudProvider", context.getCloudProvider());
destroyContext.put("region", context.getJobStatus().getRegion());
destroyContext.put("credentials", context.getCredentials());

stage.setContext(destroyContext);

destroyJobTask.execute(stage);
} catch (Exception e) {
log.error(
String.format(
"Failed to cancel run job (stageId: %s, executionId: %s), e: %s",
stage.getId(), stage.getExecution().getId(), e.getMessage()),
e);
}

return new Result(stage, destroyContext);
}

@Override
public void prepareStageForRestart(Stage stage) {
Map<String, Object> context = stage.getContext();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
*
* Copyright 2019 Netflix, 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.
*
*/
package com.netflix.spinnaker.orca.clouddriver.pipeline.job;

import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.netflix.spinnaker.orca.clouddriver.pipeline.job.model.JobStatus;
import java.util.HashMap;
import java.util.Map;

public class RunJobStageContext {
private JobStatus jobStatus;
private String cloudProvider;
private String cloudProviderType;
private String application;
private String credentials;

@JsonIgnore private Map<String, Object> other = new HashMap<>();

@JsonAnyGetter
public Map<String, Object> other() {
return other;
}

@JsonAnySetter
public void set(String name, Object value) {
other.put(name, value);
}

public JobStatus getJobStatus() {
return jobStatus;
}

public void setJobStatus(JobStatus jobStatus) {
this.jobStatus = jobStatus;
}

public String getCloudProvider() {
return cloudProvider;
}

public void setCloudProvider(String cloudProvider) {
this.cloudProvider = cloudProvider;
}

public String getCloudProviderType() {
return cloudProviderType;
}

public void setCloudProviderType(String cloudProviderType) {
this.cloudProviderType = cloudProviderType;
}

public String getApplication() {
return application;
}

public void setApplication(String application) {
this.application = application;
}

public String getCredentials() {
return credentials;
}

public void setCredentials(String credentials) {
this.credentials = credentials;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
*
* Copyright 2019 Netflix, 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.
*
*/
package com.netflix.spinnaker.orca.clouddriver.pipeline.job.model;

import lombok.Data;

@Data
public class CompletionDetails {
private String instanceId;
private String taskId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
*
* Copyright 2019 Netflix, 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.
*
*/
package com.netflix.spinnaker.orca.clouddriver.pipeline.job.model;

import lombok.Data;

@Data
public class JobStatus {
private String application;
private String provider;
private CompletionDetails completionDetails;
private String jobState;
private String name;
private Long createdTime;
private String id;
private String type;
private String region;
private String account;
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class DestroyJobTask extends AbstractCloudProviderAwareTask implements Task {
Map outputs = [
"notification.type" : "destroyjob",
"kato.last.task.id" : taskId,
"delete.name" : stage.context.jobName,
"delete.name" : stage.context.jobName ?: stage.context.jobId,
"delete.region" : stage.context.region,
"delete.account.name": account
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package com.netflix.spinnaker.orca.clouddriver.pipeline.job
import com.netflix.spinnaker.orca.clouddriver.config.PreconfiguredJobStageParameter
import com.netflix.spinnaker.orca.clouddriver.service.JobService
import com.netflix.spinnaker.orca.clouddriver.config.KubernetesPreconfiguredJobProperties
import com.netflix.spinnaker.orca.clouddriver.tasks.job.DestroyJobTask
import spock.lang.Specification

import static com.netflix.spinnaker.orca.test.model.ExecutionBuilder.stage
Expand All @@ -41,7 +42,7 @@ class PreconfiguredJobStageSpec extends Specification {
}

when:
PreconfiguredJobStage preconfiguredJobStage = new PreconfiguredJobStage(Optional.of(jobService))
PreconfiguredJobStage preconfiguredJobStage = new PreconfiguredJobStage(Mock(DestroyJobTask), Optional.of(jobService))
preconfiguredJobStage.buildTaskGraph(stage)

then:
Expand Down

0 comments on commit 30a10c1

Please sign in to comment.