Skip to content

Commit

Permalink
Unit tests for Subnets and Load Balancer operations
Browse files Browse the repository at this point in the history
Made fields of Subnets class readable so they’ll be included in the equals comparisons for unit testing. Still fully immutable.
  • Loading branch information
Joe Sondow committed Jan 5, 2014
1 parent a045505 commit f14fcd9
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 30 deletions.
6 changes: 3 additions & 3 deletions src/groovy/com/netflix/asgard/model/Subnets.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ import groovy.transform.Canonical
*/
@Canonical class Subnets {
/** All of the subnets contained in this object. */
final private Collection<SubnetData> allSubnets
final Collection<SubnetData> allSubnets

/** The identifier of the default VPC of the account-region, if available. */
final private String defaultVpcId
final String defaultVpcId

private Subnets(Collection<SubnetData> allSubnets, String defaultVpcId) {
private Subnets(Collection<SubnetData> allSubnets, String defaultVpcId = null) {
this.defaultVpcId = defaultVpcId
this.allSubnets = ImmutableSet.copyOf(allSubnets)
}
Expand Down
3 changes: 2 additions & 1 deletion test/unit/com/netflix/asgard/AwsEc2ServiceUnitSpec.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import com.netflix.asgard.model.ZoneAvailability
import spock.lang.Specification
import spock.lang.Unroll

@SuppressWarnings("GroovyAssignabilityCheck")
class AwsEc2ServiceUnitSpec extends Specification {

UserContext userContext
Expand Down Expand Up @@ -458,7 +459,7 @@ and groupName is #groupName""")

then:
subnetIds == ['subnet-luke', 'subnet-han']
1 * mockVpcCache.list() >> [new Vpc(vpcId: 'vpc-123'), new Vpc(vpcId: 'vpc-789', isDefault: true)]
2 * mockVpcCache.list() >> [new Vpc(vpcId: 'vpc-123'), new Vpc(vpcId: 'vpc-789', isDefault: true)]
1 * mockSubnetCache.list() >> [
new Subnet(subnetId: 'subnet-luke', vpcId: 'vpc-789'),
new Subnet(subnetId: 'subnet-han', vpcId: 'vpc-789'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,20 @@ import com.amazonaws.services.autoscaling.model.Instance
import com.amazonaws.services.ec2.model.SecurityGroup
import com.amazonaws.services.elasticloadbalancing.AmazonElasticLoadBalancing
import com.amazonaws.services.elasticloadbalancing.model.AttachLoadBalancerToSubnetsRequest
import com.amazonaws.services.elasticloadbalancing.model.CreateLoadBalancerListenersRequest
import com.amazonaws.services.elasticloadbalancing.model.DeleteLoadBalancerListenersRequest
import com.amazonaws.services.elasticloadbalancing.model.DescribeInstanceHealthResult
import com.amazonaws.services.elasticloadbalancing.model.DescribeLoadBalancersRequest
import com.amazonaws.services.elasticloadbalancing.model.DescribeLoadBalancersResult
import com.amazonaws.services.elasticloadbalancing.model.DetachLoadBalancerFromSubnetsRequest
import com.amazonaws.services.elasticloadbalancing.model.InstanceState
import com.amazonaws.services.elasticloadbalancing.model.Listener
import com.amazonaws.services.elasticloadbalancing.model.LoadBalancerDescription
import com.netflix.asgard.model.InstanceStateData
import spock.lang.Specification
import spock.lang.Unroll

@SuppressWarnings("GroovyAssignabilityCheck")
class AwsLoadBalancerServiceUnitSpec extends Specification {

UserContext userContext
Expand All @@ -42,7 +46,8 @@ class AwsLoadBalancerServiceUnitSpec extends Specification {
mockAmazonElasticLoadBalancing = Mock(AmazonElasticLoadBalancing)
MultiRegionAwsClient awsClient = new MultiRegionAwsClient({ mockAmazonElasticLoadBalancing })
TaskService taskService = new TaskService() {
def runTask(UserContext userContext, String name, Closure work, Link link = null) {
def runTask(UserContext userContext, String name, Closure work, Link link = null,
Task existingTask = null) {
work(new Task())
}
}
Expand Down Expand Up @@ -122,6 +127,30 @@ class AwsLoadBalancerServiceUnitSpec extends Specification {
].collect { new InstanceStateData(it) }
}

def 'should add listener'() {

List<Listener> listeners = [new Listener(protocol: 'http', loadBalancerPort: 80, instancePort: 7001)]

when:
awsLoadBalancerService.addListeners(UserContext.auto(), 'app--frontend', listeners)

then:
1 * mockAmazonElasticLoadBalancing.createLoadBalancerListeners(
new CreateLoadBalancerListenersRequest('app--frontend', listeners))
0 * _
}

def 'should remove listener'() {

when:
awsLoadBalancerService.removeListeners(UserContext.auto(), 'app--frontend', [80])

then:
1 * mockAmazonElasticLoadBalancing.deleteLoadBalancerListeners(
new DeleteLoadBalancerListenersRequest('app--frontend', [80]))
0 * _
}

def 'should update subnets'() {
Collection<String> oldSubnets = ['subnet-101', 'subnet-102', 'subnet-103']
Collection<String> newSubnets = ['subnet-103', 'subnet-104']
Expand Down
56 changes: 49 additions & 7 deletions test/unit/com/netflix/asgard/LoadBalancerControllerSpec.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@
*/
package com.netflix.asgard

import com.amazonaws.services.ec2.model.Subnet
import com.amazonaws.services.ec2.model.Tag
import com.amazonaws.services.elasticloadbalancing.model.HealthCheck
import com.amazonaws.services.elasticloadbalancing.model.LoadBalancerDescription
import com.amazonaws.services.elasticloadbalancing.AmazonElasticLoadBalancing
import com.amazonaws.services.elasticloadbalancing.model.Listener
import com.netflix.asgard.model.Subnets
import grails.test.MockUtils
import grails.test.mixin.TestFor
import spock.lang.Specification
Expand Down Expand Up @@ -53,7 +56,7 @@ class LoadBalancerControllerSpec extends Specification {
}

@Unroll('update should change zones but not subnets when default VPC subnets #defaultVpcSubnetsCondition')
def 'update without custom VPC should change zones but not subnets'() {
def 'update without custom VPC subnets should change zones but not subnets'() {
params.name = 'hello'
params.selectedZones = ['us-east-1b', 'us-east-1c']
setUpHealthCheckParams()
Expand All @@ -64,13 +67,13 @@ class LoadBalancerControllerSpec extends Specification {
then:
flash.message == "Added zone [us-east-1c] to load balancer. Removed zone [us-east-1a] from load balancer. " +
"Load Balancer 'hello' health check has been updated. "
1 * controller.awsLoadBalancerService.getLoadBalancer(_, 'hello') >>
1 * awsLoadBalancerService.getLoadBalancer(_, 'hello') >>
new LoadBalancerDescription(availabilityZones: ['us-east-1a', 'us-east-1b'], subnets: lbSubnetIds)
1 * controller.awsEc2Service.getDefaultVpcSubnetIds(_) >> defaultVpcSubnetIds
1 * controller.awsLoadBalancerService.addZones(_, 'hello', ['us-east-1c'])
1 * controller.awsLoadBalancerService.removeZones(_, 'hello', ['us-east-1a'])
1 * controller.awsLoadBalancerService.configureHealthCheck(_, 'hello', new HealthCheck(target: 'HTTP:8080/',
interval: 5, timeout: 10, unhealthyThreshold: 3, healthyThreshold: 4))
1 * awsEc2Service.getDefaultVpcSubnetIds(_) >> defaultVpcSubnetIds
1 * awsLoadBalancerService.addZones(_, 'hello', ['us-east-1c'])
1 * awsLoadBalancerService.removeZones(_, 'hello', ['us-east-1a'])
1 * awsLoadBalancerService.configureHealthCheck(_, 'hello', new HealthCheck(target: 'HTTP:8080/', interval: 5,
timeout: 10, unhealthyThreshold: 3, healthyThreshold: 4))
0 * _

where:
Expand All @@ -81,6 +84,45 @@ class LoadBalancerControllerSpec extends Specification {
['subnet-123', 'subnet-456'] | ['subnet-123', 'subnet-456'] | 'are all used by load balancer'
}

@Unroll('update should change subnets but not zones when default VPC subnets #defaultVpcSubnetsCondition')
def 'update with custom VPC subnets should change subnets but not zones'() {
params.name = 'hello'
params.selectedZones = ['us-east-1b', 'us-east-1c']
setUpHealthCheckParams()

Subnets allSubnets = Subnets.from([
new Subnet(subnetId: 'sn-123', vpcId: 'vpc-def', availabilityZone: 'us-east-1a'),
new Subnet(subnetId: 'sn-456', vpcId: 'vpc-def', availabilityZone: 'us-east-1b'),
new Subnet(subnetId: 'sn-789', vpcId: 'vpc-def', availabilityZone: 'us-east-1c'),
new Subnet(subnetId: 'sn-ant', vpcId: 'vpc-custom', availabilityZone: 'us-east-1a',
tags: [new Tag(key: 'immutable_metadata', value: '{"purpose":"external","target":"elb"}')]),
new Subnet(subnetId: 'sn-bat', vpcId: 'vpc-custom', availabilityZone: 'us-east-1b',
tags: [new Tag(key: 'immutable_metadata', value: '{"purpose":"external","target":"elb"}')]),
new Subnet(subnetId: 'sn-cat', vpcId: 'vpc-custom', availabilityZone: 'us-east-1c',
tags: [new Tag(key: 'immutable_metadata', value: '{"purpose":"external","target":"elb"}')]),
])

when:
controller.update()

then:
flash.message == "Load Balancer 'hello' health check has been updated. "
1 * awsLoadBalancerService.getLoadBalancer(_, 'hello') >> new LoadBalancerDescription(
availabilityZones: ['us-east-1a', 'us-east-1b'], subnets: ['sn-ant', 'sn-bat'])
1 * awsEc2Service.getDefaultVpcSubnetIds(_) >> defaultVpcSubnetIds
1 * awsEc2Service.getSubnets(_) >> allSubnets

1 * awsLoadBalancerService.updateSubnets(_, 'hello', ['sn-ant', 'sn-bat'], ['sn-bat', 'sn-cat'])
1 * awsLoadBalancerService.configureHealthCheck(_, 'hello', new HealthCheck(target: 'HTTP:8080/',
interval: 5, timeout: 10, unhealthyThreshold: 3, healthyThreshold: 4))
0 * _

where:
defaultVpcSubnetIds | defaultVpcSubnetsCondition
[] | 'do not exist and load balancer has subnets'
['sn-123', 'sn-456', 'sn-789'] | 'exist but load balancer has other subnets'
}

def 'addListener should fail without instance port'() {
final cmd = new AddListenerCommand(name: 'app--test')
cmd.validate()
Expand Down
106 changes: 88 additions & 18 deletions test/unit/com/netflix/asgard/model/SubnetsSpec.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,26 @@ class SubnetsSpec extends Specification {
}

Subnets subnets
Subnets subnetsForEc2Classic

void setup() {
subnets = new Subnets([
subnet('subnet-e9b0a3a1', 'us-east-1a', 'internal', SubnetTarget.EC2),
subnet('subnet-e9b0a3a2', 'us-east-1a', 'external', SubnetTarget.EC2),
subnet('subnet-e9b0a3a3', 'us-east-1a', 'internal', SubnetTarget.ELB),
subnet('subnet-e9b0a3a4', 'us-east-1a', 'external', SubnetTarget.ELB),
subnet('subnet-c1e8b2b1', 'us-east-1b', 'internal', SubnetTarget.EC2),
subnet('subnet-c1e8b2b2', 'us-east-1b', 'external', SubnetTarget.EC2),
subnet('subnet-a3770585', 'us-east-1a', null, null, 'vpc-456'),
subnet('subnet-a3770586', 'us-east-1b', null, null, 'vpc-456'),
subnet('subnet-a3770587', 'us-east-1c', null, null, 'vpc-456'),
])

List<SubnetData> subnetDatasForEc2Classic = [
subnet('subnet-e9b0a3a1', 'us-east-1a', 'internal', SubnetTarget.EC2, 'vpc-abcd'),
subnet('subnet-e9b0a3a2', 'us-east-1a', 'external', SubnetTarget.EC2, 'vpc-feed'),
subnet('subnet-e9b0a3a3', 'us-east-1a', 'internal', SubnetTarget.ELB, 'vpc-abcd'),
subnet('subnet-e9b0a3a4', 'us-east-1a', 'external', SubnetTarget.ELB, 'vpc-feed'),
subnet('subnet-c1e8b2b1', 'us-east-1b', 'internal', SubnetTarget.EC2, 'vpc-abcd'),
subnet('subnet-c1e8b2b2', 'us-east-1b', 'external', SubnetTarget.EC2, 'vpc-feed'),
]
subnetsForEc2Classic = new Subnets(subnetDatasForEc2Classic)

List<SubnetData> subnetDatas = subnetDatasForEc2Classic + [
subnet('subnet-a3770585', 'us-east-1a', null, null, 'vpc-def'),
subnet('subnet-a3770586', 'us-east-1b', null, null, 'vpc-def'),
subnet('subnet-a3770587', 'us-east-1c', null, null, 'vpc-def'),
]
subnets = new Subnets(subnetDatas, 'vpc-def')
}

def 'should create Subnets from AWS objects'() {
Expand Down Expand Up @@ -83,7 +90,7 @@ class SubnetsSpec extends Specification {

def 'should find subnet by ID'() {
SubnetData expectedSubnet = new SubnetData(subnetId: 'subnet-e9b0a3a1', availabilityZone: 'us-east-1a',
purpose: 'internal', target: SubnetTarget.EC2, vpcId: 'vpc-1')
purpose: 'internal', target: SubnetTarget.EC2, vpcId: 'vpc-abcd')
expect: expectedSubnet == subnets.findSubnetById('subnet-e9b0a3a1')
}

Expand All @@ -98,18 +105,59 @@ class SubnetsSpec extends Specification {

def 'should find subnets by VPC ID'() {
Subnets expectedSubnets = Subnets.from([
new Subnet(subnetId: 'subnet-a3770585', availabilityZone: 'us-east-1a', vpcId: 'vpc-456'),
new Subnet(subnetId: 'subnet-a3770586', availabilityZone: 'us-east-1a', vpcId: 'vpc-456'),
new Subnet(subnetId: 'subnet-a3770587', availabilityZone: 'us-east-1a', vpcId: 'vpc-456'),
new Subnet(subnetId: 'subnet-a3770585', availabilityZone: 'us-east-1a', vpcId: 'vpc-def'),
new Subnet(subnetId: 'subnet-a3770586', availabilityZone: 'us-east-1b', vpcId: 'vpc-def'),
new Subnet(subnetId: 'subnet-a3770587', availabilityZone: 'us-east-1c', vpcId: 'vpc-def'),
])
expect: expectedSubnets == subnets.findSubnetsByVpc('vpc-456')

expect: expectedSubnets == subnets.findSubnetsByVpc('vpc-def')
}

def 'should fail when finding subnets by null VPC ID'() {
when: subnets.findSubnetsByVpc(null)
then: thrown(NullPointerException)
}

def 'should get purpose from VPC zone identifier string when default VPC exists'() {
expect: purpose == subnets.getPurposeFromVpcZoneIdentifier(vpcZoneIdentifier)

where:
vpcZoneIdentifier | purpose
'subnet-e9b0a3a1,subnet-e9b0a3b1' | 'internal'
'subnet-e9b0a3a4' | 'external'
'subnet-a3770585,subnet-a3770586' | null
}

def 'should get purpose from VPC zone identifier string when default VPC does not exist'() {
expect: purpose == subnetsForEc2Classic.getPurposeFromVpcZoneIdentifier(vpcZoneIdentifier)

where:
vpcZoneIdentifier | purpose
'subnet-e9b0a3a1,subnet-e9b0a3b1' | 'internal'
'subnet-e9b0a3a4' | 'external'
'subnet-a3770585,subnet-a3770586' | null
}

def 'should get VPC ID for VPC zone identifier when default VPC exists'() {
expect: vpcId == subnets.getVpcIdForVpcZoneIdentifier(vpcZoneIdentifier)

where:
vpcZoneIdentifier | vpcId
'subnet-e9b0a3a1,subnet-e9b0a3b1' | 'vpc-abcd'
'subnet-e9b0a3a4' | 'vpc-feed'
'subnet-a3770585,subnet-a3770586' | 'vpc-def'
}

def 'should get VPC ID for VPC zone identifier when default VPC does not exist'() {
expect: vpcId == subnetsForEc2Classic.getVpcIdForVpcZoneIdentifier(vpcZoneIdentifier)

where:
vpcZoneIdentifier | vpcId
'subnet-e9b0a3a1,subnet-e9b0a3b1' | 'vpc-abcd'
'subnet-e9b0a3a4' | 'vpc-feed'
'subnet-a3770585,subnet-a3770586' | null
}

def 'should return subnets for zones'() {
List<String> zones = ['us-east-1a', 'us-east-1b']
List<String> expectedSubnets = ['subnet-e9b0a3a1', 'subnet-c1e8b2b1']
Expand Down Expand Up @@ -381,14 +429,14 @@ class SubnetsSpec extends Specification {
}

def 'should return subnet for subnet ID'() {
SubnetData expectedSubnet = subnet('subnet-e9b0a3a2', 'us-east-1a', 'external', SubnetTarget.EC2)
SubnetData expectedSubnet = subnet('subnet-e9b0a3a2', 'us-east-1a', 'external', SubnetTarget.EC2, 'vpc-feed')

expect:
subnets.coerceLoneOrNoneFromIds(['subnet-e9b0a3a2']) == expectedSubnet
}

def 'should return subnet for first subnet ID if there are multiple'() {
SubnetData expectedSubnet = subnet('subnet-e9b0a3a2', 'us-east-1a', 'external', SubnetTarget.EC2)
SubnetData expectedSubnet = subnet('subnet-e9b0a3a2', 'us-east-1a', 'external', SubnetTarget.EC2, 'vpc-feed')

expect: subnets.coerceLoneOrNoneFromIds(['subnet-e9b0a3a2', 'subnet-e9b0a3a1']) == expectedSubnet
}
Expand All @@ -401,6 +449,28 @@ class SubnetsSpec extends Specification {
expect: null == subnets.coerceLoneOrNoneFromIds(['subnet-deadbeef'])
}

def 'with default VPC, should get the VPC ID for a purpose or get the default VPC ID for empty or null purpose'() {
expect: vpcId == subnets.getVpcIdForSubnetPurpose(purpose)

where:
vpcId | purpose
'vpc-feed' | 'external'
'vpc-abcd' | 'internal'
'vpc-def' | ''
'vpc-def' | null
}

def 'in EC2 classic, should either get the VPC ID for a subnet purpose or null'() {
expect: vpcId == subnetsForEc2Classic.getVpcIdForSubnetPurpose(purpose)

where:
vpcId | purpose
'vpc-feed' | 'external'
'vpc-abcd' | 'internal'
null | ''
null | null
}

def 'should map purpose to VPC ID'() {
subnets = new Subnets([
subnet('subnet-e9b0a3a1', 'us-east-1a', 'internal', SubnetTarget.EC2),
Expand Down

0 comments on commit f14fcd9

Please sign in to comment.