Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(canary): target canary cleanup server groups by create date #1612

Merged
merged 2 commits into from
Sep 19, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@

package com.netflix.spinnaker.orca.mine.pipeline

import com.netflix.spinnaker.orca.CancellableStage.Result
import com.netflix.spinnaker.orca.clouddriver.tasks.servergroup.DestroyServerGroupTask
import com.netflix.spinnaker.orca.clouddriver.utils.OortHelper

import java.util.concurrent.TimeUnit
import com.netflix.frigga.autoscaling.AutoScalingGroupNameBuilder
import com.netflix.spinnaker.orca.CancellableStage
import com.netflix.spinnaker.orca.clouddriver.tasks.cluster.ShrinkClusterTask
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 @@ -35,7 +38,8 @@ class CanaryStage implements StageDefinitionBuilder, CancellableStage {

@Autowired DeployCanaryStage deployCanaryStage
@Autowired MonitorCanaryStage monitorCanaryStage
@Autowired ShrinkClusterTask shrinkClusterTask
@Autowired DestroyServerGroupTask destroyServerGroupTask
@Autowired OortHelper oortHelper

@Override
def <T extends Execution<T>> List<Stage<T>> aroundStages(Stage<T> stage) {
Expand All @@ -55,13 +59,18 @@ class CanaryStage implements StageDefinitionBuilder, CancellableStage {
}

@Override
CancellableStage.Result cancel(Stage stage) {
Result cancel(Stage stage) {
log.info("Cancelling stage (stageId: ${stage.id}, executionId: ${stage.execution.id}, context: ${stage.context as Map})")

// it's possible the server groups haven't been created yet, allow a grace period before cleanup
Thread.sleep(TimeUnit.MINUTES.toMillis(2))

Collection<Map<String, Object>> shrinkContexts = []
return cleanupCanary(stage)
}

protected Result cleanupCanary(Stage stage) {
Collection<Map<String, Object>> destroyContexts = []

stage.context.clusterPairs.each { Map<String, Map> clusterPair ->
[clusterPair.baseline, clusterPair.canary].each { Map<String, String> cluster ->

Expand All @@ -70,28 +79,40 @@ class CanaryStage implements StageDefinitionBuilder, CancellableStage {
builder.stack = cluster.stack
builder.detail = cluster.freeFormDetails

String region = cluster.region ?: (cluster.availabilityZones as Map).keySet().first()
Map deployedCluster = oortHelper.getCluster(cluster.application, cluster.account, builder.buildGroupName(), cluster.cloudProvider ?: 'aws').orElse([:])
Long start = stage.startTime
// add a small buffer to deal with latency between the cloud provider and Orca
Long createdTimeCutoff = stage.endTime + 5000

List<Map> serverGroups = deployedCluster.serverGroups ?: []

String clusterRegion = cluster.region ?: (cluster.availabilityZones as Map).keySet().first()
List<Map> matches = serverGroups.findAll {
it.region == clusterRegion && it.createdTime > start && it.createdTime < createdTimeCutoff
} ?: []

shrinkContexts << [
cluster : builder.buildGroupName(),
region : region,
shrinkToSize : 0,
allowDeleteActive: true,
credentials : cluster.account,
cloudProvider : cluster.cloudProvider ?: 'aws'
]
// really hope they're not running concurrent canaries in the same cluster
matches.each {
destroyContexts << [
serverGroupName: it.name,
region : it.region,
credentials : cluster.account,
cloudProvider : it.cloudProvider ?: 'aws'
]
}
}
}

def shrinkResults = shrinkContexts.collect {
def shrinkStage = new Stage<>()
shrinkStage.context.putAll(it)
shrinkClusterTask.execute(shrinkStage)
def destroyResults = destroyContexts.collect {
def destroyStage = new Stage<>()
destroyStage.execution = stage.execution
destroyStage.context.putAll(it)
destroyServerGroupTask.execute(destroyStage)
}

return new CancellableStage.Result(stage, [
shrinkContexts: shrinkContexts,
shrinkResults : shrinkResults
return new Result(stage, [
destroyContexts: destroyContexts,
destroyResults : destroyResults
])
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* 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.mine.pipeline

import com.netflix.spinnaker.orca.CancellableStage.Result
import com.netflix.spinnaker.orca.clouddriver.tasks.servergroup.DestroyServerGroupTask
import com.netflix.spinnaker.orca.clouddriver.utils.OortHelper
import com.netflix.spinnaker.orca.pipeline.model.Stage
import spock.lang.Specification
import spock.lang.Unroll
import static com.netflix.spinnaker.orca.test.model.ExecutionBuilder.stage

class CanaryStageSpec extends Specification {

@Unroll
void "cancel destroys canary/baseline if found and were deployed during canary stage"() {
given:
Map stageContext = [
clusterPairs: [
[
baseline: [application: "app", stack: "stack1", freeFormDetails: "baseline", region: "us-east-1", account: "test"],
canary: [application: "app", stack: "stack1", freeFormDetails: "canary", region: "us-east-1", account: "test"]
]
]
]

Stage canceledStage = stage {
context = stageContext
startTime = 5
endTime = 10
}

OortHelper oortHelper = Mock(OortHelper)
DestroyServerGroupTask destroyServerGroupTask = Mock(DestroyServerGroupTask)

CanaryStage canaryStage = new CanaryStage(oortHelper: oortHelper, destroyServerGroupTask: destroyServerGroupTask)

when:
Result result = canaryStage.cleanupCanary(canceledStage)

then:
result.details.destroyContexts.size() == destroyedServerGroups
1 * oortHelper.getCluster("app", "test", "app-stack1-baseline", "aws") >> [
serverGroups: [[region: "us-east-1", createdTime: createdTime, name: "app-stack1-baseline-v003"]]
]
1 * oortHelper.getCluster("app", "test", "app-stack1-canary", "aws") >> [
serverGroups: [[region: "us-east-1", createdTime: createdTime, name: "app-stack1-canary-v003"]]
]

where:
createdTime || destroyedServerGroups
4 || 0
5 || 0
6 || 2
10 || 2
5010 || 0
5011 || 0

}

}