-
Notifications
You must be signed in to change notification settings - Fork 810
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(clouddriver): Add generic cloud operation stage (#3749)
This is the first draft of a generic cloud operation stage. The goal of this stage, and the surrounding work, is to migrate all cloud provider logic from orca into Clouddriver. The "vision" of this would be that to add a cloud provider, or new operations for a cloud provider, would no longer require modification to Orca; only requiring changes to Clouddriver. There's still a long way to go on this, but it works enough as-is that I feel comfortable making a PR.
- Loading branch information
1 parent
4bc6045
commit 4f61fae
Showing
9 changed files
with
482 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
59 changes: 59 additions & 0 deletions
59
...driver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/model/OperationContext.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/* | ||
* Copyright 2020 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.model; | ||
|
||
import com.fasterxml.jackson.annotation.JsonAnyGetter; | ||
import com.fasterxml.jackson.annotation.JsonAnySetter; | ||
import com.netflix.spinnaker.kork.annotations.NonnullByDefault; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import javax.annotation.Nullable; | ||
import lombok.Data; | ||
|
||
/** A step towards a more standard cloud operation context object. */ | ||
@Data | ||
@NonnullByDefault | ||
public class OperationContext { | ||
|
||
private final Map<String, Object> backing = new HashMap<>(); | ||
|
||
/** The name AtomicOperation type. */ | ||
private String operationType; | ||
|
||
/** The cloud provider name. */ | ||
private String cloudProvider; | ||
|
||
/** The credentials name (clouddriver account name). */ | ||
private String credentials; | ||
|
||
/** | ||
* The operation lifecycle. | ||
* | ||
* <p>This is an internal-only field and should not be set by users. If this property is set by an | ||
* end-user, it will be silently scrubbed. | ||
*/ | ||
@Nullable private OperationLifecycle operationLifecycle; | ||
|
||
@JsonAnyGetter | ||
public Map<String, Object> getBacking() { | ||
return backing; | ||
} | ||
|
||
@JsonAnySetter | ||
public void setBackingValue(String key, @Nullable Object value) { | ||
backing.put(key, value); | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
...iver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/model/OperationLifecycle.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/* | ||
* Copyright 2020 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.model; | ||
|
||
/** Clouddriver operation lifecycle hook points. */ | ||
public enum OperationLifecycle { | ||
BEFORE, | ||
AFTER, | ||
FAILURE | ||
} |
24 changes: 24 additions & 0 deletions
24
...r/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/model/SubmitOperationResult.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/* | ||
* Copyright 2020 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.model; | ||
|
||
import lombok.Data; | ||
|
||
@Data | ||
public class SubmitOperationResult { | ||
private String id; | ||
private int status; | ||
} |
159 changes: 159 additions & 0 deletions
159
.../src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/CloudOperationStage.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
/* | ||
* Copyright 2020 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; | ||
|
||
import static java.lang.String.format; | ||
|
||
import com.netflix.spinnaker.orca.api.pipeline.graph.StageDefinitionBuilder; | ||
import com.netflix.spinnaker.orca.api.pipeline.graph.StageGraphBuilder; | ||
import com.netflix.spinnaker.orca.api.pipeline.graph.TaskNode; | ||
import com.netflix.spinnaker.orca.api.pipeline.models.StageExecution; | ||
import com.netflix.spinnaker.orca.clouddriver.model.OperationLifecycle; | ||
import com.netflix.spinnaker.orca.clouddriver.tasks.MonitorCloudOperationTask; | ||
import com.netflix.spinnaker.orca.clouddriver.tasks.SubmitCloudOperationTask; | ||
import com.netflix.spinnaker.orca.kato.pipeline.Nameable; | ||
import java.util.*; | ||
import javax.annotation.Nonnull; | ||
import lombok.Value; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.util.StringUtils; | ||
|
||
@Component | ||
public class CloudOperationStage implements StageDefinitionBuilder, Nameable { | ||
|
||
private static final String LIFECYCLE_KEY = "operationLifecycle"; | ||
|
||
protected TaskGraphConfigurer configureTaskGraph(@Nonnull StageExecution stage) { | ||
return new TaskGraphConfigurer(); | ||
} | ||
|
||
@Override | ||
public final void taskGraph(@Nonnull StageExecution stage, @Nonnull TaskNode.Builder builder) { | ||
if (isTopLevel(stage)) { | ||
// The lifecycle key is internal-only; it cannot be set by end-users. | ||
stage.getContext().remove(LIFECYCLE_KEY); | ||
} | ||
|
||
if (stage.getContext().containsKey(LIFECYCLE_KEY)) { | ||
String lifecycle = | ||
StringUtils.capitalize(stage.getContext().get(LIFECYCLE_KEY).toString().toLowerCase()); | ||
builder | ||
.withTask(format("submit%sOperation", lifecycle), SubmitCloudOperationTask.class) | ||
.withTask(format("monitor%sOperation", lifecycle), MonitorCloudOperationTask.class); | ||
} else { | ||
TaskGraphConfigurer configurer = configureTaskGraph(stage); | ||
|
||
configurer.beforeTasks.forEach(builder::withTask); | ||
|
||
builder | ||
.withTask(configurer.submitOperationTaskName, SubmitCloudOperationTask.class) | ||
.withTask(configurer.monitorOperationTaskName, MonitorCloudOperationTask.class); | ||
|
||
configurer.afterTasks.forEach(builder::withTask); | ||
} | ||
} | ||
|
||
@Override | ||
public void beforeStages(@Nonnull StageExecution parent, @Nonnull StageGraphBuilder graph) { | ||
if (!isTopLevel(parent)) { | ||
return; | ||
} | ||
|
||
graph.append( | ||
it -> { | ||
Map<String, Object> context = new HashMap<>(parent.getContext()); | ||
context.put(LIFECYCLE_KEY, OperationLifecycle.BEFORE); | ||
|
||
it.setType(getType()); | ||
it.setName(format("Before %s", getName())); | ||
it.setContext(context); | ||
}); | ||
} | ||
|
||
@Override | ||
public void afterStages(@Nonnull StageExecution parent, @Nonnull StageGraphBuilder graph) { | ||
if (!isTopLevel(parent)) { | ||
return; | ||
} | ||
|
||
graph.append( | ||
it -> { | ||
Map<String, Object> context = new HashMap<>(parent.getContext()); | ||
context.put(LIFECYCLE_KEY, OperationLifecycle.AFTER); | ||
|
||
it.setType(getType()); | ||
it.setName(format("After %s", getName())); | ||
it.setContext(context); | ||
}); | ||
} | ||
|
||
@Override | ||
public void onFailureStages(@Nonnull StageExecution stage, @Nonnull StageGraphBuilder graph) { | ||
if (!isTopLevel(stage)) { | ||
return; | ||
} | ||
|
||
graph.append( | ||
it -> { | ||
Map<String, Object> context = new HashMap<>(stage.getContext()); | ||
context.put(LIFECYCLE_KEY, OperationLifecycle.FAILURE); | ||
|
||
it.setType(getType()); | ||
it.setName(format("Cleanup %s", getName())); | ||
it.setContext(context); | ||
}); | ||
} | ||
|
||
@Override | ||
public String getName() { | ||
return "cloudOperation"; | ||
} | ||
|
||
private static boolean isTopLevel(StageExecution stageExecution) { | ||
return stageExecution.getParentStageId() == null; | ||
} | ||
|
||
/** Value object for configuring the Task graph of the stage. */ | ||
@Value | ||
public static class TaskGraphConfigurer { | ||
|
||
/** | ||
* Tasks that should be added to the {@link TaskNode.Builder} before the submit and monitor | ||
* tasks. | ||
*/ | ||
List<TaskNode.TaskDefinition> beforeTasks; | ||
|
||
/** | ||
* Tasks that shouild be added to the {@link TaskNode.Builder} after the submit and monitor | ||
* tasks. | ||
*/ | ||
List<TaskNode.TaskDefinition> afterTasks; | ||
|
||
/** The name of the submitOperation task name. */ | ||
String submitOperationTaskName; | ||
|
||
/** The name of the monitorOperation task name. */ | ||
String monitorOperationTaskName; | ||
|
||
/** Defaults. */ | ||
public TaskGraphConfigurer() { | ||
beforeTasks = Collections.emptyList(); | ||
afterTasks = Collections.emptyList(); | ||
submitOperationTaskName = "submitOperation"; | ||
monitorOperationTaskName = "monitorOperation"; | ||
} | ||
} | ||
} |
44 changes: 44 additions & 0 deletions
44
...c/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/MonitorCloudOperationTask.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
/* | ||
* Copyright 2020 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.tasks; | ||
|
||
import com.netflix.spinnaker.orca.api.pipeline.RetryableTask; | ||
import com.netflix.spinnaker.orca.api.pipeline.TaskResult; | ||
import com.netflix.spinnaker.orca.api.pipeline.models.StageExecution; | ||
import java.time.Duration; | ||
import javax.annotation.Nonnull; | ||
import org.springframework.stereotype.Component; | ||
|
||
@Component | ||
public class MonitorCloudOperationTask implements RetryableTask { | ||
|
||
@Nonnull | ||
@Override | ||
public TaskResult execute(@Nonnull StageExecution stage) { | ||
// TODO(rz): This would basically be the same as MonitorKatoTask. | ||
return TaskResult.SUCCEEDED; | ||
} | ||
|
||
@Override | ||
public long getBackoffPeriod() { | ||
return Duration.ofSeconds(5).toMillis(); | ||
} | ||
|
||
@Override | ||
public long getTimeout() { | ||
return Duration.ofHours(1).toMillis(); | ||
} | ||
} |
Oops, something went wrong.