Skip to content

Commit

Permalink
feat(aws): Add filtering of LoadBalancers to include/exclude based on…
Browse files Browse the repository at this point in the history
… Tags (#5108)
  • Loading branch information
gavinbunney committed Nov 24, 2020
1 parent f152ff3 commit 99ed0c9
Show file tree
Hide file tree
Showing 8 changed files with 356 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -104,20 +104,23 @@ abstract class AbstractAmazonLoadBalancerCachingAgent implements CachingAgent, O
final String region
final ObjectMapper objectMapper
final Registry registry
final AmazonCachingAgentFilter amazonCachingAgentFilter
final OnDemandMetricsSupport metricsSupport

AbstractAmazonLoadBalancerCachingAgent(AmazonCloudProvider amazonCloudProvider,
AmazonClientProvider amazonClientProvider,
NetflixAmazonCredentials account,
String region,
ObjectMapper objectMapper,
Registry registry) {
Registry registry,
AmazonCachingAgentFilter amazonCachingAgentFilter) {
this.amazonCloudProvider = amazonCloudProvider
this.amazonClientProvider = amazonClientProvider
this.account = account
this.region = region
this.objectMapper = objectMapper.copy().enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
this.registry = registry
this.amazonCachingAgentFilter = amazonCachingAgentFilter
this.metricsSupport = new OnDemandMetricsSupport(registry, this, amazonCloudProvider.id + ":" + "${amazonCloudProvider.id}:${OnDemandType.LoadBalancer}")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class AmazonApplicationLoadBalancerCachingAgent extends AbstractAmazonLoadBalanc
])

private static final String HEALTH_ID = "aws-load-balancer-v2-target-group-instance-health"
private static final int DESCRIBE_TAG_LIMIT = 20

AmazonApplicationLoadBalancerCachingAgent(AmazonCloudProvider amazonCloudProvider,
AmazonClientProvider amazonClientProvider,
Expand All @@ -67,8 +68,9 @@ class AmazonApplicationLoadBalancerCachingAgent extends AbstractAmazonLoadBalanc
EddaApi eddaApi,
ObjectMapper objectMapper,
Registry registry,
EddaTimeoutConfig eddaTimeoutConfig) {
super(amazonCloudProvider, amazonClientProvider, account, region, objectMapper, registry)
EddaTimeoutConfig eddaTimeoutConfig,
AmazonCachingAgentFilter amazonCachingAgentFilter) {
super(amazonCloudProvider, amazonClientProvider, account, region, objectMapper, registry, amazonCachingAgentFilter)
this.eddaApi = eddaApi
this.eddaTimeoutConfig = eddaTimeoutConfig
}
Expand Down Expand Up @@ -325,14 +327,43 @@ class AmazonApplicationLoadBalancerCachingAgent extends AbstractAmazonLoadBalanc
}
}

// filter load balancers if there is any filter configuration established
if (amazonCachingAgentFilter.hasTagFilter()) {
def loadBalancerPartitions = allLoadBalancers*.loadBalancerArn.collate(DESCRIBE_TAG_LIMIT)
Map<String, List<AmazonCachingAgentFilter.ResourceTag>> loadBalancerTags = [:]
loadBalancerPartitions.each {loadBalancerPartition ->
def tagsRequest = new DescribeTagsRequest().withResourceArns(loadBalancerPartition)
def tagsResponse = loadBalancing.describeTags(tagsRequest)
loadBalancerTags.putAll(tagsResponse.tagDescriptions?.collectEntries {
[(it.resourceArn): it.tags?.collect {new AmazonCachingAgentFilter.ResourceTag(it.key, it.value)} ]
})
}

allLoadBalancers = allLoadBalancers.findAll { lb ->
return amazonCachingAgentFilter.shouldRetainResource(loadBalancerTags?.get(lb.loadBalancerArn))
}
}

def loadBalancerAttributes = this.buildLoadBalancerAttributes(loadBalancing, allLoadBalancers, useEdda)

