diff --git a/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/cache/client/ApplicationCacheClient.java b/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/cache/client/ApplicationCacheClient.java new file mode 100644 index 00000000000..662054123c2 --- /dev/null +++ b/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/cache/client/ApplicationCacheClient.java @@ -0,0 +1,117 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * This file 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.clouddriver.ecs.cache.client; + +import static com.netflix.spinnaker.clouddriver.ecs.EcsCloudProvider.ID; +import static com.netflix.spinnaker.clouddriver.ecs.cache.Keys.Namespace.APPLICATIONS; +import static com.netflix.spinnaker.clouddriver.ecs.cache.Keys.Namespace.SERVICES; +import static java.util.stream.Collectors.toSet; + +import com.netflix.spinnaker.cats.cache.Cache; +import com.netflix.spinnaker.cats.cache.CacheData; +import com.netflix.spinnaker.cats.cache.RelationshipCacheFilter; +import com.netflix.spinnaker.clouddriver.ecs.cache.Keys; +import com.netflix.spinnaker.clouddriver.ecs.model.EcsApplication; +import com.netflix.spinnaker.clouddriver.model.Application; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class ApplicationCacheClient extends AbstractCacheClient { + + @Autowired + public ApplicationCacheClient(Cache cacheView) { + super(cacheView, APPLICATIONS.toString()); + } + + @Override + protected Application convert(CacheData cacheData) { + log.debug("Translating CacheData to EcsApplication"); + if (cacheData == null) { + return null; + } + + String appName = (String) cacheData.getAttributes().get("name"); + + Map attributes = new HashMap<>(); + attributes.put("name", appName); + + Map> clusterNames = new HashMap<>(); + + EcsApplication application = new EcsApplication(appName, attributes, clusterNames); + + Set services = getServiceRelationships(cacheData); + log.info("Found {} services for app {}", services.size(), appName); + services.forEach( + key -> { + Map parsedKey = Keys.parse(key); + if (application.getClusterNames().get(parsedKey.get("account")) != null) { + application + .getClusterNames() + .get(parsedKey.get("account")) + .add(parsedKey.get("serviceName")); + } else { + application + .getClusterNames() + .put( + parsedKey.get("account"), + new HashSet<>(Arrays.asList(parsedKey.get("serviceName")))); + } + }); + + log.info( + "Found {} clusterNames for application {}", + application.getClusterNames(), + application.getName()); + return application; + } + + public Application getApplication(String name) { + return convert( + cacheView.get( + APPLICATIONS.ns, + Keys.getApplicationKey(name), + RelationshipCacheFilter.include(SERVICES.ns))); + } + + public Set getApplications(boolean expand) { + RelationshipCacheFilter relationshipFilter = + expand ? RelationshipCacheFilter.include(SERVICES.ns) : RelationshipCacheFilter.none(); + Collection applications = + cacheView.getAll( + APPLICATIONS.ns, + cacheView.filterIdentifiers(APPLICATIONS.ns, ID + ";*"), + relationshipFilter); + log.info("getApplications found {} Spinnaker applications in the cache.", applications.size()); + return applications.stream().map(this::convert).collect(toSet()); + } + + private Set getServiceRelationships(CacheData cacheData) { + Collection serviceRelationships = cacheData.getRelationships().get(SERVICES.ns); + return serviceRelationships == null + ? Collections.emptySet() + : new HashSet<>(serviceRelationships); + } +} diff --git a/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/provider/agent/ApplicationCachingAgent.java b/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/provider/agent/ApplicationCachingAgent.java index f49276228ea..a1ea9eacadb 100644 --- a/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/provider/agent/ApplicationCachingAgent.java +++ b/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/provider/agent/ApplicationCachingAgent.java @@ -1,35 +1,39 @@ /* - * Copyright 2021 Amazon.com, Inc. + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. * - * 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 + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * This file 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.clouddriver.ecs.provider.agent; import static com.netflix.spinnaker.cats.agent.AgentDataType.Authority.AUTHORITATIVE; import static com.netflix.spinnaker.clouddriver.ecs.cache.Keys.Namespace.APPLICATIONS; +import static com.netflix.spinnaker.clouddriver.ecs.cache.Keys.Namespace.SERVICES; import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.services.ecs.AmazonECS; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Sets; import com.netflix.spectator.api.Registry; import com.netflix.spinnaker.cats.agent.AgentDataType; import com.netflix.spinnaker.cats.cache.CacheData; +import com.netflix.spinnaker.cats.cache.DefaultCacheData; import com.netflix.spinnaker.cats.provider.ProviderCache; import com.netflix.spinnaker.clouddriver.aws.security.AmazonClientProvider; import com.netflix.spinnaker.clouddriver.aws.security.NetflixAmazonCredentials; +import com.netflix.spinnaker.clouddriver.ecs.cache.Keys; +import com.netflix.spinnaker.clouddriver.ecs.cache.client.ServiceCacheClient; import com.netflix.spinnaker.clouddriver.ecs.cache.model.Application; +import com.netflix.spinnaker.clouddriver.ecs.cache.model.Service; import java.util.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -71,7 +75,42 @@ public String getAgentType() { @Override protected List getItems(AmazonECS ecs, ProviderCache providerCache) { + // get all ECS services + ServiceCacheClient serviceCacheClient = new ServiceCacheClient(providerCache, objectMapper); + Collection services = serviceCacheClient.getAll(); + log.info("Found {} ECS services for which to cache applications", services.size()); + + Map>> appRelationships = new HashMap<>(); + + for (Service service : services) { + String applicationKey = service.getApplicationName(); + String serviceKey = + Keys.getServiceKey(service.getAccount(), service.getRegion(), service.getServiceName()); + + appRelationships.put( + applicationKey, + updateApplicationRelationships(appRelationships, applicationKey, serviceKey) + .get(applicationKey)); + + log.debug( + "ECS application " + + applicationKey + + " with " + + appRelationships.get(applicationKey).size() + + " relationships"); + } + List applications = new ArrayList<>(); + + for (Map.Entry>> appInfo : appRelationships.entrySet()) { + Application application = new Application(); + application.setName(appInfo.getKey()); + application.setRelationships(appInfo.getValue()); + + applications.add(application); + } + + log.info("Cached {} applications for {} services", applications.size(), services.size()); return applications; } @@ -80,10 +119,44 @@ protected Map> generateFreshData( Collection applications) { Collection applicationData = new LinkedList<>(); + for (Application application : applications) { + Map attributes = convertApplicationToAttributes(application); + String applicationKey = Keys.getApplicationKey(application.getName()); + + applicationData.add( + new DefaultCacheData(applicationKey, attributes, application.getRelationships())); + } + Map> cacheDataMap = new HashMap<>(); - log.info("Amazon ECS ApplicationCachingAgent will cache applications in a future update"); + log.info("Caching " + applicationData.size() + " ECS applications in " + getAgentType()); cacheDataMap.put(APPLICATIONS.toString(), applicationData); return cacheDataMap; } + + private Map>> updateApplicationRelationships( + Map>> appRelationships, + String applicationKey, + String serviceKey) { + Map> existingAppRelation = appRelationships.get(applicationKey); + if (existingAppRelation != null && existingAppRelation.size() > 0) { + log.debug("Updating existing application relation for " + applicationKey); + + Collection serviceRelationship = existingAppRelation.get(SERVICES.ns); + if (serviceRelationship != null && !serviceRelationship.isEmpty()) { + serviceRelationship.add(serviceKey); + } else { + serviceRelationship = Sets.newHashSet(serviceKey); + } + existingAppRelation.put(SERVICES.ns, serviceRelationship); + } else { + log.debug("Creating new application relation for " + applicationKey); + + existingAppRelation = new HashMap>(); + existingAppRelation.put(SERVICES.ns, Sets.newHashSet(serviceKey)); + } + + appRelationships.put(applicationKey, existingAppRelation); + return appRelationships; + } } diff --git a/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/view/EcsApplicationProvider.java b/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/view/EcsApplicationProvider.java index e5ad5ddbc4c..c1889850a67 100644 --- a/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/view/EcsApplicationProvider.java +++ b/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/view/EcsApplicationProvider.java @@ -1,35 +1,23 @@ /* - * Copyright 2017 Lookout, Inc. + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. * - * 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 + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located 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. + * This file 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.clouddriver.ecs.view; -import com.google.common.collect.Sets; -import com.netflix.spinnaker.clouddriver.aws.security.AmazonCredentials; -import com.netflix.spinnaker.clouddriver.ecs.cache.client.ServiceCacheClient; -import com.netflix.spinnaker.clouddriver.ecs.cache.model.Service; -import com.netflix.spinnaker.clouddriver.ecs.model.EcsApplication; -import com.netflix.spinnaker.clouddriver.ecs.security.NetflixECSCredentials; +import com.netflix.spinnaker.clouddriver.ecs.cache.client.ApplicationCacheClient; import com.netflix.spinnaker.clouddriver.model.Application; import com.netflix.spinnaker.clouddriver.model.ApplicationProvider; -import com.netflix.spinnaker.credentials.CredentialsRepository; -import com.netflix.spinnaker.moniker.Moniker; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -37,109 +25,20 @@ @Component public class EcsApplicationProvider implements ApplicationProvider { - private final ServiceCacheClient serviceCacheClient; - private final CredentialsRepository credentialsRepository; + private final ApplicationCacheClient cacheClient; @Autowired - public EcsApplicationProvider( - CredentialsRepository credentialsRepository, - ServiceCacheClient serviceCacheClient) { - this.credentialsRepository = credentialsRepository; - this.serviceCacheClient = serviceCacheClient; + public EcsApplicationProvider(ApplicationCacheClient cacheClient) { + this.cacheClient = cacheClient; } @Override public Application getApplication(String name) { - - for (Application application : getApplications(true)) { - if (name.equals(application.getName())) { - return application; - } - } - - return null; + return cacheClient.getApplication(name); } @Override public Set getApplications(boolean expand) { - Set applications = new HashSet<>(); - - for (NetflixECSCredentials credentials : credentialsRepository.getAll()) { - Set retrievedApplications = findApplicationsForAllRegions(credentials, expand); - applications.addAll(retrievedApplications); - } - - return applications; - } - - private Set findApplicationsForAllRegions( - AmazonCredentials credentials, boolean expand) { - Set applications = new HashSet<>(); - - for (AmazonCredentials.AWSRegion awsRegion : credentials.getRegions()) { - applications.addAll( - findApplicationsForRegion(credentials.getName(), awsRegion.getName(), expand)); - } - - return applications; - } - - private Set findApplicationsForRegion( - String account, String region, boolean expand) { - HashMap applicationHashMap = - populateApplicationMap(account, region, expand); - return transposeApplicationMapToSet(applicationHashMap); - } - - private HashMap populateApplicationMap( - String account, String region, boolean expand) { - HashMap applicationHashMap = new HashMap<>(); - Collection services = serviceCacheClient.getAll(account, region); - - for (Service service : services) { - applicationHashMap = inferApplicationFromServices(applicationHashMap, service, expand); - } - return applicationHashMap; - } - - private Set transposeApplicationMapToSet( - HashMap applicationHashMap) { - Set applications = new HashSet<>(); - - for (Map.Entry entry : applicationHashMap.entrySet()) { - applications.add(entry.getValue()); - } - - return applications; - } - - private HashMap inferApplicationFromServices( - HashMap applicationHashMap, Service service, boolean expand) { - - HashMap attributes = new HashMap<>(); - Moniker moniker = service.getMoniker(); - - String appName = moniker.getApp(); - String serviceName = service.getServiceName(); - String accountName = service.getAccount(); - attributes.put("name", appName); - - HashMap> clusterNames = new HashMap<>(); - if (expand) { - clusterNames.put(accountName, Sets.newHashSet(serviceName)); - } - - EcsApplication application = new EcsApplication(appName, attributes, clusterNames); - - if (!applicationHashMap.containsKey(appName)) { - applicationHashMap.put(appName, application); - } else { - applicationHashMap.get(appName).getAttributes().putAll(application.getAttributes()); - if (expand) { - applicationHashMap.get(appName).getClusterNames().get(accountName).add(serviceName); - } - } - - return applicationHashMap; + return cacheClient.getApplications(expand); } } diff --git a/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/security/EcsCredentialsLifeCyclerHandlerSpec.groovy b/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/security/EcsCredentialsLifeCyclerHandlerSpec.groovy index 61ab13a8201..bb4110ef73e 100644 --- a/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/security/EcsCredentialsLifeCyclerHandlerSpec.groovy +++ b/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/security/EcsCredentialsLifeCyclerHandlerSpec.groovy @@ -42,7 +42,7 @@ class EcsCredentialsLifeCyclerHandlerSpec extends Specification { given: def handler = new EcsCredentialsLifeCycleHandler(ecsProvider, null, null, registry, null, objectMapper, null, ecsAccountMapper) - Set expectedClasses = [ IamRoleCachingAgent.class, EcsClusterCachingAgent.class, ServiceCachingAgent.class, + Set expectedClasses = [ ApplicationCachingAgent.class, IamRoleCachingAgent.class, EcsClusterCachingAgent.class, ServiceCachingAgent.class, TaskCachingAgent.class, ContainerInstanceCachingAgent.class, TaskDefinitionCachingAgent.class, TaskHealthCachingAgent.class, EcsCloudMetricAlarmCachingAgent.class, ScalableTargetsCachingAgent.class, SecretCachingAgent.class, ServiceDiscoveryCachingAgent.class, TargetHealthCachingAgent.class, diff --git a/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/view/EcsApplicationProviderSpec.groovy b/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/view/EcsApplicationProviderSpec.groovy index f6a34981af9..703407403b4 100644 --- a/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/view/EcsApplicationProviderSpec.groovy +++ b/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/view/EcsApplicationProviderSpec.groovy @@ -18,31 +18,28 @@ package com.netflix.spinnaker.clouddriver.ecs.view import com.amazonaws.services.ecs.model.DeploymentConfiguration import com.amazonaws.services.ecs.model.Service -import com.fasterxml.jackson.databind.ObjectMapper import com.netflix.spinnaker.cats.cache.Cache import com.netflix.spinnaker.cats.cache.DefaultCacheData import com.netflix.spinnaker.clouddriver.ecs.TestCredential -import com.netflix.spinnaker.clouddriver.ecs.cache.client.ServiceCacheClient +import com.netflix.spinnaker.clouddriver.ecs.cache.client.ApplicationCacheClient +import com.netflix.spinnaker.clouddriver.ecs.cache.Keys import com.netflix.spinnaker.clouddriver.ecs.model.EcsApplication -import com.netflix.spinnaker.clouddriver.ecs.provider.agent.ServiceCachingAgent import com.netflix.spinnaker.clouddriver.ecs.provider.agent.TestServiceCachingAgentFactory import com.netflix.spinnaker.clouddriver.ecs.security.NetflixECSCredentials import com.netflix.spinnaker.clouddriver.model.Application -import com.netflix.spinnaker.credentials.CredentialsRepository import spock.lang.Specification import spock.lang.Subject class EcsApplicationProviderSpec extends Specification { - def mapper = new ObjectMapper() def cache = Mock(Cache) - def serviceCacheClient = new ServiceCacheClient(cache, mapper) - def credentialsRepository = Mock(CredentialsRepository) + def applicationCacheClient = new ApplicationCacheClient(cache) @Subject - def provider = new EcsApplicationProvider(credentialsRepository, serviceCacheClient) + def provider = new EcsApplicationProvider(applicationCacheClient) def 'should return an application'() { given: def accountName = 'test-account' + def region = 'us-east-1' def credentials = new NetflixECSCredentials(TestCredential.named(accountName)) def appName = 'testapp' def serviceName = appName + '-kcats-liated' @@ -65,10 +62,14 @@ class EcsApplicationProviderSpec extends Specification { ) def attributes = TestServiceCachingAgentFactory.create(credentials, credentials.getRegions()[0].getName()).convertServiceToAttributes(service) + attributes.put("name", appName) + attributes.put("account", accountName) + attributes.put("region", region) + + def relationships = [Keys.getServiceKey(accountName, region, serviceName)] - credentialsRepository.getAll() >> [credentials] cache.filterIdentifiers(_, _) >> [] - cache.getAll(_, _) >> [new DefaultCacheData('key', attributes, [:])] + cache.get(_, _, _) >> new DefaultCacheData(appName, attributes, [(Keys.Namespace.SERVICES.ns):relationships]) when: def retrievedApp = provider.getApplication(appName) diff --git a/clouddriver-ecs/src/test/java/com/netflix/spinnaker/clouddriver/ecs/cache/ApplicationCacheClientTest.java b/clouddriver-ecs/src/test/java/com/netflix/spinnaker/clouddriver/ecs/cache/ApplicationCacheClientTest.java new file mode 100644 index 00000000000..f8450081a90 --- /dev/null +++ b/clouddriver-ecs/src/test/java/com/netflix/spinnaker/clouddriver/ecs/cache/ApplicationCacheClientTest.java @@ -0,0 +1,163 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * This file 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.clouddriver.ecs.cache; + +import static com.netflix.spinnaker.clouddriver.ecs.cache.Keys.Namespace.APPLICATIONS; +import static com.netflix.spinnaker.clouddriver.ecs.cache.Keys.Namespace.SERVICES; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.when; + +import com.google.common.collect.Sets; +import com.netflix.spinnaker.cats.cache.DefaultCacheData; +import com.netflix.spinnaker.clouddriver.ecs.cache.client.ApplicationCacheClient; +import com.netflix.spinnaker.clouddriver.ecs.model.EcsApplication; +import com.netflix.spinnaker.clouddriver.model.Application; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import org.junit.Test; +import spock.lang.Subject; + +public class ApplicationCacheClientTest extends CommonCacheClient { + + @Subject private final ApplicationCacheClient client = new ApplicationCacheClient(cacheView); + + @Test + public void shouldConvert() { + // Given + String key = Keys.getApplicationKey(APP_NAME); + + Map appAttributes = new HashMap<>(); + appAttributes.put("name", APP_NAME); + + Map> clusterNames = new HashMap<>(); + + Application application = new EcsApplication(APP_NAME, appAttributes, clusterNames); + + Map cacheAttributes = new HashMap<>(); + cacheAttributes.put("name", application.getName()); + + when(cacheView.get(APPLICATIONS.toString(), key)) + .thenReturn(new DefaultCacheData(key, cacheAttributes, Collections.emptyMap())); + + // When + Application retrievedApplication = client.get(key); + + // Then + assertTrue( + "Expected the application to be " + application + " but got " + retrievedApplication, + application.equals(retrievedApplication)); + } + + @Test + public void shouldGetApplication() { + // Given + String key = Keys.getApplicationKey(APP_NAME); + String serviceKey = Keys.getServiceKey(ACCOUNT, REGION, SERVICE_NAME); + + Map appAttributes = new HashMap<>(); + appAttributes.put("name", APP_NAME); + + Map> relationships = new HashMap<>(); + relationships.put(SERVICES.ns, Sets.newHashSet(serviceKey)); + + Map> clusterNames = new HashMap<>(); + clusterNames.put(ACCOUNT, Sets.newHashSet(SERVICE_NAME)); + + Application application = new EcsApplication(APP_NAME, appAttributes, clusterNames); + + Map cacheAttributes = new HashMap<>(); + cacheAttributes.put("name", application.getName()); + + when(cacheView.get(eq(APPLICATIONS.ns), eq(key), any())) + .thenReturn(new DefaultCacheData(key, cacheAttributes, relationships)); + + // When + Application retrievedApplication = client.getApplication(APP_NAME); + + // Then + assertTrue( + "Expected the application to be " + application + " but got " + retrievedApplication, + application.equals(retrievedApplication)); + } + + @Test + public void shouldGetApplications() { + // Given + String key = Keys.getApplicationKey(APP_NAME); + String serviceKey = Keys.getServiceKey(ACCOUNT, REGION, SERVICE_NAME); + + Map appAttributes = new HashMap<>(); + appAttributes.put("name", APP_NAME); + + Map> relationships = new HashMap<>(); + relationships.put(SERVICES.ns, Sets.newHashSet(serviceKey)); + + Map> clusterNames = new HashMap<>(); + clusterNames.put(ACCOUNT, Sets.newHashSet(SERVICE_NAME)); + + Application application = new EcsApplication(APP_NAME, appAttributes, clusterNames); + + Map cacheAttributes = new HashMap<>(); + cacheAttributes.put("name", application.getName()); + + when(cacheView.getAll(eq(APPLICATIONS.ns), any(), any())) + .thenReturn(Sets.newHashSet(new DefaultCacheData(key, cacheAttributes, relationships))); + + // When + Set retrievedApplication = client.getApplications(false); + + // Then + assertTrue( + "Expected the application to be " + application + " but got " + retrievedApplication, + Sets.newHashSet(application).equals(retrievedApplication)); + } + + @Test + public void shouldGetApplicationsExpanded() { + // Given + String key = Keys.getApplicationKey(APP_NAME); + String serviceKey = Keys.getServiceKey(ACCOUNT, REGION, SERVICE_NAME); + + Map appAttributes = new HashMap<>(); + appAttributes.put("name", APP_NAME); + + Map> relationships = new HashMap<>(); + relationships.put(SERVICES.ns, Sets.newHashSet(serviceKey)); + + Map> clusterNames = new HashMap<>(); + clusterNames.put(ACCOUNT, Sets.newHashSet(SERVICE_NAME)); + + Application application = new EcsApplication(APP_NAME, appAttributes, clusterNames); + + Map cacheAttributes = new HashMap<>(); + cacheAttributes.put("name", application.getName()); + + when(cacheView.getAll(eq(APPLICATIONS.ns), any(), any())) + .thenReturn(Sets.newHashSet(new DefaultCacheData(key, cacheAttributes, relationships))); + + // When + Set retrievedApplication = client.getApplications(true); + + // Then + assertTrue( + "Expected the application to be " + application + " but got " + retrievedApplication, + Sets.newHashSet(application).equals(retrievedApplication)); + } +} diff --git a/clouddriver-ecs/src/test/java/com/netflix/spinnaker/clouddriver/ecs/cache/CommonCacheClient.java b/clouddriver-ecs/src/test/java/com/netflix/spinnaker/clouddriver/ecs/cache/CommonCacheClient.java index c9950d2473e..31a1c6f5e33 100644 --- a/clouddriver-ecs/src/test/java/com/netflix/spinnaker/clouddriver/ecs/cache/CommonCacheClient.java +++ b/clouddriver-ecs/src/test/java/com/netflix/spinnaker/clouddriver/ecs/cache/CommonCacheClient.java @@ -23,6 +23,8 @@ class CommonCacheClient { static final String REGION = "us-west-2"; static final String ACCOUNT = "test-account"; + static final String APP_NAME = "testapp"; + static final String SERVICE_NAME = "test-service"; final Cache cacheView = mock(Cache.class); } diff --git a/clouddriver-ecs/src/test/java/com/netflix/spinnaker/clouddriver/ecs/provider/agent/ApplicationCacheTest.java b/clouddriver-ecs/src/test/java/com/netflix/spinnaker/clouddriver/ecs/provider/agent/ApplicationCacheTest.java new file mode 100644 index 00000000000..b2de3720e32 --- /dev/null +++ b/clouddriver-ecs/src/test/java/com/netflix/spinnaker/clouddriver/ecs/provider/agent/ApplicationCacheTest.java @@ -0,0 +1,100 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * This file 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.clouddriver.ecs.provider.agent; + +import static com.netflix.spinnaker.clouddriver.ecs.cache.Keys.Namespace.APPLICATIONS; +import static junit.framework.TestCase.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Sets; +import com.netflix.spinnaker.cats.agent.CacheResult; +import com.netflix.spinnaker.cats.cache.CacheData; +import com.netflix.spinnaker.cats.cache.DefaultCacheData; +import com.netflix.spinnaker.clouddriver.ecs.cache.Keys; +import com.netflix.spinnaker.clouddriver.ecs.cache.client.ApplicationCacheClient; +import com.netflix.spinnaker.clouddriver.ecs.model.EcsApplication; +import com.netflix.spinnaker.clouddriver.model.Application; +import java.util.*; +import org.junit.Assert; +import org.junit.Test; +import spock.lang.Subject; + +public class ApplicationCacheTest extends CommonCachingAgent { + private final ObjectMapper mapper = new ObjectMapper(); + + @Subject + private final ApplicationCachingAgent agent = + new ApplicationCachingAgent( + netflixAmazonCredentials, REGION, clientProvider, credentialsProvider, registry, mapper); + + @Subject private final ApplicationCacheClient client = new ApplicationCacheClient(providerCache); + + @Test + public void shouldRetrieveFromWrittenCache() { + // Given + String key = Keys.getApplicationKey(APP_NAME); + + Map attributes = new HashMap<>(); + attributes.put("name", APP_NAME); + + Map> clusterNames = new HashMap<>(); + clusterNames.put(ACCOUNT, Sets.newHashSet(SERVICE_NAME_1)); + + EcsApplication application = new EcsApplication(APP_NAME, attributes, clusterNames); + + Map serviceAttr = new HashMap<>(); + serviceAttr.put("account", ACCOUNT); + serviceAttr.put("region", REGION); + serviceAttr.put("taskDefinition", TASK_DEFINITION_ARN_1); + serviceAttr.put("desiredCount", 1); + serviceAttr.put("applicationName", APP_NAME); + serviceAttr.put("serviceName", SERVICE_NAME_1); + serviceAttr.put("maximumPercent", 200); + serviceAttr.put("minimumHealthyPercent", 50); + serviceAttr.put("createdAt", 8976543L); + + DefaultCacheData serviceCache = + new DefaultCacheData("test-service", serviceAttr, Collections.emptyMap()); + + when(providerCache.filterIdentifiers( + APPLICATIONS.toString(), "ecs;services;test-account;us-west-2;*")) + .thenReturn(Collections.singletonList("test-service")); + when(providerCache.getAll(anyString())).thenReturn(Collections.singletonList(serviceCache)); + + // When + CacheResult cacheResult = agent.loadData(providerCache); + when(providerCache.get(APPLICATIONS.toString(), key)) + .thenReturn(cacheResult.getCacheResults().get(APPLICATIONS.toString()).iterator().next()); + Application retrievedApplication = client.get(key); + + // Then + Collection cacheData = cacheResult.getCacheResults().get(APPLICATIONS.toString()); + assertNotNull("Expected CacheData to be returned but null is returned", cacheData); + assertEquals("Expected 1 CacheData but returned " + cacheData.size(), 1, cacheData.size()); + String retrievedKey = cacheData.iterator().next().getId(); + assertEquals( + "Expected CacheData with ID " + key + " but retrieved ID " + retrievedKey, + retrievedKey, + key); + + Assert.assertEquals( + "Expected the application to be " + application + " but got " + retrievedApplication, + application, + retrievedApplication); + } +} diff --git a/clouddriver-ecs/src/test/java/com/netflix/spinnaker/clouddriver/ecs/provider/agent/ApplicationCachingAgentTest.java b/clouddriver-ecs/src/test/java/com/netflix/spinnaker/clouddriver/ecs/provider/agent/ApplicationCachingAgentTest.java new file mode 100644 index 00000000000..b51eca6f7c6 --- /dev/null +++ b/clouddriver-ecs/src/test/java/com/netflix/spinnaker/clouddriver/ecs/provider/agent/ApplicationCachingAgentTest.java @@ -0,0 +1,133 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * This file 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.clouddriver.ecs.provider.agent; + +import static com.netflix.spinnaker.clouddriver.ecs.cache.Keys.Namespace.APPLICATIONS; +import static com.netflix.spinnaker.clouddriver.ecs.cache.Keys.Namespace.SERVICES; +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.netflix.spinnaker.cats.cache.CacheData; +import com.netflix.spinnaker.cats.cache.DefaultCacheData; +import com.netflix.spinnaker.clouddriver.ecs.cache.Keys; +import com.netflix.spinnaker.clouddriver.ecs.cache.model.Application; +import java.util.*; +import org.junit.Test; +import spock.lang.Subject; + +public class ApplicationCachingAgentTest extends CommonCachingAgent { + ObjectMapper mapper = new ObjectMapper(); + + @Subject + private final ApplicationCachingAgent agent = + new ApplicationCachingAgent( + netflixAmazonCredentials, REGION, clientProvider, credentialsProvider, registry, mapper); + + @Test + public void shouldGetListOfApplications() { + // Given + Map serviceAttr = new HashMap<>(); + serviceAttr.put("account", ACCOUNT); + serviceAttr.put("region", REGION); + serviceAttr.put("taskDefinition", TASK_DEFINITION_ARN_1); + serviceAttr.put("desiredCount", 1); + serviceAttr.put("applicationName", APP_NAME); + serviceAttr.put("serviceName", SERVICE_NAME_1); + serviceAttr.put("maximumPercent", 200); + serviceAttr.put("minimumHealthyPercent", 50); + serviceAttr.put("createdAt", 8976543L); + + DefaultCacheData serviceCache = + new DefaultCacheData("test-service", serviceAttr, Collections.emptyMap()); + when(providerCache.filterIdentifiers( + SERVICES.toString(), "ecs;services;test-account;us-west-2;*")) + .thenReturn(Collections.singletonList("test-service")); + when(providerCache.getAll(anyString())).thenReturn(Collections.singletonList(serviceCache)); + + // When + List returnedApplications = agent.getItems(ecs, providerCache); + + // Then + assertEquals( + "Expected the list to contain 1 ECS application, but got " + returnedApplications.size(), + 1, + returnedApplications.size()); + for (Application application : returnedApplications) { + assertEquals( + "Expected the application to be " + APP_NAME + " but it was: " + application.getName(), + application.getName(), + APP_NAME); + } + } + + @Test + public void shouldGenerateFreshData() { + // Given + List applicationNames = new LinkedList<>(); + applicationNames.add(APP_NAME); + applicationNames.add(APP_NAME_2); + + List applications = new LinkedList<>(); + Set keys = new HashSet<>(); + for (String appName : applicationNames) { + keys.add(Keys.getApplicationKey(appName)); + Application application = new Application(); + application.setName(appName); + applications.add(application); + } + + // When + Map> dataMap = agent.generateFreshData(applications); + + // Then + assertTrue( + "Expected the data map to contain 1 namespaces, but it contains " + + dataMap.keySet().size() + + " namespaces.", + dataMap.keySet().size() == 1); + assertTrue( + "Expected the data map to contain " + + APPLICATIONS.toString() + + " namespace, but it contains " + + dataMap.keySet() + + " namespaces.", + dataMap.containsKey(APPLICATIONS.toString())); + assertTrue( + "Expected there to be 2 CacheData, instead there is " + + dataMap.get(APPLICATIONS.toString()).size(), + dataMap.get(APPLICATIONS.toString()).size() == 2); + + for (CacheData cacheData : dataMap.get(APPLICATIONS.toString())) { + assertTrue( + "Expected the key to be one of the following keys: " + + keys.toString() + + ". The key is: " + + cacheData.getId() + + ".", + keys.contains(cacheData.getId())); + assertTrue( + "Expected the application name to be one of the following " + + applicationNames.toString() + + ". The application name is: " + + cacheData.getAttributes().get("name") + + ".", + applicationNames.contains(cacheData.getAttributes().get("name"))); + } + } +} diff --git a/clouddriver-ecs/src/test/java/com/netflix/spinnaker/clouddriver/ecs/provider/agent/CommonCachingAgent.java b/clouddriver-ecs/src/test/java/com/netflix/spinnaker/clouddriver/ecs/provider/agent/CommonCachingAgent.java index bb245466a15..15c17bff207 100644 --- a/clouddriver-ecs/src/test/java/com/netflix/spinnaker/clouddriver/ecs/provider/agent/CommonCachingAgent.java +++ b/clouddriver-ecs/src/test/java/com/netflix/spinnaker/clouddriver/ecs/provider/agent/CommonCachingAgent.java @@ -36,6 +36,7 @@ public class CommonCachingAgent { private static final String ECS_SERIVCE = "arn:aws:ecs:" + REGION + ":" + ACCOUNT_ID + ":"; static final String ACCOUNT = "test-account"; static final String APP_NAME = "testapp"; + static final String APP_NAME_2 = "testapp-2"; static final String ROLE_ARN = ECS_SERIVCE + "service/test-role"; static final String STATUS = "RUNNING";