Skip to content

Commit

Permalink
feat(rollback): Support rolling back to a server group that no longer…
Browse files Browse the repository at this point in the history
… exists

This PR provides a rollback strategy that will clone forward with the
image details that were tagged as part of spinnaker#1705.

It also supports the `imageName` being explicitly provided, but that's
an exceptional case that would not be supported in the UI.
  • Loading branch information
ajordens committed Oct 19, 2017
1 parent 03c5dba commit 6c147c6
Show file tree
Hide file tree
Showing 8 changed files with 536 additions and 153 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@
package com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup

import com.fasterxml.jackson.annotation.JsonIgnore
import com.netflix.spinnaker.orca.clouddriver.pipeline.providers.aws.ApplySourceServerGroupCapacityStage
import com.netflix.spinnaker.orca.clouddriver.pipeline.providers.aws.CaptureSourceServerGroupCapacityStage
import com.netflix.spinnaker.orca.kato.pipeline.support.ResizeStrategy
import com.netflix.frigga.Names
import com.netflix.spinnaker.orca.RetrySupport
import com.netflix.spinnaker.orca.clouddriver.OortService
import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.rollback.ExplicitRollback
import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.rollback.PreviousImageRollback
import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.rollback.Rollback
import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.rollback.TestRollback
import com.netflix.spinnaker.orca.pipeline.StageDefinitionBuilder
import com.netflix.spinnaker.orca.pipeline.model.Execution
import com.netflix.spinnaker.orca.pipeline.model.Stage
Expand All @@ -37,7 +41,7 @@ class RollbackServerGroupStage implements StageDefinitionBuilder {
AutowireCapableBeanFactory autowireCapableBeanFactory

@Override
def <T extends Execution<T>> List<Stage<T>> aroundStages(Stage<T> stage) {
<T extends Execution<T>> List<Stage<T>> aroundStages(Stage<T> stage) {
def stageData = stage.mapTo(StageData)

if (!stageData.rollbackType) {
Expand All @@ -50,7 +54,9 @@ class RollbackServerGroupStage implements StageDefinitionBuilder {
}

static enum RollbackType {
EXPLICIT(ExplicitRollback)
EXPLICIT(ExplicitRollback),
PREVIOUS_IMAGE(PreviousImageRollback),
TEST(TestRollback)

final Class implementationClass

Expand All @@ -62,117 +68,4 @@ class RollbackServerGroupStage implements StageDefinitionBuilder {
static class StageData {
RollbackType rollbackType
}

static interface Rollback {
def <T extends Execution<T>> List<Stage<T>> buildStages(Stage<T> parentStage)
}

static class ExplicitRollback implements Rollback {
String rollbackServerGroupName
String restoreServerGroupName
Integer targetHealthyRollbackPercentage

@Autowired
@JsonIgnore
EnableServerGroupStage enableServerGroupStage

@Autowired
@JsonIgnore
DisableServerGroupStage disableServerGroupStage

@Autowired
@JsonIgnore
ResizeServerGroupStage resizeServerGroupStage

@Autowired
@JsonIgnore
CaptureSourceServerGroupCapacityStage captureSourceServerGroupCapacityStage

@Autowired
@JsonIgnore
ApplySourceServerGroupCapacityStage applySourceServerGroupCapacityStage

@JsonIgnore
def <T extends Execution<T>> List<Stage<T>> buildStages(Stage<T> parentStage) {
def stages = []

Map enableServerGroupContext = new HashMap(parentStage.context)
enableServerGroupContext.targetHealthyDeployPercentage = targetHealthyRollbackPercentage
enableServerGroupContext.serverGroupName = restoreServerGroupName
stages << newStage(
parentStage.execution, enableServerGroupStage.type, "enable", enableServerGroupContext, parentStage, SyntheticStageOwner.STAGE_AFTER
)

stages << buildCaptureSourceServerGroupCapacityStage(parentStage, parentStage.mapTo(ResizeStrategy.Source))

Map resizeServerGroupContext = new HashMap(parentStage.context) + [
action : ResizeStrategy.ResizeAction.scale_to_server_group.toString(),
source : {
def source = parentStage.mapTo(ResizeStrategy.Source)
source.serverGroupName = rollbackServerGroupName
return source
}.call(),
asgName : restoreServerGroupName,
pinMinimumCapacity: true,
targetHealthyDeployPercentage: targetHealthyRollbackPercentage
]
stages << newStage(
parentStage.execution, resizeServerGroupStage.type, "resize", resizeServerGroupContext, parentStage, SyntheticStageOwner.STAGE_AFTER
)

Map disableServerGroupContext = new HashMap(parentStage.context)
disableServerGroupContext.serverGroupName = rollbackServerGroupName
stages << newStage(
parentStage.execution, disableServerGroupStage.type, "disable", disableServerGroupContext, parentStage, SyntheticStageOwner.STAGE_AFTER
)

stages << buildApplySourceServerGroupCapacityStage(parentStage, parentStage.mapTo(ResizeStrategy.Source))
return stages
}

Stage buildCaptureSourceServerGroupCapacityStage(Stage parentStage,
ResizeStrategy.Source source) {
Map captureSourceServerGroupCapacityContext = [
useSourceCapacity: true,
source : [
asgName : rollbackServerGroupName,
serverGroupName: rollbackServerGroupName,
region : source.region,
account : source.credentials,
cloudProvider : source.cloudProvider
]
]
return newStage(
parentStage.execution,
captureSourceServerGroupCapacityStage.type,
"snapshot",
captureSourceServerGroupCapacityContext,
parentStage,
SyntheticStageOwner.STAGE_AFTER
)
}

Stage buildApplySourceServerGroupCapacityStage(Stage parentStage,
ResizeStrategy.Source source) {
Map applySourceServerGroupCapacityContext = [
credentials: source.credentials,
target : [
asgName : restoreServerGroupName,
serverGroupName: restoreServerGroupName,
region : source.region,
account : source.credentials,
cloudProvider : source.cloudProvider
]
]
return newStage(
parentStage.execution,
applySourceServerGroupCapacityStage.type,
"apply",
applySourceServerGroupCapacityContext,
parentStage,
SyntheticStageOwner.STAGE_AFTER
)
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* Copyright 2017 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.servergroup.rollback

import com.fasterxml.jackson.annotation.JsonIgnore
import com.netflix.spinnaker.orca.clouddriver.pipeline.providers.aws.ApplySourceServerGroupCapacityStage
import com.netflix.spinnaker.orca.clouddriver.pipeline.providers.aws.CaptureSourceServerGroupCapacityStage
import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.DisableServerGroupStage
import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.EnableServerGroupStage
import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.ResizeServerGroupStage
import com.netflix.spinnaker.orca.kato.pipeline.support.ResizeStrategy
import com.netflix.spinnaker.orca.pipeline.model.Execution
import com.netflix.spinnaker.orca.pipeline.model.Stage
import com.netflix.spinnaker.orca.pipeline.model.SyntheticStageOwner
import org.springframework.beans.factory.annotation.Autowired;

import static com.netflix.spinnaker.orca.pipeline.StageDefinitionBuilder.newStage

class ExplicitRollback implements Rollback {
String rollbackServerGroupName
String restoreServerGroupName
Integer targetHealthyRollbackPercentage

@Autowired
@JsonIgnore
EnableServerGroupStage enableServerGroupStage

@Autowired
@JsonIgnore
DisableServerGroupStage disableServerGroupStage

@Autowired
@JsonIgnore
ResizeServerGroupStage resizeServerGroupStage

@Autowired
@JsonIgnore
CaptureSourceServerGroupCapacityStage captureSourceServerGroupCapacityStage

@Autowired
@JsonIgnore
ApplySourceServerGroupCapacityStage applySourceServerGroupCapacityStage

@JsonIgnore
def <T extends Execution<T>> List<Stage<T>> buildStages(Stage<T> parentStage) {
def stages = []

Map enableServerGroupContext = new HashMap(parentStage.context)
enableServerGroupContext.targetHealthyDeployPercentage = targetHealthyRollbackPercentage
enableServerGroupContext.serverGroupName = restoreServerGroupName
stages << newStage(
parentStage.execution, enableServerGroupStage.type, "enable", enableServerGroupContext, parentStage, SyntheticStageOwner.STAGE_AFTER
)

stages << buildCaptureSourceServerGroupCapacityStage(parentStage, parentStage.mapTo(ResizeStrategy.Source))

Map resizeServerGroupContext = new HashMap(parentStage.context) + [
action : ResizeStrategy.ResizeAction.scale_to_server_group.toString(),
source : {
def source = parentStage.mapTo(ResizeStrategy.Source)
source.serverGroupName = rollbackServerGroupName
return source
}.call(),
asgName : restoreServerGroupName,
pinMinimumCapacity : true,
targetHealthyDeployPercentage: targetHealthyRollbackPercentage
]
stages << newStage(
parentStage.execution, resizeServerGroupStage.type, "resize", resizeServerGroupContext, parentStage, SyntheticStageOwner.STAGE_AFTER
)

Map disableServerGroupContext = new HashMap(parentStage.context)
disableServerGroupContext.serverGroupName = rollbackServerGroupName
stages << newStage(
parentStage.execution, disableServerGroupStage.type, "disable", disableServerGroupContext, parentStage, SyntheticStageOwner.STAGE_AFTER
)

stages << buildApplySourceServerGroupCapacityStage(parentStage, parentStage.mapTo(ResizeStrategy.Source))
return stages
}

Stage buildCaptureSourceServerGroupCapacityStage(Stage parentStage,
ResizeStrategy.Source source) {
Map captureSourceServerGroupCapacityContext = [
useSourceCapacity: true,
source : [
asgName : rollbackServerGroupName,
serverGroupName: rollbackServerGroupName,
region : source.region,
account : source.credentials,
cloudProvider : source.cloudProvider
]
]
return newStage(
parentStage.execution,
captureSourceServerGroupCapacityStage.type,
"snapshot",
captureSourceServerGroupCapacityContext,
parentStage,
SyntheticStageOwner.STAGE_AFTER
)
}

Stage buildApplySourceServerGroupCapacityStage(Stage parentStage,
ResizeStrategy.Source source) {
Map applySourceServerGroupCapacityContext = [
credentials: source.credentials,
target : [
asgName : restoreServerGroupName,
serverGroupName: restoreServerGroupName,
region : source.region,
account : source.credentials,
cloudProvider : source.cloudProvider
]
]
return newStage(
parentStage.execution,
applySourceServerGroupCapacityStage.type,
"apply",
applySourceServerGroupCapacityContext,
parentStage,
SyntheticStageOwner.STAGE_AFTER
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright 2017 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.servergroup.rollback

import com.fasterxml.jackson.annotation.JsonIgnore
import com.netflix.frigga.Names
import com.netflix.spinnaker.orca.RetrySupport
import com.netflix.spinnaker.orca.clouddriver.OortService
import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.CloneServerGroupStage
import com.netflix.spinnaker.orca.pipeline.model.Execution
import com.netflix.spinnaker.orca.pipeline.model.Stage
import com.netflix.spinnaker.orca.pipeline.model.SyntheticStageOwner
import org.springframework.beans.factory.annotation.Autowired;

import static com.netflix.spinnaker.orca.pipeline.StageDefinitionBuilder.newStage

class PreviousImageRollback implements Rollback {
String rollbackServerGroupName
String imageName
Integer targetHealthyRollbackPercentage

@Autowired
@JsonIgnore
CloneServerGroupStage cloneServerGroupStage

@Autowired
@JsonIgnore
OortService oortService

@Autowired
@JsonIgnore
RetrySupport retrySupport

@Override
<T extends Execution<T>> List<Stage<T>> buildStages(Stage<T> parentStage) {
def stages = []

def parentStageContext = parentStage.context

if (!imageName) {
def entityTags = retrySupport.retry({
oortService.getEntityTags(
parentStageContext.cloudProvider as String,
"serverGroup",
rollbackServerGroupName,
parentStageContext.credentials as String,
parentStageContext.region as String
)
}, 15, 2000, false)

if (entityTags) {
List<Map> tags = entityTags[0].tags
imageName = tags.find { it.name == "spinnaker:metadata" }?.value?.previousServerGroup?.imageName
}
}

if (!imageName) {
throw new IllegalStateException("Unable to determine rollback image (serverGroupName: ${rollbackServerGroupName})")
}

def names = Names.parseName(rollbackServerGroupName)

Map cloneServerGroupContext = [
targetHealthyDeployPercentage: targetHealthyRollbackPercentage,
amiName : imageName,
strategy : "redblack",
application : parentStageContext.moniker?.app ?: names.app,
stack : parentStageContext.moniker?.stack ?: names.stack,
detail : parentStageContext.moniker?.detail ?: names.detail,
region : parentStageContext.region,
credentials : parentStageContext.credentials,
cloudProvider : parentStageContext.cloudProvider,
source : [
asgName : rollbackServerGroupName,
serverGroupName: rollbackServerGroupName,
account : parentStageContext.credentials,
region : parentStageContext.region,
cloudProvider : parentStageContext.cloudProvider
]
]
stages << newStage(
parentStage.execution, cloneServerGroupStage.type, "clone", cloneServerGroupContext, parentStage, SyntheticStageOwner.STAGE_AFTER
)

return stages
}
}
Loading

0 comments on commit 6c147c6

Please sign in to comment.