Skip to content

Commit 36aa330

Browse files
perf(ecs): Narrowing the cache search for the ECS provider on views
1 parent a5fc30d commit 36aa330

File tree

11 files changed

+122
-38
lines changed

11 files changed

+122
-38
lines changed

clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/cache/client/AbstractCacheClient.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ public Collection<T> getAll(String account, String region) {
6565
return convertAll(data);
6666
}
6767

68+
public Collection<T> getAll(Collection<String> identifiers) {
69+
Collection<CacheData> allData = cacheView.getAll(keyNamespace, identifiers);
70+
return convertAll(allData);
71+
}
72+
6873
/**
6974
* @param key A key within the key namespace that will be used to retrieve the object.
7075
* @return An object of the generic type that is associated to the key.
@@ -110,4 +115,8 @@ private Collection<CacheData> fetchFromCache(String account, String region) {
110115

111116
return allData;
112117
}
118+
119+
public Collection<String> filterIdentifiers(String glob) {
120+
return cacheView.filterIdentifiers(keyNamespace, glob);
121+
}
113122
}

clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/cache/client/EcsCloudWatchAlarmCacheClient.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ protected EcsMetricAlarm convert(CacheData cacheData) {
7373
public List<EcsMetricAlarm> getMetricAlarms(
7474
String serviceName, String accountName, String region) {
7575
List<EcsMetricAlarm> metricAlarms = new LinkedList<>();
76+
// we can filter more here.
77+
7678
Collection<EcsMetricAlarm> allMetricAlarms = getAll(accountName, region);
7779

7880
outLoop:

clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/model/EcsApplication.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,16 @@ public class EcsApplication implements Application {
2626
private String name;
2727
Map<String, String> attributes;
2828
Map<String, Set<String>> clusterNames;
29+
Map<String, Set<String>> clusterNameMetadata;
2930

3031
public EcsApplication(
31-
String name, Map<String, String> attributes, Map<String, Set<String>> clusterNames) {
32+
String name,
33+
Map<String, String> attributes,
34+
Map<String, Set<String>> clusterNames,
35+
Map<String, Set<String>> getClusterNameMetadata) {
3236
this.name = name;
3337
this.attributes = attributes;
3438
this.clusterNames = clusterNames;
39+
this.clusterNameMetadata = getClusterNameMetadata;
3540
}
3641
}

clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/provider/view/EcsClusterProvider.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.google.common.collect.Lists;
2424
import com.netflix.spinnaker.cats.cache.Cache;
2525
import com.netflix.spinnaker.clouddriver.aws.security.AmazonClientProvider;
26+
import com.netflix.spinnaker.clouddriver.ecs.cache.Keys;
2627
import com.netflix.spinnaker.clouddriver.ecs.cache.client.EcsClusterCacheClient;
2728
import com.netflix.spinnaker.clouddriver.ecs.cache.model.EcsCluster;
2829
import com.netflix.spinnaker.clouddriver.ecs.security.NetflixECSCredentials;
@@ -55,9 +56,11 @@ public Collection<EcsCluster> getAllEcsClusters() {
5556
// TODO include[] input of Describe Cluster is not a part of this implementation, need to
5657
// implement in the future if additional properties are needed.
5758
public Collection<Cluster> getEcsClusterDescriptions(String account, String region) {
59+
String glob = Keys.getClusterKey(account, region, "*");
60+
Collection<String> ecsClustersIdentifiers = ecsClusterCacheClient.filterIdentifiers(glob);
5861
Collection<Cluster> clusters = new ArrayList<>();
5962
List<String> filteredEcsClusters =
60-
ecsClusterCacheClient.getAll().stream()
63+
ecsClusterCacheClient.getAll(ecsClustersIdentifiers).stream()
6164
.filter(
6265
cluster ->
6366
account.equals(cluster.getAccount()) && region.equals(cluster.getRegion()))

clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/provider/view/EcsLoadBalancerProvider.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.netflix.spinnaker.clouddriver.aws.AmazonCloudProvider;
2424
import com.netflix.spinnaker.clouddriver.aws.data.ArnUtils;
2525
import com.netflix.spinnaker.clouddriver.ecs.EcsCloudProvider;
26+
import com.netflix.spinnaker.clouddriver.ecs.cache.Keys;
2627
import com.netflix.spinnaker.clouddriver.ecs.cache.client.EcsLoadbalancerCacheClient;
2728
import com.netflix.spinnaker.clouddriver.ecs.cache.client.EcsTargetGroupCacheClient;
2829
import com.netflix.spinnaker.clouddriver.ecs.cache.client.ServiceCacheClient;
@@ -118,8 +119,13 @@ public List<Details> byAccountAndRegionAndName(String account, String region, St
118119
@Override
119120
public Set<EcsLoadBalancer> getApplicationLoadBalancers(String application) {
120121
// Find the load balancers currently in use by ECS services in this application
122+
String glob =
123+
application != null
124+
? Keys.getServiceKey("*", "*", application + "*")
125+
: Keys.getServiceKey("*", "*", "*");
126+
Collection<String> ecsServices = ecsServiceCacheClient.filterIdentifiers(glob);
121127
Set<Service> services =
122-
ecsServiceCacheClient.getAll().stream()
128+
ecsServiceCacheClient.getAll(ecsServices).stream()
123129
.filter(service -> service.getApplicationName().equals(application))
124130
.collect(Collectors.toSet());
125131
log.debug("Retrieved {} services for application '{}'", services.size(), application);

clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/provider/view/EcsServerClusterProvider.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,13 @@ private Map<String, Set<EcsServerCluster>> findClustersForRegion(
120120
AmazonCredentials.AWSRegion awsRegion,
121121
String application) {
122122

123-
Collection<Service> services =
124-
serviceCacheClient.getAll(credentials.getName(), awsRegion.getName());
123+
String glob =
124+
application != null
125+
? Keys.getServiceKey(credentials.getName(), awsRegion.getName(), application + "*")
126+
: Keys.getServiceKey(credentials.getName(), awsRegion.getName(), "*");
127+
128+
Collection<String> ecsServices = serviceCacheClient.filterIdentifiers(glob);
129+
Collection<Service> services = serviceCacheClient.getAll(ecsServices);
125130
Collection<Task> allTasks = taskCacheClient.getAll(credentials.getName(), awsRegion.getName());
126131

127132
for (Service service : services) {
@@ -456,7 +461,7 @@ private AmazonCredentials getEcsCredentials(String account) {
456461

457462
@Override
458463
public Map<String, Set<EcsServerCluster>> getClusterSummaries(String application) {
459-
return getClusters();
464+
return getClusters0(application);
460465
}
461466

462467
@Override
@@ -480,6 +485,15 @@ public Map<String, Set<EcsServerCluster>> getClusters() {
480485
return clusterMap;
481486
}
482487

488+
public Map<String, Set<EcsServerCluster>> getClusters0(String application) {
489+
Map<String, Set<EcsServerCluster>> clusterMap = new HashMap<>();
490+
491+
for (AmazonCredentials credentials : getEcsCredentials()) {
492+
clusterMap = findClusters(clusterMap, credentials, application);
493+
}
494+
return clusterMap;
495+
}
496+
483497
/** Gets Spinnaker clusters for a given Spinnaker application and ECS account. */
484498
@Override
485499
public Set<EcsServerCluster> getClusters(String application, String account) {

clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/view/EcsApplicationProvider.java

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import com.google.common.collect.Sets;
2020
import com.netflix.spinnaker.clouddriver.aws.security.AmazonCredentials;
21+
import com.netflix.spinnaker.clouddriver.ecs.cache.Keys;
2122
import com.netflix.spinnaker.clouddriver.ecs.cache.client.ServiceCacheClient;
2223
import com.netflix.spinnaker.clouddriver.ecs.cache.model.Service;
2324
import com.netflix.spinnaker.clouddriver.ecs.model.EcsApplication;
@@ -50,31 +51,32 @@ public EcsApplicationProvider(
5051

5152
@Override
5253
public Application getApplication(String name) {
53-
54-
for (Application application : getApplications(true)) {
54+
name = name.toLowerCase();
55+
String glob = Keys.getServiceKey("*", "*", name + "*");
56+
Collection<String> ecsServices = serviceCacheClient.filterIdentifiers(glob);
57+
for (Application application : populateApplicationSet(ecsServices, true)) {
5558
if (name.equals(application.getName())) {
5659
return application;
5760
}
5861
}
59-
6062
return null;
6163
}
6264

6365
@Override
64-
public Set<Application> getApplications(boolean expand) {
65-
Set<Application> applications = new HashSet<>();
66-
66+
public Set<EcsApplication> getApplications(boolean expand) {
67+
Set<EcsApplication> applications = new HashSet<>();
6768
for (NetflixECSCredentials credentials : credentialsRepository.getAll()) {
68-
Set<Application> retrievedApplications = findApplicationsForAllRegions(credentials, expand);
69+
Set<EcsApplication> retrievedApplications =
70+
findApplicationsForAllRegions(credentials, expand);
6971
applications.addAll(retrievedApplications);
7072
}
7173

7274
return applications;
7375
}
7476

75-
private Set<Application> findApplicationsForAllRegions(
77+
private Set<EcsApplication> findApplicationsForAllRegions(
7678
AmazonCredentials credentials, boolean expand) {
77-
Set<Application> applications = new HashSet<>();
79+
Set<EcsApplication> applications = new HashSet<>();
7880

7981
for (AmazonCredentials.AWSRegion awsRegion : credentials.getRegions()) {
8082
applications.addAll(
@@ -84,16 +86,16 @@ private Set<Application> findApplicationsForAllRegions(
8486
return applications;
8587
}
8688

87-
private Set<Application> findApplicationsForRegion(
89+
private Set<EcsApplication> findApplicationsForRegion(
8890
String account, String region, boolean expand) {
89-
HashMap<String, Application> applicationHashMap =
91+
HashMap<String, EcsApplication> applicationHashMap =
9092
populateApplicationMap(account, region, expand);
9193
return transposeApplicationMapToSet(applicationHashMap);
9294
}
9395

94-
private HashMap<String, Application> populateApplicationMap(
96+
private HashMap<String, EcsApplication> populateApplicationMap(
9597
String account, String region, boolean expand) {
96-
HashMap<String, Application> applicationHashMap = new HashMap<>();
98+
HashMap<String, EcsApplication> applicationHashMap = new HashMap<>();
9799
Collection<Service> services = serviceCacheClient.getAll(account, region);
98100

99101
for (Service service : services) {
@@ -102,19 +104,32 @@ private HashMap<String, Application> populateApplicationMap(
102104
return applicationHashMap;
103105
}
104106

105-
private Set<Application> transposeApplicationMapToSet(
106-
HashMap<String, Application> applicationHashMap) {
107-
Set<Application> applications = new HashSet<>();
107+
private Set<EcsApplication> populateApplicationSet(
108+
Collection<String> identifiers, boolean expand) {
109+
HashMap<String, EcsApplication> applicationHashMap = new HashMap<>();
110+
Collection<Service> services = serviceCacheClient.getAll(identifiers);
108111

109-
for (Map.Entry<String, Application> entry : applicationHashMap.entrySet()) {
112+
for (Service service : services) {
113+
if (credentialsRepository.has(service.getAccount())) {
114+
applicationHashMap = inferApplicationFromServices(applicationHashMap, service, expand);
115+
}
116+
}
117+
return transposeApplicationMapToSet(applicationHashMap);
118+
}
119+
120+
private Set<EcsApplication> transposeApplicationMapToSet(
121+
HashMap<String, EcsApplication> applicationHashMap) {
122+
Set<EcsApplication> applications = new HashSet<>();
123+
124+
for (Map.Entry<String, EcsApplication> entry : applicationHashMap.entrySet()) {
110125
applications.add(entry.getValue());
111126
}
112127

113128
return applications;
114129
}
115130

116-
private HashMap<String, Application> inferApplicationFromServices(
117-
HashMap<String, Application> applicationHashMap, Service service, boolean expand) {
131+
private HashMap<String, EcsApplication> inferApplicationFromServices(
132+
HashMap<String, EcsApplication> applicationHashMap, Service service, boolean expand) {
118133

119134
HashMap<String, String> attributes = new HashMap<>();
120135
Moniker moniker = service.getMoniker();
@@ -125,18 +140,31 @@ private HashMap<String, Application> inferApplicationFromServices(
125140
attributes.put("name", appName);
126141

127142
HashMap<String, Set<String>> clusterNames = new HashMap<>();
143+
HashMap<String, Set<String>> clusterNamesMetadata = new HashMap<>();
144+
128145
if (expand) {
129146
clusterNames.put(accountName, Sets.newHashSet(serviceName));
147+
clusterNamesMetadata.put(accountName, Sets.newHashSet(moniker.getCluster()));
130148
}
131149

132-
EcsApplication application = new EcsApplication(appName, attributes, clusterNames);
150+
EcsApplication application =
151+
new EcsApplication(appName, attributes, clusterNames, clusterNamesMetadata);
133152

134153
if (!applicationHashMap.containsKey(appName)) {
135154
applicationHashMap.put(appName, application);
136155
} else {
137156
applicationHashMap.get(appName).getAttributes().putAll(application.getAttributes());
138157
if (expand) {
139-
applicationHashMap.get(appName).getClusterNames().get(accountName).add(serviceName);
158+
applicationHashMap
159+
.get(appName)
160+
.getClusterNames()
161+
.computeIfAbsent(accountName, k -> Sets.newHashSet())
162+
.add(serviceName);
163+
applicationHashMap
164+
.get(appName)
165+
.getClusterNameMetadata()
166+
.computeIfAbsent(accountName, k -> Sets.newHashSet())
167+
.add(moniker.getCluster());
140168
}
141169
}
142170

clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/provider/view/EcsClusterProviderSpec.groovy

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,21 +47,24 @@ class EcsClusterProviderSpec extends Specification {
4747
Set<String> clusterNames = new HashSet<>()
4848
Collection<CacheData> cacheData = new HashSet<>()
4949
Collection<Cluster> clustersResponse = new ArrayList<>()
50+
Collection<String> ecsClustersIdentifiers = new ArrayList<>()
5051
clusterNames.add("example-app-test-Cluster-NSnYsTXmCfV2")
5152
clusterNames.add("TestCluster")
5253
clusterNames.add("spinnaker-deployment-cluster")
5354

5455
for (int x = 0; x < numberOfClusters; x++) {
5556
String clusterKey = Keys.getClusterKey(ACCOUNT, REGION, clusterNames[x])
5657
Map<String, Object> attributes = new HashMap<>()
58+
ecsClustersIdentifiers.add(Keys.getClusterKey(ACCOUNT, REGION, clusterNames[x]))
5759
attributes.put("account", ACCOUNT)
5860
attributes.put("region", REGION)
5961
attributes.put("clusterArn", "arn:aws:ecs:::cluster/" + clusterNames[x])
6062
attributes.put("clusterName", clusterNames[x])
6163

6264
cacheData.add(new DefaultCacheData(clusterKey, attributes, Collections.emptyMap()))
6365
}
64-
cacheView.getAll(_) >> cacheData
66+
cacheView.filterIdentifiers(_, _) >> ecsClustersIdentifiers
67+
cacheView.getAll(_, ecsClustersIdentifiers) >> cacheData
6568

6669
for (int x = 0; x < numberOfClusters; x++) {
6770
Cluster cluster = new Cluster()
@@ -102,12 +105,14 @@ class EcsClusterProviderSpec extends Specification {
102105
Set<String> clusterNames = new HashSet<>()
103106
Collection<CacheData> cacheData = new HashSet<>()
104107
Collection<Cluster> clustersResponse = new ArrayList<>()
108+
Collection<String> ecsClustersIdentifiers = new ArrayList<>()
105109
clusterNames.add("example-app-test-Cluster-NSnYsTXmCfV2")
106110
clusterNames.add("TestCluster")
107111
clusterNames.add("spinnaker-deployment-cluster")
108112

109113
for (int x = 0; x < 2; x++) {
110114
String clusterKey = Keys.getClusterKey(ACCOUNT, REGION, clusterNames[x])
115+
ecsClustersIdentifiers.add(Keys.getClusterKey(ACCOUNT, REGION, clusterNames[x]))
111116
Map<String, Object> attributes = new HashMap<>()
112117
attributes.put("account", ACCOUNT)
113118
attributes.put("region", REGION)
@@ -126,8 +131,8 @@ class EcsClusterProviderSpec extends Specification {
126131

127132
cacheData.add(new DefaultCacheData(clusterKey, attributes, Collections.emptyMap()))
128133

129-
130-
cacheView.getAll(_) >> cacheData
134+
cacheView.filterIdentifiers(_, _) >> ecsClustersIdentifiers
135+
cacheView.getAll(_, ecsClustersIdentifiers) >> cacheData
131136

132137
//Adding only two clusters in the response which belongs to the expected region.
133138
for (int x = 0; x < 2; x++) {

clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/provider/view/EcsLoadBalancerProviderSpec.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ class EcsLoadBalancerProviderSpec extends Specification {
174174
def loadBalancerList = provider.getApplicationLoadBalancers(applicationName)
175175

176176
then:
177-
mockServiceCache.getAll() >> Collections.singletonList(ecsService)
177+
mockServiceCache.getAll(_) >> Collections.singletonList(ecsService)
178178
mockTargetGroupCache.getAllKeys() >> ['fake-tg-key-1', 'fake-tg-key-2']
179179
mockTargetGroupCache.find(_) >> [ecsTg1, ecsTg2]
180180
mockLBCache.findWithTargetGroups(_) >> [ecsLoadBalancerCache1, ecsLoadBalancerCache2]
@@ -249,7 +249,7 @@ class EcsLoadBalancerProviderSpec extends Specification {
249249
def loadBalancerList = provider.getApplicationLoadBalancers(applicationName)
250250

251251
then:
252-
mockServiceCache.getAll() >> [ecsService1, ecsService2]
252+
mockServiceCache.getAll(_) >> [ecsService1, ecsService2]
253253
mockTargetGroupCache.getAllKeys() >> ['fake-tg-key-1']
254254
mockTargetGroupCache.find(_) >> [ecsTg]
255255
mockLBCache.findWithTargetGroups(_) >> Collections.singletonList(ecsLoadBalancerCache)

clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/view/EcsApplicationProviderSpec.groovy

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
2222
import com.netflix.spinnaker.cats.cache.Cache
2323
import com.netflix.spinnaker.cats.cache.DefaultCacheData
2424
import com.netflix.spinnaker.clouddriver.ecs.TestCredential
25+
import com.netflix.spinnaker.clouddriver.ecs.cache.Keys
2526
import com.netflix.spinnaker.clouddriver.ecs.cache.client.ServiceCacheClient
2627
import com.netflix.spinnaker.clouddriver.ecs.model.EcsApplication
2728
import com.netflix.spinnaker.clouddriver.ecs.provider.agent.ServiceCachingAgent
@@ -45,15 +46,21 @@ class EcsApplicationProviderSpec extends Specification {
4546
def accountName = 'test-account'
4647
def credentials = new NetflixECSCredentials(TestCredential.named(accountName))
4748
def appName = 'testapp'
48-
def serviceName = appName + '-kcats-liated'
49+
def serviceName = appName + '-kcats-liated-v001'
50+
def monikerCluster = appName + '-kcats-liated'
4951
Map<String, Set<String>> clusterNames = new HashMap<>()
5052
clusterNames.put(accountName, Collections.singleton(serviceName))
53+
Map<String, Set<String>> clusterNameMetadata = new HashMap<>()
54+
clusterNameMetadata.put(accountName, Collections.singleton(monikerCluster))
55+
56+
5157

5258
def givenApp = (Application) new EcsApplication(appName,
5359
[
5460
name: appName
5561
],
56-
clusterNames)
62+
clusterNames,
63+
clusterNameMetadata)
5764

5865
def service = new Service(
5966
serviceName: serviceName,
@@ -67,8 +74,9 @@ class EcsApplicationProviderSpec extends Specification {
6774
credentials.getRegions()[0].getName()).convertServiceToAttributes(service)
6875

6976
credentialsRepository.getAll() >> [credentials]
70-
cache.filterIdentifiers(_, _) >> []
71-
cache.getAll(_, _) >> [new DefaultCacheData('key', attributes, [:])]
77+
credentialsRepository.has(accountName) >> true
78+
cache.filterIdentifiers(_, _) >> [Keys.getServiceKey(accountName,"us-east-1",serviceName)]
79+
cache.getAll(_, _) >> [new DefaultCacheData(Keys.getServiceKey(accountName,"us-east-1",serviceName), attributes, [:])]
7280

7381
when:
7482
def retrievedApp = provider.getApplication(appName)

0 commit comments

Comments
 (0)