From a00e286076bea147638325faaecf4ef73aa5df97 Mon Sep 17 00:00:00 2001 From: takapi327 Date: Fri, 11 Oct 2024 00:34:51 +0900 Subject: [PATCH 01/20] Added OffsetDateTime decoder --- .../src/main/scala/feral/lambda/events/codecs.scala | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lambda/shared/src/main/scala/feral/lambda/events/codecs.scala b/lambda/shared/src/main/scala/feral/lambda/events/codecs.scala index 44713caf..e64a0422 100644 --- a/lambda/shared/src/main/scala/feral/lambda/events/codecs.scala +++ b/lambda/shared/src/main/scala/feral/lambda/events/codecs.scala @@ -23,7 +23,8 @@ import io.circe.KeyDecoder import io.circe.KeyEncoder import org.typelevel.ci.CIString -import java.time.Instant +import java.time.{Instant, OffsetDateTime} +import java.time.format.DateTimeFormatter import scala.util.Try private object codecs { @@ -38,6 +39,12 @@ private object codecs { } } + implicit def decodeOffsetDateTime: Decoder[OffsetDateTime] = + Decoder.decodeString.emapTry { str => + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") + Try(OffsetDateTime.parse(str, formatter)) + } + implicit def decodeIpAddress: Decoder[IpAddress] = Decoder.decodeString.emap(IpAddress.fromString(_).toRight("Cannot parse IP address")) From 00a0b8924ec65b1300a4c96e2a5db1d9a74c1ca0 Mon Sep 17 00:00:00 2001 From: takapi327 Date: Fri, 11 Oct 2024 00:35:22 +0900 Subject: [PATCH 02/20] Create ScheduledEvent lambda event model --- .../feral/lambda/events/ScheduledEvent.scala | 293 ++++++++++++++++++ 1 file changed, 293 insertions(+) create mode 100644 lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala diff --git a/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala b/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala new file mode 100644 index 00000000..7ca69766 --- /dev/null +++ b/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala @@ -0,0 +1,293 @@ +package feral.lambda.events + +import java.time.{Instant, OffsetDateTime} + +import io.circe.Decoder + +import codecs.decodeOffsetDateTime + +// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/aws-lambda/trigger/eventbridge.d.ts +// https://github.com/aws/aws-lambda-java-libs/blob/main/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/ScheduledEvent.java +sealed abstract class ScheduledEvent { + def id: String + def version: String + def account: String + def time: Instant + def region: String + def resources: List[String] + def source: String + def `detail-type`: String + def detail: ScheduledEventDetail + def `replay-name`: Option[String] +} + +object ScheduledEvent { + + def apply( + id: String, + version: String, + account: String, + time: Instant, + region: String, + resources: List[String], + source: String, + `detail-type`: String, + detail: ScheduledEventDetail, + `replay-name`: Option[String] + ): ScheduledEvent = + new Impl( + id, + version, + account, + time, + region, + resources, + source, + `detail-type`, + detail, + `replay-name` + ) + + implicit val decoder: Decoder[ScheduledEvent] = Decoder.forProduct10( + "id", + "version", + "account", + "time", + "region", + "resources", + "source", + "detail-type", + "detail", + "replay-name" + )(ScheduledEvent.apply) + + private final case class Impl( + id: String, + version: String, + account: String, + time: Instant, + region: String, + resources: List[String], + source: String, + `detail-type`: String, + detail: ScheduledEventDetail, + `replay-name`: Option[String] + ) extends ScheduledEvent { + override def productPrefix = "ScheduledEvent" + } +} + +sealed abstract class ScheduledEventDetail { + def alarmName: String + def operation: Option[String] + def configuration: ScheduledEventConfiguration + def previousState: Option[ScheduledEventPreviousState] + def state: Option[ScheduledEventState] +} + +object ScheduledEventDetail { + def apply( + alarmName: String, + operation: Option[String], + configuration: ScheduledEventConfiguration, + previousState: Option[ScheduledEventPreviousState], + state: Option[ScheduledEventState] + ): ScheduledEventDetail = + new Impl(alarmName, operation, configuration, previousState, state) + + implicit val decoder: Decoder[ScheduledEventDetail] = Decoder.forProduct5( + "alarmName", + "operation", + "configuration", + "previousState", + "state" + )(ScheduledEventDetail.apply) + + private final case class Impl( + alarmName: String, + operation: Option[String], + configuration: ScheduledEventConfiguration, + previousState: Option[ScheduledEventPreviousState], + state: Option[ScheduledEventState] + ) extends ScheduledEventDetail { + override def productPrefix = "ScheduledEventDetail" + } +} + +sealed abstract class ScheduledEventConfiguration { + def description: String + def metrics: List[ScheduledEventMetric] +} + +object ScheduledEventConfiguration { + def apply( + description: String, + metrics: List[ScheduledEventMetric] + ): ScheduledEventConfiguration = + new Impl(description, metrics) + + implicit val decoder: Decoder[ScheduledEventConfiguration] = Decoder.forProduct2( + "description", + "metrics" + )(ScheduledEventConfiguration.apply) + + private final case class Impl( + description: String, + metrics: List[ScheduledEventMetric] + ) extends ScheduledEventConfiguration { + override def productPrefix = "ScheduledEventConfiguration" + } +} + +sealed abstract class ScheduledEventMetric { + def id: String + def metricStat: ScheduledEventMetricStat + def returnData: Boolean +} + +object ScheduledEventMetric { + def apply( + id: String, + metricStat: ScheduledEventMetricStat, + returnData: Boolean + ): ScheduledEventMetric = + new Impl(id, metricStat, returnData) + + implicit val decoder: Decoder[ScheduledEventMetric] = Decoder.forProduct3( + "id", + "metricStat", + "returnData" + )(ScheduledEventMetric.apply) + + private final case class Impl( + id: String, + metricStat: ScheduledEventMetricStat, + returnData: Boolean + ) extends ScheduledEventMetric { + override def productPrefix = "ScheduledEventMetric" + } +} + +sealed abstract class ScheduledEventMetricStat { + def metric: Metric + def period: Int + def stat: String +} + +object ScheduledEventMetricStat { + def apply( + metric: Metric, + period: Int, + stat: String + ): ScheduledEventMetricStat = + new Impl(metric, period, stat) + + implicit val decoder: Decoder[ScheduledEventMetricStat] = Decoder.forProduct3( + "metric", + "period", + "stat" + )(ScheduledEventMetricStat.apply) + + private final case class Impl( + metric: Metric, + period: Int, + stat: String + ) extends ScheduledEventMetricStat { + override def productPrefix = "ScheduledEventMetricStat" + } +} + +sealed abstract class Metric { + def dimensions: Map[String, String] + def name: String + def namespace: String +} + +object Metric { + def apply( + dimensions: Map[String, String], + name: String, + namespace: String + ): Metric = + new Impl(dimensions, name, namespace) + + implicit val decoder: Decoder[Metric] = Decoder.forProduct3( + "dimensions", + "name", + "namespace" + )(Metric.apply) + + private final case class Impl( + dimensions: Map[String, String], + name: String, + namespace: String + ) extends Metric { + override def productPrefix = "Metric" + } +} + +sealed abstract class ScheduledEventState { + def reason: String + def reasonData: String + def timestamp: OffsetDateTime + def value: String +} + +object ScheduledEventState { + def apply( + reason: String, + reasonData: String, + timestamp: OffsetDateTime, + value: String + ): ScheduledEventState = + new Impl(reason, reasonData, timestamp, value) + + implicit val decoder: Decoder[ScheduledEventState] = Decoder.forProduct4( + "reason", + "reasonData", + "timestamp", + "value" + )(ScheduledEventState.apply) + + private final case class Impl( + reason: String, + reasonData: String, + timestamp: OffsetDateTime, + value: String + ) extends ScheduledEventState { + override def productPrefix = "ScheduledEventState" + } +} + +sealed abstract class ScheduledEventPreviousState { + def reason: String + def reasonData: Option[String] + def timestamp: OffsetDateTime + def value: String +} + +object ScheduledEventPreviousState { + def apply( + reason: String, + reasonData: Option[String], + timestamp: OffsetDateTime, + value: String + ): ScheduledEventPreviousState = + new Impl(reason, reasonData, timestamp, value) + + implicit val decoder: Decoder[ScheduledEventPreviousState] = Decoder.forProduct4( + "reason", + "reasonData", + "timestamp", + "value" + )(ScheduledEventPreviousState.apply) + + private final case class Impl( + reason: String, + reasonData: Option[String], + timestamp: OffsetDateTime, + value: String + ) extends ScheduledEventPreviousState { + override def productPrefix = "ScheduledEventPreviousState" + } +} From e15f00b7ed9c5b97e17a5fda4b533de68f5ffc29 Mon Sep 17 00:00:00 2001 From: takapi327 Date: Fri, 11 Oct 2024 00:53:58 +0900 Subject: [PATCH 03/20] Delete unused --- .../feral/lambda/events/ScheduledEvent.scala | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala b/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala index 7ca69766..d3e972be 100644 --- a/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala +++ b/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala @@ -141,28 +141,36 @@ object ScheduledEventConfiguration { sealed abstract class ScheduledEventMetric { def id: String - def metricStat: ScheduledEventMetricStat def returnData: Boolean + def expression: Option[String] + def label: Option[String] + def metricStat: Option[ScheduledEventMetricStat] } object ScheduledEventMetric { def apply( id: String, - metricStat: ScheduledEventMetricStat, - returnData: Boolean + returnData: Boolean, + expression: Option[String], + label: Option[String], + metricStat: Option[ScheduledEventMetricStat], ): ScheduledEventMetric = - new Impl(id, metricStat, returnData) + new Impl(id, returnData, expression, label, metricStat) - implicit val decoder: Decoder[ScheduledEventMetric] = Decoder.forProduct3( + implicit val decoder: Decoder[ScheduledEventMetric] = Decoder.forProduct5( "id", - "metricStat", - "returnData" + "returnData", + "expression", + "label", + "metricStat" )(ScheduledEventMetric.apply) private final case class Impl( id: String, - metricStat: ScheduledEventMetricStat, - returnData: Boolean + returnData: Boolean, + expression: Option[String], + label: Option[String], + metricStat: Option[ScheduledEventMetricStat], ) extends ScheduledEventMetric { override def productPrefix = "ScheduledEventMetric" } From 8f1178f49aedbc2822d3b8bbc295495240b00951 Mon Sep 17 00:00:00 2001 From: takapi327 Date: Fri, 11 Oct 2024 00:54:14 +0900 Subject: [PATCH 04/20] Added Decoding of change of state of abnormality detection alarms test --- .../lambda/events/ScheduledEventSuite.scala | 372 ++++++++++++++++++ 1 file changed, 372 insertions(+) create mode 100644 lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala diff --git a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala new file mode 100644 index 00000000..904861d3 --- /dev/null +++ b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala @@ -0,0 +1,372 @@ +package feral.lambda.events + +import java.time.{Instant, OffsetDateTime} +import java.time.format.DateTimeFormatter + +import io.circe.literal._ + +import munit.FunSuite + +class ScheduledEventSuite extends FunSuite { + + private val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") + + test("Decoding of alarm status changes based on a single metric") { + assertEquals(singleMetricEvent.as[ScheduledEvent].toTry.get, singleMetricResult) + } + + test("Decoding of alarm state changes based on metrics formulas") { + assertEquals(metricsFormulaEvent.as[ScheduledEvent].toTry.get, metricsFormulaResult) + } + + test("Decoding of change of state of abnormality detection alarms") { + assertEquals(abnormalityDetectionEvent.as[ScheduledEvent].toTry.get, abnormalityDetectionResult) + } + + // Alarm status changes based on a single metric + private def singleMetricEvent = json""" + { + "version": "0", + "id": "c4c1c1c9-6542-e61b-6ef0-8c4d36933a92", + "detail-type": "CloudWatch Alarm State Change", + "source": "aws.cloudwatch", + "account": "123456789012", + "time": "2019-10-02T17:04:40Z", + "region": "us-east-1", + "resources": [ + "arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServerCpuTooHigh" + ], + "detail": { + "alarmName": "ServerCpuTooHigh", + "configuration": { + "description": "Goes into alarm when server CPU utilization is too high!", + "metrics": [ + { + "id": "30b6c6b2-a864-43a2-4877-c09a1afc3b87", + "metricStat": { + "metric": { + "dimensions": { + "InstanceId": "i-12345678901234567" + }, + "name": "CPUUtilization", + "namespace": "AWS/EC2" + }, + "period": 300, + "stat": "Average" + }, + "returnData": true + } + ] + }, + "previousState": { + "reason": "Threshold Crossed: 1 out of the last 1 datapoints [0.0666851903306472 (01/10/19 13:46:00)] was not greater than the threshold (50.0) (minimum 1 datapoint for ALARM -> OK transition).", + "reasonData": "{\"version\":\"1.0\",\"queryDate\":\"2019-10-01T13:56:40.985+0000\",\"startDate\":\"2019-10-01T13:46:00.000+0000\",\"statistic\":\"Average\",\"period\":300,\"recentDatapoints\":[0.0666851903306472],\"threshold\":50.0}", + "timestamp": "2019-10-01T13:56:40.987+0000", + "value": "OK" + }, + "state": { + "reason": "Threshold Crossed: 1 out of the last 1 datapoints [99.50160229693434 (02/10/19 16:59:00)] was greater than the threshold (50.0) (minimum 1 datapoint for OK -> ALARM transition).", + "reasonData": "{\"version\":\"1.0\",\"queryDate\":\"2019-10-02T17:04:40.985+0000\",\"startDate\":\"2019-10-02T16:59:00.000+0000\",\"statistic\":\"Average\",\"period\":300,\"recentDatapoints\":[99.50160229693434],\"threshold\":50.0}", + "timestamp": "2019-10-02T17:04:40.989+0000", + "value": "ALARM" + } + } + } + """ + + private def singleMetricResult: ScheduledEvent = ScheduledEvent( + id = "c4c1c1c9-6542-e61b-6ef0-8c4d36933a92", + version = "0", + account = "123456789012", + time = Instant.parse("2019-10-02T17:04:40Z"), + region = "us-east-1", + resources = List("arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServerCpuTooHigh"), + source = "aws.cloudwatch", + `detail-type` = "CloudWatch Alarm State Change", + detail = ScheduledEventDetail( + alarmName = "ServerCpuTooHigh", + operation = None, + configuration = ScheduledEventConfiguration( + description = "Goes into alarm when server CPU utilization is too high!", + metrics = List( + ScheduledEventMetric( + id = "30b6c6b2-a864-43a2-4877-c09a1afc3b87", + metricStat = Some(ScheduledEventMetricStat( + metric = Metric( + dimensions = Map("InstanceId" -> "i-12345678901234567"), + name = "CPUUtilization", + namespace = "AWS/EC2" + ), + period = 300, + stat = "Average" + )), + returnData = true, + expression = None, + label = None + ) + ) + ), + previousState = Some(ScheduledEventPreviousState( + reason = "Threshold Crossed: 1 out of the last 1 datapoints [0.0666851903306472 (01/10/19 13:46:00)] was not greater than the threshold (50.0) (minimum 1 datapoint for ALARM -> OK transition).", + reasonData = Some("{\"version\":\"1.0\",\"queryDate\":\"2019-10-01T13:56:40.985+0000\",\"startDate\":\"2019-10-01T13:46:00.000+0000\",\"statistic\":\"Average\",\"period\":300,\"recentDatapoints\":[0.0666851903306472],\"threshold\":50.0}"), + timestamp = OffsetDateTime.parse("2019-10-01T13:56:40.987+0000", formatter), + value = "OK" + )), + state = Some(ScheduledEventState( + reason = "Threshold Crossed: 1 out of the last 1 datapoints [99.50160229693434 (02/10/19 16:59:00)] was greater than the threshold (50.0) (minimum 1 datapoint for OK -> ALARM transition).", + reasonData = "{\"version\":\"1.0\",\"queryDate\":\"2019-10-02T17:04:40.985+0000\",\"startDate\":\"2019-10-02T16:59:00.000+0000\",\"statistic\":\"Average\",\"period\":300,\"recentDatapoints\":[99.50160229693434],\"threshold\":50.0}", + timestamp = OffsetDateTime.parse("2019-10-02T17:04:40.989+0000", formatter), + value = "ALARM" + )) + ), + `replay-name` = None + ) + + // Decoding of alarm state changes based on metrics formulas + private def metricsFormulaEvent =json""" + { + "version": "0", + "id": "2dde0eb1-528b-d2d5-9ca6-6d590caf2329", + "detail-type": "CloudWatch Alarm State Change", + "source": "aws.cloudwatch", + "account": "123456789012", + "time": "2019-10-02T17:20:48Z", + "region": "us-east-1", + "resources": [ + "arn:aws:cloudwatch:us-east-1:123456789012:alarm:TotalNetworkTrafficTooHigh" + ], + "detail": { + "alarmName": "TotalNetworkTrafficTooHigh", + "configuration": { + "description": "Goes into alarm if total network traffic exceeds 10Kb", + "metrics": [ + { + "expression": "SUM(METRICS())", + "id": "e1", + "label": "Total Network Traffic", + "returnData": true + }, + { + "id": "m1", + "metricStat": { + "metric": { + "dimensions": { + "InstanceId": "i-12345678901234567" + }, + "name": "NetworkIn", + "namespace": "AWS/EC2" + }, + "period": 300, + "stat": "Maximum" + }, + "returnData": false + }, + { + "id": "m2", + "metricStat": { + "metric": { + "dimensions": { + "InstanceId": "i-12345678901234567" + }, + "name": "NetworkOut", + "namespace": "AWS/EC2" + }, + "period": 300, + "stat": "Maximum" + }, + "returnData": false + } + ] + }, + "previousState": { + "reason": "Unchecked: Initial alarm creation", + "timestamp": "2019-10-02T17:20:03.642+0000", + "value": "INSUFFICIENT_DATA" + }, + "state": { + "reason": "Threshold Crossed: 1 out of the last 1 datapoints [45628.0 (02/10/19 17:10:00)] was greater than the threshold (10000.0) (minimum 1 datapoint for OK -> ALARM transition).", + "reasonData": "{\"version\":\"1.0\",\"queryDate\":\"2019-10-02T17:20:48.551+0000\",\"startDate\":\"2019-10-02T17:10:00.000+0000\",\"period\":300,\"recentDatapoints\":[45628.0],\"threshold\":10000.0}", + "timestamp": "2019-10-02T17:20:48.554+0000", + "value": "ALARM" + } + } + } + """ + + private def metricsFormulaResult: ScheduledEvent = ScheduledEvent( + id = "2dde0eb1-528b-d2d5-9ca6-6d590caf2329", + version = "0", + account = "123456789012", + time = Instant.parse("2019-10-02T17:20:48Z"), + region = "us-east-1", + resources = List("arn:aws:cloudwatch:us-east-1:123456789012:alarm:TotalNetworkTrafficTooHigh"), + source = "aws.cloudwatch", + `detail-type` = "CloudWatch Alarm State Change", + detail = ScheduledEventDetail( + alarmName = "TotalNetworkTrafficTooHigh", + operation = None, + configuration = ScheduledEventConfiguration( + description = "Goes into alarm if total network traffic exceeds 10Kb", + metrics = List( + ScheduledEventMetric( + expression = Some("SUM(METRICS())"), + id = "e1", + label = Some("Total Network Traffic"), + returnData = true, + metricStat = None + ), + ScheduledEventMetric( + id = "m1", + metricStat = Some(ScheduledEventMetricStat( + metric = Metric( + dimensions = Map("InstanceId" -> "i-12345678901234567"), + name = "NetworkIn", + namespace = "AWS/EC2" + ), + period = 300, + stat = "Maximum" + )), + returnData = false, + expression = None, + label = None + ), + ScheduledEventMetric( + id = "m2", + metricStat = Some(ScheduledEventMetricStat( + metric = Metric( + dimensions = Map("InstanceId" -> "i-12345678901234567"), + name = "NetworkOut", + namespace = "AWS/EC2" + ), + period = 300, + stat = "Maximum" + )), + returnData = false, + expression = None, + label = None + ) + ) + ), + previousState = Some(ScheduledEventPreviousState( + reason = "Unchecked: Initial alarm creation", + reasonData = None, + timestamp = OffsetDateTime.parse("2019-10-02T17:20:03.642+0000", formatter), + value = "INSUFFICIENT_DATA" + )), + state = Some(ScheduledEventState( + reason = "Threshold Crossed: 1 out of the last 1 datapoints [45628.0 (02/10/19 17:10:00)] was greater than the threshold (10000.0) (minimum 1 datapoint for OK -> ALARM transition).", + reasonData = "{\"version\":\"1.0\",\"queryDate\":\"2019-10-02T17:20:48.551+0000\",\"startDate\":\"2019-10-02T17:10:00.000+0000\",\"period\":300,\"recentDatapoints\":[45628.0],\"threshold\":10000.0}", + timestamp = OffsetDateTime.parse("2019-10-02T17:20:48.554+0000", formatter), + value = "ALARM" + )) + ), + `replay-name` = None + ) + + // Abnormality detection alarm status change + private def abnormalityDetectionEvent =json""" + { + "version": "0", + "id": "daafc9f1-bddd-c6c9-83af-74971fcfc4ef", + "detail-type": "CloudWatch Alarm State Change", + "source": "aws.cloudwatch", + "account": "123456789012", + "time": "2019-10-03T16:00:04Z", + "region": "us-east-1", + "resources": ["arn:aws:cloudwatch:us-east-1:123456789012:alarm:EC2 CPU Utilization Anomaly"], + "detail": { + "alarmName": "EC2 CPU Utilization Anomaly", + "state": { + "value": "ALARM", + "reason": "Thresholds Crossed: 1 out of the last 1 datapoints [0.0 (03/10/19 15:58:00)] was less than the lower thresholds [0.020599444741798756] or greater than the upper thresholds [0.3006915352732461] (minimum 1 datapoint for OK -> ALARM transition).", + "reasonData": "{\"version\":\"1.0\",\"queryDate\":\"2019-10-03T16:00:04.650+0000\",\"startDate\":\"2019-10-03T15:58:00.000+0000\",\"period\":60,\"recentDatapoints\":[0.0],\"recentLowerThresholds\":[0.020599444741798756],\"recentUpperThresholds\":[0.3006915352732461]}", + "timestamp": "2019-10-03T16:00:04.653+0000" + }, + "previousState": { + "value": "OK", + "reason": "Thresholds Crossed: 1 out of the last 1 datapoints [0.166666666664241 (03/10/19 15:57:00)] was not less than the lower thresholds [0.0206719426210418] or not greater than the upper thresholds [0.30076870222143803] (minimum 1 datapoint for ALARM -> OK transition).", + "reasonData": "{\"version\":\"1.0\",\"queryDate\":\"2019-10-03T15:59:04.670+0000\",\"startDate\":\"2019-10-03T15:57:00.000+0000\",\"period\":60,\"recentDatapoints\":[0.166666666664241],\"recentLowerThresholds\":[0.0206719426210418],\"recentUpperThresholds\":[0.30076870222143803]}", + "timestamp": "2019-10-03T15:59:04.672+0000" + }, + "configuration": { + "description": "Goes into alarm if CPU Utilization is out of band", + "metrics": [{ + "id": "m1", + "metricStat": { + "metric": { + "namespace": "AWS/EC2", + "name": "CPUUtilization", + "dimensions": { + "InstanceId": "i-12345678901234567" + } + }, + "period": 60, + "stat": "Average" + }, + "returnData": true + }, { + "id": "ad1", + "expression": "ANOMALY_DETECTION_BAND(m1, 0.8)", + "label": "CPUUtilization (expected)", + "returnData": true + }] + } + } + } + """ + + private def abnormalityDetectionResult: ScheduledEvent = ScheduledEvent( + id = "daafc9f1-bddd-c6c9-83af-74971fcfc4ef", + version = "0", + account = "123456789012", + time = Instant.parse("2019-10-03T16:00:04Z"), + region = "us-east-1", + resources = List("arn:aws:cloudwatch:us-east-1:123456789012:alarm:EC2 CPU Utilization Anomaly"), + source = "aws.cloudwatch", + `detail-type` = "CloudWatch Alarm State Change", + detail = ScheduledEventDetail( + alarmName = "EC2 CPU Utilization Anomaly", + operation = None, + configuration = ScheduledEventConfiguration( + description = "Goes into alarm if CPU Utilization is out of band", + metrics = List( + ScheduledEventMetric( + id = "m1", + metricStat = Some(ScheduledEventMetricStat( + metric = Metric( + dimensions = Map("InstanceId" -> "i-12345678901234567"), + name = "CPUUtilization", + namespace = "AWS/EC2" + ), + period = 60, + stat = "Average" + )), + returnData = true, + expression = None, + label = None + ), + ScheduledEventMetric( + id = "ad1", + expression = Some("ANOMALY_DETECTION_BAND(m1, 0.8)"), + label = Some("CPUUtilization (expected)"), + returnData = true, + metricStat = None + ) + ) + ), + state = Some(ScheduledEventState( + reason = "Thresholds Crossed: 1 out of the last 1 datapoints [0.0 (03/10/19 15:58:00)] was less than the lower thresholds [0.020599444741798756] or greater than the upper thresholds [0.3006915352732461] (minimum 1 datapoint for OK -> ALARM transition).", + reasonData = "{\"version\":\"1.0\",\"queryDate\":\"2019-10-03T16:00:04.650+0000\",\"startDate\":\"2019-10-03T15:58:00.000+0000\",\"period\":60,\"recentDatapoints\":[0.0],\"recentLowerThresholds\":[0.020599444741798756],\"recentUpperThresholds\":[0.3006915352732461]}", + timestamp = OffsetDateTime.parse("2019-10-03T16:00:04.653+0000", formatter), + value = "ALARM", + )), + previousState = Some(ScheduledEventPreviousState( + reason = "Thresholds Crossed: 1 out of the last 1 datapoints [0.166666666664241 (03/10/19 15:57:00)] was not less than the lower thresholds [0.0206719426210418] or not greater than the upper thresholds [0.30076870222143803] (minimum 1 datapoint for ALARM -> OK transition).", + reasonData = Some("{\"version\":\"1.0\",\"queryDate\":\"2019-10-03T15:59:04.670+0000\",\"startDate\":\"2019-10-03T15:57:00.000+0000\",\"period\":60,\"recentDatapoints\":[0.166666666664241],\"recentLowerThresholds\":[0.0206719426210418],\"recentUpperThresholds\":[0.30076870222143803]}"), + timestamp = OffsetDateTime.parse("2019-10-03T15:59:04.672+0000", formatter), + value = "OK" + )) + ), + `replay-name` = None + ) +} From d0df02ef55dca04df119c3c710efab673a571d85 Mon Sep 17 00:00:00 2001 From: takapi327 Date: Fri, 11 Oct 2024 00:55:05 +0900 Subject: [PATCH 05/20] Action sbt scalafmtAll --- .../feral/lambda/events/ScheduledEvent.scala | 158 +++++++-------- .../lambda/events/ScheduledEventSuite.scala | 180 ++++++++++-------- 2 files changed, 181 insertions(+), 157 deletions(-) diff --git a/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala b/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala index d3e972be..cf6b31c2 100644 --- a/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala +++ b/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala @@ -24,16 +24,16 @@ sealed abstract class ScheduledEvent { object ScheduledEvent { def apply( - id: String, - version: String, - account: String, - time: Instant, - region: String, - resources: List[String], - source: String, - `detail-type`: String, - detail: ScheduledEventDetail, - `replay-name`: Option[String] + id: String, + version: String, + account: String, + time: Instant, + region: String, + resources: List[String], + source: String, + `detail-type`: String, + detail: ScheduledEventDetail, + `replay-name`: Option[String] ): ScheduledEvent = new Impl( id, @@ -62,16 +62,16 @@ object ScheduledEvent { )(ScheduledEvent.apply) private final case class Impl( - id: String, - version: String, - account: String, - time: Instant, - region: String, - resources: List[String], - source: String, - `detail-type`: String, - detail: ScheduledEventDetail, - `replay-name`: Option[String] + id: String, + version: String, + account: String, + time: Instant, + region: String, + resources: List[String], + source: String, + `detail-type`: String, + detail: ScheduledEventDetail, + `replay-name`: Option[String] ) extends ScheduledEvent { override def productPrefix = "ScheduledEvent" } @@ -87,12 +87,12 @@ sealed abstract class ScheduledEventDetail { object ScheduledEventDetail { def apply( - alarmName: String, - operation: Option[String], - configuration: ScheduledEventConfiguration, - previousState: Option[ScheduledEventPreviousState], - state: Option[ScheduledEventState] - ): ScheduledEventDetail = + alarmName: String, + operation: Option[String], + configuration: ScheduledEventConfiguration, + previousState: Option[ScheduledEventPreviousState], + state: Option[ScheduledEventState] + ): ScheduledEventDetail = new Impl(alarmName, operation, configuration, previousState, state) implicit val decoder: Decoder[ScheduledEventDetail] = Decoder.forProduct5( @@ -104,11 +104,11 @@ object ScheduledEventDetail { )(ScheduledEventDetail.apply) private final case class Impl( - alarmName: String, - operation: Option[String], - configuration: ScheduledEventConfiguration, - previousState: Option[ScheduledEventPreviousState], - state: Option[ScheduledEventState] + alarmName: String, + operation: Option[String], + configuration: ScheduledEventConfiguration, + previousState: Option[ScheduledEventPreviousState], + state: Option[ScheduledEventState] ) extends ScheduledEventDetail { override def productPrefix = "ScheduledEventDetail" } @@ -121,9 +121,9 @@ sealed abstract class ScheduledEventConfiguration { object ScheduledEventConfiguration { def apply( - description: String, - metrics: List[ScheduledEventMetric] - ): ScheduledEventConfiguration = + description: String, + metrics: List[ScheduledEventMetric] + ): ScheduledEventConfiguration = new Impl(description, metrics) implicit val decoder: Decoder[ScheduledEventConfiguration] = Decoder.forProduct2( @@ -132,8 +132,8 @@ object ScheduledEventConfiguration { )(ScheduledEventConfiguration.apply) private final case class Impl( - description: String, - metrics: List[ScheduledEventMetric] + description: String, + metrics: List[ScheduledEventMetric] ) extends ScheduledEventConfiguration { override def productPrefix = "ScheduledEventConfiguration" } @@ -149,12 +149,12 @@ sealed abstract class ScheduledEventMetric { object ScheduledEventMetric { def apply( - id: String, - returnData: Boolean, - expression: Option[String], - label: Option[String], - metricStat: Option[ScheduledEventMetricStat], - ): ScheduledEventMetric = + id: String, + returnData: Boolean, + expression: Option[String], + label: Option[String], + metricStat: Option[ScheduledEventMetricStat] + ): ScheduledEventMetric = new Impl(id, returnData, expression, label, metricStat) implicit val decoder: Decoder[ScheduledEventMetric] = Decoder.forProduct5( @@ -166,11 +166,11 @@ object ScheduledEventMetric { )(ScheduledEventMetric.apply) private final case class Impl( - id: String, - returnData: Boolean, - expression: Option[String], - label: Option[String], - metricStat: Option[ScheduledEventMetricStat], + id: String, + returnData: Boolean, + expression: Option[String], + label: Option[String], + metricStat: Option[ScheduledEventMetricStat] ) extends ScheduledEventMetric { override def productPrefix = "ScheduledEventMetric" } @@ -184,10 +184,10 @@ sealed abstract class ScheduledEventMetricStat { object ScheduledEventMetricStat { def apply( - metric: Metric, - period: Int, - stat: String - ): ScheduledEventMetricStat = + metric: Metric, + period: Int, + stat: String + ): ScheduledEventMetricStat = new Impl(metric, period, stat) implicit val decoder: Decoder[ScheduledEventMetricStat] = Decoder.forProduct3( @@ -197,9 +197,9 @@ object ScheduledEventMetricStat { )(ScheduledEventMetricStat.apply) private final case class Impl( - metric: Metric, - period: Int, - stat: String + metric: Metric, + period: Int, + stat: String ) extends ScheduledEventMetricStat { override def productPrefix = "ScheduledEventMetricStat" } @@ -213,10 +213,10 @@ sealed abstract class Metric { object Metric { def apply( - dimensions: Map[String, String], - name: String, - namespace: String - ): Metric = + dimensions: Map[String, String], + name: String, + namespace: String + ): Metric = new Impl(dimensions, name, namespace) implicit val decoder: Decoder[Metric] = Decoder.forProduct3( @@ -226,9 +226,9 @@ object Metric { )(Metric.apply) private final case class Impl( - dimensions: Map[String, String], - name: String, - namespace: String + dimensions: Map[String, String], + name: String, + namespace: String ) extends Metric { override def productPrefix = "Metric" } @@ -243,11 +243,11 @@ sealed abstract class ScheduledEventState { object ScheduledEventState { def apply( - reason: String, - reasonData: String, - timestamp: OffsetDateTime, - value: String - ): ScheduledEventState = + reason: String, + reasonData: String, + timestamp: OffsetDateTime, + value: String + ): ScheduledEventState = new Impl(reason, reasonData, timestamp, value) implicit val decoder: Decoder[ScheduledEventState] = Decoder.forProduct4( @@ -258,10 +258,10 @@ object ScheduledEventState { )(ScheduledEventState.apply) private final case class Impl( - reason: String, - reasonData: String, - timestamp: OffsetDateTime, - value: String + reason: String, + reasonData: String, + timestamp: OffsetDateTime, + value: String ) extends ScheduledEventState { override def productPrefix = "ScheduledEventState" } @@ -276,11 +276,11 @@ sealed abstract class ScheduledEventPreviousState { object ScheduledEventPreviousState { def apply( - reason: String, - reasonData: Option[String], - timestamp: OffsetDateTime, - value: String - ): ScheduledEventPreviousState = + reason: String, + reasonData: Option[String], + timestamp: OffsetDateTime, + value: String + ): ScheduledEventPreviousState = new Impl(reason, reasonData, timestamp, value) implicit val decoder: Decoder[ScheduledEventPreviousState] = Decoder.forProduct4( @@ -291,10 +291,10 @@ object ScheduledEventPreviousState { )(ScheduledEventPreviousState.apply) private final case class Impl( - reason: String, - reasonData: Option[String], - timestamp: OffsetDateTime, - value: String + reason: String, + reasonData: Option[String], + timestamp: OffsetDateTime, + value: String ) extends ScheduledEventPreviousState { override def productPrefix = "ScheduledEventPreviousState" } diff --git a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala index 904861d3..84a2cdfc 100644 --- a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala +++ b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala @@ -20,7 +20,9 @@ class ScheduledEventSuite extends FunSuite { } test("Decoding of change of state of abnormality detection alarms") { - assertEquals(abnormalityDetectionEvent.as[ScheduledEvent].toTry.get, abnormalityDetectionResult) + assertEquals( + abnormalityDetectionEvent.as[ScheduledEvent].toTry.get, + abnormalityDetectionResult) } // Alarm status changes based on a single metric @@ -91,39 +93,46 @@ class ScheduledEventSuite extends FunSuite { metrics = List( ScheduledEventMetric( id = "30b6c6b2-a864-43a2-4877-c09a1afc3b87", - metricStat = Some(ScheduledEventMetricStat( - metric = Metric( - dimensions = Map("InstanceId" -> "i-12345678901234567"), - name = "CPUUtilization", - namespace = "AWS/EC2" - ), - period = 300, - stat = "Average" - )), + metricStat = Some( + ScheduledEventMetricStat( + metric = Metric( + dimensions = Map("InstanceId" -> "i-12345678901234567"), + name = "CPUUtilization", + namespace = "AWS/EC2" + ), + period = 300, + stat = "Average" + )), returnData = true, expression = None, label = None ) ) ), - previousState = Some(ScheduledEventPreviousState( - reason = "Threshold Crossed: 1 out of the last 1 datapoints [0.0666851903306472 (01/10/19 13:46:00)] was not greater than the threshold (50.0) (minimum 1 datapoint for ALARM -> OK transition).", - reasonData = Some("{\"version\":\"1.0\",\"queryDate\":\"2019-10-01T13:56:40.985+0000\",\"startDate\":\"2019-10-01T13:46:00.000+0000\",\"statistic\":\"Average\",\"period\":300,\"recentDatapoints\":[0.0666851903306472],\"threshold\":50.0}"), - timestamp = OffsetDateTime.parse("2019-10-01T13:56:40.987+0000", formatter), - value = "OK" - )), - state = Some(ScheduledEventState( - reason = "Threshold Crossed: 1 out of the last 1 datapoints [99.50160229693434 (02/10/19 16:59:00)] was greater than the threshold (50.0) (minimum 1 datapoint for OK -> ALARM transition).", - reasonData = "{\"version\":\"1.0\",\"queryDate\":\"2019-10-02T17:04:40.985+0000\",\"startDate\":\"2019-10-02T16:59:00.000+0000\",\"statistic\":\"Average\",\"period\":300,\"recentDatapoints\":[99.50160229693434],\"threshold\":50.0}", - timestamp = OffsetDateTime.parse("2019-10-02T17:04:40.989+0000", formatter), - value = "ALARM" - )) + previousState = Some( + ScheduledEventPreviousState( + reason = + "Threshold Crossed: 1 out of the last 1 datapoints [0.0666851903306472 (01/10/19 13:46:00)] was not greater than the threshold (50.0) (minimum 1 datapoint for ALARM -> OK transition).", + reasonData = Some( + "{\"version\":\"1.0\",\"queryDate\":\"2019-10-01T13:56:40.985+0000\",\"startDate\":\"2019-10-01T13:46:00.000+0000\",\"statistic\":\"Average\",\"period\":300,\"recentDatapoints\":[0.0666851903306472],\"threshold\":50.0}"), + timestamp = OffsetDateTime.parse("2019-10-01T13:56:40.987+0000", formatter), + value = "OK" + )), + state = Some( + ScheduledEventState( + reason = + "Threshold Crossed: 1 out of the last 1 datapoints [99.50160229693434 (02/10/19 16:59:00)] was greater than the threshold (50.0) (minimum 1 datapoint for OK -> ALARM transition).", + reasonData = + "{\"version\":\"1.0\",\"queryDate\":\"2019-10-02T17:04:40.985+0000\",\"startDate\":\"2019-10-02T16:59:00.000+0000\",\"statistic\":\"Average\",\"period\":300,\"recentDatapoints\":[99.50160229693434],\"threshold\":50.0}", + timestamp = OffsetDateTime.parse("2019-10-02T17:04:40.989+0000", formatter), + value = "ALARM" + )) ), - `replay-name` = None + `replay-name` = None ) // Decoding of alarm state changes based on metrics formulas - private def metricsFormulaEvent =json""" + private def metricsFormulaEvent = json""" { "version": "0", "id": "2dde0eb1-528b-d2d5-9ca6-6d590caf2329", @@ -199,7 +208,8 @@ class ScheduledEventSuite extends FunSuite { account = "123456789012", time = Instant.parse("2019-10-02T17:20:48Z"), region = "us-east-1", - resources = List("arn:aws:cloudwatch:us-east-1:123456789012:alarm:TotalNetworkTrafficTooHigh"), + resources = + List("arn:aws:cloudwatch:us-east-1:123456789012:alarm:TotalNetworkTrafficTooHigh"), source = "aws.cloudwatch", `detail-type` = "CloudWatch Alarm State Change", detail = ScheduledEventDetail( @@ -217,54 +227,60 @@ class ScheduledEventSuite extends FunSuite { ), ScheduledEventMetric( id = "m1", - metricStat = Some(ScheduledEventMetricStat( - metric = Metric( - dimensions = Map("InstanceId" -> "i-12345678901234567"), - name = "NetworkIn", - namespace = "AWS/EC2" - ), - period = 300, - stat = "Maximum" - )), + metricStat = Some( + ScheduledEventMetricStat( + metric = Metric( + dimensions = Map("InstanceId" -> "i-12345678901234567"), + name = "NetworkIn", + namespace = "AWS/EC2" + ), + period = 300, + stat = "Maximum" + )), returnData = false, expression = None, label = None ), ScheduledEventMetric( id = "m2", - metricStat = Some(ScheduledEventMetricStat( - metric = Metric( - dimensions = Map("InstanceId" -> "i-12345678901234567"), - name = "NetworkOut", - namespace = "AWS/EC2" - ), - period = 300, - stat = "Maximum" - )), + metricStat = Some( + ScheduledEventMetricStat( + metric = Metric( + dimensions = Map("InstanceId" -> "i-12345678901234567"), + name = "NetworkOut", + namespace = "AWS/EC2" + ), + period = 300, + stat = "Maximum" + )), returnData = false, expression = None, label = None ) ) ), - previousState = Some(ScheduledEventPreviousState( - reason = "Unchecked: Initial alarm creation", - reasonData = None, - timestamp = OffsetDateTime.parse("2019-10-02T17:20:03.642+0000", formatter), - value = "INSUFFICIENT_DATA" - )), - state = Some(ScheduledEventState( - reason = "Threshold Crossed: 1 out of the last 1 datapoints [45628.0 (02/10/19 17:10:00)] was greater than the threshold (10000.0) (minimum 1 datapoint for OK -> ALARM transition).", - reasonData = "{\"version\":\"1.0\",\"queryDate\":\"2019-10-02T17:20:48.551+0000\",\"startDate\":\"2019-10-02T17:10:00.000+0000\",\"period\":300,\"recentDatapoints\":[45628.0],\"threshold\":10000.0}", - timestamp = OffsetDateTime.parse("2019-10-02T17:20:48.554+0000", formatter), - value = "ALARM" - )) + previousState = Some( + ScheduledEventPreviousState( + reason = "Unchecked: Initial alarm creation", + reasonData = None, + timestamp = OffsetDateTime.parse("2019-10-02T17:20:03.642+0000", formatter), + value = "INSUFFICIENT_DATA" + )), + state = Some( + ScheduledEventState( + reason = + "Threshold Crossed: 1 out of the last 1 datapoints [45628.0 (02/10/19 17:10:00)] was greater than the threshold (10000.0) (minimum 1 datapoint for OK -> ALARM transition).", + reasonData = + "{\"version\":\"1.0\",\"queryDate\":\"2019-10-02T17:20:48.551+0000\",\"startDate\":\"2019-10-02T17:10:00.000+0000\",\"period\":300,\"recentDatapoints\":[45628.0],\"threshold\":10000.0}", + timestamp = OffsetDateTime.parse("2019-10-02T17:20:48.554+0000", formatter), + value = "ALARM" + )) ), `replay-name` = None ) // Abnormality detection alarm status change - private def abnormalityDetectionEvent =json""" + private def abnormalityDetectionEvent = json""" { "version": "0", "id": "daafc9f1-bddd-c6c9-83af-74971fcfc4ef", @@ -321,7 +337,8 @@ class ScheduledEventSuite extends FunSuite { account = "123456789012", time = Instant.parse("2019-10-03T16:00:04Z"), region = "us-east-1", - resources = List("arn:aws:cloudwatch:us-east-1:123456789012:alarm:EC2 CPU Utilization Anomaly"), + resources = + List("arn:aws:cloudwatch:us-east-1:123456789012:alarm:EC2 CPU Utilization Anomaly"), source = "aws.cloudwatch", `detail-type` = "CloudWatch Alarm State Change", detail = ScheduledEventDetail( @@ -332,15 +349,16 @@ class ScheduledEventSuite extends FunSuite { metrics = List( ScheduledEventMetric( id = "m1", - metricStat = Some(ScheduledEventMetricStat( - metric = Metric( - dimensions = Map("InstanceId" -> "i-12345678901234567"), - name = "CPUUtilization", - namespace = "AWS/EC2" - ), - period = 60, - stat = "Average" - )), + metricStat = Some( + ScheduledEventMetricStat( + metric = Metric( + dimensions = Map("InstanceId" -> "i-12345678901234567"), + name = "CPUUtilization", + namespace = "AWS/EC2" + ), + period = 60, + stat = "Average" + )), returnData = true, expression = None, label = None @@ -354,18 +372,24 @@ class ScheduledEventSuite extends FunSuite { ) ) ), - state = Some(ScheduledEventState( - reason = "Thresholds Crossed: 1 out of the last 1 datapoints [0.0 (03/10/19 15:58:00)] was less than the lower thresholds [0.020599444741798756] or greater than the upper thresholds [0.3006915352732461] (minimum 1 datapoint for OK -> ALARM transition).", - reasonData = "{\"version\":\"1.0\",\"queryDate\":\"2019-10-03T16:00:04.650+0000\",\"startDate\":\"2019-10-03T15:58:00.000+0000\",\"period\":60,\"recentDatapoints\":[0.0],\"recentLowerThresholds\":[0.020599444741798756],\"recentUpperThresholds\":[0.3006915352732461]}", - timestamp = OffsetDateTime.parse("2019-10-03T16:00:04.653+0000", formatter), - value = "ALARM", - )), - previousState = Some(ScheduledEventPreviousState( - reason = "Thresholds Crossed: 1 out of the last 1 datapoints [0.166666666664241 (03/10/19 15:57:00)] was not less than the lower thresholds [0.0206719426210418] or not greater than the upper thresholds [0.30076870222143803] (minimum 1 datapoint for ALARM -> OK transition).", - reasonData = Some("{\"version\":\"1.0\",\"queryDate\":\"2019-10-03T15:59:04.670+0000\",\"startDate\":\"2019-10-03T15:57:00.000+0000\",\"period\":60,\"recentDatapoints\":[0.166666666664241],\"recentLowerThresholds\":[0.0206719426210418],\"recentUpperThresholds\":[0.30076870222143803]}"), - timestamp = OffsetDateTime.parse("2019-10-03T15:59:04.672+0000", formatter), - value = "OK" - )) + state = Some( + ScheduledEventState( + reason = + "Thresholds Crossed: 1 out of the last 1 datapoints [0.0 (03/10/19 15:58:00)] was less than the lower thresholds [0.020599444741798756] or greater than the upper thresholds [0.3006915352732461] (minimum 1 datapoint for OK -> ALARM transition).", + reasonData = + "{\"version\":\"1.0\",\"queryDate\":\"2019-10-03T16:00:04.650+0000\",\"startDate\":\"2019-10-03T15:58:00.000+0000\",\"period\":60,\"recentDatapoints\":[0.0],\"recentLowerThresholds\":[0.020599444741798756],\"recentUpperThresholds\":[0.3006915352732461]}", + timestamp = OffsetDateTime.parse("2019-10-03T16:00:04.653+0000", formatter), + value = "ALARM" + )), + previousState = Some( + ScheduledEventPreviousState( + reason = + "Thresholds Crossed: 1 out of the last 1 datapoints [0.166666666664241 (03/10/19 15:57:00)] was not less than the lower thresholds [0.0206719426210418] or not greater than the upper thresholds [0.30076870222143803] (minimum 1 datapoint for ALARM -> OK transition).", + reasonData = Some( + "{\"version\":\"1.0\",\"queryDate\":\"2019-10-03T15:59:04.670+0000\",\"startDate\":\"2019-10-03T15:57:00.000+0000\",\"period\":60,\"recentDatapoints\":[0.166666666664241],\"recentLowerThresholds\":[0.0206719426210418],\"recentUpperThresholds\":[0.30076870222143803]}"), + timestamp = OffsetDateTime.parse("2019-10-03T15:59:04.672+0000", formatter), + value = "OK" + )) ), `replay-name` = None ) From 38960306199d5dd1f2743577b801fe0bf22e3c42 Mon Sep 17 00:00:00 2001 From: takapi327 Date: Fri, 11 Oct 2024 01:23:02 +0900 Subject: [PATCH 06/20] Added Decoding change of state of compound alarm with suppressor alarm test --- .../feral/lambda/events/ScheduledEvent.scala | 52 +++++-- .../lambda/events/ScheduledEventSuite.scala | 135 ++++++++++++++++-- 2 files changed, 160 insertions(+), 27 deletions(-) diff --git a/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala b/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala index cf6b31c2..34081e4b 100644 --- a/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala +++ b/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala @@ -115,25 +115,41 @@ object ScheduledEventDetail { } sealed abstract class ScheduledEventConfiguration { - def description: String - def metrics: List[ScheduledEventMetric] + def description: Option[String] + def metrics: Option[List[ScheduledEventMetric]] + def alarmRule: Option[String] + def actionsSuppressor: Option[String] + def actionsSuppressorWaitPeriod: Option[Int] + def actionsSuppressorExtensionPeriod: Option[Int] } object ScheduledEventConfiguration { def apply( - description: String, - metrics: List[ScheduledEventMetric] + description: Option[String], + metrics: Option[List[ScheduledEventMetric]], + alarmRule: Option[String], + actionsSuppressor: Option[String], + actionsSuppressorWaitPeriod: Option[Int], + actionsSuppressorExtensionPeriod: Option[Int] ): ScheduledEventConfiguration = - new Impl(description, metrics) + new Impl(description, metrics, alarmRule, actionsSuppressor, actionsSuppressorWaitPeriod, actionsSuppressorExtensionPeriod) - implicit val decoder: Decoder[ScheduledEventConfiguration] = Decoder.forProduct2( + implicit val decoder: Decoder[ScheduledEventConfiguration] = Decoder.forProduct6( "description", - "metrics" + "metrics", + "alarmRule", + "actionsSuppressor", + "actionsSuppressorWaitPeriod", + "actionsSuppressorExtensionPeriod" )(ScheduledEventConfiguration.apply) private final case class Impl( - description: String, - metrics: List[ScheduledEventMetric] + description: Option[String], + metrics: Option[List[ScheduledEventMetric]], + alarmRule: Option[String], + actionsSuppressor: Option[String], + actionsSuppressorWaitPeriod: Option[Int], + actionsSuppressorExtensionPeriod: Option[Int] ) extends ScheduledEventConfiguration { override def productPrefix = "ScheduledEventConfiguration" } @@ -239,6 +255,8 @@ sealed abstract class ScheduledEventState { def reasonData: String def timestamp: OffsetDateTime def value: String + def actionsSuppressedBy: Option[String] + def actionsSuppressedReason: Option[String] } object ScheduledEventState { @@ -246,22 +264,28 @@ object ScheduledEventState { reason: String, reasonData: String, timestamp: OffsetDateTime, - value: String + value: String, + actionsSuppressedBy: Option[String], + actionsSuppressedReason: Option[String] ): ScheduledEventState = - new Impl(reason, reasonData, timestamp, value) + new Impl(reason, reasonData, timestamp, value, actionsSuppressedBy, actionsSuppressedReason) - implicit val decoder: Decoder[ScheduledEventState] = Decoder.forProduct4( + implicit val decoder: Decoder[ScheduledEventState] = Decoder.forProduct6( "reason", "reasonData", "timestamp", - "value" + "value", + "actionsSuppressedBy", + "actionsSuppressedReason" )(ScheduledEventState.apply) private final case class Impl( reason: String, reasonData: String, timestamp: OffsetDateTime, - value: String + value: String, + actionsSuppressedBy: Option[String], + actionsSuppressedReason: Option[String] ) extends ScheduledEventState { override def productPrefix = "ScheduledEventState" } diff --git a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala index 84a2cdfc..8803dff6 100644 --- a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala +++ b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala @@ -22,7 +22,15 @@ class ScheduledEventSuite extends FunSuite { test("Decoding of change of state of abnormality detection alarms") { assertEquals( abnormalityDetectionEvent.as[ScheduledEvent].toTry.get, - abnormalityDetectionResult) + abnormalityDetectionResult + ) + } + + test("Decoding change of state of compound alarm with suppressor alarm") { + assertEquals( + compoundAlarmSuppressorEvent.as[ScheduledEvent].toTry.get, + compoundAlarmSuppressorResult + ) } // Alarm status changes based on a single metric @@ -89,8 +97,8 @@ class ScheduledEventSuite extends FunSuite { alarmName = "ServerCpuTooHigh", operation = None, configuration = ScheduledEventConfiguration( - description = "Goes into alarm when server CPU utilization is too high!", - metrics = List( + description =Some( "Goes into alarm when server CPU utilization is too high!"), + metrics = Some(List( ScheduledEventMetric( id = "30b6c6b2-a864-43a2-4877-c09a1afc3b87", metricStat = Some( @@ -107,7 +115,11 @@ class ScheduledEventSuite extends FunSuite { expression = None, label = None ) - ) + )), + alarmRule = None, + actionsSuppressor = None, + actionsSuppressorWaitPeriod = None, + actionsSuppressorExtensionPeriod = None ), previousState = Some( ScheduledEventPreviousState( @@ -125,7 +137,9 @@ class ScheduledEventSuite extends FunSuite { reasonData = "{\"version\":\"1.0\",\"queryDate\":\"2019-10-02T17:04:40.985+0000\",\"startDate\":\"2019-10-02T16:59:00.000+0000\",\"statistic\":\"Average\",\"period\":300,\"recentDatapoints\":[99.50160229693434],\"threshold\":50.0}", timestamp = OffsetDateTime.parse("2019-10-02T17:04:40.989+0000", formatter), - value = "ALARM" + value = "ALARM", + actionsSuppressedBy = None, + actionsSuppressedReason = None )) ), `replay-name` = None @@ -216,8 +230,8 @@ class ScheduledEventSuite extends FunSuite { alarmName = "TotalNetworkTrafficTooHigh", operation = None, configuration = ScheduledEventConfiguration( - description = "Goes into alarm if total network traffic exceeds 10Kb", - metrics = List( + description = Some("Goes into alarm if total network traffic exceeds 10Kb"), + metrics = Some(List( ScheduledEventMetric( expression = Some("SUM(METRICS())"), id = "e1", @@ -257,7 +271,11 @@ class ScheduledEventSuite extends FunSuite { expression = None, label = None ) - ) + )), + alarmRule = None, + actionsSuppressor = None, + actionsSuppressorWaitPeriod = None, + actionsSuppressorExtensionPeriod = None ), previousState = Some( ScheduledEventPreviousState( @@ -273,7 +291,9 @@ class ScheduledEventSuite extends FunSuite { reasonData = "{\"version\":\"1.0\",\"queryDate\":\"2019-10-02T17:20:48.551+0000\",\"startDate\":\"2019-10-02T17:10:00.000+0000\",\"period\":300,\"recentDatapoints\":[45628.0],\"threshold\":10000.0}", timestamp = OffsetDateTime.parse("2019-10-02T17:20:48.554+0000", formatter), - value = "ALARM" + value = "ALARM", + actionsSuppressedBy = None, + actionsSuppressedReason = None )) ), `replay-name` = None @@ -345,8 +365,8 @@ class ScheduledEventSuite extends FunSuite { alarmName = "EC2 CPU Utilization Anomaly", operation = None, configuration = ScheduledEventConfiguration( - description = "Goes into alarm if CPU Utilization is out of band", - metrics = List( + description = Some("Goes into alarm if CPU Utilization is out of band"), + metrics = Some(List( ScheduledEventMetric( id = "m1", metricStat = Some( @@ -370,7 +390,11 @@ class ScheduledEventSuite extends FunSuite { returnData = true, metricStat = None ) - ) + )), + alarmRule = None, + actionsSuppressor = None, + actionsSuppressorWaitPeriod = None, + actionsSuppressorExtensionPeriod = None ), state = Some( ScheduledEventState( @@ -379,7 +403,9 @@ class ScheduledEventSuite extends FunSuite { reasonData = "{\"version\":\"1.0\",\"queryDate\":\"2019-10-03T16:00:04.650+0000\",\"startDate\":\"2019-10-03T15:58:00.000+0000\",\"period\":60,\"recentDatapoints\":[0.0],\"recentLowerThresholds\":[0.020599444741798756],\"recentUpperThresholds\":[0.3006915352732461]}", timestamp = OffsetDateTime.parse("2019-10-03T16:00:04.653+0000", formatter), - value = "ALARM" + value = "ALARM", + actionsSuppressedBy = None, + actionsSuppressedReason = None )), previousState = Some( ScheduledEventPreviousState( @@ -393,4 +419,87 @@ class ScheduledEventSuite extends FunSuite { ), `replay-name` = None ) + + // Change of state of combined alarm with suppressor alarm + private def compoundAlarmSuppressorEvent = json""" + { + "version": "0", + "id": "d3dfc86d-384d-24c8-0345-9f7986db0b80", + "detail-type": "CloudWatch Alarm State Change", + "source": "aws.cloudwatch", + "account": "123456789012", + "time": "2022-07-22T15:57:45Z", + "region": "us-east-1", + "resources": [ + "arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServiceAggregatedAlarm" + ], + "detail": { + "alarmName": "ServiceAggregatedAlarm", + "state": { + "actionsSuppressedBy": "WaitPeriod", + "actionsSuppressedReason": "Actions suppressed by WaitPeriod", + "value": "ALARM", + "reason": "arn:aws:cloudwatch:us-east-1:123456789012:alarm:SuppressionDemo.EventBridge.FirstChild transitioned to ALARM at Friday 22 July, 2022 15:57:45 UTC", + "reasonData": "{\"triggeringAlarms\":[{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServerCpuTooHigh\",\"state\":{\"value\":\"ALARM\",\"timestamp\":\"2022-07-22T15:57:45.394+0000\"}}]}", + "timestamp": "2022-07-22T15:57:45.394+0000" + }, + "previousState": { + "value": "OK", + "reason": "arn:aws:cloudwatch:us-east-1:123456789012:alarm:SuppressionDemo.EventBridge.Main was created and its alarm rule evaluates to OK", + "reasonData": "{\"triggeringAlarms\":[{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:TotalNetworkTrafficTooHigh\",\"state\":{\"value\":\"OK\",\"timestamp\":\"2022-07-14T16:28:57.770+0000\"}},{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServerCpuTooHigh\",\"state\":{\"value\":\"OK\",\"timestamp\":\"2022-07-14T16:28:54.191+0000\"}}]}", + "timestamp": "2022-07-22T15:56:14.552+0000" + }, + "configuration": { + "alarmRule": "ALARM(ServerCpuTooHigh) OR ALARM(TotalNetworkTrafficTooHigh)", + "actionsSuppressor": "ServiceMaintenanceAlarm", + "actionsSuppressorWaitPeriod": 120, + "actionsSuppressorExtensionPeriod": 180 + } + } + } + """ + + private def compoundAlarmSuppressorResult: ScheduledEvent = ScheduledEvent( + id = "d3dfc86d-384d-24c8-0345-9f7986db0b80", + version = "0", + account = "123456789012", + time = Instant.parse("2022-07-22T15:57:45Z"), + region = "us-east-1", + resources = List("arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServiceAggregatedAlarm"), + source = "aws.cloudwatch", + `detail-type` = "CloudWatch Alarm State Change", + detail = ScheduledEventDetail( + alarmName = "ServiceAggregatedAlarm", + operation = None, + state = Some( + ScheduledEventState( + actionsSuppressedBy = Some("WaitPeriod"), + actionsSuppressedReason = Some("Actions suppressed by WaitPeriod"), + value = "ALARM", + reason = + "arn:aws:cloudwatch:us-east-1:123456789012:alarm:SuppressionDemo.EventBridge.FirstChild transitioned to ALARM at Friday 22 July, 2022 15:57:45 UTC", + reasonData = "{\"triggeringAlarms\":[{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServerCpuTooHigh\",\"state\":{\"value\":\"ALARM\",\"timestamp\":\"2022-07-22T15:57:45.394+0000\"}}]}", + timestamp = OffsetDateTime.parse("2022-07-22T15:57:45.394+0000", formatter) + ) + ), + previousState = Some( + ScheduledEventPreviousState( + value = "OK", + reason = + "arn:aws:cloudwatch:us-east-1:123456789012:alarm:SuppressionDemo.EventBridge.Main was created and its alarm rule evaluates to OK", + reasonData = Some("{\"triggeringAlarms\":[{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:TotalNetworkTrafficTooHigh\",\"state\":{\"value\":\"OK\",\"timestamp\":\"2022-07-14T16:28:57.770+0000\"}},{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServerCpuTooHigh\",\"state\":{\"value\":\"OK\",\"timestamp\":\"2022-07-14T16:28:54.191+0000\"}}]}"), + timestamp = OffsetDateTime.parse("2022-07-22T15:56:14.552+0000", formatter) + ) + ), + configuration = ScheduledEventConfiguration( + alarmRule = Some("ALARM(ServerCpuTooHigh) OR ALARM(TotalNetworkTrafficTooHigh)"), + actionsSuppressor = Some("ServiceMaintenanceAlarm"), + actionsSuppressorWaitPeriod = Some(120), + actionsSuppressorExtensionPeriod = Some(180), + metrics = None, + description = None + ), + ), + `replay-name` = None + ) } From dbdacfc21a869a41b133c42845ef90eb8ff52eea Mon Sep 17 00:00:00 2001 From: takapi327 Date: Fri, 11 Oct 2024 01:23:18 +0900 Subject: [PATCH 07/20] Action sbt scalafmtAll --- .../feral/lambda/events/ScheduledEvent.scala | 20 +- .../lambda/events/ScheduledEventSuite.scala | 181 +++++++++--------- 2 files changed, 106 insertions(+), 95 deletions(-) diff --git a/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala b/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala index 34081e4b..48d70c7c 100644 --- a/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala +++ b/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala @@ -132,7 +132,13 @@ object ScheduledEventConfiguration { actionsSuppressorWaitPeriod: Option[Int], actionsSuppressorExtensionPeriod: Option[Int] ): ScheduledEventConfiguration = - new Impl(description, metrics, alarmRule, actionsSuppressor, actionsSuppressorWaitPeriod, actionsSuppressorExtensionPeriod) + new Impl( + description, + metrics, + alarmRule, + actionsSuppressor, + actionsSuppressorWaitPeriod, + actionsSuppressorExtensionPeriod) implicit val decoder: Decoder[ScheduledEventConfiguration] = Decoder.forProduct6( "description", @@ -144,12 +150,12 @@ object ScheduledEventConfiguration { )(ScheduledEventConfiguration.apply) private final case class Impl( - description: Option[String], - metrics: Option[List[ScheduledEventMetric]], - alarmRule: Option[String], - actionsSuppressor: Option[String], - actionsSuppressorWaitPeriod: Option[Int], - actionsSuppressorExtensionPeriod: Option[Int] + description: Option[String], + metrics: Option[List[ScheduledEventMetric]], + alarmRule: Option[String], + actionsSuppressor: Option[String], + actionsSuppressorWaitPeriod: Option[Int], + actionsSuppressorExtensionPeriod: Option[Int] ) extends ScheduledEventConfiguration { override def productPrefix = "ScheduledEventConfiguration" } diff --git a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala index 8803dff6..6ceb6dbb 100644 --- a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala +++ b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala @@ -97,25 +97,26 @@ class ScheduledEventSuite extends FunSuite { alarmName = "ServerCpuTooHigh", operation = None, configuration = ScheduledEventConfiguration( - description =Some( "Goes into alarm when server CPU utilization is too high!"), - metrics = Some(List( - ScheduledEventMetric( - id = "30b6c6b2-a864-43a2-4877-c09a1afc3b87", - metricStat = Some( - ScheduledEventMetricStat( - metric = Metric( - dimensions = Map("InstanceId" -> "i-12345678901234567"), - name = "CPUUtilization", - namespace = "AWS/EC2" - ), - period = 300, - stat = "Average" - )), - returnData = true, - expression = None, - label = None - ) - )), + description = Some("Goes into alarm when server CPU utilization is too high!"), + metrics = Some( + List( + ScheduledEventMetric( + id = "30b6c6b2-a864-43a2-4877-c09a1afc3b87", + metricStat = Some( + ScheduledEventMetricStat( + metric = Metric( + dimensions = Map("InstanceId" -> "i-12345678901234567"), + name = "CPUUtilization", + namespace = "AWS/EC2" + ), + period = 300, + stat = "Average" + )), + returnData = true, + expression = None, + label = None + ) + )), alarmRule = None, actionsSuppressor = None, actionsSuppressorWaitPeriod = None, @@ -231,47 +232,48 @@ class ScheduledEventSuite extends FunSuite { operation = None, configuration = ScheduledEventConfiguration( description = Some("Goes into alarm if total network traffic exceeds 10Kb"), - metrics = Some(List( - ScheduledEventMetric( - expression = Some("SUM(METRICS())"), - id = "e1", - label = Some("Total Network Traffic"), - returnData = true, - metricStat = None - ), - ScheduledEventMetric( - id = "m1", - metricStat = Some( - ScheduledEventMetricStat( - metric = Metric( - dimensions = Map("InstanceId" -> "i-12345678901234567"), - name = "NetworkIn", - namespace = "AWS/EC2" - ), - period = 300, - stat = "Maximum" - )), - returnData = false, - expression = None, - label = None - ), - ScheduledEventMetric( - id = "m2", - metricStat = Some( - ScheduledEventMetricStat( - metric = Metric( - dimensions = Map("InstanceId" -> "i-12345678901234567"), - name = "NetworkOut", - namespace = "AWS/EC2" - ), - period = 300, - stat = "Maximum" - )), - returnData = false, - expression = None, - label = None - ) - )), + metrics = Some( + List( + ScheduledEventMetric( + expression = Some("SUM(METRICS())"), + id = "e1", + label = Some("Total Network Traffic"), + returnData = true, + metricStat = None + ), + ScheduledEventMetric( + id = "m1", + metricStat = Some( + ScheduledEventMetricStat( + metric = Metric( + dimensions = Map("InstanceId" -> "i-12345678901234567"), + name = "NetworkIn", + namespace = "AWS/EC2" + ), + period = 300, + stat = "Maximum" + )), + returnData = false, + expression = None, + label = None + ), + ScheduledEventMetric( + id = "m2", + metricStat = Some( + ScheduledEventMetricStat( + metric = Metric( + dimensions = Map("InstanceId" -> "i-12345678901234567"), + name = "NetworkOut", + namespace = "AWS/EC2" + ), + period = 300, + stat = "Maximum" + )), + returnData = false, + expression = None, + label = None + ) + )), alarmRule = None, actionsSuppressor = None, actionsSuppressorWaitPeriod = None, @@ -366,31 +368,32 @@ class ScheduledEventSuite extends FunSuite { operation = None, configuration = ScheduledEventConfiguration( description = Some("Goes into alarm if CPU Utilization is out of band"), - metrics = Some(List( - ScheduledEventMetric( - id = "m1", - metricStat = Some( - ScheduledEventMetricStat( - metric = Metric( - dimensions = Map("InstanceId" -> "i-12345678901234567"), - name = "CPUUtilization", - namespace = "AWS/EC2" - ), - period = 60, - stat = "Average" - )), - returnData = true, - expression = None, - label = None - ), - ScheduledEventMetric( - id = "ad1", - expression = Some("ANOMALY_DETECTION_BAND(m1, 0.8)"), - label = Some("CPUUtilization (expected)"), - returnData = true, - metricStat = None - ) - )), + metrics = Some( + List( + ScheduledEventMetric( + id = "m1", + metricStat = Some( + ScheduledEventMetricStat( + metric = Metric( + dimensions = Map("InstanceId" -> "i-12345678901234567"), + name = "CPUUtilization", + namespace = "AWS/EC2" + ), + period = 60, + stat = "Average" + )), + returnData = true, + expression = None, + label = None + ), + ScheduledEventMetric( + id = "ad1", + expression = Some("ANOMALY_DETECTION_BAND(m1, 0.8)"), + label = Some("CPUUtilization (expected)"), + returnData = true, + metricStat = None + ) + )), alarmRule = None, actionsSuppressor = None, actionsSuppressorWaitPeriod = None, @@ -478,7 +481,8 @@ class ScheduledEventSuite extends FunSuite { value = "ALARM", reason = "arn:aws:cloudwatch:us-east-1:123456789012:alarm:SuppressionDemo.EventBridge.FirstChild transitioned to ALARM at Friday 22 July, 2022 15:57:45 UTC", - reasonData = "{\"triggeringAlarms\":[{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServerCpuTooHigh\",\"state\":{\"value\":\"ALARM\",\"timestamp\":\"2022-07-22T15:57:45.394+0000\"}}]}", + reasonData = + "{\"triggeringAlarms\":[{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServerCpuTooHigh\",\"state\":{\"value\":\"ALARM\",\"timestamp\":\"2022-07-22T15:57:45.394+0000\"}}]}", timestamp = OffsetDateTime.parse("2022-07-22T15:57:45.394+0000", formatter) ) ), @@ -487,7 +491,8 @@ class ScheduledEventSuite extends FunSuite { value = "OK", reason = "arn:aws:cloudwatch:us-east-1:123456789012:alarm:SuppressionDemo.EventBridge.Main was created and its alarm rule evaluates to OK", - reasonData = Some("{\"triggeringAlarms\":[{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:TotalNetworkTrafficTooHigh\",\"state\":{\"value\":\"OK\",\"timestamp\":\"2022-07-14T16:28:57.770+0000\"}},{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServerCpuTooHigh\",\"state\":{\"value\":\"OK\",\"timestamp\":\"2022-07-14T16:28:54.191+0000\"}}]}"), + reasonData = Some( + "{\"triggeringAlarms\":[{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:TotalNetworkTrafficTooHigh\",\"state\":{\"value\":\"OK\",\"timestamp\":\"2022-07-14T16:28:57.770+0000\"}},{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServerCpuTooHigh\",\"state\":{\"value\":\"OK\",\"timestamp\":\"2022-07-14T16:28:54.191+0000\"}}]}"), timestamp = OffsetDateTime.parse("2022-07-22T15:56:14.552+0000", formatter) ) ), @@ -498,7 +503,7 @@ class ScheduledEventSuite extends FunSuite { actionsSuppressorExtensionPeriod = Some(180), metrics = None, description = None - ), + ) ), `replay-name` = None ) From 391bd2fecef8ca423218ccf8f369b9c115589069 Mon Sep 17 00:00:00 2001 From: takapi327 Date: Fri, 11 Oct 2024 09:21:33 +0900 Subject: [PATCH 08/20] Change model to JsonObject --- .../feral/lambda/events/ScheduledEvent.scala | 265 +----------- .../lambda/events/ScheduledEventSuite.scala | 377 ++++++++---------- 2 files changed, 170 insertions(+), 472 deletions(-) diff --git a/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala b/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala index 48d70c7c..abb75aef 100644 --- a/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala +++ b/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala @@ -1,10 +1,8 @@ package feral.lambda.events -import java.time.{Instant, OffsetDateTime} +import java.time.Instant -import io.circe.Decoder - -import codecs.decodeOffsetDateTime +import io.circe.{Decoder, JsonObject} // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/aws-lambda/trigger/eventbridge.d.ts // https://github.com/aws/aws-lambda-java-libs/blob/main/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/ScheduledEvent.java @@ -17,7 +15,7 @@ sealed abstract class ScheduledEvent { def resources: List[String] def source: String def `detail-type`: String - def detail: ScheduledEventDetail + def detail: JsonObject def `replay-name`: Option[String] } @@ -32,7 +30,7 @@ object ScheduledEvent { resources: List[String], source: String, `detail-type`: String, - detail: ScheduledEventDetail, + detail: JsonObject, `replay-name`: Option[String] ): ScheduledEvent = new Impl( @@ -70,262 +68,9 @@ object ScheduledEvent { resources: List[String], source: String, `detail-type`: String, - detail: ScheduledEventDetail, + detail: JsonObject, `replay-name`: Option[String] ) extends ScheduledEvent { override def productPrefix = "ScheduledEvent" } } - -sealed abstract class ScheduledEventDetail { - def alarmName: String - def operation: Option[String] - def configuration: ScheduledEventConfiguration - def previousState: Option[ScheduledEventPreviousState] - def state: Option[ScheduledEventState] -} - -object ScheduledEventDetail { - def apply( - alarmName: String, - operation: Option[String], - configuration: ScheduledEventConfiguration, - previousState: Option[ScheduledEventPreviousState], - state: Option[ScheduledEventState] - ): ScheduledEventDetail = - new Impl(alarmName, operation, configuration, previousState, state) - - implicit val decoder: Decoder[ScheduledEventDetail] = Decoder.forProduct5( - "alarmName", - "operation", - "configuration", - "previousState", - "state" - )(ScheduledEventDetail.apply) - - private final case class Impl( - alarmName: String, - operation: Option[String], - configuration: ScheduledEventConfiguration, - previousState: Option[ScheduledEventPreviousState], - state: Option[ScheduledEventState] - ) extends ScheduledEventDetail { - override def productPrefix = "ScheduledEventDetail" - } -} - -sealed abstract class ScheduledEventConfiguration { - def description: Option[String] - def metrics: Option[List[ScheduledEventMetric]] - def alarmRule: Option[String] - def actionsSuppressor: Option[String] - def actionsSuppressorWaitPeriod: Option[Int] - def actionsSuppressorExtensionPeriod: Option[Int] -} - -object ScheduledEventConfiguration { - def apply( - description: Option[String], - metrics: Option[List[ScheduledEventMetric]], - alarmRule: Option[String], - actionsSuppressor: Option[String], - actionsSuppressorWaitPeriod: Option[Int], - actionsSuppressorExtensionPeriod: Option[Int] - ): ScheduledEventConfiguration = - new Impl( - description, - metrics, - alarmRule, - actionsSuppressor, - actionsSuppressorWaitPeriod, - actionsSuppressorExtensionPeriod) - - implicit val decoder: Decoder[ScheduledEventConfiguration] = Decoder.forProduct6( - "description", - "metrics", - "alarmRule", - "actionsSuppressor", - "actionsSuppressorWaitPeriod", - "actionsSuppressorExtensionPeriod" - )(ScheduledEventConfiguration.apply) - - private final case class Impl( - description: Option[String], - metrics: Option[List[ScheduledEventMetric]], - alarmRule: Option[String], - actionsSuppressor: Option[String], - actionsSuppressorWaitPeriod: Option[Int], - actionsSuppressorExtensionPeriod: Option[Int] - ) extends ScheduledEventConfiguration { - override def productPrefix = "ScheduledEventConfiguration" - } -} - -sealed abstract class ScheduledEventMetric { - def id: String - def returnData: Boolean - def expression: Option[String] - def label: Option[String] - def metricStat: Option[ScheduledEventMetricStat] -} - -object ScheduledEventMetric { - def apply( - id: String, - returnData: Boolean, - expression: Option[String], - label: Option[String], - metricStat: Option[ScheduledEventMetricStat] - ): ScheduledEventMetric = - new Impl(id, returnData, expression, label, metricStat) - - implicit val decoder: Decoder[ScheduledEventMetric] = Decoder.forProduct5( - "id", - "returnData", - "expression", - "label", - "metricStat" - )(ScheduledEventMetric.apply) - - private final case class Impl( - id: String, - returnData: Boolean, - expression: Option[String], - label: Option[String], - metricStat: Option[ScheduledEventMetricStat] - ) extends ScheduledEventMetric { - override def productPrefix = "ScheduledEventMetric" - } -} - -sealed abstract class ScheduledEventMetricStat { - def metric: Metric - def period: Int - def stat: String -} - -object ScheduledEventMetricStat { - def apply( - metric: Metric, - period: Int, - stat: String - ): ScheduledEventMetricStat = - new Impl(metric, period, stat) - - implicit val decoder: Decoder[ScheduledEventMetricStat] = Decoder.forProduct3( - "metric", - "period", - "stat" - )(ScheduledEventMetricStat.apply) - - private final case class Impl( - metric: Metric, - period: Int, - stat: String - ) extends ScheduledEventMetricStat { - override def productPrefix = "ScheduledEventMetricStat" - } -} - -sealed abstract class Metric { - def dimensions: Map[String, String] - def name: String - def namespace: String -} - -object Metric { - def apply( - dimensions: Map[String, String], - name: String, - namespace: String - ): Metric = - new Impl(dimensions, name, namespace) - - implicit val decoder: Decoder[Metric] = Decoder.forProduct3( - "dimensions", - "name", - "namespace" - )(Metric.apply) - - private final case class Impl( - dimensions: Map[String, String], - name: String, - namespace: String - ) extends Metric { - override def productPrefix = "Metric" - } -} - -sealed abstract class ScheduledEventState { - def reason: String - def reasonData: String - def timestamp: OffsetDateTime - def value: String - def actionsSuppressedBy: Option[String] - def actionsSuppressedReason: Option[String] -} - -object ScheduledEventState { - def apply( - reason: String, - reasonData: String, - timestamp: OffsetDateTime, - value: String, - actionsSuppressedBy: Option[String], - actionsSuppressedReason: Option[String] - ): ScheduledEventState = - new Impl(reason, reasonData, timestamp, value, actionsSuppressedBy, actionsSuppressedReason) - - implicit val decoder: Decoder[ScheduledEventState] = Decoder.forProduct6( - "reason", - "reasonData", - "timestamp", - "value", - "actionsSuppressedBy", - "actionsSuppressedReason" - )(ScheduledEventState.apply) - - private final case class Impl( - reason: String, - reasonData: String, - timestamp: OffsetDateTime, - value: String, - actionsSuppressedBy: Option[String], - actionsSuppressedReason: Option[String] - ) extends ScheduledEventState { - override def productPrefix = "ScheduledEventState" - } -} - -sealed abstract class ScheduledEventPreviousState { - def reason: String - def reasonData: Option[String] - def timestamp: OffsetDateTime - def value: String -} - -object ScheduledEventPreviousState { - def apply( - reason: String, - reasonData: Option[String], - timestamp: OffsetDateTime, - value: String - ): ScheduledEventPreviousState = - new Impl(reason, reasonData, timestamp, value) - - implicit val decoder: Decoder[ScheduledEventPreviousState] = Decoder.forProduct4( - "reason", - "reasonData", - "timestamp", - "value" - )(ScheduledEventPreviousState.apply) - - private final case class Impl( - reason: String, - reasonData: Option[String], - timestamp: OffsetDateTime, - value: String - ) extends ScheduledEventPreviousState { - override def productPrefix = "ScheduledEventPreviousState" - } -} diff --git a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala index 6ceb6dbb..43bc392e 100644 --- a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala +++ b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala @@ -1,16 +1,14 @@ package feral.lambda.events -import java.time.{Instant, OffsetDateTime} -import java.time.format.DateTimeFormatter +import java.time.Instant import io.circe.literal._ +import io.circe.{JsonObject, Json} import munit.FunSuite class ScheduledEventSuite extends FunSuite { - private val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") - test("Decoding of alarm status changes based on a single metric") { assertEquals(singleMetricEvent.as[ScheduledEvent].toTry.get, singleMetricResult) } @@ -93,55 +91,43 @@ class ScheduledEventSuite extends FunSuite { resources = List("arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServerCpuTooHigh"), source = "aws.cloudwatch", `detail-type` = "CloudWatch Alarm State Change", - detail = ScheduledEventDetail( - alarmName = "ServerCpuTooHigh", - operation = None, - configuration = ScheduledEventConfiguration( - description = Some("Goes into alarm when server CPU utilization is too high!"), - metrics = Some( - List( - ScheduledEventMetric( - id = "30b6c6b2-a864-43a2-4877-c09a1afc3b87", - metricStat = Some( - ScheduledEventMetricStat( - metric = Metric( - dimensions = Map("InstanceId" -> "i-12345678901234567"), - name = "CPUUtilization", - namespace = "AWS/EC2" - ), - period = 300, - stat = "Average" - )), - returnData = true, - expression = None, - label = None - ) - )), - alarmRule = None, - actionsSuppressor = None, - actionsSuppressorWaitPeriod = None, - actionsSuppressorExtensionPeriod = None - ), - previousState = Some( - ScheduledEventPreviousState( - reason = - "Threshold Crossed: 1 out of the last 1 datapoints [0.0666851903306472 (01/10/19 13:46:00)] was not greater than the threshold (50.0) (minimum 1 datapoint for ALARM -> OK transition).", - reasonData = Some( - "{\"version\":\"1.0\",\"queryDate\":\"2019-10-01T13:56:40.985+0000\",\"startDate\":\"2019-10-01T13:46:00.000+0000\",\"statistic\":\"Average\",\"period\":300,\"recentDatapoints\":[0.0666851903306472],\"threshold\":50.0}"), - timestamp = OffsetDateTime.parse("2019-10-01T13:56:40.987+0000", formatter), - value = "OK" - )), - state = Some( - ScheduledEventState( - reason = - "Threshold Crossed: 1 out of the last 1 datapoints [99.50160229693434 (02/10/19 16:59:00)] was greater than the threshold (50.0) (minimum 1 datapoint for OK -> ALARM transition).", - reasonData = - "{\"version\":\"1.0\",\"queryDate\":\"2019-10-02T17:04:40.985+0000\",\"startDate\":\"2019-10-02T16:59:00.000+0000\",\"statistic\":\"Average\",\"period\":300,\"recentDatapoints\":[99.50160229693434],\"threshold\":50.0}", - timestamp = OffsetDateTime.parse("2019-10-02T17:04:40.989+0000", formatter), - value = "ALARM", - actionsSuppressedBy = None, - actionsSuppressedReason = None - )) + detail = JsonObject.apply( + "alarmName" -> Json.fromString("ServerCpuTooHigh"), + "configuration" -> JsonObject.apply( + "description" -> Json.fromString("Goes into alarm when server CPU utilization is too high!"), + "metrics" -> Json.arr( + JsonObject.apply( + "id" -> Json.fromString("30b6c6b2-a864-43a2-4877-c09a1afc3b87"), + "metricStat" -> JsonObject.apply( + "metric" -> JsonObject.apply( + "dimensions" -> JsonObject.apply("InstanceId" -> Json.fromString("i-12345678901234567")).toJson, + "name" -> Json.fromString("CPUUtilization"), + "namespace" -> Json.fromString("AWS/EC2") + ).toJson, + "period" -> Json.fromInt(300), + "stat" -> Json.fromString("Average") + ).toJson, + "returnData" -> Json.fromBoolean(true) + ).toJson + ) + ).toJson, + "previousState" -> JsonObject.apply( + "reason" -> Json.fromString( + "Threshold Crossed: 1 out of the last 1 datapoints [0.0666851903306472 (01/10/19 13:46:00)] was not greater than the threshold (50.0) (minimum 1 datapoint for ALARM -> OK transition)."), + "reasonData" -> Json.fromString( + "{\"version\":\"1.0\",\"queryDate\":\"2019-10-01T13:56:40.985+0000\",\"startDate\":\"2019-10-01T13:46:00.000+0000\",\"statistic\":\"Average\",\"period\":300,\"recentDatapoints\":[0.0666851903306472],\"threshold\":50.0}" + ), + "timestamp" -> Json.fromString("2019-10-01T13:56:40.987+0000"), + "value" -> Json.fromString("OK") + ).toJson, + "state" -> JsonObject.apply( + "reason" -> Json.fromString( + "Threshold Crossed: 1 out of the last 1 datapoints [99.50160229693434 (02/10/19 16:59:00)] was greater than the threshold (50.0) (minimum 1 datapoint for OK -> ALARM transition)." + ), + "reasonData" -> Json.fromString("{\"version\":\"1.0\",\"queryDate\":\"2019-10-02T17:04:40.985+0000\",\"startDate\":\"2019-10-02T16:59:00.000+0000\",\"statistic\":\"Average\",\"period\":300,\"recentDatapoints\":[99.50160229693434],\"threshold\":50.0}"), + "timestamp" -> Json.fromString("2019-10-02T17:04:40.989+0000"), + "value" -> Json.fromString("ALARM") + ).toJson, ), `replay-name` = None ) @@ -227,76 +213,60 @@ class ScheduledEventSuite extends FunSuite { List("arn:aws:cloudwatch:us-east-1:123456789012:alarm:TotalNetworkTrafficTooHigh"), source = "aws.cloudwatch", `detail-type` = "CloudWatch Alarm State Change", - detail = ScheduledEventDetail( - alarmName = "TotalNetworkTrafficTooHigh", - operation = None, - configuration = ScheduledEventConfiguration( - description = Some("Goes into alarm if total network traffic exceeds 10Kb"), - metrics = Some( - List( - ScheduledEventMetric( - expression = Some("SUM(METRICS())"), - id = "e1", - label = Some("Total Network Traffic"), - returnData = true, - metricStat = None - ), - ScheduledEventMetric( - id = "m1", - metricStat = Some( - ScheduledEventMetricStat( - metric = Metric( - dimensions = Map("InstanceId" -> "i-12345678901234567"), - name = "NetworkIn", - namespace = "AWS/EC2" - ), - period = 300, - stat = "Maximum" - )), - returnData = false, - expression = None, - label = None - ), - ScheduledEventMetric( - id = "m2", - metricStat = Some( - ScheduledEventMetricStat( - metric = Metric( - dimensions = Map("InstanceId" -> "i-12345678901234567"), - name = "NetworkOut", - namespace = "AWS/EC2" - ), - period = 300, - stat = "Maximum" - )), - returnData = false, - expression = None, - label = None - ) - )), - alarmRule = None, - actionsSuppressor = None, - actionsSuppressorWaitPeriod = None, - actionsSuppressorExtensionPeriod = None - ), - previousState = Some( - ScheduledEventPreviousState( - reason = "Unchecked: Initial alarm creation", - reasonData = None, - timestamp = OffsetDateTime.parse("2019-10-02T17:20:03.642+0000", formatter), - value = "INSUFFICIENT_DATA" - )), - state = Some( - ScheduledEventState( - reason = - "Threshold Crossed: 1 out of the last 1 datapoints [45628.0 (02/10/19 17:10:00)] was greater than the threshold (10000.0) (minimum 1 datapoint for OK -> ALARM transition).", - reasonData = - "{\"version\":\"1.0\",\"queryDate\":\"2019-10-02T17:20:48.551+0000\",\"startDate\":\"2019-10-02T17:10:00.000+0000\",\"period\":300,\"recentDatapoints\":[45628.0],\"threshold\":10000.0}", - timestamp = OffsetDateTime.parse("2019-10-02T17:20:48.554+0000", formatter), - value = "ALARM", - actionsSuppressedBy = None, - actionsSuppressedReason = None - )) + detail = JsonObject.apply( + "alarmName" -> Json.fromString("TotalNetworkTrafficTooHigh"), + "configuration" -> JsonObject.apply( + "description" -> Json.fromString("Goes into alarm if total network traffic exceeds 10Kb"), + "metrics" -> Json.arr( + JsonObject.apply( + "expression" -> Json.fromString("SUM(METRICS())"), + "id" -> Json.fromString("e1"), + "label" -> Json.fromString("Total Network Traffic"), + "returnData" -> Json.fromBoolean(true) + ).toJson, + JsonObject.apply( + "id" -> Json.fromString("m1"), + "metricStat" -> JsonObject.apply( + "metric" -> JsonObject.apply( + "dimensions" -> JsonObject.apply("InstanceId" -> Json.fromString("i-12345678901234567")).toJson, + "name" -> Json.fromString("NetworkIn"), + "namespace" -> Json.fromString("AWS/EC2") + ).toJson, + "period" -> Json.fromInt(300), + "stat" -> Json.fromString("Maximum") + ).toJson, + "returnData" -> Json.fromBoolean(false) + ).toJson, + JsonObject.apply( + "id" -> Json.fromString("m2"), + "metricStat" -> JsonObject.apply( + "metric" -> JsonObject.apply( + "dimensions" -> JsonObject.apply("InstanceId" -> Json.fromString("i-12345678901234567")).toJson, + "name" -> Json.fromString("NetworkOut"), + "namespace" -> Json.fromString("AWS/EC2") + ).toJson, + "period" -> Json.fromInt(300), + "stat" -> Json.fromString("Maximum") + ).toJson, + "returnData" -> Json.fromBoolean(false) + ).toJson + ) + ).toJson, + "previousState" -> JsonObject.apply( + "reason" -> Json.fromString("Unchecked: Initial alarm creation"), + "timestamp" -> Json.fromString("2019-10-02T17:20:03.642+0000"), + "value" -> Json.fromString("INSUFFICIENT_DATA") + ).toJson, + "state" -> JsonObject.apply( + "reason" -> Json.fromString( + "Threshold Crossed: 1 out of the last 1 datapoints [45628.0 (02/10/19 17:10:00)] was greater than the threshold (10000.0) (minimum 1 datapoint for OK -> ALARM transition)." + ), + "reasonData" -> Json.fromString( + "{\"version\":\"1.0\",\"queryDate\":\"2019-10-02T17:20:48.551+0000\",\"startDate\":\"2019-10-02T17:10:00.000+0000\",\"period\":300,\"recentDatapoints\":[45628.0],\"threshold\":10000.0}", + ), + "timestamp" -> Json.fromString("2019-10-02T17:20:48.554+0000"), + "value" -> Json.fromString("ALARM") + ).toJson, ), `replay-name` = None ) @@ -363,62 +333,48 @@ class ScheduledEventSuite extends FunSuite { List("arn:aws:cloudwatch:us-east-1:123456789012:alarm:EC2 CPU Utilization Anomaly"), source = "aws.cloudwatch", `detail-type` = "CloudWatch Alarm State Change", - detail = ScheduledEventDetail( - alarmName = "EC2 CPU Utilization Anomaly", - operation = None, - configuration = ScheduledEventConfiguration( - description = Some("Goes into alarm if CPU Utilization is out of band"), - metrics = Some( - List( - ScheduledEventMetric( - id = "m1", - metricStat = Some( - ScheduledEventMetricStat( - metric = Metric( - dimensions = Map("InstanceId" -> "i-12345678901234567"), - name = "CPUUtilization", - namespace = "AWS/EC2" - ), - period = 60, - stat = "Average" - )), - returnData = true, - expression = None, - label = None - ), - ScheduledEventMetric( - id = "ad1", - expression = Some("ANOMALY_DETECTION_BAND(m1, 0.8)"), - label = Some("CPUUtilization (expected)"), - returnData = true, - metricStat = None - ) - )), - alarmRule = None, - actionsSuppressor = None, - actionsSuppressorWaitPeriod = None, - actionsSuppressorExtensionPeriod = None - ), - state = Some( - ScheduledEventState( - reason = - "Thresholds Crossed: 1 out of the last 1 datapoints [0.0 (03/10/19 15:58:00)] was less than the lower thresholds [0.020599444741798756] or greater than the upper thresholds [0.3006915352732461] (minimum 1 datapoint for OK -> ALARM transition).", - reasonData = - "{\"version\":\"1.0\",\"queryDate\":\"2019-10-03T16:00:04.650+0000\",\"startDate\":\"2019-10-03T15:58:00.000+0000\",\"period\":60,\"recentDatapoints\":[0.0],\"recentLowerThresholds\":[0.020599444741798756],\"recentUpperThresholds\":[0.3006915352732461]}", - timestamp = OffsetDateTime.parse("2019-10-03T16:00:04.653+0000", formatter), - value = "ALARM", - actionsSuppressedBy = None, - actionsSuppressedReason = None - )), - previousState = Some( - ScheduledEventPreviousState( - reason = - "Thresholds Crossed: 1 out of the last 1 datapoints [0.166666666664241 (03/10/19 15:57:00)] was not less than the lower thresholds [0.0206719426210418] or not greater than the upper thresholds [0.30076870222143803] (minimum 1 datapoint for ALARM -> OK transition).", - reasonData = Some( - "{\"version\":\"1.0\",\"queryDate\":\"2019-10-03T15:59:04.670+0000\",\"startDate\":\"2019-10-03T15:57:00.000+0000\",\"period\":60,\"recentDatapoints\":[0.166666666664241],\"recentLowerThresholds\":[0.0206719426210418],\"recentUpperThresholds\":[0.30076870222143803]}"), - timestamp = OffsetDateTime.parse("2019-10-03T15:59:04.672+0000", formatter), - value = "OK" - )) + detail = JsonObject.apply( + "alarmName" -> Json.fromString("EC2 CPU Utilization Anomaly"), + "state" -> JsonObject.apply( + "value" -> Json.fromString("ALARM"), + "reason" -> Json.fromString( + "Thresholds Crossed: 1 out of the last 1 datapoints [0.0 (03/10/19 15:58:00)] was less than the lower thresholds [0.020599444741798756] or greater than the upper thresholds [0.3006915352732461] (minimum 1 datapoint for OK -> ALARM transition)." + ), + "reasonData" -> Json.fromString( + "{\"version\":\"1.0\",\"queryDate\":\"2019-10-03T16:00:04.650+0000\",\"startDate\":\"2019-10-03T15:58:00.000+0000\",\"period\":60,\"recentDatapoints\":[0.0],\"recentLowerThresholds\":[0.020599444741798756],\"recentUpperThresholds\":[0.3006915352732461]}" + ), + "timestamp" -> Json.fromString("2019-10-03T16:00:04.653+0000") + ).toJson, + "previousState" -> JsonObject.apply( + "value" -> Json.fromString("OK"), + "reason" -> Json.fromString("Thresholds Crossed: 1 out of the last 1 datapoints [0.166666666664241 (03/10/19 15:57:00)] was not less than the lower thresholds [0.0206719426210418] or not greater than the upper thresholds [0.30076870222143803] (minimum 1 datapoint for ALARM -> OK transition)."), + "reasonData" -> Json.fromString("{\"version\":\"1.0\",\"queryDate\":\"2019-10-03T15:59:04.670+0000\",\"startDate\":\"2019-10-03T15:57:00.000+0000\",\"period\":60,\"recentDatapoints\":[0.166666666664241],\"recentLowerThresholds\":[0.0206719426210418],\"recentUpperThresholds\":[0.30076870222143803]}"), + "timestamp" -> Json.fromString("2019-10-03T15:59:04.672+0000") + ).toJson, + "configuration" -> JsonObject.apply( + "description" -> Json.fromString("Goes into alarm if CPU Utilization is out of band"), + "metrics" -> Json.arr( + JsonObject.apply( + "id" -> Json.fromString("m1"), + "metricStat" -> JsonObject.apply( + "metric" -> JsonObject.apply( + "namespace" -> Json.fromString("AWS/EC2"), + "name" -> Json.fromString("CPUUtilization"), + "dimensions" -> JsonObject.apply("InstanceId" -> Json.fromString("i-12345678901234567")).toJson + ).toJson, + "period" -> Json.fromInt(60), + "stat" -> Json.fromString("Average") + ).toJson, + "returnData" -> Json.fromBoolean(true) + ).toJson, + JsonObject.apply( + "id" -> Json.fromString("ad1"), + "expression" -> Json.fromString("ANOMALY_DETECTION_BAND(m1, 0.8)"), + "label" -> Json.fromString("CPUUtilization (expected)"), + "returnData" -> Json.fromBoolean(true) + ).toJson + ) + ).toJson ), `replay-name` = None ) @@ -471,39 +427,36 @@ class ScheduledEventSuite extends FunSuite { resources = List("arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServiceAggregatedAlarm"), source = "aws.cloudwatch", `detail-type` = "CloudWatch Alarm State Change", - detail = ScheduledEventDetail( - alarmName = "ServiceAggregatedAlarm", - operation = None, - state = Some( - ScheduledEventState( - actionsSuppressedBy = Some("WaitPeriod"), - actionsSuppressedReason = Some("Actions suppressed by WaitPeriod"), - value = "ALARM", - reason = - "arn:aws:cloudwatch:us-east-1:123456789012:alarm:SuppressionDemo.EventBridge.FirstChild transitioned to ALARM at Friday 22 July, 2022 15:57:45 UTC", - reasonData = - "{\"triggeringAlarms\":[{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServerCpuTooHigh\",\"state\":{\"value\":\"ALARM\",\"timestamp\":\"2022-07-22T15:57:45.394+0000\"}}]}", - timestamp = OffsetDateTime.parse("2022-07-22T15:57:45.394+0000", formatter) - ) - ), - previousState = Some( - ScheduledEventPreviousState( - value = "OK", - reason = - "arn:aws:cloudwatch:us-east-1:123456789012:alarm:SuppressionDemo.EventBridge.Main was created and its alarm rule evaluates to OK", - reasonData = Some( - "{\"triggeringAlarms\":[{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:TotalNetworkTrafficTooHigh\",\"state\":{\"value\":\"OK\",\"timestamp\":\"2022-07-14T16:28:57.770+0000\"}},{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServerCpuTooHigh\",\"state\":{\"value\":\"OK\",\"timestamp\":\"2022-07-14T16:28:54.191+0000\"}}]}"), - timestamp = OffsetDateTime.parse("2022-07-22T15:56:14.552+0000", formatter) - ) - ), - configuration = ScheduledEventConfiguration( - alarmRule = Some("ALARM(ServerCpuTooHigh) OR ALARM(TotalNetworkTrafficTooHigh)"), - actionsSuppressor = Some("ServiceMaintenanceAlarm"), - actionsSuppressorWaitPeriod = Some(120), - actionsSuppressorExtensionPeriod = Some(180), - metrics = None, - description = None - ) + detail = JsonObject.apply( + "alarmName" -> Json.fromString("ServiceAggregatedAlarm"), + "state" -> JsonObject.apply( + "actionsSuppressedBy" -> Json.fromString("WaitPeriod"), + "actionsSuppressedReason" -> Json.fromString("Actions suppressed by WaitPeriod"), + "value" -> Json.fromString("ALARM"), + "reason" -> Json.fromString( + "arn:aws:cloudwatch:us-east-1:123456789012:alarm:SuppressionDemo.EventBridge.FirstChild transitioned to ALARM at Friday 22 July, 2022 15:57:45 UTC" + ), + "reasonData" -> Json.fromString( + "{\"triggeringAlarms\":[{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServerCpuTooHigh\",\"state\":{\"value\":\"ALARM\",\"timestamp\":\"2022-07-22T15:57:45.394+0000\"}}]}" + ), + "timestamp" -> Json.fromString("2022-07-22T15:57:45.394+0000") + ).toJson, + "previousState" -> JsonObject.apply( + "value" -> Json.fromString("OK"), + "reason" -> Json.fromString( + "arn:aws:cloudwatch:us-east-1:123456789012:alarm:SuppressionDemo.EventBridge.Main was created and its alarm rule evaluates to OK" + ), + "reasonData" -> Json.fromString( + "{\"triggeringAlarms\":[{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:TotalNetworkTrafficTooHigh\",\"state\":{\"value\":\"OK\",\"timestamp\":\"2022-07-14T16:28:57.770+0000\"}},{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServerCpuTooHigh\",\"state\":{\"value\":\"OK\",\"timestamp\":\"2022-07-14T16:28:54.191+0000\"}}]}" + ), + "timestamp" -> Json.fromString("2022-07-22T15:56:14.552+0000") + ).toJson, + "configuration" -> JsonObject.apply( + "alarmRule" -> Json.fromString("ALARM(ServerCpuTooHigh) OR ALARM(TotalNetworkTrafficTooHigh)"), + "actionsSuppressor" -> Json.fromString("ServiceMaintenanceAlarm"), + "actionsSuppressorWaitPeriod" -> Json.fromInt(120), + "actionsSuppressorExtensionPeriod" -> Json.fromInt(180) + ).toJson ), `replay-name` = None ) From d853d129a27ec03c3b85ebc0db6c0a162b0376ce Mon Sep 17 00:00:00 2001 From: takapi327 Date: Fri, 11 Oct 2024 09:21:50 +0900 Subject: [PATCH 09/20] Action sbt scalafmtAll --- .../lambda/events/ScheduledEventSuite.scala | 372 +++++++++++------- 1 file changed, 219 insertions(+), 153 deletions(-) diff --git a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala index 43bc392e..80545261 100644 --- a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala +++ b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala @@ -3,7 +3,7 @@ package feral.lambda.events import java.time.Instant import io.circe.literal._ -import io.circe.{JsonObject, Json} +import io.circe.{Json, JsonObject} import munit.FunSuite @@ -93,41 +93,57 @@ class ScheduledEventSuite extends FunSuite { `detail-type` = "CloudWatch Alarm State Change", detail = JsonObject.apply( "alarmName" -> Json.fromString("ServerCpuTooHigh"), - "configuration" -> JsonObject.apply( - "description" -> Json.fromString("Goes into alarm when server CPU utilization is too high!"), - "metrics" -> Json.arr( - JsonObject.apply( - "id" -> Json.fromString("30b6c6b2-a864-43a2-4877-c09a1afc3b87"), - "metricStat" -> JsonObject.apply( - "metric" -> JsonObject.apply( - "dimensions" -> JsonObject.apply("InstanceId" -> Json.fromString("i-12345678901234567")).toJson, - "name" -> Json.fromString("CPUUtilization"), - "namespace" -> Json.fromString("AWS/EC2") - ).toJson, - "period" -> Json.fromInt(300), - "stat" -> Json.fromString("Average") - ).toJson, - "returnData" -> Json.fromBoolean(true) - ).toJson + "configuration" -> JsonObject + .apply( + "description" -> Json.fromString( + "Goes into alarm when server CPU utilization is too high!"), + "metrics" -> Json.arr( + JsonObject + .apply( + "id" -> Json.fromString("30b6c6b2-a864-43a2-4877-c09a1afc3b87"), + "metricStat" -> JsonObject + .apply( + "metric" -> JsonObject + .apply( + "dimensions" -> JsonObject + .apply("InstanceId" -> Json.fromString("i-12345678901234567")) + .toJson, + "name" -> Json.fromString("CPUUtilization"), + "namespace" -> Json.fromString("AWS/EC2") + ) + .toJson, + "period" -> Json.fromInt(300), + "stat" -> Json.fromString("Average") + ) + .toJson, + "returnData" -> Json.fromBoolean(true) + ) + .toJson + ) ) - ).toJson, - "previousState" -> JsonObject.apply( - "reason" -> Json.fromString( - "Threshold Crossed: 1 out of the last 1 datapoints [0.0666851903306472 (01/10/19 13:46:00)] was not greater than the threshold (50.0) (minimum 1 datapoint for ALARM -> OK transition)."), - "reasonData" -> Json.fromString( - "{\"version\":\"1.0\",\"queryDate\":\"2019-10-01T13:56:40.985+0000\",\"startDate\":\"2019-10-01T13:46:00.000+0000\",\"statistic\":\"Average\",\"period\":300,\"recentDatapoints\":[0.0666851903306472],\"threshold\":50.0}" - ), - "timestamp" -> Json.fromString("2019-10-01T13:56:40.987+0000"), - "value" -> Json.fromString("OK") - ).toJson, - "state" -> JsonObject.apply( - "reason" -> Json.fromString( - "Threshold Crossed: 1 out of the last 1 datapoints [99.50160229693434 (02/10/19 16:59:00)] was greater than the threshold (50.0) (minimum 1 datapoint for OK -> ALARM transition)." - ), - "reasonData" -> Json.fromString("{\"version\":\"1.0\",\"queryDate\":\"2019-10-02T17:04:40.985+0000\",\"startDate\":\"2019-10-02T16:59:00.000+0000\",\"statistic\":\"Average\",\"period\":300,\"recentDatapoints\":[99.50160229693434],\"threshold\":50.0}"), - "timestamp" -> Json.fromString("2019-10-02T17:04:40.989+0000"), - "value" -> Json.fromString("ALARM") - ).toJson, + .toJson, + "previousState" -> JsonObject + .apply( + "reason" -> Json.fromString( + "Threshold Crossed: 1 out of the last 1 datapoints [0.0666851903306472 (01/10/19 13:46:00)] was not greater than the threshold (50.0) (minimum 1 datapoint for ALARM -> OK transition)."), + "reasonData" -> Json.fromString( + "{\"version\":\"1.0\",\"queryDate\":\"2019-10-01T13:56:40.985+0000\",\"startDate\":\"2019-10-01T13:46:00.000+0000\",\"statistic\":\"Average\",\"period\":300,\"recentDatapoints\":[0.0666851903306472],\"threshold\":50.0}" + ), + "timestamp" -> Json.fromString("2019-10-01T13:56:40.987+0000"), + "value" -> Json.fromString("OK") + ) + .toJson, + "state" -> JsonObject + .apply( + "reason" -> Json.fromString( + "Threshold Crossed: 1 out of the last 1 datapoints [99.50160229693434 (02/10/19 16:59:00)] was greater than the threshold (50.0) (minimum 1 datapoint for OK -> ALARM transition)." + ), + "reasonData" -> Json.fromString( + "{\"version\":\"1.0\",\"queryDate\":\"2019-10-02T17:04:40.985+0000\",\"startDate\":\"2019-10-02T16:59:00.000+0000\",\"statistic\":\"Average\",\"period\":300,\"recentDatapoints\":[99.50160229693434],\"threshold\":50.0}"), + "timestamp" -> Json.fromString("2019-10-02T17:04:40.989+0000"), + "value" -> Json.fromString("ALARM") + ) + .toJson ), `replay-name` = None ) @@ -215,58 +231,83 @@ class ScheduledEventSuite extends FunSuite { `detail-type` = "CloudWatch Alarm State Change", detail = JsonObject.apply( "alarmName" -> Json.fromString("TotalNetworkTrafficTooHigh"), - "configuration" -> JsonObject.apply( - "description" -> Json.fromString("Goes into alarm if total network traffic exceeds 10Kb"), - "metrics" -> Json.arr( - JsonObject.apply( - "expression" -> Json.fromString("SUM(METRICS())"), - "id" -> Json.fromString("e1"), - "label" -> Json.fromString("Total Network Traffic"), - "returnData" -> Json.fromBoolean(true) - ).toJson, - JsonObject.apply( - "id" -> Json.fromString("m1"), - "metricStat" -> JsonObject.apply( - "metric" -> JsonObject.apply( - "dimensions" -> JsonObject.apply("InstanceId" -> Json.fromString("i-12345678901234567")).toJson, - "name" -> Json.fromString("NetworkIn"), - "namespace" -> Json.fromString("AWS/EC2") - ).toJson, - "period" -> Json.fromInt(300), - "stat" -> Json.fromString("Maximum") - ).toJson, - "returnData" -> Json.fromBoolean(false) - ).toJson, - JsonObject.apply( - "id" -> Json.fromString("m2"), - "metricStat" -> JsonObject.apply( - "metric" -> JsonObject.apply( - "dimensions" -> JsonObject.apply("InstanceId" -> Json.fromString("i-12345678901234567")).toJson, - "name" -> Json.fromString("NetworkOut"), - "namespace" -> Json.fromString("AWS/EC2") - ).toJson, - "period" -> Json.fromInt(300), - "stat" -> Json.fromString("Maximum") - ).toJson, - "returnData" -> Json.fromBoolean(false) - ).toJson + "configuration" -> JsonObject + .apply( + "description" -> Json.fromString( + "Goes into alarm if total network traffic exceeds 10Kb"), + "metrics" -> Json.arr( + JsonObject + .apply( + "expression" -> Json.fromString("SUM(METRICS())"), + "id" -> Json.fromString("e1"), + "label" -> Json.fromString("Total Network Traffic"), + "returnData" -> Json.fromBoolean(true) + ) + .toJson, + JsonObject + .apply( + "id" -> Json.fromString("m1"), + "metricStat" -> JsonObject + .apply( + "metric" -> JsonObject + .apply( + "dimensions" -> JsonObject + .apply("InstanceId" -> Json.fromString("i-12345678901234567")) + .toJson, + "name" -> Json.fromString("NetworkIn"), + "namespace" -> Json.fromString("AWS/EC2") + ) + .toJson, + "period" -> Json.fromInt(300), + "stat" -> Json.fromString("Maximum") + ) + .toJson, + "returnData" -> Json.fromBoolean(false) + ) + .toJson, + JsonObject + .apply( + "id" -> Json.fromString("m2"), + "metricStat" -> JsonObject + .apply( + "metric" -> JsonObject + .apply( + "dimensions" -> JsonObject + .apply("InstanceId" -> Json.fromString("i-12345678901234567")) + .toJson, + "name" -> Json.fromString("NetworkOut"), + "namespace" -> Json.fromString("AWS/EC2") + ) + .toJson, + "period" -> Json.fromInt(300), + "stat" -> Json.fromString("Maximum") + ) + .toJson, + "returnData" -> Json.fromBoolean(false) + ) + .toJson + ) + ) + .toJson, + "previousState" -> JsonObject + .apply( + "reason" -> Json.fromString("Unchecked: Initial alarm creation"), + "timestamp" -> Json.fromString("2019-10-02T17:20:03.642+0000"), + "value" -> Json.fromString("INSUFFICIENT_DATA") ) - ).toJson, - "previousState" -> JsonObject.apply( - "reason" -> Json.fromString("Unchecked: Initial alarm creation"), - "timestamp" -> Json.fromString("2019-10-02T17:20:03.642+0000"), - "value" -> Json.fromString("INSUFFICIENT_DATA") - ).toJson, - "state" -> JsonObject.apply( - "reason" -> Json.fromString( - "Threshold Crossed: 1 out of the last 1 datapoints [45628.0 (02/10/19 17:10:00)] was greater than the threshold (10000.0) (minimum 1 datapoint for OK -> ALARM transition)." - ), - "reasonData" -> Json.fromString( - "{\"version\":\"1.0\",\"queryDate\":\"2019-10-02T17:20:48.551+0000\",\"startDate\":\"2019-10-02T17:10:00.000+0000\",\"period\":300,\"recentDatapoints\":[45628.0],\"threshold\":10000.0}", - ), - "timestamp" -> Json.fromString("2019-10-02T17:20:48.554+0000"), - "value" -> Json.fromString("ALARM") - ).toJson, + .toJson, + "state" -> JsonObject + .apply( + "reason" -> Json.fromString( + "Threshold Crossed: 1 out of the last 1 datapoints [45628.0 (02/10/19 17:10:00)] was greater than the threshold (10000.0) (minimum 1 datapoint for OK -> ALARM transition)." + ), + "reasonData" -> Json.fromString( + "{\"version\":\"1.0\",\"queryDate\":\"2019-10-02T17:20:48.551+0000\",\"startDate\":\"2019-10-02T17:10:00.000+0000\",\"period\":300,\"recentDatapoints\":[45628.0],\"threshold\":10000.0}" + ), + "timestamp" -> Json.fromString("2019-10-02T17:20:48.554+0000"), + "value" -> Json.fromString("ALARM") + ) + .toJson ), `replay-name` = None ) @@ -335,46 +376,64 @@ class ScheduledEventSuite extends FunSuite { `detail-type` = "CloudWatch Alarm State Change", detail = JsonObject.apply( "alarmName" -> Json.fromString("EC2 CPU Utilization Anomaly"), - "state" -> JsonObject.apply( - "value" -> Json.fromString("ALARM"), - "reason" -> Json.fromString( - "Thresholds Crossed: 1 out of the last 1 datapoints [0.0 (03/10/19 15:58:00)] was less than the lower thresholds [0.020599444741798756] or greater than the upper thresholds [0.3006915352732461] (minimum 1 datapoint for OK -> ALARM transition)." - ), - "reasonData" -> Json.fromString( - "{\"version\":\"1.0\",\"queryDate\":\"2019-10-03T16:00:04.650+0000\",\"startDate\":\"2019-10-03T15:58:00.000+0000\",\"period\":60,\"recentDatapoints\":[0.0],\"recentLowerThresholds\":[0.020599444741798756],\"recentUpperThresholds\":[0.3006915352732461]}" - ), - "timestamp" -> Json.fromString("2019-10-03T16:00:04.653+0000") - ).toJson, - "previousState" -> JsonObject.apply( - "value" -> Json.fromString("OK"), - "reason" -> Json.fromString("Thresholds Crossed: 1 out of the last 1 datapoints [0.166666666664241 (03/10/19 15:57:00)] was not less than the lower thresholds [0.0206719426210418] or not greater than the upper thresholds [0.30076870222143803] (minimum 1 datapoint for ALARM -> OK transition)."), - "reasonData" -> Json.fromString("{\"version\":\"1.0\",\"queryDate\":\"2019-10-03T15:59:04.670+0000\",\"startDate\":\"2019-10-03T15:57:00.000+0000\",\"period\":60,\"recentDatapoints\":[0.166666666664241],\"recentLowerThresholds\":[0.0206719426210418],\"recentUpperThresholds\":[0.30076870222143803]}"), - "timestamp" -> Json.fromString("2019-10-03T15:59:04.672+0000") - ).toJson, - "configuration" -> JsonObject.apply( - "description" -> Json.fromString("Goes into alarm if CPU Utilization is out of band"), - "metrics" -> Json.arr( - JsonObject.apply( - "id" -> Json.fromString("m1"), - "metricStat" -> JsonObject.apply( - "metric" -> JsonObject.apply( - "namespace" -> Json.fromString("AWS/EC2"), - "name" -> Json.fromString("CPUUtilization"), - "dimensions" -> JsonObject.apply("InstanceId" -> Json.fromString("i-12345678901234567")).toJson - ).toJson, - "period" -> Json.fromInt(60), - "stat" -> Json.fromString("Average") - ).toJson, - "returnData" -> Json.fromBoolean(true) - ).toJson, - JsonObject.apply( - "id" -> Json.fromString("ad1"), - "expression" -> Json.fromString("ANOMALY_DETECTION_BAND(m1, 0.8)"), - "label" -> Json.fromString("CPUUtilization (expected)"), - "returnData" -> Json.fromBoolean(true) - ).toJson + "state" -> JsonObject + .apply( + "value" -> Json.fromString("ALARM"), + "reason" -> Json.fromString( + "Thresholds Crossed: 1 out of the last 1 datapoints [0.0 (03/10/19 15:58:00)] was less than the lower thresholds [0.020599444741798756] or greater than the upper thresholds [0.3006915352732461] (minimum 1 datapoint for OK -> ALARM transition)." + ), + "reasonData" -> Json.fromString( + "{\"version\":\"1.0\",\"queryDate\":\"2019-10-03T16:00:04.650+0000\",\"startDate\":\"2019-10-03T15:58:00.000+0000\",\"period\":60,\"recentDatapoints\":[0.0],\"recentLowerThresholds\":[0.020599444741798756],\"recentUpperThresholds\":[0.3006915352732461]}" + ), + "timestamp" -> Json.fromString("2019-10-03T16:00:04.653+0000") + ) + .toJson, + "previousState" -> JsonObject + .apply( + "value" -> Json.fromString("OK"), + "reason" -> Json.fromString( + "Thresholds Crossed: 1 out of the last 1 datapoints [0.166666666664241 (03/10/19 15:57:00)] was not less than the lower thresholds [0.0206719426210418] or not greater than the upper thresholds [0.30076870222143803] (minimum 1 datapoint for ALARM -> OK transition)."), + "reasonData" -> Json.fromString( + "{\"version\":\"1.0\",\"queryDate\":\"2019-10-03T15:59:04.670+0000\",\"startDate\":\"2019-10-03T15:57:00.000+0000\",\"period\":60,\"recentDatapoints\":[0.166666666664241],\"recentLowerThresholds\":[0.0206719426210418],\"recentUpperThresholds\":[0.30076870222143803]}"), + "timestamp" -> Json.fromString("2019-10-03T15:59:04.672+0000") ) - ).toJson + .toJson, + "configuration" -> JsonObject + .apply( + "description" -> Json.fromString("Goes into alarm if CPU Utilization is out of band"), + "metrics" -> Json.arr( + JsonObject + .apply( + "id" -> Json.fromString("m1"), + "metricStat" -> JsonObject + .apply( + "metric" -> JsonObject + .apply( + "namespace" -> Json.fromString("AWS/EC2"), + "name" -> Json.fromString("CPUUtilization"), + "dimensions" -> JsonObject + .apply("InstanceId" -> Json.fromString("i-12345678901234567")) + .toJson + ) + .toJson, + "period" -> Json.fromInt(60), + "stat" -> Json.fromString("Average") + ) + .toJson, + "returnData" -> Json.fromBoolean(true) + ) + .toJson, + JsonObject + .apply( + "id" -> Json.fromString("ad1"), + "expression" -> Json.fromString("ANOMALY_DETECTION_BAND(m1, 0.8)"), + "label" -> Json.fromString("CPUUtilization (expected)"), + "returnData" -> Json.fromBoolean(true) + ) + .toJson + ) + ) + .toJson ), `replay-name` = None ) @@ -429,34 +488,41 @@ class ScheduledEventSuite extends FunSuite { `detail-type` = "CloudWatch Alarm State Change", detail = JsonObject.apply( "alarmName" -> Json.fromString("ServiceAggregatedAlarm"), - "state" -> JsonObject.apply( - "actionsSuppressedBy" -> Json.fromString("WaitPeriod"), - "actionsSuppressedReason" -> Json.fromString("Actions suppressed by WaitPeriod"), - "value" -> Json.fromString("ALARM"), - "reason" -> Json.fromString( - "arn:aws:cloudwatch:us-east-1:123456789012:alarm:SuppressionDemo.EventBridge.FirstChild transitioned to ALARM at Friday 22 July, 2022 15:57:45 UTC" - ), - "reasonData" -> Json.fromString( - "{\"triggeringAlarms\":[{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServerCpuTooHigh\",\"state\":{\"value\":\"ALARM\",\"timestamp\":\"2022-07-22T15:57:45.394+0000\"}}]}" - ), - "timestamp" -> Json.fromString("2022-07-22T15:57:45.394+0000") - ).toJson, - "previousState" -> JsonObject.apply( - "value" -> Json.fromString("OK"), - "reason" -> Json.fromString( - "arn:aws:cloudwatch:us-east-1:123456789012:alarm:SuppressionDemo.EventBridge.Main was created and its alarm rule evaluates to OK" - ), - "reasonData" -> Json.fromString( - "{\"triggeringAlarms\":[{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:TotalNetworkTrafficTooHigh\",\"state\":{\"value\":\"OK\",\"timestamp\":\"2022-07-14T16:28:57.770+0000\"}},{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServerCpuTooHigh\",\"state\":{\"value\":\"OK\",\"timestamp\":\"2022-07-14T16:28:54.191+0000\"}}]}" - ), - "timestamp" -> Json.fromString("2022-07-22T15:56:14.552+0000") - ).toJson, - "configuration" -> JsonObject.apply( - "alarmRule" -> Json.fromString("ALARM(ServerCpuTooHigh) OR ALARM(TotalNetworkTrafficTooHigh)"), - "actionsSuppressor" -> Json.fromString("ServiceMaintenanceAlarm"), - "actionsSuppressorWaitPeriod" -> Json.fromInt(120), - "actionsSuppressorExtensionPeriod" -> Json.fromInt(180) - ).toJson + "state" -> JsonObject + .apply( + "actionsSuppressedBy" -> Json.fromString("WaitPeriod"), + "actionsSuppressedReason" -> Json.fromString("Actions suppressed by WaitPeriod"), + "value" -> Json.fromString("ALARM"), + "reason" -> Json.fromString( + "arn:aws:cloudwatch:us-east-1:123456789012:alarm:SuppressionDemo.EventBridge.FirstChild transitioned to ALARM at Friday 22 July, 2022 15:57:45 UTC" + ), + "reasonData" -> Json.fromString( + "{\"triggeringAlarms\":[{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServerCpuTooHigh\",\"state\":{\"value\":\"ALARM\",\"timestamp\":\"2022-07-22T15:57:45.394+0000\"}}]}" + ), + "timestamp" -> Json.fromString("2022-07-22T15:57:45.394+0000") + ) + .toJson, + "previousState" -> JsonObject + .apply( + "value" -> Json.fromString("OK"), + "reason" -> Json.fromString( + "arn:aws:cloudwatch:us-east-1:123456789012:alarm:SuppressionDemo.EventBridge.Main was created and its alarm rule evaluates to OK" + ), + "reasonData" -> Json.fromString( + "{\"triggeringAlarms\":[{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:TotalNetworkTrafficTooHigh\",\"state\":{\"value\":\"OK\",\"timestamp\":\"2022-07-14T16:28:57.770+0000\"}},{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServerCpuTooHigh\",\"state\":{\"value\":\"OK\",\"timestamp\":\"2022-07-14T16:28:54.191+0000\"}}]}" + ), + "timestamp" -> Json.fromString("2022-07-22T15:56:14.552+0000") + ) + .toJson, + "configuration" -> JsonObject + .apply( + "alarmRule" -> Json.fromString( + "ALARM(ServerCpuTooHigh) OR ALARM(TotalNetworkTrafficTooHigh)"), + "actionsSuppressor" -> Json.fromString("ServiceMaintenanceAlarm"), + "actionsSuppressorWaitPeriod" -> Json.fromInt(120), + "actionsSuppressorExtensionPeriod" -> Json.fromInt(180) + ) + .toJson ), `replay-name` = None ) From 4b77739576b37ea0655fd4f685058ef579cffe88 Mon Sep 17 00:00:00 2001 From: takapi327 Date: Fri, 11 Oct 2024 09:28:00 +0900 Subject: [PATCH 10/20] Added Decoding the creation of compound alarms test --- .../lambda/events/ScheduledEventSuite.scala | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala index 80545261..01adef0a 100644 --- a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala +++ b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala @@ -31,6 +31,13 @@ class ScheduledEventSuite extends FunSuite { ) } + test("Decoding the creation of compound alarms") { + assertEquals( + compoundAlarmCreationEvent.as[ScheduledEvent].toTry.get, + compoundAlarmCreationResult + ) + } + // Alarm status changes based on a single metric private def singleMetricEvent = json""" { @@ -526,4 +533,73 @@ class ScheduledEventSuite extends FunSuite { ), `replay-name` = None ) + + // Creating compound alarms + private def compoundAlarmCreationEvent = json""" + { + "version": "0", + "id": "91535fdd-1e9c-849d-624b-9a9f2b1d09d0", + "detail-type": "CloudWatch Alarm Configuration Change", + "source": "aws.cloudwatch", + "account": "123456789012", + "time": "2022-03-03T17:06:22Z", + "region": "us-east-1", + "resources": [ + "arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServiceAggregatedAlarm" + ], + "detail": { + "alarmName": "ServiceAggregatedAlarm", + "operation": "create", + "state": { + "value": "INSUFFICIENT_DATA", + "timestamp": "2022-03-03T17:06:22.289+0000" + }, + "configuration": { + "alarmRule": "ALARM(ServerCpuTooHigh) OR ALARM(TotalNetworkTrafficTooHigh)", + "alarmName": "ServiceAggregatedAlarm", + "description": "Aggregated monitor for instance", + "actionsEnabled": true, + "timestamp": "2022-03-03T17:06:22.289+0000", + "okActions": [], + "alarmActions": [], + "insufficientDataActions": [] + } + } + } + """ + + private def compoundAlarmCreationResult: ScheduledEvent = ScheduledEvent( + id = "91535fdd-1e9c-849d-624b-9a9f2b1d09d0", + version = "0", + account = "123456789012", + time = Instant.parse("2022-03-03T17:06:22Z"), + region = "us-east-1", + resources = List("arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServiceAggregatedAlarm"), + source = "aws.cloudwatch", + `detail-type` = "CloudWatch Alarm Configuration Change", + detail = JsonObject.apply( + "alarmName" -> Json.fromString("ServiceAggregatedAlarm"), + "operation" -> Json.fromString("create"), + "state" -> JsonObject + .apply( + "value" -> Json.fromString("INSUFFICIENT_DATA"), + "timestamp" -> Json.fromString("2022-03-03T17:06:22.289+0000") + ) + .toJson, + "configuration" -> JsonObject + .apply( + "alarmRule" -> Json.fromString( + "ALARM(ServerCpuTooHigh) OR ALARM(TotalNetworkTrafficTooHigh)"), + "alarmName" -> Json.fromString("ServiceAggregatedAlarm"), + "description" -> Json.fromString("Aggregated monitor for instance"), + "actionsEnabled" -> Json.fromBoolean(true), + "timestamp" -> Json.fromString("2022-03-03T17:06:22.289+0000"), + "okActions" -> Json.arr(), + "alarmActions" -> Json.arr(), + "insufficientDataActions" -> Json.arr() + ) + .toJson + ), + `replay-name` = None + ) } From 62c27403dc68ebb2444f22eb0727829e782f182e Mon Sep 17 00:00:00 2001 From: takapi327 Date: Fri, 11 Oct 2024 09:33:51 +0900 Subject: [PATCH 11/20] Added Decoding of creation of compound alarm with suppressor alarm test --- .../lambda/events/ScheduledEventSuite.scala | 91 +++++++++++++++++-- 1 file changed, 85 insertions(+), 6 deletions(-) diff --git a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala index 01adef0a..de6544f0 100644 --- a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala +++ b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala @@ -24,20 +24,27 @@ class ScheduledEventSuite extends FunSuite { ) } - test("Decoding change of state of compound alarm with suppressor alarm") { + test("Decoding of change of state of compound alarm with suppressor alarm") { assertEquals( - compoundAlarmSuppressorEvent.as[ScheduledEvent].toTry.get, - compoundAlarmSuppressorResult + stateChangeCompoundAlarmSuppressorEvent.as[ScheduledEvent].toTry.get, + stateChangeCompoundAlarmSuppressorResult ) } - test("Decoding the creation of compound alarms") { + test("Decoding of creation of compound alarms") { assertEquals( compoundAlarmCreationEvent.as[ScheduledEvent].toTry.get, compoundAlarmCreationResult ) } + test("Decoding of creation of compound alarm with suppressor alarm") { + assertEquals( + createCompoundAlarmSuppressorEvent.as[ScheduledEvent].toTry.get, + createCompoundAlarmSuppressorResult + ) + } + // Alarm status changes based on a single metric private def singleMetricEvent = json""" { @@ -446,7 +453,7 @@ class ScheduledEventSuite extends FunSuite { ) // Change of state of combined alarm with suppressor alarm - private def compoundAlarmSuppressorEvent = json""" + private def stateChangeCompoundAlarmSuppressorEvent = json""" { "version": "0", "id": "d3dfc86d-384d-24c8-0345-9f7986db0b80", @@ -484,7 +491,7 @@ class ScheduledEventSuite extends FunSuite { } """ - private def compoundAlarmSuppressorResult: ScheduledEvent = ScheduledEvent( + private def stateChangeCompoundAlarmSuppressorResult: ScheduledEvent = ScheduledEvent( id = "d3dfc86d-384d-24c8-0345-9f7986db0b80", version = "0", account = "123456789012", @@ -602,4 +609,76 @@ class ScheduledEventSuite extends FunSuite { ), `replay-name` = None ) + + private def createCompoundAlarmSuppressorEvent = json""" + { + "version": "0", + "id": "454773e1-09f7-945b-aa2c-590af1c3f8e0", + "detail-type": "CloudWatch Alarm Configuration Change", + "source": "aws.cloudwatch", + "account": "123456789012", + "time": "2022-07-14T13:59:46Z", + "region": "us-east-1", + "resources": [ + "arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServiceAggregatedAlarm" + ], + "detail": { + "alarmName": "ServiceAggregatedAlarm", + "operation": "create", + "state": { + "value": "INSUFFICIENT_DATA", + "timestamp": "2022-07-14T13:59:46.425+0000" + }, + "configuration": { + "alarmRule": "ALARM(ServerCpuTooHigh) OR ALARM(TotalNetworkTrafficTooHigh)", + "actionsSuppressor": "ServiceMaintenanceAlarm", + "actionsSuppressorWaitPeriod": 120, + "actionsSuppressorExtensionPeriod": 180, + "alarmName": "ServiceAggregatedAlarm", + "actionsEnabled": true, + "timestamp": "2022-07-14T13:59:46.425+0000", + "okActions": [], + "alarmActions": [], + "insufficientDataActions": [] + } + } + } + """ + + private def createCompoundAlarmSuppressorResult: ScheduledEvent = ScheduledEvent( + id = "454773e1-09f7-945b-aa2c-590af1c3f8e0", + version = "0", + account = "123456789012", + time = Instant.parse("2022-07-14T13:59:46Z"), + region = "us-east-1", + resources = List("arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServiceAggregatedAlarm"), + source = "aws.cloudwatch", + `detail-type` = "CloudWatch Alarm Configuration Change", + detail = JsonObject.apply( + "alarmName" -> Json.fromString("ServiceAggregatedAlarm"), + "operation" -> Json.fromString("create"), + "state" -> JsonObject + .apply( + "value" -> Json.fromString("INSUFFICIENT_DATA"), + "timestamp" -> Json.fromString("2022-07-14T13:59:46.425+0000") + ) + .toJson, + "configuration" -> JsonObject + .apply( + "alarmRule" -> Json.fromString( + "ALARM(ServerCpuTooHigh) OR ALARM(TotalNetworkTrafficTooHigh)"), + "actionsSuppressor" -> Json.fromString("ServiceMaintenanceAlarm"), + "actionsSuppressorWaitPeriod" -> Json.fromInt(120), + "actionsSuppressorExtensionPeriod" -> Json.fromInt(180), + "alarmName" -> Json.fromString("ServiceAggregatedAlarm"), + "actionsEnabled" -> Json.fromBoolean(true), + "timestamp" -> Json.fromString("2022-07-14T13:59:46.425+0000"), + "okActions" -> Json.arr(), + "alarmActions" -> Json.arr(), + "insufficientDataActions" -> Json.arr() + ) + .toJson + ), + `replay-name` = None + ) } From 6f636af9f37c5e18497cdc7dc62178e218e140ab Mon Sep 17 00:00:00 2001 From: takapi327 Date: Fri, 11 Oct 2024 09:45:07 +0900 Subject: [PATCH 12/20] Added Decoding of metric alarm updates test --- .../lambda/events/ScheduledEventSuite.scala | 186 ++++++++++++++++++ 1 file changed, 186 insertions(+) diff --git a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala index de6544f0..aa5cfa35 100644 --- a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala +++ b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala @@ -45,6 +45,13 @@ class ScheduledEventSuite extends FunSuite { ) } + test("Decoding of metric alarm updates") { + assertEquals( + metricAlarmUpdateEvent.as[ScheduledEvent].toTry.get, + metricAlarmUpdateResult + ) + } + // Alarm status changes based on a single metric private def singleMetricEvent = json""" { @@ -681,4 +688,183 @@ class ScheduledEventSuite extends FunSuite { ), `replay-name` = None ) + + private def metricAlarmUpdateEvent = json""" + { + + "version": "0", + "id": "bc7d3391-47f8-ae47-f457-1b4d06118d50", + "detail-type": "CloudWatch Alarm Configuration Change", + "source": "aws.cloudwatch", + "account": "123456789012", + "time": "2022-03-03T17:06:34Z", + "region": "us-east-1", + "resources": [ + "arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServerCpuTooHigh" + ], + "detail": { + "alarmName": "ServerCpuTooHigh", + "operation": "update", + "state": { + "value": "INSUFFICIENT_DATA", + "timestamp": "2022-03-03T17:06:13.757+0000" + }, + "configuration": { + "evaluationPeriods": 1, + "threshold": 80, + "comparisonOperator": "GreaterThanThreshold", + "treatMissingData": "ignore", + "metrics": [ + { + "id": "86bfa85f-b14c-ebf7-8916-7da014ce23c0", + "metricStat": { + "metric": { + "namespace": "AWS/EC2", + "name": "CPUUtilization", + "dimensions": { + "InstanceId": "i-12345678901234567" + } + }, + "period": 300, + "stat": "Average" + }, + "returnData": true + } + ], + "alarmName": "ServerCpuTooHigh", + "description": "Goes into alarm when server CPU utilization is too high!", + "actionsEnabled": true, + "timestamp": "2022-03-03T17:06:34.267+0000", + "okActions": [], + "alarmActions": [], + "insufficientDataActions": [] + }, + "previousConfiguration": { + "evaluationPeriods": 1, + "threshold": 70, + "comparisonOperator": "GreaterThanThreshold", + "treatMissingData": "ignore", + "metrics": [ + { + "id": "d6bfa85f-893e-b052-a58b-4f9295c9111a", + "metricStat": { + "metric": { + "namespace": "AWS/EC2", + "name": "CPUUtilization", + "dimensions": { + "InstanceId": "i-12345678901234567" + } + }, + "period": 300, + "stat": "Average" + }, + "returnData": true + } + ], + "alarmName": "ServerCpuTooHigh", + "description": "Goes into alarm when server CPU utilization is too high!", + "actionsEnabled": true, + "timestamp": "2022-03-03T17:06:13.757+0000", + "okActions": [], + "alarmActions": [], + "insufficientDataActions": [] + } + } + } + """ + + private def metricAlarmUpdateResult: ScheduledEvent = ScheduledEvent( + id = "bc7d3391-47f8-ae47-f457-1b4d06118d50", + version = "0", + account = "123456789012", + time = Instant.parse("2022-03-03T17:06:34Z"), + region = "us-east-1", + resources = List("arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServerCpuTooHigh"), + source = "aws.cloudwatch", + `detail-type` = "CloudWatch Alarm Configuration Change", + detail = JsonObject.apply( + "alarmName" -> Json.fromString("ServerCpuTooHigh"), + "operation" -> Json.fromString("update"), + "state" -> JsonObject + .apply( + "value" -> Json.fromString("INSUFFICIENT_DATA"), + "timestamp" -> Json.fromString("2022-03-03T17:06:13.757+0000") + ) + .toJson, + "configuration" -> JsonObject + .apply( + "evaluationPeriods" -> Json.fromInt(1), + "threshold" -> Json.fromInt(80), + "comparisonOperator" -> Json.fromString("GreaterThanThreshold"), + "treatMissingData" -> Json.fromString("ignore"), + "metrics" -> Json.arr( + JsonObject + .apply( + "id" -> Json.fromString("86bfa85f-b14c-ebf7-8916-7da014ce23c0"), + "metricStat" -> JsonObject + .apply( + "metric" -> JsonObject + .apply( + "namespace" -> Json.fromString("AWS/EC2"), + "name" -> Json.fromString("CPUUtilization"), + "dimensions" -> JsonObject + .apply("InstanceId" -> Json.fromString("i-12345678901234567")) + .toJson + ) + .toJson, + "period" -> Json.fromInt(300), + "stat" -> Json.fromString("Average") + ) + .toJson, + "returnData" -> Json.fromBoolean(true) + ) + .toJson + ), + "alarmName" -> Json.fromString("ServerCpuTooHigh"), + "description" -> Json.fromString("Goes into alarm when server CPU utilization is too high!"), + "actionsEnabled" -> Json.fromBoolean(true), + "timestamp" -> Json.fromString("2022-03-03T17:06:34.267+0000"), + "okActions" -> Json.arr(), + "alarmActions" -> Json.arr(), + "insufficientDataActions" -> Json.arr() + ) + .toJson, + "previousConfiguration" -> JsonObject + .apply( + "evaluationPeriods" -> Json.fromInt(1), + "threshold" -> Json.fromInt(70), + "comparisonOperator" -> Json.fromString("GreaterThanThreshold"), + "treatMissingData" -> Json.fromString("ignore"), + "metrics" -> Json.arr( + JsonObject.apply( + "id" -> Json.fromString("d6bfa85f-893e-b052-a58b-4f9295c9111a"), + "metricStat" -> JsonObject + .apply( + "metric" -> JsonObject + .apply( + "namespace" -> Json.fromString("AWS/EC2"), + "name" -> Json.fromString("CPUUtilization"), + "dimensions" -> JsonObject + .apply("InstanceId" -> Json.fromString("i-12345678901234567")) + .toJson + ) + .toJson, + "period" -> Json.fromInt(300), + "stat" -> Json.fromString("Average") + ) + .toJson, + "returnData" -> Json.fromBoolean(true) + ).toJson, + ), + "alarmName" -> Json.fromString("ServerCpuTooHigh"), + "description" -> Json.fromString("Goes into alarm when server CPU utilization is too high!"), + "actionsEnabled" -> Json.fromBoolean(true), + "timestamp" -> Json.fromString("2022-03-03T17:06:13.757+0000"), + "okActions" -> Json.arr(), + "alarmActions" -> Json.arr(), + "insufficientDataActions" -> Json.arr() + ).toJson + ), + `replay-name` = None + ) } From 776b19a7841e3c32de48ed88267886dadd6d9e79 Mon Sep 17 00:00:00 2001 From: takapi327 Date: Fri, 11 Oct 2024 09:48:46 +0900 Subject: [PATCH 13/20] Added Decoding of updates of compound alarms with suppressor alarms test --- .../lambda/events/ScheduledEventSuite.scala | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala index aa5cfa35..07bcbd12 100644 --- a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala +++ b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala @@ -52,6 +52,13 @@ class ScheduledEventSuite extends FunSuite { ) } + test("Decoding of updates of compound alarms with suppressor alarms") { + assertEquals( + updateCompoundAlarmSuppressorEvent.as[ScheduledEvent].toTry.get, + updateCompoundAlarmSuppressorResult + ) + } + // Alarm status changes based on a single metric private def singleMetricEvent = json""" { @@ -867,4 +874,106 @@ class ScheduledEventSuite extends FunSuite { ), `replay-name` = None ) + + // Update compound alarm with suppressor alarm + private def updateCompoundAlarmSuppressorEvent = json""" + { + "version": "0", + "id": "4c6f4177-6bd5-c0ca-9f05-b4151c54568b", + "detail-type": "CloudWatch Alarm Configuration Change", + "source": "aws.cloudwatch", + "account": "123456789012", + "time": "2022-07-14T13:59:56Z", + "region": "us-east-1", + "resources": [ + "arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServiceAggregatedAlarm" + ], + "detail": { + "alarmName": "ServiceAggregatedAlarm", + "operation": "update", + "state": { + "actionsSuppressedBy": "WaitPeriod", + "value": "ALARM", + "timestamp": "2022-07-14T13:59:46.425+0000" + }, + "configuration": { + "alarmRule": "ALARM(ServerCpuTooHigh) OR ALARM(TotalNetworkTrafficTooHigh)", + "actionsSuppressor": "ServiceMaintenanceAlarm", + "actionsSuppressorWaitPeriod": 120, + "actionsSuppressorExtensionPeriod": 360, + "alarmName": "ServiceAggregatedAlarm", + "actionsEnabled": true, + "timestamp": "2022-07-14T13:59:56.290+0000", + "okActions": [], + "alarmActions": [], + "insufficientDataActions": [] + }, + "previousConfiguration": { + "alarmRule": "ALARM(ServerCpuTooHigh) OR ALARM(TotalNetworkTrafficTooHigh)", + "actionsSuppressor": "ServiceMaintenanceAlarm", + "actionsSuppressorWaitPeriod": 120, + "actionsSuppressorExtensionPeriod": 180, + "alarmName": "ServiceAggregatedAlarm", + "actionsEnabled": true, + "timestamp": "2022-07-14T13:59:46.425+0000", + "okActions": [], + "alarmActions": [], + "insufficientDataActions": [] + } + } + } + """ + + private def updateCompoundAlarmSuppressorResult: ScheduledEvent = ScheduledEvent( + id = "4c6f4177-6bd5-c0ca-9f05-b4151c54568b", + version = "0", + account = "123456789012", + time = Instant.parse("2022-07-14T13:59:56Z"), + region = "us-east-1", + resources = List("arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServiceAggregatedAlarm"), + source = "aws.cloudwatch", + `detail-type` = "CloudWatch Alarm Configuration Change", + detail = JsonObject.apply( + "alarmName" -> Json.fromString("ServiceAggregatedAlarm"), + "operation" -> Json.fromString("update"), + "state" -> JsonObject + .apply( + "actionsSuppressedBy" -> Json.fromString("WaitPeriod"), + "value" -> Json.fromString("ALARM"), + "timestamp" -> Json.fromString("2022-07-14T13:59:46.425+0000") + ) + .toJson, + "configuration" -> JsonObject + .apply( + "alarmRule" -> Json.fromString( + "ALARM(ServerCpuTooHigh) OR ALARM(TotalNetworkTrafficTooHigh)"), + "actionsSuppressor" -> Json.fromString("ServiceMaintenanceAlarm"), + "actionsSuppressorWaitPeriod" -> Json.fromInt(120), + "actionsSuppressorExtensionPeriod" -> Json.fromInt(360), + "alarmName" -> Json.fromString("ServiceAggregatedAlarm"), + "actionsEnabled" -> Json.fromBoolean(true), + "timestamp" -> Json.fromString("2022-07-14T13:59:56.290+0000"), + "okActions" -> Json.arr(), + "alarmActions" -> Json.arr(), + "insufficientDataActions" -> Json.arr() + ) + .toJson, + "previousConfiguration" -> JsonObject + .apply( + "alarmRule" -> Json.fromString( + "ALARM(ServerCpuTooHigh) OR ALARM(TotalNetworkTrafficTooHigh)"), + "actionsSuppressor" -> Json.fromString("ServiceMaintenanceAlarm"), + "actionsSuppressorWaitPeriod" -> Json.fromInt(120), + "actionsSuppressorExtensionPeriod" -> Json.fromInt(180), + "alarmName" -> Json.fromString("ServiceAggregatedAlarm"), + "actionsEnabled" -> Json.fromBoolean(true), + "timestamp" -> Json.fromString("2022-07-14T13:59:46.425+0000"), + "okActions" -> Json.arr(), + "alarmActions" -> Json.arr(), + "insufficientDataActions" -> Json.arr() + ) + .toJson + ), + `replay-name` = None + ) } From 01d07c25065edb06a572a1d1bc1f8b01912198d4 Mon Sep 17 00:00:00 2001 From: takapi327 Date: Fri, 11 Oct 2024 09:53:15 +0900 Subject: [PATCH 14/20] Added Decoding of alarm deletion based on metrics formulas test --- .../lambda/events/ScheduledEventSuite.scala | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) diff --git a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala index 07bcbd12..dc4ff4aa 100644 --- a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala +++ b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala @@ -59,6 +59,13 @@ class ScheduledEventSuite extends FunSuite { ) } + test("Decoding of alarm deletion based on metrics formulas") { + assertEquals( + metricsFormulaDeletionEvent.as[ScheduledEvent].toTry.get, + metricsFormulaDeletionResult + ) + } + // Alarm status changes based on a single metric private def singleMetricEvent = json""" { @@ -976,4 +983,165 @@ class ScheduledEventSuite extends FunSuite { ), `replay-name` = None ) + + private def metricsFormulaDeletionEvent = json""" + { + "version": "0", + "id": "f171d220-9e1c-c252-5042-2677347a83ed", + "detail-type": "CloudWatch Alarm Configuration Change", + "source": "aws.cloudwatch", + "account": "123456789012", + "time": "2022-03-03T17:07:13Z", + "region": "us-east-1", + "resources": [ + "arn:aws:cloudwatch:us-east-1:123456789012:alarm:TotalNetworkTrafficTooHigh" + ], + "detail": { + "alarmName": "TotalNetworkTrafficTooHigh", + "operation": "delete", + "state": { + "value": "INSUFFICIENT_DATA", + "timestamp": "2022-03-03T17:06:17.672+0000" + }, + "configuration": { + "evaluationPeriods": 1, + "threshold": 10000, + "comparisonOperator": "GreaterThanThreshold", + "treatMissingData": "ignore", + "metrics": [{ + "id": "m1", + "metricStat": { + "metric": { + "namespace": "AWS/EC2", + "name": "NetworkIn", + "dimensions": { + "InstanceId": "i-12345678901234567" + } + }, + "period": 300, + "stat": "Maximum" + }, + "returnData": false + }, + { + "id": "m2", + "metricStat": { + "metric": { + "namespace": "AWS/EC2", + "name": "NetworkOut", + "dimensions": { + "InstanceId": "i-12345678901234567" + } + }, + "period": 300, + "stat": "Maximum" + }, + "returnData": false + }, + { + "id": "e1", + "expression": "SUM(METRICS())", + "label": "Total Network Traffic", + "returnData": true + } + ], + "alarmName": "TotalNetworkTrafficTooHigh", + "description": "Goes into alarm if total network traffic exceeds 10Kb", + "actionsEnabled": true, + "timestamp": "2022-03-03T17:06:17.672+0000", + "okActions": [], + "alarmActions": [], + "insufficientDataActions": [] + + } + } + } + """ + + private def metricsFormulaDeletionResult: ScheduledEvent = ScheduledEvent( + id = "f171d220-9e1c-c252-5042-2677347a83ed", + version = "0", + account = "123456789012", + time = Instant.parse("2022-03-03T17:07:13Z"), + region = "us-east-1", + resources = List("arn:aws:cloudwatch:us-east-1:123456789012:alarm:TotalNetworkTrafficTooHigh"), + source = "aws.cloudwatch", + `detail-type` = "CloudWatch Alarm Configuration Change", + detail = JsonObject.apply( + "alarmName" -> Json.fromString("TotalNetworkTrafficTooHigh"), + "operation" -> Json.fromString("delete"), + "state" -> JsonObject + .apply( + "value" -> Json.fromString("INSUFFICIENT_DATA"), + "timestamp" -> Json.fromString("2022-03-03T17:06:17.672+0000") + ) + .toJson, + "configuration" -> JsonObject + .apply( + "evaluationPeriods" -> Json.fromInt(1), + "threshold" -> Json.fromInt(10000), + "comparisonOperator" -> Json.fromString("GreaterThanThreshold"), + "treatMissingData" -> Json.fromString("ignore"), + "metrics" -> Json.arr( + JsonObject + .apply( + "id" -> Json.fromString("m1"), + "metricStat" -> JsonObject + .apply( + "metric" -> JsonObject + .apply( + "namespace" -> Json.fromString("AWS/EC2"), + "name" -> Json.fromString("NetworkIn"), + "dimensions" -> JsonObject + .apply("InstanceId" -> Json.fromString("i-12345678901234567")) + .toJson + ) + .toJson, + "period" -> Json.fromInt(300), + "stat" -> Json.fromString("Maximum") + ) + .toJson, + "returnData" -> Json.fromBoolean(false) + ) + .toJson, + JsonObject + .apply( + "id" -> Json.fromString("m2"), + "metricStat" -> JsonObject + .apply( + "metric" -> JsonObject + .apply( + "namespace" -> Json.fromString("AWS/EC2"), + "name" -> Json.fromString("NetworkOut"), + "dimensions" -> JsonObject + .apply("InstanceId" -> Json.fromString("i-12345678901234567")) + .toJson + ) + .toJson, + "period" -> Json.fromInt(300), + "stat" -> Json.fromString("Maximum") + ) + .toJson, + "returnData" -> Json.fromBoolean(false) + ) + .toJson, + JsonObject + .apply( + "id" -> Json.fromString("e1"), + "expression" -> Json.fromString("SUM(METRICS())"), + "label" -> Json.fromString("Total Network Traffic"), + "returnData" -> Json.fromBoolean(true) + ).toJson + ), + "alarmName" -> Json.fromString("TotalNetworkTrafficTooHigh"), + "description" -> Json.fromString("Goes into alarm if total network traffic exceeds 10Kb"), + "actionsEnabled" -> Json.fromBoolean(true), + "timestamp" -> Json.fromString("2022-03-03T17:06:17.672+0000"), + "okActions" -> Json.arr(), + "alarmActions" -> Json.arr(), + "insufficientDataActions" -> Json.arr() + ).toJson + ), + `replay-name` = None + ) } From c493240894ef711ea89a242856c96f147185b841 Mon Sep 17 00:00:00 2001 From: takapi327 Date: Fri, 11 Oct 2024 09:57:19 +0900 Subject: [PATCH 15/20] Added Decoding of combined alarm with suppressor alarm test --- .../lambda/events/ScheduledEventSuite.scala | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala index dc4ff4aa..e1c93092 100644 --- a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala +++ b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala @@ -66,6 +66,13 @@ class ScheduledEventSuite extends FunSuite { ) } + test("Decoding of combined alarm with suppressor alarm") { + assertEquals( + combinedAlarmSuppressorEvent.as[ScheduledEvent].toTry.get, + combinedAlarmSuppressorResult + ) + } + // Alarm status changes based on a single metric private def singleMetricEvent = json""" { @@ -1144,4 +1151,78 @@ class ScheduledEventSuite extends FunSuite { ), `replay-name` = None ) + + private def combinedAlarmSuppressorEvent = json""" + { + "version": "0", + "id": "e34592a1-46c0-b316-f614-1b17a87be9dc", + "detail-type": "CloudWatch Alarm Configuration Change", + "source": "aws.cloudwatch", + "account": "123456789012", + "time": "2022-07-14T14:00:01Z", + "region": "us-east-1", + "resources": [ + "arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServiceAggregatedAlarm" + ], + "detail": { + "alarmName": "ServiceAggregatedAlarm", + "operation": "delete", + "state": { + "actionsSuppressedBy": "WaitPeriod", + "value": "ALARM", + "timestamp": "2022-07-14T13:59:46.425+0000" + }, + "configuration": { + "alarmRule": "ALARM(ServerCpuTooHigh) OR ALARM(TotalNetworkTrafficTooHigh)", + "actionsSuppressor": "ServiceMaintenanceAlarm", + "actionsSuppressorWaitPeriod": 120, + "actionsSuppressorExtensionPeriod": 360, + "alarmName": "ServiceAggregatedAlarm", + "actionsEnabled": true, + "timestamp": "2022-07-14T13:59:56.290+0000", + "okActions": [], + "alarmActions": [], + "insufficientDataActions": [] + } + } + } + """ + + private def combinedAlarmSuppressorResult: ScheduledEvent = ScheduledEvent( + id = "e34592a1-46c0-b316-f614-1b17a87be9dc", + version = "0", + account = "123456789012", + time = Instant.parse("2022-07-14T14:00:01Z"), + region = "us-east-1", + resources = List("arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServiceAggregatedAlarm"), + source = "aws.cloudwatch", + `detail-type` = "CloudWatch Alarm Configuration Change", + detail = JsonObject.apply( + "alarmName" -> Json.fromString("ServiceAggregatedAlarm"), + "operation" -> Json.fromString("delete"), + "state" -> JsonObject + .apply( + "actionsSuppressedBy" -> Json.fromString("WaitPeriod"), + "value" -> Json.fromString("ALARM"), + "timestamp" -> Json.fromString("2022-07-14T13:59:46.425+0000") + ) + .toJson, + "configuration" -> JsonObject + .apply( + "alarmRule" -> Json.fromString( + "ALARM(ServerCpuTooHigh) OR ALARM(TotalNetworkTrafficTooHigh)"), + "actionsSuppressor" -> Json.fromString("ServiceMaintenanceAlarm"), + "actionsSuppressorWaitPeriod" -> Json.fromInt(120), + "actionsSuppressorExtensionPeriod" -> Json.fromInt(360), + "alarmName" -> Json.fromString("ServiceAggregatedAlarm"), + "actionsEnabled" -> Json.fromBoolean(true), + "timestamp" -> Json.fromString("2022-07-14T13:59:56.290+0000"), + "okActions" -> Json.arr(), + "alarmActions" -> Json.arr(), + "insufficientDataActions" -> Json.arr() + ) + .toJson + ), + `replay-name` = None + ) } From 866c39a689211c9bc670d08444965c7f466d09b0 Mon Sep 17 00:00:00 2001 From: takapi327 Date: Fri, 11 Oct 2024 10:07:22 +0900 Subject: [PATCH 16/20] Change version type String -> Option[String] --- .../feral/lambda/events/ScheduledEvent.scala | 6 +-- .../lambda/events/ScheduledEventSuite.scala | 52 +++++++++++++++---- 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala b/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala index abb75aef..54ef54be 100644 --- a/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala +++ b/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala @@ -8,7 +8,7 @@ import io.circe.{Decoder, JsonObject} // https://github.com/aws/aws-lambda-java-libs/blob/main/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/ScheduledEvent.java sealed abstract class ScheduledEvent { def id: String - def version: String + def version: Option[String] def account: String def time: Instant def region: String @@ -23,7 +23,7 @@ object ScheduledEvent { def apply( id: String, - version: String, + version: Option[String], account: String, time: Instant, region: String, @@ -61,7 +61,7 @@ object ScheduledEvent { private final case class Impl( id: String, - version: String, + version: Option[String], account: String, time: Instant, region: String, diff --git a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala index e1c93092..358aca12 100644 --- a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala +++ b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala @@ -9,6 +9,10 @@ import munit.FunSuite class ScheduledEventSuite extends FunSuite { + test("Decoding of a simple event") { + assertEquals(simpleEvent.as[ScheduledEvent].toTry.get, simpleResult) + } + test("Decoding of alarm status changes based on a single metric") { assertEquals(singleMetricEvent.as[ScheduledEvent].toTry.get, singleMetricResult) } @@ -73,6 +77,34 @@ class ScheduledEventSuite extends FunSuite { ) } + private def simpleEvent = json""" + { + "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c", + "detail-type": "Scheduled Event", + "source": "aws.events", + "account": "123456789012", + "time": "1970-01-01T00:00:00Z", + "region": "us-east-1", + "resources": [ + "arn:aws:events:us-east-1:123456789012:rule/ExampleRule" + ], + "detail": {} + } + """ + + private def simpleResult: ScheduledEvent = ScheduledEvent( + id = "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c", + version = None, + account = "123456789012", + time = Instant.parse("1970-01-01T00:00:00Z"), + region = "us-east-1", + resources = List("arn:aws:events:us-east-1:123456789012:rule/ExampleRule"), + source = "aws.events", + `detail-type` = "Scheduled Event", + detail = JsonObject.empty, + `replay-name` = None + ) + // Alarm status changes based on a single metric private def singleMetricEvent = json""" { @@ -126,7 +158,7 @@ class ScheduledEventSuite extends FunSuite { private def singleMetricResult: ScheduledEvent = ScheduledEvent( id = "c4c1c1c9-6542-e61b-6ef0-8c4d36933a92", - version = "0", + version = Some("0"), account = "123456789012", time = Instant.parse("2019-10-02T17:04:40Z"), region = "us-east-1", @@ -263,7 +295,7 @@ class ScheduledEventSuite extends FunSuite { private def metricsFormulaResult: ScheduledEvent = ScheduledEvent( id = "2dde0eb1-528b-d2d5-9ca6-6d590caf2329", - version = "0", + version = Some("0"), account = "123456789012", time = Instant.parse("2019-10-02T17:20:48Z"), region = "us-east-1", @@ -408,7 +440,7 @@ class ScheduledEventSuite extends FunSuite { private def abnormalityDetectionResult: ScheduledEvent = ScheduledEvent( id = "daafc9f1-bddd-c6c9-83af-74971fcfc4ef", - version = "0", + version = Some("0"), account = "123456789012", time = Instant.parse("2019-10-03T16:00:04Z"), region = "us-east-1", @@ -521,7 +553,7 @@ class ScheduledEventSuite extends FunSuite { private def stateChangeCompoundAlarmSuppressorResult: ScheduledEvent = ScheduledEvent( id = "d3dfc86d-384d-24c8-0345-9f7986db0b80", - version = "0", + version = Some("0"), account = "123456789012", time = Instant.parse("2022-07-22T15:57:45Z"), region = "us-east-1", @@ -605,7 +637,7 @@ class ScheduledEventSuite extends FunSuite { private def compoundAlarmCreationResult: ScheduledEvent = ScheduledEvent( id = "91535fdd-1e9c-849d-624b-9a9f2b1d09d0", - version = "0", + version = Some("0"), account = "123456789012", time = Instant.parse("2022-03-03T17:06:22Z"), region = "us-east-1", @@ -675,7 +707,7 @@ class ScheduledEventSuite extends FunSuite { private def createCompoundAlarmSuppressorResult: ScheduledEvent = ScheduledEvent( id = "454773e1-09f7-945b-aa2c-590af1c3f8e0", - version = "0", + version = Some("0"), account = "123456789012", time = Instant.parse("2022-07-14T13:59:46Z"), region = "us-east-1", @@ -796,7 +828,7 @@ class ScheduledEventSuite extends FunSuite { private def metricAlarmUpdateResult: ScheduledEvent = ScheduledEvent( id = "bc7d3391-47f8-ae47-f457-1b4d06118d50", - version = "0", + version = Some("0"), account = "123456789012", time = Instant.parse("2022-03-03T17:06:34Z"), region = "us-east-1", @@ -940,7 +972,7 @@ class ScheduledEventSuite extends FunSuite { private def updateCompoundAlarmSuppressorResult: ScheduledEvent = ScheduledEvent( id = "4c6f4177-6bd5-c0ca-9f05-b4151c54568b", - version = "0", + version = Some("0"), account = "123456789012", time = Instant.parse("2022-07-14T13:59:56Z"), region = "us-east-1", @@ -1067,7 +1099,7 @@ class ScheduledEventSuite extends FunSuite { private def metricsFormulaDeletionResult: ScheduledEvent = ScheduledEvent( id = "f171d220-9e1c-c252-5042-2677347a83ed", - version = "0", + version = Some("0"), account = "123456789012", time = Instant.parse("2022-03-03T17:07:13Z"), region = "us-east-1", @@ -1190,7 +1222,7 @@ class ScheduledEventSuite extends FunSuite { private def combinedAlarmSuppressorResult: ScheduledEvent = ScheduledEvent( id = "e34592a1-46c0-b316-f614-1b17a87be9dc", - version = "0", + version = Some("0"), account = "123456789012", time = Instant.parse("2022-07-14T14:00:01Z"), region = "us-east-1", From b933bfc01850c30c3ab87e578194d8c4e7a55b6e Mon Sep 17 00:00:00 2001 From: takapi327 Date: Fri, 11 Oct 2024 10:07:41 +0900 Subject: [PATCH 17/20] Action sbt scalafmtAll --- .../lambda/events/ScheduledEventSuite.scala | 61 +++++++++++-------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala index 358aca12..90c03cbb 100644 --- a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala +++ b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala @@ -874,7 +874,8 @@ class ScheduledEventSuite extends FunSuite { .toJson ), "alarmName" -> Json.fromString("ServerCpuTooHigh"), - "description" -> Json.fromString("Goes into alarm when server CPU utilization is too high!"), + "description" -> Json.fromString( + "Goes into alarm when server CPU utilization is too high!"), "actionsEnabled" -> Json.fromBoolean(true), "timestamp" -> Json.fromString("2022-03-03T17:06:34.267+0000"), "okActions" -> Json.arr(), @@ -889,34 +890,38 @@ class ScheduledEventSuite extends FunSuite { "comparisonOperator" -> Json.fromString("GreaterThanThreshold"), "treatMissingData" -> Json.fromString("ignore"), "metrics" -> Json.arr( - JsonObject.apply( - "id" -> Json.fromString("d6bfa85f-893e-b052-a58b-4f9295c9111a"), - "metricStat" -> JsonObject - .apply( - "metric" -> JsonObject - .apply( - "namespace" -> Json.fromString("AWS/EC2"), - "name" -> Json.fromString("CPUUtilization"), - "dimensions" -> JsonObject - .apply("InstanceId" -> Json.fromString("i-12345678901234567")) - .toJson - ) - .toJson, - "period" -> Json.fromInt(300), - "stat" -> Json.fromString("Average") - ) - .toJson, - "returnData" -> Json.fromBoolean(true) - ).toJson, + JsonObject + .apply( + "id" -> Json.fromString("d6bfa85f-893e-b052-a58b-4f9295c9111a"), + "metricStat" -> JsonObject + .apply( + "metric" -> JsonObject + .apply( + "namespace" -> Json.fromString("AWS/EC2"), + "name" -> Json.fromString("CPUUtilization"), + "dimensions" -> JsonObject + .apply("InstanceId" -> Json.fromString("i-12345678901234567")) + .toJson + ) + .toJson, + "period" -> Json.fromInt(300), + "stat" -> Json.fromString("Average") + ) + .toJson, + "returnData" -> Json.fromBoolean(true) + ) + .toJson ), "alarmName" -> Json.fromString("ServerCpuTooHigh"), - "description" -> Json.fromString("Goes into alarm when server CPU utilization is too high!"), + "description" -> Json.fromString( + "Goes into alarm when server CPU utilization is too high!"), "actionsEnabled" -> Json.fromBoolean(true), "timestamp" -> Json.fromString("2022-03-03T17:06:13.757+0000"), "okActions" -> Json.arr(), "alarmActions" -> Json.arr(), "insufficientDataActions" -> Json.arr() - ).toJson + ) + .toJson ), `replay-name` = None ) @@ -1103,7 +1108,8 @@ class ScheduledEventSuite extends FunSuite { account = "123456789012", time = Instant.parse("2022-03-03T17:07:13Z"), region = "us-east-1", - resources = List("arn:aws:cloudwatch:us-east-1:123456789012:alarm:TotalNetworkTrafficTooHigh"), + resources = + List("arn:aws:cloudwatch:us-east-1:123456789012:alarm:TotalNetworkTrafficTooHigh"), source = "aws.cloudwatch", `detail-type` = "CloudWatch Alarm Configuration Change", detail = JsonObject.apply( @@ -1170,16 +1176,19 @@ class ScheduledEventSuite extends FunSuite { "expression" -> Json.fromString("SUM(METRICS())"), "label" -> Json.fromString("Total Network Traffic"), "returnData" -> Json.fromBoolean(true) - ).toJson + ) + .toJson ), "alarmName" -> Json.fromString("TotalNetworkTrafficTooHigh"), - "description" -> Json.fromString("Goes into alarm if total network traffic exceeds 10Kb"), + "description" -> Json.fromString( + "Goes into alarm if total network traffic exceeds 10Kb"), "actionsEnabled" -> Json.fromBoolean(true), "timestamp" -> Json.fromString("2022-03-03T17:06:17.672+0000"), "okActions" -> Json.arr(), "alarmActions" -> Json.arr(), "insufficientDataActions" -> Json.arr() - ).toJson + ) + .toJson ), `replay-name` = None ) From c047efc01a5de341f86e93ccdc175a0c501330cd Mon Sep 17 00:00:00 2001 From: takapi327 Date: Fri, 11 Oct 2024 11:45:18 +0900 Subject: [PATCH 18/20] Delete unused --- .../src/main/scala/feral/lambda/events/codecs.scala | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/lambda/shared/src/main/scala/feral/lambda/events/codecs.scala b/lambda/shared/src/main/scala/feral/lambda/events/codecs.scala index e64a0422..44713caf 100644 --- a/lambda/shared/src/main/scala/feral/lambda/events/codecs.scala +++ b/lambda/shared/src/main/scala/feral/lambda/events/codecs.scala @@ -23,8 +23,7 @@ import io.circe.KeyDecoder import io.circe.KeyEncoder import org.typelevel.ci.CIString -import java.time.{Instant, OffsetDateTime} -import java.time.format.DateTimeFormatter +import java.time.Instant import scala.util.Try private object codecs { @@ -39,12 +38,6 @@ private object codecs { } } - implicit def decodeOffsetDateTime: Decoder[OffsetDateTime] = - Decoder.decodeString.emapTry { str => - val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") - Try(OffsetDateTime.parse(str, formatter)) - } - implicit def decodeIpAddress: Decoder[IpAddress] = Decoder.decodeString.emap(IpAddress.fromString(_).toRight("Cannot parse IP address")) From 4ca97b714b7267c93b5dca7381058d5e0c1c23eb Mon Sep 17 00:00:00 2001 From: takapi327 Date: Fri, 11 Oct 2024 17:33:24 +0900 Subject: [PATCH 19/20] Added Header comment --- .../feral/lambda/events/ScheduledEvent.scala | 16 ++++++++++++++++ .../lambda/events/ScheduledEventSuite.scala | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala b/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala index 54ef54be..297c4acf 100644 --- a/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala +++ b/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2021 Typelevel + * + * 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 + * + * 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. + */ + package feral.lambda.events import java.time.Instant diff --git a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala index 90c03cbb..778194ee 100644 --- a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala +++ b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2021 Typelevel + * + * 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 + * + * 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. + */ + package feral.lambda.events import java.time.Instant From 831aa7826d7a6b864044e633329c16b0e8d8b96b Mon Sep 17 00:00:00 2001 From: takapi327 Date: Fri, 11 Oct 2024 17:37:12 +0900 Subject: [PATCH 20/20] Action sbt scalafixAll --- .../main/scala/feral/lambda/events/ScheduledEvent.scala | 5 +++-- .../scala/feral/lambda/events/ScheduledEventSuite.scala | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala b/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala index 297c4acf..c424f567 100644 --- a/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala +++ b/lambda/shared/src/main/scala/feral/lambda/events/ScheduledEvent.scala @@ -16,9 +16,10 @@ package feral.lambda.events -import java.time.Instant +import io.circe.Decoder +import io.circe.JsonObject -import io.circe.{Decoder, JsonObject} +import java.time.Instant // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/aws-lambda/trigger/eventbridge.d.ts // https://github.com/aws/aws-lambda-java-libs/blob/main/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/ScheduledEvent.java diff --git a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala index 778194ee..8cd5ea58 100644 --- a/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala +++ b/lambda/shared/src/test/scala/feral/lambda/events/ScheduledEventSuite.scala @@ -16,13 +16,13 @@ package feral.lambda.events -import java.time.Instant - +import io.circe.Json +import io.circe.JsonObject import io.circe.literal._ -import io.circe.{Json, JsonObject} - import munit.FunSuite +import java.time.Instant + class ScheduledEventSuite extends FunSuite { test("Decoding of a simple event") {