From 3e932e47639129c2feaadefd63059615f339eca9 Mon Sep 17 00:00:00 2001 From: Christos Arvanitis Date: Fri, 3 Jan 2025 21:14:36 +0200 Subject: [PATCH] fix(ecs): Alarms with custom dimensions should be processed (#6324) --- .../client/EcsCloudWatchAlarmCacheClient.java | 13 ++++- .../EcsCloudWatchAlarmCacheClientSpec.groovy | 57 +++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/cache/client/EcsCloudWatchAlarmCacheClient.java b/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/cache/client/EcsCloudWatchAlarmCacheClient.java index ec9c6659aa..d710f87a2e 100644 --- a/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/cache/client/EcsCloudWatchAlarmCacheClient.java +++ b/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/cache/client/EcsCloudWatchAlarmCacheClient.java @@ -27,6 +27,8 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -77,7 +79,16 @@ public List getMetricAlarms( String glob = Keys.getAlarmKey(accountName, region, "*", ecsClusterName); Collection metricAlarmsIds = filterIdentifiers(glob); - Collection allMetricAlarms = getAll(metricAlarmsIds); + String globEmptyDimension = Keys.getAlarmKey(accountName, region, "*", ""); + Collection otherMetricAlarmsIds = filterIdentifiers(globEmptyDimension); + + Collection combinedMetricIds = + Stream.of(metricAlarmsIds, otherMetricAlarmsIds) + .filter(m -> m != null) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + + Collection allMetricAlarms = getAll(combinedMetricIds); for (EcsMetricAlarm metricAlarm : allMetricAlarms) { if (metricAlarm.getAlarmActions().stream().anyMatch(action -> action.contains(serviceName)) diff --git a/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/cache/EcsCloudWatchAlarmCacheClientSpec.groovy b/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/cache/EcsCloudWatchAlarmCacheClientSpec.groovy index f4ffb21b68..6e08c8a002 100644 --- a/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/cache/EcsCloudWatchAlarmCacheClientSpec.groovy +++ b/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/cache/EcsCloudWatchAlarmCacheClientSpec.groovy @@ -32,6 +32,9 @@ import spock.lang.Shared import spock.lang.Specification import spock.lang.Subject +import java.util.stream.Collectors +import java.util.stream.Stream + class EcsCloudWatchAlarmCacheClientSpec extends Specification { @Subject EcsCloudWatchAlarmCacheClient client @@ -246,4 +249,58 @@ def 'should return metric alarms with actions matching the service'() { ) } + + def 'should return metric alarms for a service - single cluster with Custom Alarms/Cloudwatch Dimensions'() { + given: + def serviceName = 'my-service' + def serviceName2 = 'not-matching-service' + + def ecsClusterName = 'my-cluster' + def metricAlarms = Set.of( + new MetricAlarm().withAlarmName("alarm-name").withAlarmArn("alarmArn") + .withAlarmActions("arn:aws:sns:us-west-1:123456789012:${serviceName}") + .withDimensions([new Dimension().withName("ClusterName").withValue(ecsClusterName)]), + new MetricAlarm().withAlarmName("alarm-name-2").withAlarmArn("alarmArn2") + .withAlarmActions("arn:aws:sns:us-west-1:123456789012:${serviceName}") + .withDimensions([new Dimension().withName("ClusterName").withValue(ecsClusterName)]), + new MetricAlarm().withAlarmName("alarm-name").withAlarmArn("alarmArn3") + .withAlarmActions("arn:aws:sns:us-west-1:123456789012:${serviceName2}") + .withDimensions([new Dimension().withName("ClusterName").withValue(ecsClusterName)]) + ) + def metricAlarmCustomDimension = Set.of ( + new MetricAlarm().withAlarmName("alarm-name-2-custom").withAlarmArn("alarmArn2-custom") + .withAlarmActions("arn:aws:sns:us-west-1:123456789012:${serviceName}") + .withDimensions([new Dimension().withName("CustomDimension").withValue("customValue")]), + ) + + def keys = metricAlarms.collect { alarm -> + def key = Keys.getAlarmKey(ACCOUNT, REGION, alarm.getAlarmArn(), ecsClusterName) + def attributes = agent.convertMetricAlarmToAttributes(alarm, ACCOUNT, REGION) + [key, new DefaultCacheData(key, attributes, [:])] + } + def keysCustom = metricAlarmCustomDimension.collect { alarm -> + def key = Keys.getAlarmKey(ACCOUNT, REGION, alarm.getAlarmArn(), "") + def attributes = agent.convertMetricAlarmToAttributes(alarm, ACCOUNT, REGION) + [key, new DefaultCacheData(key, attributes, [:])] + } + + cacheView.filterIdentifiers(Keys.Namespace.ALARMS.ns, Keys.getAlarmKey(ACCOUNT, REGION, "*", ecsClusterName)) >> keys*.first() + cacheView.filterIdentifiers(Keys.Namespace.ALARMS.ns, Keys.getAlarmKey(ACCOUNT, REGION, "*", "")) >> keysCustom*.first() + def combinedMetricIds = Stream.of( keys*.first(), keysCustom*.first()) + .filter { it != null } + .flatMap { it.stream() } + .collect(Collectors.toList()) + + cacheView.getAll(Keys.Namespace.ALARMS.ns, combinedMetricIds) >> keys*.last() + keysCustom*.last() + + when: + def metricAlarmsReturned = client.getMetricAlarms(serviceName, ACCOUNT, REGION, ecsClusterName) + + then: + metricAlarmsReturned.size() == 3 + metricAlarmsReturned*.alarmName.containsAll(["alarm-name", "alarm-name-2", "alarm-name-2-custom"]) + metricAlarmsReturned*.alarmArn.containsAll(["alarmArn", "alarmArn2","alarmArn2-custom"]) + !metricAlarmsReturned*.alarmArn.contains(["alarmArn3"]) + } + }