// Get all the target groups
List<TargetGroup> allTargetGroups = []
DescribeTargetGroupsRequest describeTargetGroupsRequest = new DescribeTargetGroupsRequest()
HashSet<String> allLoadBalancerArns = new HashSet(allLoadBalancers*.loadBalancerArn)
while (true) {
def resp = loadBalancing.describeTargetGroups(describeTargetGroupsRequest)
allTargetGroups.addAll(resp.targetGroups)

// only keep target groups which are for the set of filtered load balancers
def targetGroups = resp.targetGroups
if (amazonCachingAgentFilter.hasTagFilter()) {
targetGroups?.retainAll{ tg ->
tg.loadBalancerArns?.find { tgLB ->
allLoadBalancerArns.contains(tgLB)
} != null
}
}

allTargetGroups.addAll(targetGroups)
if (resp.nextMarker) {
describeTargetGroupsRequest.withMarker(resp.nextMarker)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ public boolean shouldRetainResource(List<ResourceTag> tags) {

// retain the resource by default if there isn't an include filter setup
boolean retainResource = !this.hasIncludeTagFilter();
if (tags == null || tags.size() == 0) {
return retainResource;
}

if (this.hasIncludeTagFilter()) {
retainResource =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,17 @@ import static com.netflix.spinnaker.clouddriver.core.provider.agent.Namespace.*
class AmazonLoadBalancerCachingAgent extends AbstractAmazonLoadBalancerCachingAgent {

private EddaApi eddaApi
private static final int DESCRIBE_TAG_LIMIT = 20

AmazonLoadBalancerCachingAgent(AmazonCloudProvider amazonCloudProvider,
AmazonClientProvider amazonClientProvider,
NetflixAmazonCredentials account,
String region,
EddaApi eddaApi,
ObjectMapper objectMapper,
Registry registry) {
super(amazonCloudProvider, amazonClientProvider, account, region, objectMapper, registry)
Registry registry,
AmazonCachingAgentFilter amazonCachingAgentFilter) {
super(amazonCloudProvider, amazonClientProvider, account, region, objectMapper, registry, amazonCachingAgentFilter)
this.eddaApi = eddaApi
}

Expand Down Expand Up @@ -154,6 +156,24 @@ class AmazonLoadBalancerCachingAgent extends AbstractAmazonLoadBalancerCachingAg
}
}

// filter load balancers if there is any filter configuration established
if (amazonCachingAgentFilter.hasTagFilter()) {

def loadBalancerPartitions = allLoadBalancers*.loadBalancerName.collate(DESCRIBE_TAG_LIMIT)
Map<String, List<AmazonCachingAgentFilter.ResourceTag>> loadBalancerTags = [:]
loadBalancerPartitions.each {loadBalancerPartition ->
def tagsRequest = new DescribeTagsRequest().withLoadBalancerNames(loadBalancerPartition)
def tagsResponse = loadBalancing.describeTags(tagsRequest)
loadBalancerTags.putAll(tagsResponse.tagDescriptions?.collectEntries {
[(it.loadBalancerName): it.tags?.collect {new AmazonCachingAgentFilter.ResourceTag(it.key, it.value)} ]
})
}

allLoadBalancers = allLoadBalancers.findAll { lb ->
return amazonCachingAgentFilter.shouldRetainResource(loadBalancerTags?.get(lb.loadBalancerName))
}
}

Map<String, LoadBalancerAttributes> loadBalancerAttributes = buildLoadBalancerAttributes(loadBalancing, allLoadBalancers, account.eddaEnabled)

if (!start) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,8 @@ public static BuildResult buildAwsProviderAgents(
region.getName(),
eddaApiFactory.createApi(credentials.getEdda(), region.getName()),
objectMapper,
registry));
registry,
amazonCachingAgentFilter));
newlyAddedAgents.add(
new AmazonApplicationLoadBalancerCachingAgent(
amazonCloudProvider,
Expand All @@ -197,7 +198,8 @@ public static BuildResult buildAwsProviderAgents(
eddaApiFactory.createApi(credentials.getEdda(), region.getName()),
objectMapper,
registry,
eddaTimeoutConfig));
eddaTimeoutConfig,
amazonCachingAgentFilter));
newlyAddedAgents.add(
new ReservedInstancesCachingAgent(
amazonClientProvider, credentials, region.getName(), objectMapper, registry));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package com.netflix.spinnaker.clouddriver.aws.provider.agent

