Skip to content

Commit

Permalink
feat(aws): add support for resizing ASGs with optional min/max/desired (
Browse files Browse the repository at this point in the history
#2430)

* feat(aws): add support for resizing ASGs with optional min/max/desired

We need to replace ResizeAsgDescription.Capacity with something that has
optional (i.e. nullable) fields. ServerGroup.Capacity happens to have
exactly that.

* feat(aws): skip no-op resize ASG requests
  • Loading branch information
dreynaud committed Mar 19, 2018
1 parent c41dfbf commit 498cbb6
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@

package com.netflix.spinnaker.clouddriver.aws.deploy.description

class ResizeAsgDescription extends AbstractAmazonCredentialsDescription {
import com.netflix.spinnaker.clouddriver.model.ServerGroup

class ResizeAsgDescription extends AbstractAmazonCredentialsDescription {
String serverGroupName
String region

Capacity capacity = new Capacity()

ServerGroup.Capacity capacity
List<AsgTargetDescription> asgs = []

@Deprecated
Expand All @@ -31,23 +30,12 @@ class ResizeAsgDescription extends AbstractAmazonCredentialsDescription {
@Deprecated
List<String> regions = []

static class Capacity {
int min
int max
int desired

@Override
String toString() {
return "min: $min, max: $max, desired: $desired"
}
}

static class Constraints {
Capacity capacity
ServerGroup.Capacity capacity
}

static class AsgTargetDescription extends AsgDescription {
Capacity capacity = new Capacity()
ServerGroup.Capacity capacity = new ServerGroup.Capacity()
Constraints constraints = new Constraints()

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.netflix.spinnaker.clouddriver.aws.deploy.description.ResizeAsgDescrip
import com.netflix.spinnaker.clouddriver.aws.security.AmazonClientProvider
import com.netflix.spinnaker.clouddriver.data.task.Task
import com.netflix.spinnaker.clouddriver.data.task.TaskRepository
import com.netflix.spinnaker.clouddriver.model.ServerGroup
import com.netflix.spinnaker.clouddriver.orchestration.AtomicOperation
import org.springframework.beans.factory.annotation.Autowired

Expand Down Expand Up @@ -57,9 +58,15 @@ class ResizeAsgAtomicOperation implements AtomicOperation<Void> {

private void resizeAsg(String asgName,
String region,
ResizeAsgDescription.Capacity capacity,
ServerGroup.Capacity capacity,
ResizeAsgDescription.Constraints constraints) {
task.updateStatus PHASE, "Beginning resize of ${asgName} in ${region} to ${capacity}."

if (capacity.min == null && capacity.max == null && capacity.desired == null) {
task.updateStatus PHASE, "Skipping resize of ${asgName} in ${region}, at least one field in ${capacity} needs to be non-null"
return
}

def autoScaling = amazonClientProvider.getAutoScaling(description.credentials, region, true)
def describeAutoScalingGroups = autoScaling.describeAutoScalingGroups(
new DescribeAutoScalingGroupsRequest().withAutoScalingGroupNames(asgName)
Expand All @@ -71,6 +78,7 @@ class ResizeAsgAtomicOperation implements AtomicOperation<Void> {

validateConstraints(constraints, describeAutoScalingGroups.getAutoScalingGroups().get(0))

// min, max and desired may be null
def request = new UpdateAutoScalingGroupRequest().withAutoScalingGroupName(asgName)
.withMinSize(capacity.min)
.withMaxSize(capacity.max)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ import spock.lang.Specification
import spock.lang.Unroll

class ResizeAsgAtomicOperationUnitSpec extends Specification {
def mockAutoScaling = Mock(AmazonAutoScaling)
def mockAmazonClientProvider = Mock(AmazonClientProvider) {
_ * getAutoScaling(_, _, true) >> { return mockAutoScaling }
}

def setupSpec() {
TaskRepository.threadLocalTask.set(Mock(Task))
Expand All @@ -38,14 +42,11 @@ class ResizeAsgAtomicOperationUnitSpec extends Specification {
@Unroll
void "should update ASG iff it exists and is not in the process of being deleted"() {
setup:
def mockAutoScaling = Mock(AmazonAutoScaling)
def mockAmazonClientProvider = Mock(AmazonClientProvider)
mockAmazonClientProvider.getAutoScaling(_, _, true) >> mockAutoScaling
def description = new ResizeAsgDescription(
asgs: [[
serverGroupName: "myasg-stack-v000",
region : "us-west-1",
capacity : new ResizeAsgDescription.Capacity(
capacity : new ServerGroup.Capacity(
min : 1,
max : 2,
desired: 5
Expand Down Expand Up @@ -110,8 +111,46 @@ class ResizeAsgAtomicOperationUnitSpec extends Specification {
capacity(0, 2, 3) | 1 | 2 | 3 || true
}

@Unroll
void "should support resize requests with only some fields to update (min: #minSize, max: #maxSize, desired: #desired)"() {
setup:
def description = new ResizeAsgDescription(
asgs: [[
serverGroupName: "myasg-stack-v000",
region : "us-west-1",
capacity : new ServerGroup.Capacity(min: minSize, max: maxSize, desired: desired)
]]
)
def operation = new ResizeAsgAtomicOperation(description)
operation.amazonClientProvider = mockAmazonClientProvider
when:
operation.operate([])
then:
expectedOps * mockAutoScaling.describeAutoScalingGroups(_) >> new DescribeAutoScalingGroupsResult().withAutoScalingGroups(
new AutoScalingGroup().withAutoScalingGroupName("myasg-stack-v0001")
)
expectedOps * mockAutoScaling.updateAutoScalingGroup(_) >> {
request ->
assert request.size() == 1
assert request[0].minSize == minSize
assert request[0].maxSize == maxSize
assert request[0].desiredCapacity == desired
}
where:
minSize | maxSize | desired | expectedOps
42 | null | null | 1
null | 42 | null | 1
null | null | 42 | 1
0 | null | null | 1
0 | 0 | 0 | 1 // not the same as (null, null, null)
null | null | null | 0
}
private static ResizeAsgDescription.Capacity capacity(int min, int max, int desired) {
return new ResizeAsgDescription.Capacity(min: min, max: max, desired: desired)
private static ServerGroup.Capacity capacity(int min, int max, int desired) {
return new ServerGroup.Capacity(min: min, max: max, desired: desired)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ import com.netflix.spinnaker.clouddriver.aws.TestCredential
import com.netflix.spinnaker.clouddriver.aws.security.NetflixAmazonCredentials
import com.netflix.spinnaker.clouddriver.deploy.DescriptionValidator
import com.netflix.spinnaker.clouddriver.aws.deploy.description.ResizeAsgDescription
import com.netflix.spinnaker.clouddriver.model.ServerGroup
import org.springframework.validation.Errors
import spock.lang.Shared
import spock.lang.Specification

class ResizeAsgDescriptionValidatorSpec {
class ResizeAsgDescriptionValidatorSpec extends Specification {

@Shared
DescriptionValidator validator = new ResizeAsgDescriptionValidator()
Expand All @@ -34,7 +36,7 @@ class ResizeAsgDescriptionValidatorSpec {
asgs: [new ResizeAsgDescription.AsgTargetDescription(
serverGroupName: "foo",
region: "us-west-1",
capacity: new ResizeAsgDescription.Capacity(min: 5, max: 3)
capacity: new ServerGroup.Capacity(min: 5, max: 3)
)],
credentials: Stub(NetflixAmazonCredentials)
)
Expand All @@ -47,7 +49,7 @@ class ResizeAsgDescriptionValidatorSpec {
1 * errors.rejectValue('capacity', _, ['5', '3'], _)

when:
description.asgs[0].capacity = new ResizeAsgDescription.Capacity(min: 3, max: 5, desired: 7)
description.asgs[0].capacity = new ServerGroup.Capacity(min: 3, max: 5, desired: 7)
validator.validate([], description, errors)

then:
Expand Down

0 comments on commit 498cbb6

Please sign in to comment.