diff --git a/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/deploy/description/CreateServerGroupDescription.java b/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/deploy/description/CreateServerGroupDescription.java index 4cf318b0933..6c12ae141ad 100644 --- a/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/deploy/description/CreateServerGroupDescription.java +++ b/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/deploy/description/CreateServerGroupDescription.java @@ -48,7 +48,9 @@ public class CreateServerGroupDescription extends AbstractECSDescription { Map> availabilityZones; - List autoscalingPolicies; + boolean copySourceScalingPoliciesAndActions = true; + Source source = new Source(); + List placementStrategySequence; String networkMode; String subnetType; @@ -66,4 +68,13 @@ public String getRegion() { //CreateServerGroupDescription does not contain a region. Instead it has AvailabilityZones return getAvailabilityZones().keySet().iterator().next(); } + + @Data + @EqualsAndHashCode(callSuper = false) + public static class Source { + String account; + String region; + String asgName; + Boolean useSourceCapacity; + } } diff --git a/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/deploy/ops/CreateServerGroupAtomicOperation.java b/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/deploy/ops/CreateServerGroupAtomicOperation.java index 0fa600704c2..7717e9492e3 100644 --- a/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/deploy/ops/CreateServerGroupAtomicOperation.java +++ b/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/deploy/ops/CreateServerGroupAtomicOperation.java @@ -18,10 +18,7 @@ import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.services.applicationautoscaling.AWSApplicationAutoScaling; -import com.amazonaws.services.applicationautoscaling.model.RegisterScalableTargetRequest; -import com.amazonaws.services.applicationautoscaling.model.ScalableDimension; -import com.amazonaws.services.applicationautoscaling.model.ServiceNamespace; -import com.amazonaws.services.cloudwatch.model.MetricAlarm; +import com.amazonaws.services.applicationautoscaling.model.*; import com.amazonaws.services.ecs.AmazonECS; import com.amazonaws.services.ecs.model.*; import com.amazonaws.services.elasticloadbalancingv2.AmazonElasticLoadBalancing; @@ -47,13 +44,7 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; public class CreateServerGroupAtomicOperation extends AbstractEcsAtomicOperation { @@ -96,6 +87,8 @@ public DeploymentResult operate(List priorOutputs) { String newServerGroupName = serverGroupNameResolver.resolveNextServerGroupName(description.getApplication(), description.getStack(), description.getFreeFormDetails(), false); + ScalableTarget sourceTarget = getSourceScalableTarget(); + String ecsServiceRole = inferAssumedRoleArn(credentials); updateTaskStatus("Creating Amazon ECS Task Definition..."); @@ -106,11 +99,19 @@ public DeploymentResult operate(List priorOutputs) { String resourceId = registerAutoScalingGroup(credentials, service); - if (!description.getAutoscalingPolicies().isEmpty()) { - List alarmNames = description.getAutoscalingPolicies().stream() - .map(MetricAlarm::getAlarmName) - .collect(Collectors.toList()); - ecsCloudMetricService.associateAsgWithMetrics(description.getCredentialAccount(), getRegion(), alarmNames, service.getServiceName(), resourceId); + if (description.isCopySourceScalingPoliciesAndActions() && sourceTarget != null) { + updateTaskStatus("Copying scaling policies..."); + ecsCloudMetricService.copyScalingPolicies( + description.getCredentialAccount(), + getRegion(), + service.getServiceName(), + resourceId, + description.getSource().getAccount(), + description.getSource().getRegion(), + description.getSource().getAsgName(), + sourceTarget.getResourceId(), + description.getEcsClusterName()); + updateTaskStatus("Done copying scaling policies..."); } return makeDeploymentResult(service); @@ -308,6 +309,30 @@ private String registerAutoScalingGroup(AmazonCredentials credentials, return request.getResourceId(); } + private ScalableTarget getSourceScalableTarget() { + if (description.getSource() != null + && description.getSource().getRegion() != null + && description.getSource().getAccount() != null + && description.getSource().getAsgName() != null) { + + AWSApplicationAutoScaling autoScalingClient = getSourceAmazonApplicationAutoScalingClient(); + + DescribeScalableTargetsRequest request = new DescribeScalableTargetsRequest() + .withServiceNamespace(ServiceNamespace.Ecs) + .withScalableDimension(ScalableDimension.EcsServiceDesiredCount) + .withResourceIds(String.format("service/%s/%s", description.getEcsClusterName(), description.getSource().getAsgName())); + + DescribeScalableTargetsResult result = autoScalingClient.describeScalableTargets(request); + if (result.getScalableTargets() != null && !result.getScalableTargets().isEmpty()) { + return result.getScalableTargets().get(0); + } + + return null; + } + + return null; + } + private String inferAssumedRoleArn(AmazonCredentials credentials) { String role; if (credentials instanceof AssumeRoleAmazonCredentials) { @@ -377,6 +402,12 @@ private LoadBalancer retrieveLoadBalancer(String containerName) { return loadBalancer; } + private AWSApplicationAutoScaling getSourceAmazonApplicationAutoScalingClient() { + String sourceRegion = description.getSource().getRegion(); + NetflixAmazonCredentials sourceCredentials = (NetflixAmazonCredentials)accountCredentialsProvider.getCredentials(description.getSource().getAccount()); + return amazonClientProvider.getAmazonApplicationAutoScaling(sourceCredentials, sourceRegion, false); + } + private AWSApplicationAutoScaling getAmazonApplicationAutoScalingClient() { AWSCredentialsProvider credentialsProvider = getCredentials().getCredentialsProvider(); NetflixAmazonCredentials credentialAccount = description.getCredentials(); diff --git a/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/deploy/validators/EcsCreateServerGroupDescriptionValidator.java b/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/deploy/validators/EcsCreateServerGroupDescriptionValidator.java index dfb161acbd9..e3b127330ca 100644 --- a/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/deploy/validators/EcsCreateServerGroupDescriptionValidator.java +++ b/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/deploy/validators/EcsCreateServerGroupDescriptionValidator.java @@ -98,10 +98,6 @@ public void validate(List priorDescriptions, Object description, Errors errors) rejectValue(errors, "placementStrategySequence", "not.nullable"); } - if (createServerGroupDescription.getAutoscalingPolicies() == null) { - rejectValue(errors, "autoscalingPolicies", "not.nullable"); - } - if (createServerGroupDescription.getApplication() == null) { rejectValue(errors, "application", "not.nullable"); } diff --git a/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/services/EcsCloudMetricService.java b/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/services/EcsCloudMetricService.java index 5419555a55b..7f8b3cef224 100644 --- a/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/services/EcsCloudMetricService.java +++ b/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/services/EcsCloudMetricService.java @@ -17,21 +17,10 @@ package com.netflix.spinnaker.clouddriver.ecs.services; import com.amazonaws.services.applicationautoscaling.AWSApplicationAutoScaling; -import com.amazonaws.services.applicationautoscaling.model.DeregisterScalableTargetRequest; -import com.amazonaws.services.applicationautoscaling.model.DescribeScalableTargetsRequest; -import com.amazonaws.services.applicationautoscaling.model.DescribeScalableTargetsResult; -import com.amazonaws.services.applicationautoscaling.model.DescribeScalingPoliciesRequest; -import com.amazonaws.services.applicationautoscaling.model.DescribeScalingPoliciesResult; -import com.amazonaws.services.applicationautoscaling.model.PutScalingPolicyRequest; -import com.amazonaws.services.applicationautoscaling.model.PutScalingPolicyResult; -import com.amazonaws.services.applicationautoscaling.model.ScalingPolicy; -import com.amazonaws.services.applicationautoscaling.model.ServiceNamespace; +import com.amazonaws.services.applicationautoscaling.model.*; import com.amazonaws.services.cloudwatch.AmazonCloudWatch; -import com.amazonaws.services.cloudwatch.model.DeleteAlarmsRequest; -import com.amazonaws.services.cloudwatch.model.DescribeAlarmsRequest; -import com.amazonaws.services.cloudwatch.model.DescribeAlarmsResult; -import com.amazonaws.services.cloudwatch.model.MetricAlarm; -import com.amazonaws.services.cloudwatch.model.PutMetricAlarmRequest; +import com.amazonaws.services.cloudwatch.model.*; +import com.google.common.collect.Iterables; import com.netflix.spinnaker.clouddriver.aws.security.AmazonClientProvider; import com.netflix.spinnaker.clouddriver.aws.security.AmazonCredentials; import com.netflix.spinnaker.clouddriver.aws.security.NetflixAmazonCredentials; @@ -39,15 +28,12 @@ import com.netflix.spinnaker.clouddriver.ecs.cache.model.EcsMetricAlarm; import com.netflix.spinnaker.clouddriver.security.AccountCredentialsProvider; import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; @Component @@ -59,6 +45,8 @@ public class EcsCloudMetricService { @Autowired AmazonClientProvider amazonClientProvider; + private final Logger log = LoggerFactory.getLogger(getClass()); + public void deleteMetrics(String serviceName, String account, String region) { List metricAlarms = metricAlarmCacheClient.getMetricAlarms(serviceName, account, region); @@ -66,8 +54,8 @@ public void deleteMetrics(String serviceName, String account, String region) { return; } - AmazonCredentials credentials = (AmazonCredentials) accountCredentialsProvider.getCredentials(account); - AmazonCloudWatch amazonCloudWatch = amazonClientProvider.getAmazonCloudWatch(account, credentials.getCredentialsProvider(), region); + NetflixAmazonCredentials credentials = (NetflixAmazonCredentials) accountCredentialsProvider.getCredentials(account); + AmazonCloudWatch amazonCloudWatch = amazonClientProvider.getAmazonCloudWatch(credentials, region, false); amazonCloudWatch.deleteAlarms(new DeleteAlarmsRequest().withAlarmNames(metricAlarms.stream() .map(MetricAlarm::getAlarmName) @@ -143,18 +131,24 @@ private void deregisterScalableTargets(Set resources, String account, St } private PutMetricAlarmRequest buildPutMetricAlarmRequest(MetricAlarm metricAlarm, - String serviceName, - Set insufficientActionPolicyArns, - Set okActionPolicyArns, - Set alarmActionPolicyArns) { + String alarmName, + String dstServiceName, + String clusterName, + String srcRegion, + String dstRegion, + String srcAccountId, + String dstAccountId, + Map policyArnReplacements) { return new PutMetricAlarmRequest() - .withAlarmName(metricAlarm.getAlarmName() + "-" + serviceName) + .withAlarmName(alarmName) .withEvaluationPeriods(metricAlarm.getEvaluationPeriods()) .withThreshold(metricAlarm.getThreshold()) .withActionsEnabled(metricAlarm.getActionsEnabled()) .withAlarmDescription(metricAlarm.getAlarmDescription()) .withComparisonOperator(metricAlarm.getComparisonOperator()) - .withDimensions(metricAlarm.getDimensions()) + .withDimensions(metricAlarm.getDimensions().stream() + .map(dimension -> buildNewServiceAlarmDimension(dimension, metricAlarm.getNamespace(), dstServiceName, clusterName)) + .collect(Collectors.toSet())) .withMetricName(metricAlarm.getMetricName()) .withUnit(metricAlarm.getUnit()) .withPeriod(metricAlarm.getPeriod()) @@ -163,9 +157,51 @@ private PutMetricAlarmRequest buildPutMetricAlarmRequest(MetricAlarm metricAlarm .withEvaluateLowSampleCountPercentile(metricAlarm.getEvaluateLowSampleCountPercentile()) .withTreatMissingData(metricAlarm.getTreatMissingData()) .withExtendedStatistic(metricAlarm.getExtendedStatistic()) - .withInsufficientDataActions(insufficientActionPolicyArns) - .withOKActions(okActionPolicyArns) - .withAlarmActions(alarmActionPolicyArns); + .withInsufficientDataActions(replacePolicyArnActions( + srcRegion, dstRegion, srcAccountId, dstAccountId, policyArnReplacements, metricAlarm.getInsufficientDataActions())) + .withOKActions(replacePolicyArnActions( + srcRegion, dstRegion, srcAccountId, dstAccountId, policyArnReplacements, metricAlarm.getOKActions())) + .withAlarmActions(replacePolicyArnActions( + srcRegion, dstRegion, srcAccountId, dstAccountId, policyArnReplacements, metricAlarm.getAlarmActions())); + } + + protected Collection replacePolicyArnActions(String srcRegion, + String dstRegion, + String srcAccountId, + String dstAccountId, + Map replacements, + Collection actions) { + return actions.stream() + // Replace src scaling policy ARNs with dst scaling policy ARNs + .map(action -> replacements.keySet().contains(action) ? replacements.get(action) : action) + // If we are copying across accounts or regions, do not copy over unrelated actions like SNS topics + .filter(action -> srcRegion.equals(dstRegion) || !action.contains(srcRegion)) + .filter(action -> srcAccountId.equals(dstAccountId) || !action.contains(srcAccountId)) + .collect(Collectors.toSet()); + } + + private Dimension buildNewServiceAlarmDimension(Dimension oldDimension, String namespace, String serviceName, String clusterName) { + String value = oldDimension.getValue(); + if (namespace.equals("AWS/ECS")) { + if (oldDimension.getName().equals("ClusterName")) { + value = clusterName; + } else if (oldDimension.getName().equals("ServiceName")) { + value = serviceName; + } + } + return new Dimension().withName(oldDimension.getName()).withValue(value); + } + + private MetricDimension buildNewServiceTargetTrackingDimension(MetricDimension oldDimension, String namespace, String serviceName, String clusterName) { + String value = oldDimension.getValue(); + if (namespace.equals("AWS/ECS")) { + if (oldDimension.getName().equals("ClusterName")) { + value = clusterName; + } else if (oldDimension.getName().equals("ServiceName")) { + value = serviceName; + } + } + return new MetricDimension().withName(oldDimension.getName()).withValue(value); } private PutScalingPolicyRequest buildPutScalingPolicyRequest(ScalingPolicy policy) { @@ -179,51 +215,50 @@ private PutScalingPolicyRequest buildPutScalingPolicyRequest(ScalingPolicy polic .withTargetTrackingScalingPolicyConfiguration(policy.getTargetTrackingScalingPolicyConfiguration()); } - public void associateAsgWithMetrics(String account, - String region, - List alarmNames, - String serviceName, - String resourceId) { - - NetflixAmazonCredentials credentials = (NetflixAmazonCredentials) accountCredentialsProvider.getCredentials(account); - - AmazonCloudWatch cloudWatch = amazonClientProvider.getAmazonCloudWatch(credentials, region, false); - AWSApplicationAutoScaling autoScalingClient = amazonClientProvider.getAmazonApplicationAutoScaling(credentials, region, false); - - DescribeAlarmsResult describeAlarmsResult = cloudWatch.describeAlarms(new DescribeAlarmsRequest() - .withAlarmNames(alarmNames)); - - for (MetricAlarm metricAlarm : describeAlarmsResult.getMetricAlarms()) { - Set okScalingPolicyArns = putScalingPolicies(autoScalingClient, metricAlarm.getOKActions(), - serviceName, resourceId, "ok", "scaling-policy-" + metricAlarm.getAlarmName()); - Set alarmScalingPolicyArns = putScalingPolicies(autoScalingClient, metricAlarm.getAlarmActions(), - serviceName, resourceId, "alarm", "scaling-policy-" + metricAlarm.getAlarmName()); - Set insufficientActionPolicyArns = putScalingPolicies(autoScalingClient, metricAlarm.getInsufficientDataActions(), - serviceName, resourceId, "insuffiicient", "scaling-policy-" + metricAlarm.getAlarmName()); - - cloudWatch.putMetricAlarm(buildPutMetricAlarmRequest(metricAlarm, serviceName, - insufficientActionPolicyArns, okScalingPolicyArns, alarmScalingPolicyArns)); - } + public void copyScalingPolicies(String dstAccount, + String dstRegion, + String dstServiceName, + String dstResourceId, + String srcAccount, + String srcRegion, + String srcServiceName, + String srcResourceId, + String clusterName) { + NetflixAmazonCredentials dstCredentials = (NetflixAmazonCredentials) accountCredentialsProvider.getCredentials(dstAccount); + NetflixAmazonCredentials srcCredentials = (NetflixAmazonCredentials) accountCredentialsProvider.getCredentials(srcAccount); + + AWSApplicationAutoScaling dstAutoScalingClient = amazonClientProvider.getAmazonApplicationAutoScaling(dstCredentials, dstRegion, false); + AWSApplicationAutoScaling srcAutoScalingClient = amazonClientProvider.getAmazonApplicationAutoScaling(srcCredentials, srcRegion, false); + AmazonCloudWatch dstCloudWatchClient = amazonClientProvider.getAmazonCloudWatch(dstCredentials, dstRegion, false); + AmazonCloudWatch srcCloudWatchClient = amazonClientProvider.getAmazonCloudWatch(srcCredentials, srcRegion, false); + + // Copy the scaling policies + Set sourceScalingPolicies = getScalingPolicies(srcAutoScalingClient, srcResourceId); + + Map srcPolicyArnToDstPolicyArn = putScalingPolicies(dstAutoScalingClient, + srcServiceName, dstServiceName, dstResourceId, clusterName, sourceScalingPolicies); + + // Copy the alarms that target the scaling policies + Set allSourceAlarmNames = sourceScalingPolicies.stream() + .flatMap(policy -> policy.getAlarms().stream()) + .map(alarm -> alarm.getAlarmName()) + .collect(Collectors.toSet()); + copyAlarmsForAsg(srcCloudWatchClient, dstCloudWatchClient, + srcRegion, dstRegion, + srcCredentials.getAccountId(), dstCredentials.getAccountId(), + srcServiceName, dstServiceName, + clusterName, + allSourceAlarmNames, srcPolicyArnToDstPolicyArn); } - private Set putScalingPolicies(AWSApplicationAutoScaling autoScalingClient, - List actionArns, - String serviceName, - String resourceId, - String type, - String suffix) { - if (actionArns.isEmpty()) { - return Collections.emptySet(); - } - + private Set getScalingPolicies(AWSApplicationAutoScaling autoScalingClient, String resourceId) { Set scalingPolicies = new HashSet<>(); String nextToken = null; do { - DescribeScalingPoliciesRequest request = new DescribeScalingPoliciesRequest().withPolicyNames(actionArns.stream() - .map(arn -> StringUtils.substringAfterLast(arn, ":policyName/")) - .collect(Collectors.toSet())) - .withServiceNamespace(ServiceNamespace.Ecs); + DescribeScalingPoliciesRequest request = new DescribeScalingPoliciesRequest() + .withServiceNamespace(ServiceNamespace.Ecs) + .withResourceId(resourceId); if (nextToken != null) { request.setNextToken(nextToken); } @@ -234,17 +269,75 @@ private Set putScalingPolicies(AWSApplicationAutoScaling autoScalingClie nextToken = result.getNextToken(); } while (nextToken != null && nextToken.length() != 0); - Set policyArns = new HashSet<>(); - for (ScalingPolicy scalingPolicy : scalingPolicies) { - String newPolicyName = serviceName + "-" + type + "-" + suffix; + return scalingPolicies; + } + + // Return map of src policy ARN -> dst policy ARN + private Map putScalingPolicies(AWSApplicationAutoScaling dstAutoScalingClient, + String srcServiceName, + String dstServiceName, + String dstResourceId, + String clusterName, + Set srcScalingPolicies) { + Map srcPolicyArnToDstPolicyArn = new HashMap<>(); + + for (ScalingPolicy scalingPolicy : srcScalingPolicies) { + String newPolicyName = scalingPolicy.getPolicyName().replaceAll(srcServiceName, dstServiceName); + if (!newPolicyName.contains(dstServiceName)) { + newPolicyName = newPolicyName + "-" + dstServiceName; + } + ScalingPolicy clone = scalingPolicy.clone(); clone.setPolicyName(newPolicyName); - clone.setResourceId(resourceId); + clone.setResourceId(dstResourceId); + + if (clone.getTargetTrackingScalingPolicyConfiguration() != null && + clone.getTargetTrackingScalingPolicyConfiguration().getCustomizedMetricSpecification() != null) { + CustomizedMetricSpecification spec = clone.getTargetTrackingScalingPolicyConfiguration().getCustomizedMetricSpecification(); + spec.setDimensions( + spec.getDimensions().stream() + .map(dimension -> buildNewServiceTargetTrackingDimension(dimension, spec.getNamespace(), dstServiceName, clusterName)) + .collect(Collectors.toSet())); + } - PutScalingPolicyResult result = autoScalingClient.putScalingPolicy(buildPutScalingPolicyRequest(clone)); - policyArns.add(result.getPolicyARN()); + PutScalingPolicyResult result = dstAutoScalingClient.putScalingPolicy(buildPutScalingPolicyRequest(clone)); + + srcPolicyArnToDstPolicyArn.put(scalingPolicy.getPolicyARN(), result.getPolicyARN()); } - return policyArns; + return srcPolicyArnToDstPolicyArn; + } + + private void copyAlarmsForAsg(AmazonCloudWatch srcCloudWatchClient, + AmazonCloudWatch dstCloudWatchClient, + String srcRegion, + String dstRegion, + String srcAccountId, + String dstAccountId, + String srcServiceName, + String dstServiceName, + String clusterName, + Set srcAlarmNames, + Map srcPolicyArnToDstPolicyArn) { + + for(List srcAlarmsPartition : Iterables.partition(srcAlarmNames, 100)) { + DescribeAlarmsResult describeAlarmsResult = srcCloudWatchClient.describeAlarms(new DescribeAlarmsRequest() + .withAlarmNames(srcAlarmsPartition)); + + for (MetricAlarm srcMetricAlarm : describeAlarmsResult.getMetricAlarms()) { + if (srcMetricAlarm.getAlarmName().startsWith("TargetTracking-")) { + // Target Tracking policies auto-create their alarms, so we don't need to copy them + continue; + } + + String dstAlarmName = srcMetricAlarm.getAlarmName().replaceAll(srcServiceName, dstServiceName); + if (!dstAlarmName.contains(dstServiceName)) { + dstAlarmName = dstAlarmName + "-" + dstServiceName; + } + + dstCloudWatchClient.putMetricAlarm(buildPutMetricAlarmRequest(srcMetricAlarm, dstAlarmName, dstServiceName, clusterName, + srcRegion, dstRegion, srcAccountId, dstAccountId, srcPolicyArnToDstPolicyArn)); + } + } } } diff --git a/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/deploy/converters/EcsCreateServerGroupAtomicOperationConverterSpec.groovy b/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/deploy/converters/EcsCreateServerGroupAtomicOperationConverterSpec.groovy index 042525e8a55..f39d9f37411 100644 --- a/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/deploy/converters/EcsCreateServerGroupAtomicOperationConverterSpec.groovy +++ b/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/deploy/converters/EcsCreateServerGroupAtomicOperationConverterSpec.groovy @@ -47,7 +47,6 @@ class EcsCreateServerGroupAtomicOperationConverterSpec extends Specification { dockerImageAddress : 'docker-url', capacity : new ServerGroup.Capacity(0, 2, 1,), availabilityZones : ['us-west-1': ['us-west-1a']], - autoscalingPolicies : [], placementStrategySequence: [new PlacementStrategy().withType(PlacementStrategyType.Random)], region : 'us-west-1', credentials : 'test' diff --git a/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/deploy/ops/CreateServerGroupAtomicOperationSpec.groovy b/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/deploy/ops/CreateServerGroupAtomicOperationSpec.groovy index 4e1c3ee5b36..db9c872dcda 100644 --- a/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/deploy/ops/CreateServerGroupAtomicOperationSpec.groovy +++ b/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/deploy/ops/CreateServerGroupAtomicOperationSpec.groovy @@ -17,6 +17,7 @@ package com.netflix.spinnaker.clouddriver.ecs.deploy.ops import com.amazonaws.services.applicationautoscaling.AWSApplicationAutoScaling +import com.amazonaws.services.applicationautoscaling.model.* import com.amazonaws.services.ecs.model.* import com.amazonaws.services.elasticloadbalancingv2.AmazonElasticLoadBalancing import com.amazonaws.services.elasticloadbalancingv2.model.DescribeTargetGroupsResult @@ -25,7 +26,7 @@ import com.amazonaws.services.identitymanagement.AmazonIdentityManagement import com.amazonaws.services.identitymanagement.model.GetRoleResult import com.amazonaws.services.identitymanagement.model.Role import com.netflix.spinnaker.clouddriver.aws.security.AmazonCredentials -import com.netflix.spinnaker.clouddriver.aws.security.AssumeRoleAmazonCredentials +import com.netflix.spinnaker.clouddriver.aws.security.NetflixAssumeRoleAmazonCredentials import com.netflix.spinnaker.clouddriver.ecs.TestCredential import com.netflix.spinnaker.clouddriver.ecs.deploy.description.CreateServerGroupDescription import com.netflix.spinnaker.clouddriver.ecs.provider.agent.IamPolicyReader @@ -34,10 +35,8 @@ import com.netflix.spinnaker.clouddriver.ecs.services.EcsCloudMetricService import com.netflix.spinnaker.clouddriver.ecs.services.SecurityGroupSelector import com.netflix.spinnaker.clouddriver.ecs.services.SubnetSelector import com.netflix.spinnaker.clouddriver.model.ServerGroup -import com.netflix.spinnaker.fiat.model.resources.Permissions import static com.netflix.spinnaker.clouddriver.ecs.deploy.ops.CreateServerGroupAtomicOperation.DOCKER_LABEL_KEY_SERVERGROUP -import static com.netflix.spinnaker.clouddriver.ecs.deploy.ops.CreateServerGroupAtomicOperation.NO_IAM_ROLE class CreateServerGroupAtomicOperationSpec extends CommonAtomicOperation { def iamClient = Mock(AmazonIdentityManagement) @@ -57,19 +56,28 @@ class CreateServerGroupAtomicOperationSpec extends CommonAtomicOperation { def role = new Role(assumeRolePolicyDocument: "json-encoded-string-here") - def creds = new AssumeRoleAmazonCredentials("test", "test", "test", "test", "test", - [new AmazonCredentials.AWSRegion('us-west-1', ['us-west-1a', 'us-west-1b'])], - [], [], Permissions.factory([:]), [], false, 'test-role', "test") + def creds = Mock(NetflixAssumeRoleAmazonCredentials) { + getName() >> { "test" } + getRegions() >> { [new AmazonCredentials.AWSRegion('us-west-1', ['us-west-1a', 'us-west-1b'])] } + getAssumeRole() >> { 'test-role' } + getAccountId() >> { 'test' } + } def taskDefinition = new TaskDefinition().withTaskDefinitionArn("task-def-arn") def targetGroup = new TargetGroup().withLoadBalancerArns("loadbalancer-arn").withTargetGroupArn('target-group-arn') - def service = new Service(serviceName: "${serviceName}") + def service = new Service(serviceName: "${serviceName}-v008") def 'should create a service'() { given: + def source = new CreateServerGroupDescription.Source() + source.account = "test" + source.region = "us-west-1" + source.asgName = "${serviceName}-v007" + def description = new CreateServerGroupDescription( + credentials: TestCredential.named('Test', [:]), application: applicationName, stack: stack, freeFormDetails: detail, @@ -83,8 +91,8 @@ class CreateServerGroupAtomicOperationSpec extends CommonAtomicOperation { dockerImageAddress: 'docker-image-url', capacity: new ServerGroup.Capacity(1, 1, 1), availabilityZones: ['us-west-1': ['us-west-1a', 'us-west-1b', 'us-west-1c']], - autoscalingPolicies: [], - placementStrategySequence: [] + placementStrategySequence: [], + source: source ) def operation = new CreateServerGroupAtomicOperation(description) @@ -127,7 +135,7 @@ class CreateServerGroupAtomicOperationSpec extends CommonAtomicOperation { 1 * loadBalancingV2.describeTargetGroups(_) >> new DescribeTargetGroupsResult().withTargetGroups(targetGroup) 1 * ecs.createService({ CreateServiceRequest request -> - request.serviceName.startsWith(serviceName) + request.serviceName == "service/test-cluster/${serviceName}-v008" request.desiredCount == 1 request.cluster = 'test-cluster' request.loadBalancers.size() == 1 @@ -140,9 +148,36 @@ class CreateServerGroupAtomicOperationSpec extends CommonAtomicOperation { result.getServerGroupNames().size() == 1 result.getServerGroupNameByRegion().size() == 1 - result.getServerGroupNames().contains("us-west-1:" + serviceName) + result.getServerGroupNames().contains("us-west-1:" + serviceName + "-v008") result.getServerGroupNameByRegion().containsKey('us-west-1') - result.getServerGroupNameByRegion().get('us-west-1').contains(serviceName) + result.getServerGroupNameByRegion().get('us-west-1').contains(serviceName + "-v008") + + 1 * autoScalingClient.registerScalableTarget({RegisterScalableTargetRequest request -> + request.serviceNamespace == ServiceNamespace.Ecs + request.scalableDimension == ScalableDimension.EcsServiceDesiredCount + request.resourceId == "service/test-cluster/${serviceName}-v008" + request.roleARN == 'test-role' + request.minCapacity == 1 + request.maxCapacity == 1 + }) + + 1 * autoScalingClient.describeScalableTargets({ DescribeScalableTargetsRequest request -> + request.scalableDimension == ScalableDimension.EcsServiceDesiredCount + request.serviceNamespace == ServiceNamespace.Ecs + request.resourceIds == ["service/test-cluster/${serviceName}-v007"] + }) >> new DescribeScalableTargetsResult() + .withScalableTargets(new ScalableTarget().withResourceId("service/test-cluster/${serviceName}-v007")) + + 1 * operation.ecsCloudMetricService.copyScalingPolicies( + "Test", + "us-west-1", + "${serviceName}-v008", + "service/test-cluster/${serviceName}-v008", + "test", + "us-west-1", + "${serviceName}-v007", + "service/test-cluster/${serviceName}-v007", + "test-cluster"); } def 'should create a service using VPC and Fargate mode'() { @@ -162,7 +197,6 @@ class CreateServerGroupAtomicOperationSpec extends CommonAtomicOperation { dockerImageAddress: 'docker-image-url', capacity: new ServerGroup.Capacity(1, 1, 1), availabilityZones: ['us-west-1': ['us-west-1a', 'us-west-1b', 'us-west-1c']], - autoscalingPolicies: [], placementStrategySequence: [], launchType: 'FARGATE', networkMode: 'awsvpc', @@ -228,9 +262,9 @@ class CreateServerGroupAtomicOperationSpec extends CommonAtomicOperation { result.getServerGroupNames().size() == 1 result.getServerGroupNameByRegion().size() == 1 - result.getServerGroupNames().contains("us-west-1:" + serviceName) + result.getServerGroupNames().contains("us-west-1:" + serviceName + "-v008") result.getServerGroupNameByRegion().containsKey('us-west-1') - result.getServerGroupNameByRegion().get('us-west-1').contains(serviceName) + result.getServerGroupNameByRegion().get('us-west-1').contains(serviceName + "-v008") } def 'should create default Docker labels'() { @@ -383,7 +417,6 @@ class CreateServerGroupAtomicOperationSpec extends CommonAtomicOperation { description.getDockerImageAddress() >> 'docker-image-url' description.capacity = new ServerGroup.Capacity(1, 1, 1) description.availabilityZones = ['us-west-1': ['us-west-1a', 'us-west-1b', 'us-west-1c']] - description.autoscalingPolicies = [] description.placementStrategySequence = [] def operation = new CreateServerGroupAtomicOperation(description) diff --git a/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/deploy/validators/EcsCreateServergroupDescriptionValidatorSpec.groovy b/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/deploy/validators/EcsCreateServergroupDescriptionValidatorSpec.groovy index 9a162c4667d..01f17f368d2 100644 --- a/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/deploy/validators/EcsCreateServergroupDescriptionValidatorSpec.groovy +++ b/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/deploy/validators/EcsCreateServergroupDescriptionValidatorSpec.groovy @@ -111,7 +111,6 @@ class EcsCreateServergroupDescriptionValidatorSpec extends AbstractValidatorSpec def description = (CreateServerGroupDescription) getDescription() description.placementStrategySequence = null description.availabilityZones = null - description.autoscalingPolicies = null description.application = null description.ecsClusterName = null description.dockerImageAddress = null @@ -127,7 +126,7 @@ class EcsCreateServergroupDescriptionValidatorSpec extends AbstractValidatorSpec @Override Set notNullableProperties() { - ['placementStrategySequence', 'availabilityZones', 'autoscalingPolicies', 'application', + ['placementStrategySequence', 'availabilityZones', 'application', 'ecsClusterName', 'dockerImageAddress', 'credentials', 'containerPort', 'computeUnits', 'reservedMemory', 'capacity.desired', 'capacity.min', 'capacity.max'] } @@ -184,7 +183,6 @@ class EcsCreateServergroupDescriptionValidatorSpec extends AbstractValidatorSpec description.dockerImageAddress = 'docker-image-url' description.capacity = new ServerGroup.Capacity(1, 2, 1) description.availabilityZones = ['us-west-1': ['us-west-1a']] - description.autoscalingPolicies = [] description.placementStrategySequence = [new PlacementStrategy().withType(PlacementStrategyType.Random)] description diff --git a/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/services/EcsCloudMetricServiceSpec.groovy b/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/services/EcsCloudMetricServiceSpec.groovy index df538f1aa32..769ca8f9463 100644 --- a/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/services/EcsCloudMetricServiceSpec.groovy +++ b/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/services/EcsCloudMetricServiceSpec.groovy @@ -16,9 +16,15 @@ package com.netflix.spinnaker.clouddriver.ecs.services +import com.amazonaws.services.applicationautoscaling.AWSApplicationAutoScaling +import com.amazonaws.services.applicationautoscaling.model.* import com.amazonaws.services.cloudwatch.AmazonCloudWatch +import com.amazonaws.services.cloudwatch.model.DescribeAlarmsResult +import com.amazonaws.services.cloudwatch.model.Dimension +import com.amazonaws.services.cloudwatch.model.MetricAlarm +import com.amazonaws.services.cloudwatch.model.PutMetricAlarmRequest import com.netflix.spinnaker.clouddriver.aws.security.AmazonClientProvider -import com.netflix.spinnaker.clouddriver.ecs.TestCredential +import com.netflix.spinnaker.clouddriver.aws.security.NetflixAmazonCredentials import com.netflix.spinnaker.clouddriver.ecs.cache.client.EcsCloudWatchAlarmCacheClient import com.netflix.spinnaker.clouddriver.ecs.cache.model.EcsMetricAlarm import com.netflix.spinnaker.clouddriver.security.AccountCredentialsProvider @@ -27,41 +33,392 @@ import spock.lang.Subject class EcsCloudMetricServiceSpec extends Specification { def metricAlarmCacheClient = Mock(EcsCloudWatchAlarmCacheClient) - def accountCredentialsProvider = Mock(AccountCredentialsProvider) - def amazonClientProvider = Mock(AmazonClientProvider) + + def sourceAutoScaling = Mock(AWSApplicationAutoScaling) + def targetAutoScaling = Mock(AWSApplicationAutoScaling) + def sourceCloudWatch = Mock(AmazonCloudWatch) + def targetCloudWatch = Mock(AmazonCloudWatch) + def sourceAccountName = 'abc123' + def targetAccountName = 'def456' + def sourceAccountId = 'abc' + def targetAccountId = 'def' + def sourceRegion = 'us-east-1' + def targetRegion = 'us-west-1' + def clusterName = 'default' + def sourceServiceName = 'asgard-v000' + def targetServiceName = 'asgard-v001' + def sourceResourceId = 'service/default/asgard-v000' + def targetResourceId = 'service/default/asgard-v001' + def sourceCredentials = Stub(NetflixAmazonCredentials) { + getAccountId() >> sourceAccountId + } + def targetCredentials = Stub(NetflixAmazonCredentials) { + getAccountId() >> targetAccountId + } + def accountCredentialsProvider = Stub(AccountCredentialsProvider) { + getCredentials(sourceAccountName) >> sourceCredentials + getCredentials(targetAccountName) >> targetCredentials + } + def amazonClientProvider = Stub(AmazonClientProvider) { + getAmazonApplicationAutoScaling(sourceCredentials, sourceRegion, false) >> sourceAutoScaling + getAmazonApplicationAutoScaling(targetCredentials, targetRegion, false) >> targetAutoScaling + getAmazonCloudWatch(sourceCredentials, sourceRegion, false) >> sourceCloudWatch + getAmazonCloudWatch(targetCredentials, targetRegion, false) >> targetCloudWatch + } @Subject def service = new EcsCloudMetricService() - def 'should delete metric alarms'() { + def setup() { + service.amazonClientProvider = amazonClientProvider + service.accountCredentialsProvider = accountCredentialsProvider + service.metricAlarmCacheClient = metricAlarmCacheClient + } + + void 'should copy nothing when there are no scaling policies'() { + when: + service.copyScalingPolicies(targetAccountName, targetRegion, targetServiceName, targetResourceId, + sourceAccountName, sourceRegion, sourceServiceName, sourceResourceId, clusterName) + + then: + 1 * sourceAutoScaling.describeScalingPolicies(new DescribeScalingPoliciesRequest( + serviceNamespace: "ecs", + resourceId: sourceResourceId)) >> new DescribeScalingPoliciesResult(scalingPolicies: []) + 0 * targetAutoScaling.putScalingPolicy(_) + 0 * sourceCloudWatch.describeAlarms(_) + 0 * targetCloudWatch.putMetricAlarm(_) + } + + void 'should replace scaling policy ARNs and omit actions that are specific to the source account/region when they differ'() { given: - def creds = TestCredential.named('test') - def region = creds.getRegions()[0].getName() - def serviceName = 'test-kcats-liated' + def replacements = ['oldPolicyARN': 'newPolicyARN'] + def actions = ['oldPolicyARN', 'sns:us-east-1', "sns:${sourceCredentials.accountId}:someQueue".toString(), 'ok-one'] + + when: + def replacedActions = service.replacePolicyArnActions( + sourceRegion, targetRegion, + sourceAccountId, targetAccountId, + replacements, actions) + + then: + replacedActions.sort() == ['newPolicyARN', 'ok-one'] + } + void 'should copy scaling policies and alarms'() { + when: + service.copyScalingPolicies(targetAccountName, targetRegion, targetServiceName, targetResourceId, + sourceAccountName, sourceRegion, sourceServiceName, sourceResourceId, clusterName) + + then: + 1 * sourceAutoScaling.describeScalingPolicies(new DescribeScalingPoliciesRequest( + serviceNamespace: ServiceNamespace.Ecs, + resourceId: sourceResourceId)) >> + new DescribeScalingPoliciesResult(scalingPolicies: [ + new ScalingPolicy( + policyName: 'policy1', + policyARN: 'oldPolicyARN1', + resourceId: 'service/default/asgard-v000', + policyType: 'TargetTrackingScaling', + serviceNamespace: 'ecs', + scalableDimension: 'ecs:service:DesiredCount', + targetTrackingScalingPolicyConfiguration: new TargetTrackingScalingPolicyConfiguration( + targetValue: 30.0, + predefinedMetricSpecification: new PredefinedMetricSpecification( + predefinedMetricType: 'ECSServiceAverageCPUUtilization' + ), + scaleOutCooldown: 300, + scaleInCooldown: 300 + ), + alarms: ['TargetTracking-alarm1', 'TargetTracking-alarm2'].collect { new Alarm(alarmName: it) } + ), + new ScalingPolicy( + policyName: 'policy2', + policyARN: 'oldPolicyARN2', + resourceId: 'service/default/asgard-v000', + policyType: 'TargetTrackingScaling', + serviceNamespace: 'ecs', + scalableDimension: 'ecs:service:DesiredCount', + targetTrackingScalingPolicyConfiguration: new TargetTrackingScalingPolicyConfiguration( + targetValue: 20.0, + customizedMetricSpecification: new CustomizedMetricSpecification( + metricName: 'CPUUtilization', + dimensions: [ + new MetricDimension(name: 'ClusterName', value: 'default'), + new MetricDimension(name: 'ServiceName', value: 'asgard-v000') + ], + namespace: 'AWS/ECS', + statistic: 'Average', + unit: 'Percent' + ), + scaleOutCooldown: 200, + scaleInCooldown: 200 + ), + alarms: ['TargetTracking-alarm3', 'TargetTracking-alarm4'].collect { new Alarm(alarmName: it) } + ), + new ScalingPolicy( + policyName: 'policy3-asgard-v000', + policyARN: 'oldPolicyARN3', + resourceId: 'service/default/asgard-v000', + policyType: 'StepScaling', + serviceNamespace: 'ecs', + scalableDimension: 'ecs:service:DesiredCount', + stepScalingPolicyConfiguration: new StepScalingPolicyConfiguration( + adjustmentType: 'ChangeInCapacity', + minAdjustmentMagnitude: 20, + metricAggregationType: 'Average', + cooldown: 100, + stepAdjustments: [ + new StepAdjustment( + metricIntervalLowerBound: 10.5, + metricIntervalUpperBound: 11.5, + scalingAdjustment: 90, + ) + ], + ), + alarms: ['alarm5', 'alarm6-asgard-v000'].collect { new Alarm(alarmName: it) } + ) + ] + ) + + 1 * targetAutoScaling.putScalingPolicy(new PutScalingPolicyRequest( + policyName: 'policy1-asgard-v001', + resourceId: 'service/default/asgard-v001', + policyType: 'TargetTrackingScaling', + serviceNamespace: 'ecs', + scalableDimension: 'ecs:service:DesiredCount', + targetTrackingScalingPolicyConfiguration: new TargetTrackingScalingPolicyConfiguration( + targetValue: 30.0, + predefinedMetricSpecification: new PredefinedMetricSpecification( + predefinedMetricType: 'ECSServiceAverageCPUUtilization' + ), + scaleOutCooldown: 300, + scaleInCooldown: 300 + ) + )) >> new PutScalingPolicyResult(policyARN: 'newPolicyARN1') + + 1 * targetAutoScaling.putScalingPolicy(new PutScalingPolicyRequest( + policyName: 'policy2-asgard-v001', + resourceId: 'service/default/asgard-v001', + policyType: 'TargetTrackingScaling', + serviceNamespace: 'ecs', + scalableDimension: 'ecs:service:DesiredCount', + targetTrackingScalingPolicyConfiguration: new TargetTrackingScalingPolicyConfiguration( + targetValue: 20.0, + customizedMetricSpecification: new CustomizedMetricSpecification( + metricName: 'CPUUtilization', + dimensions: [ + new MetricDimension(name: 'ClusterName', value: 'default'), + new MetricDimension(name: 'ServiceName', value: 'asgard-v001') + ], + namespace: 'AWS/ECS', + statistic: 'Average', + unit: 'Percent' + ), + scaleOutCooldown: 200, + scaleInCooldown: 200 + ) + )) >> new PutScalingPolicyResult(policyARN: 'newPolicyARN2') + + 1 * targetAutoScaling.putScalingPolicy(new PutScalingPolicyRequest( + policyName: 'policy3-asgard-v001', + resourceId: 'service/default/asgard-v001', + policyType: 'StepScaling', + serviceNamespace: 'ecs', + scalableDimension: 'ecs:service:DesiredCount', + stepScalingPolicyConfiguration: new StepScalingPolicyConfiguration( + adjustmentType: 'ChangeInCapacity', + minAdjustmentMagnitude: 20, + metricAggregationType: 'Average', + cooldown: 100, + stepAdjustments: [ + new StepAdjustment( + metricIntervalLowerBound: 10.5, + metricIntervalUpperBound: 11.5, + scalingAdjustment: 90, + ) + ], + ) + )) >> new PutScalingPolicyResult(policyARN: 'newPolicyARN3') + + 1 * sourceCloudWatch.describeAlarms(_) >> new DescribeAlarmsResult(metricAlarms: [ + new MetricAlarm( + alarmName: 'TargetTracking-alarm1', + alarmDescription: 'alarm 1 description', + actionsEnabled: true, + oKActions: [], + alarmActions: ['oldPolicyARN1'], + insufficientDataActions: [], + metricName: 'metric1', + namespace: 'AWS/ECS', + statistic: 'statistic1', + dimensions: [ + new Dimension(name: 'ClusterName', value: 'default'), + new Dimension(name: 'ServiceName', value: 'asgard-v000') + ], + period: 1, + unit: 'unit1', + evaluationPeriods: 2, + threshold: 4.2, + comparisonOperator: 'GreaterThanOrEqualToThreshold' + ), + new MetricAlarm( + alarmName: 'TargetTracking-alarm2', + alarmDescription: 'alarm 2 description', + actionsEnabled: true, + oKActions: [], + alarmActions: ['oldPolicyARN1'], + insufficientDataActions: [], + metricName: 'metric2', + namespace: 'hello', + statistic: 'statistic2', + dimensions: [ + new Dimension(name: 'ClusterName', value: 'default'), + new Dimension(name: 'ServiceName', value: 'asgard-v000'), + new Dimension(name: 'other', value: 'dimension1') + ], + period: 10, + unit: 'unit2', + evaluationPeriods: 20, + threshold: 40.2, + comparisonOperator: 'LessThanOrEqualToThreshold' + ), + new MetricAlarm( + alarmName: 'TargetTracking-alarm3', + alarmDescription: 'alarm 3 description', + actionsEnabled: true, + oKActions: [], + alarmActions: ['oldPolicyARN2'], + insufficientDataActions: [], + metricName: 'metric3', + namespace: 'AWS/ECS', + statistic: 'statistic3', + dimensions: [ + new Dimension(name: 'ClusterName', value: 'default'), + new Dimension(name: 'ServiceName', value: 'asgard-v000') + ], + period: 1, + unit: 'unit3', + evaluationPeriods: 2, + threshold: 4.2, + comparisonOperator: 'GreaterThanOrEqualToThreshold' + ), + new MetricAlarm( + alarmName: 'TargetTracking-alarm4', + alarmDescription: 'alarm 4 description', + actionsEnabled: true, + oKActions: [], + alarmActions: ['oldPolicyARN2'], + insufficientDataActions: [], + metricName: 'metric4', + namespace: 'hello', + statistic: 'statistic4', + dimensions: [ + new Dimension(name: 'ClusterName', value: 'default'), + new Dimension(name: 'ServiceName', value: 'asgard-v000') + ], + period: 10, + unit: 'unit4', + evaluationPeriods: 20, + threshold: 40.2, + comparisonOperator: 'LessThanOrEqualToThreshold' + ), + new MetricAlarm( + alarmName: 'alarm5', + alarmDescription: 'alarm 5 description', + actionsEnabled: true, + oKActions: [], + alarmActions: ['oldPolicyARN3'], + insufficientDataActions: [], + metricName: 'metric5', + namespace: 'other', + statistic: 'statistic5', + dimensions: [ + new Dimension(name: 'hello', value: 'world') + ], + period: 1, + unit: 'unit5', + evaluationPeriods: 2, + threshold: 4.2, + comparisonOperator: 'GreaterThanOrEqualToThreshold' + ), + new MetricAlarm( + alarmName: 'alarm6-asgard-v000', + alarmDescription: 'alarm 6 description', + actionsEnabled: true, + oKActions: [], + alarmActions: ['oldPolicyARN3'], + insufficientDataActions: [], + metricName: 'metric6', + namespace: 'AWS/ECS', + statistic: 'statistic6', + dimensions: [ + new Dimension(name: 'ClusterName', value: 'default'), + new Dimension(name: 'ServiceName', value: 'asgard-v000') + ], + period: 10, + unit: 'unit6', + evaluationPeriods: 20, + threshold: 40.2, + comparisonOperator: 'LessThanOrEqualToThreshold' + ), + ]) + 1 * targetCloudWatch.putMetricAlarm(new PutMetricAlarmRequest( + alarmName: 'alarm5-asgard-v001', + alarmDescription: 'alarm 5 description', + actionsEnabled: true, + oKActions: [], + alarmActions: ['newPolicyARN3'], + insufficientDataActions: [], + metricName: 'metric5', + namespace: 'other', + statistic: 'statistic5', + dimensions: [ + new Dimension(name: 'hello', value: 'world') + ], + period: 1, + unit: 'unit5', + evaluationPeriods: 2, + threshold: 4.2, + comparisonOperator: 'GreaterThanOrEqualToThreshold' + )) + 1 * targetCloudWatch.putMetricAlarm(new PutMetricAlarmRequest( + alarmName: 'alarm6-asgard-v001', + alarmDescription: 'alarm 6 description', + actionsEnabled: true, + oKActions: [], + alarmActions: ['newPolicyARN3'], + insufficientDataActions: [], + metricName: 'metric6', + namespace: 'AWS/ECS', + statistic: 'statistic6', + dimensions: [ + new Dimension(name: 'ClusterName', value: 'default'), + new Dimension(name: 'ServiceName', value: 'asgard-v001') + ], + period: 10, + unit: 'unit6', + evaluationPeriods: 20, + threshold: 40.2, + comparisonOperator: 'LessThanOrEqualToThreshold' + )) + } + + def 'should delete metric alarms'() { + given: def metricAlarms = [] 5.times { metricAlarms << new EcsMetricAlarm( - accountName: creds.getName(), - region: region + accountName: targetAccountName, + region: targetRegion ) } - def amazonCloudWatch = Mock(AmazonCloudWatch) - - service.amazonClientProvider = amazonClientProvider - service.accountCredentialsProvider = accountCredentialsProvider - service.metricAlarmCacheClient = metricAlarmCacheClient - - accountCredentialsProvider.getCredentials(_) >> creds - amazonClientProvider.getAmazonCloudWatch(_, _, _) >> amazonCloudWatch metricAlarmCacheClient.getMetricAlarms(_, _, _) >> metricAlarms - when: - service.deleteMetrics(serviceName, creds.getName(), region) + service.deleteMetrics(targetServiceName, targetAccountName, targetRegion) then: - 1 * amazonCloudWatch.deleteAlarms(_) + 1 * targetCloudWatch.deleteAlarms(_) } }