import com.amazonaws.services.elasticloadbalancingv2.AmazonElasticLoadBalancing
import com.amazonaws.services.elasticloadbalancingv2.model.DescribeListenersResult
import com.amazonaws.services.elasticloadbalancingv2.model.DescribeLoadBalancersResult
import com.amazonaws.services.elasticloadbalancingv2.model.DescribeTagsResult
import com.amazonaws.services.elasticloadbalancingv2.model.DescribeTargetGroupsResult
import com.amazonaws.services.elasticloadbalancingv2.model.Listener
import com.amazonaws.services.elasticloadbalancingv2.model.LoadBalancer
import com.amazonaws.services.elasticloadbalancingv2.model.Tag
import com.amazonaws.services.elasticloadbalancingv2.model.TargetGroup
import com.amazonaws.services.elasticloadbalancingv2.model.TagDescription
import com.netflix.awsobjectmapper.AmazonObjectMapperConfigurer
import com.netflix.spectator.api.Spectator
import com.netflix.spinnaker.cats.provider.ProviderCache
import com.netflix.spinnaker.clouddriver.aws.AmazonCloudProvider
import com.netflix.spinnaker.clouddriver.aws.edda.EddaApi
import com.netflix.spinnaker.clouddriver.aws.security.AmazonClientProvider
import com.netflix.spinnaker.clouddriver.aws.security.EddaTimeoutConfig
import com.netflix.spinnaker.clouddriver.aws.security.NetflixAmazonCredentials
import spock.lang.Shared
import spock.lang.Specification

import static com.netflix.spinnaker.clouddriver.core.provider.agent.Namespace.INSTANCES
import static com.netflix.spinnaker.clouddriver.core.provider.agent.Namespace.LOAD_BALANCERS
import static com.netflix.spinnaker.clouddriver.core.provider.agent.Namespace.TARGET_GROUPS

class AmazonApplicationLoadBalancerCachingAgentSpec extends Specification {
static String region = 'region'
static String accountName = 'accountName'
static String accountId = 'accountId'

@Shared
AmazonElasticLoadBalancing elasticLoadBalancing = Mock(AmazonElasticLoadBalancing)

@Shared
EddaApi eddaApi = Mock(EddaApi)

@Shared
EddaTimeoutConfig eddaTimeoutConfig = Mock(EddaTimeoutConfig)

@Shared
AmazonCachingAgentFilter filter = new AmazonCachingAgentFilter()

def getAgent() {
def creds = Stub(NetflixAmazonCredentials) {
getName() >> accountName
it.getAccountId() >> accountId
}
def cloud = Stub(AmazonCloudProvider)
def client = Stub(AmazonClientProvider) {
getAmazonElasticLoadBalancingV2(_, _) >> Stub(AmazonElasticLoadBalancing) {
describeLoadBalancers(_) >> new DescribeLoadBalancersResult() {
List<LoadBalancer> getLoadBalancers() {
return filterableLBs().keySet() as List
}
}

describeTags(_) >> new DescribeTagsResult() {
List<TagDescription> getTagDescriptions() {
return filterableLBs().values().flatten()
}
}

describeTargetGroups(_) >> new DescribeTargetGroupsResult() {
List<TargetGroup> getTargetGroups() {
return filterableTargetGroups()
}
}

describeListeners(_) >> new DescribeListenersResult() {
List<Listener> getListeners() {
return []
}
}
}
}

new AmazonApplicationLoadBalancerCachingAgent(cloud, client, creds, region, eddaApi, AmazonObjectMapperConfigurer.createConfigured(), Spectator.globalRegistry(), eddaTimeoutConfig, filter)
}

void "should filter by tags"() {
given:
def agent = getAgent()
filter.includeTags = includeTags
filter.excludeTags = excludeTags
ProviderCache providerCache = Stub(ProviderCache) {
getAll(_, _) >> {
return []
}
}
providerCache.addCacheResult(INSTANCES.ns, [], null)

when:
def result = agent.loadDataInternal(providerCache)

then:
result.cacheResults[LOAD_BALANCERS.ns]*.getId() == expected
result.cacheResults[TARGET_GROUPS.ns]*.relationships[LOAD_BALANCERS.ns].flatten() == expected

where:
includeTags | excludeTags | expected
null | null | filterableLBs()*.getKey().collect { buildCacheKey(it.loadBalancerName) }
[taggify("hello")] | null | buildCacheKeys(["test-hello-tag-value", "test-hello-tag-value-different", "test-hello-tag-no-value"])
[taggify("hello", "goodbye")] | null | buildCacheKeys(["test-hello-tag-value"])
[taggify("hello", "goo")] | null | buildCacheKeys([])
[taggify("hello", ".*bye")] | null | buildCacheKeys(["test-hello-tag-value"])
[taggify(".*a.*")] | null | buildCacheKeys(["test-no-hello-tag"])
null | [taggify("hello")] | buildCacheKeys(["test-no-hello-tag", "test-no-tags"])
null | [taggify("hello", "goodbye")] | buildCacheKeys(["test-hello-tag-value-different", "test-hello-tag-no-value", "test-no-hello-tag", "test-no-tags"])
[taggify("hello", "goodbye")] | [taggify("hello")] | buildCacheKeys([])
[taggify(".*", "ciao")] | [taggify("hello", ".*")] | buildCacheKeys([])
}

private static final Map<LoadBalancer, List<TagDescription>> filterableLBs() {
return [
(new LoadBalancer().withLoadBalancerName("test-hello-tag-value").withLoadBalancerArn(buildELBArn("test-hello-tag-value"))) :
[new TagDescription().withResourceArn(buildELBArn("test-hello-tag-value")).withTags(new Tag().withKey("hello").withValue("goodbye"))],
(new LoadBalancer().withLoadBalancerName("test-hello-tag-value-different").withLoadBalancerArn(buildELBArn("test-hello-tag-value-different"))):
[new TagDescription().withResourceArn(buildELBArn("test-hello-tag-value-different")).withTags(new Tag().withKey("hello").withValue("ciao"))],
(new LoadBalancer().withLoadBalancerName("test-hello-tag-no-value").withLoadBalancerArn(buildELBArn("test-hello-tag-no-value"))) :
[new TagDescription().withResourceArn(buildELBArn("test-hello-tag-no-value")).withTags(new Tag().withKey("hello"))],
(new LoadBalancer().withLoadBalancerName("test-no-hello-tag").withLoadBalancerArn(buildELBArn("test-no-hello-tag"))) :
[new TagDescription().withResourceArn(buildELBArn("test-no-hello-tag")).withTags(new Tag().withKey("Name"))],
(new LoadBalancer().withLoadBalancerName("test-no-tags").withLoadBalancerArn(buildELBArn("test-no-tags"))) : []
] as Map
}

private static final List<TargetGroup> filterableTargetGroups() {
return [
new TargetGroup().withTargetGroupName("tg-test-hello-tag-value").withLoadBalancerArns(buildELBArn("test-hello-tag-value")),
new TargetGroup().withTargetGroupName("tg-test-hello-tag-value-different").withLoadBalancerArns(buildELBArn("test-hello-tag-value-different")),
new TargetGroup().withTargetGroupName("tg-test-hello-tag-no-value").withLoadBalancerArns(buildELBArn("test-hello-tag-no-value")),
new TargetGroup().withTargetGroupName("tg-test-no-hello-tag").withLoadBalancerArns(buildELBArn("test-no-hello-tag")),
new TargetGroup().withTargetGroupName("tg-test-no-tags").withLoadBalancerArns(buildELBArn("test-no-tags")),
]
}

private static String buildCacheKey(String name) {
return "aws:loadBalancers:accountName:region:${name}"
}

private static List<String> buildCacheKeys(List<String> names) {
return names.collect {"aws:loadBalancers:accountName:region:${it}" } as List<String>
}

private static String buildTargetGroupCacheKey(String name) {
return "aws:targetGroups:accountName:region:${name}:null:null"
}

private static List<String> buildTargetGroupCacheKeys(List<String> names) {
return names.collect {"aws:targetGroups:accountName:region:${it}:null:null" } as List<String>
}

private static String buildELBArn(String name) {
return "arn:aws:elasticloadbalancing:${region}:${accountId}:loadbalancer/net/${name}/1234567890"
}

private static def taggify(String name = null, String value = null) {
return new AmazonCachingAgentFilter.TagFilterOption(name, value)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ class AmazonCachingAgentFilterSpec extends Specification {
resourceTag("Name", "primary"),] | [filterTag("hello")] | null | true
[resourceTag("hello", "goodbye"),
resourceTag("Name", "primary"),] | null | [filterTag("hello")] | false
null | [filterTag("hello")] | null | false
null | [filterTag("hello")] | [] | false
null | null | [filterTag("hello")] | true
null | [] | [filterTag("hello")] | true
}

private static def resourceTag(String name = null, String value = null) {
Expand Down
Loading

0 comments on commit 99ed0c9

Please sign in to comment.