From 05827e042dea22e4721dad1a39f9fc2d7bda1bd3 Mon Sep 17 00:00:00 2001 From: Mark Sailes Date: Thu, 11 Jul 2024 11:00:56 +0100 Subject: [PATCH 1/4] Separated the events to two different Java classes Moved the tests to the test module --- aws-lambda-java-events/README.md | 2 + aws-lambda-java-events/pom.xml | 2 + .../events/CloudWatchCompositeAlarmEvent.java | 70 +++++++++ .../events/CloudWatchMetricAlarmEvent.java | 99 +++++++++++++ .../lambda/runtime/tests/EventLoader.java | 8 + .../lambda/runtime/tests/EventLoaderTest.java | 137 +++++++++++++++++- .../resources/cloudwatch_composite_alarm.json | 30 ++++ .../resources/cloudwatch_metric_alarm.json | 42 ++++++ 8 files changed, 388 insertions(+), 2 deletions(-) create mode 100644 aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CloudWatchCompositeAlarmEvent.java create mode 100644 aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CloudWatchMetricAlarmEvent.java create mode 100644 aws-lambda-java-tests/src/test/resources/cloudwatch_composite_alarm.json create mode 100644 aws-lambda-java-tests/src/test/resources/cloudwatch_metric_alarm.json diff --git a/aws-lambda-java-events/README.md b/aws-lambda-java-events/README.md index 8c60b3a2..c0d90d85 100644 --- a/aws-lambda-java-events/README.md +++ b/aws-lambda-java-events/README.md @@ -16,7 +16,9 @@ * `AppSyncLambdaAuthorizerResponse` * `CloudFormationCustomResourceEvent` * `CloudFrontEvent` +* `CloudWatchCompositeAlarmEvent` * `CloudWatchLogsEvent` +* `CloudWatchMetricAlarmEvent` * `CodeCommitEvent` * `CognitoEvent` * `CognitoUserPoolCreateAuthChallengeEvent` diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml index 2c7442c0..4abea0f8 100644 --- a/aws-lambda-java-events/pom.xml +++ b/aws-lambda-java-events/pom.xml @@ -35,6 +35,8 @@ 1.8 1.8 1.18.22 + UTF-8 + UTF-8 diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CloudWatchCompositeAlarmEvent.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CloudWatchCompositeAlarmEvent.java new file mode 100644 index 00000000..d4090b55 --- /dev/null +++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CloudWatchCompositeAlarmEvent.java @@ -0,0 +1,70 @@ +package com.amazonaws.services.lambda.runtime.events; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Represents an CloudWatch Composite Alarm event. This event occurs when a composite alarm is triggered. + * + * @see Using Amazon CloudWatch alarms + */ +@Data +@Builder(setterPrefix = "with") +@NoArgsConstructor +@AllArgsConstructor +public class CloudWatchCompositeAlarmEvent { + private String source; + private String alarmArn; + private String accountId; + private String time; + private String region; + private AlarmData alarmData; + + @Data + @Builder(setterPrefix = "with") + @NoArgsConstructor + @AllArgsConstructor + public static class AlarmData { + private String alarmName; + private State state; + private PreviousState previousState; + private Configuration configuration; + } + + @Data + @Builder(setterPrefix = "with") + @NoArgsConstructor + @AllArgsConstructor + public static class State { + private String value; + private String reason; + private String reasonData; + private String timestamp; + } + + @Data + @Builder(setterPrefix = "with") + @NoArgsConstructor + @AllArgsConstructor + public static class PreviousState { + private String value; + private String reason; + private String reasonData; + private String timestamp; + private String actionsSuppressedBy; + private String actionsSuppressedReason; + } + + @Data + @Builder(setterPrefix = "with") + @NoArgsConstructor + @AllArgsConstructor + public static class Configuration { + private String alarmRule; + private String actionsSuppressor; + private Integer actionsSuppressorWaitPeriod; + private Integer actionsSuppressorExtensionPeriod; + } +} diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CloudWatchMetricAlarmEvent.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CloudWatchMetricAlarmEvent.java new file mode 100644 index 00000000..2b5f503c --- /dev/null +++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CloudWatchMetricAlarmEvent.java @@ -0,0 +1,99 @@ +package com.amazonaws.services.lambda.runtime.events; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Map; + +/** + * Represents an CloudWatch Metric Alarm event. This event occurs when a metric alarm is triggered. + * + * @see Using Amazon CloudWatch alarms + */ +@Data +@Builder(setterPrefix = "with") +@NoArgsConstructor +@AllArgsConstructor +public class CloudWatchMetricAlarmEvent { + private String source; + private String alarmArn; + private String accountId; + private String time; + private String region; + private AlarmData alarmData; + + @Data + @Builder(setterPrefix = "with") + @NoArgsConstructor + @AllArgsConstructor + public static class AlarmData { + private String alarmName; + private State state; + private PreviousState previousState; + private Configuration configuration; + } + + @Data + @Builder(setterPrefix = "with") + @NoArgsConstructor + @AllArgsConstructor + public static class State { + private String value; + private String reason; + private String timestamp; + } + + @Data + @Builder(setterPrefix = "with") + @NoArgsConstructor + @AllArgsConstructor + public static class PreviousState { + private String value; + private String reason; + private String reasonData; + private String timestamp; + } + + @Data + @Builder(setterPrefix = "with") + @NoArgsConstructor + @AllArgsConstructor + public static class Configuration { + private String description; + private List metrics; + } + + @Data + @Builder(setterPrefix = "with") + @NoArgsConstructor + @AllArgsConstructor + public static class Metric { + private String id; + private MetricStat metricStat; + private Boolean returnData; + } + + @Data + @Builder(setterPrefix = "with") + @NoArgsConstructor + @AllArgsConstructor + public static class MetricStat { + private MetricDetail metric; + private Integer period; + private String stat; + private String unit; + } + + @Data + @Builder(setterPrefix = "with") + @NoArgsConstructor + @AllArgsConstructor + public static class MetricDetail { + private String namespace; + private String name; + private Map dimensions; + } +} diff --git a/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/EventLoader.java b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/EventLoader.java index aa600749..86886ea2 100644 --- a/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/EventLoader.java +++ b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/EventLoader.java @@ -45,10 +45,18 @@ public static CloudFrontEvent loadCloudFrontEvent(String filename) { return loadEvent(filename, CloudFrontEvent.class); } + public static CloudWatchCompositeAlarmEvent loadCloudWatchCompositeAlarmEvent(String filename) { + return loadEvent(filename, CloudWatchCompositeAlarmEvent.class); + } + public static CloudWatchLogsEvent loadCloudWatchLogsEvent(String filename) { return loadEvent(filename, CloudWatchLogsEvent.class); } + public static CloudWatchMetricAlarmEvent loadCloudWatchMetricAlarmEvent(String filename) { + return loadEvent(filename, CloudWatchMetricAlarmEvent.class); + } + public static CodeCommitEvent loadCodeCommitEvent(String filename) { return loadEvent(filename, CodeCommitEvent.class); } diff --git a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java index 1c9d17e1..70da98a5 100644 --- a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java +++ b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java @@ -1,6 +1,37 @@ /* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ package com.amazonaws.services.lambda.runtime.tests; +import com.amazonaws.services.lambda.runtime.events.APIGatewayCustomAuthorizerEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayV2CustomAuthorizerEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent; +import com.amazonaws.services.lambda.runtime.events.ActiveMQEvent; +import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent; +import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; +import com.amazonaws.services.lambda.runtime.events.CloudFrontEvent; +import com.amazonaws.services.lambda.runtime.events.CloudWatchCompositeAlarmEvent; +import com.amazonaws.services.lambda.runtime.events.CloudWatchCompositeAlarmEvent.AlarmData; +import com.amazonaws.services.lambda.runtime.events.CloudWatchCompositeAlarmEvent.Configuration; +import com.amazonaws.services.lambda.runtime.events.CloudWatchCompositeAlarmEvent.PreviousState; +import com.amazonaws.services.lambda.runtime.events.CloudWatchCompositeAlarmEvent.State; +import com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent; +import com.amazonaws.services.lambda.runtime.events.CloudWatchMetricAlarmEvent; +import com.amazonaws.services.lambda.runtime.events.CodeCommitEvent; +import com.amazonaws.services.lambda.runtime.events.CognitoUserPoolPreTokenGenerationEventV2; +import com.amazonaws.services.lambda.runtime.events.ConfigEvent; +import com.amazonaws.services.lambda.runtime.events.ConnectEvent; +import com.amazonaws.services.lambda.runtime.events.DynamodbEvent; +import com.amazonaws.services.lambda.runtime.events.KafkaEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisFirehoseEvent; +import com.amazonaws.services.lambda.runtime.events.LambdaDestinationEvent; +import com.amazonaws.services.lambda.runtime.events.LexEvent; +import com.amazonaws.services.lambda.runtime.events.RabbitMQEvent; +import com.amazonaws.services.lambda.runtime.events.S3Event; +import com.amazonaws.services.lambda.runtime.events.SNSEvent; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import com.amazonaws.services.lambda.runtime.events.ScheduledEvent; +import com.amazonaws.services.lambda.runtime.events.SecretsManagerRotationEvent; import com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue; import com.amazonaws.services.lambda.runtime.events.models.dynamodb.Record; import com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord; @@ -15,8 +46,6 @@ import static java.time.Instant.ofEpochSecond; import static org.assertj.core.api.Assertions.*; -import com.amazonaws.services.lambda.runtime.events.*; - public class EventLoaderTest { @Test @@ -376,4 +405,108 @@ public void testLoadCognitoUserPoolPreTokenGenerationEventV2() { String[] requestScopes = request.getScopes(); assertThat("aws.cognito.signin.user.admin").isEqualTo(requestScopes[0]); } + + @Test + public void testCloudWatchCompositeAlarmEvent() { + CloudWatchCompositeAlarmEvent event = EventLoader.loadCloudWatchCompositeAlarmEvent("cloudwatch_composite_alarm.json"); + assertThat(event).isNotNull(); + assertThat(event) + .returns("aws.cloudwatch", from(CloudWatchCompositeAlarmEvent::getSource)) + .returns("arn:aws:cloudwatch:us-east-1:111122223333:alarm:SuppressionDemo.Main", from(CloudWatchCompositeAlarmEvent::getAlarmArn)) + .returns("111122223333", from(CloudWatchCompositeAlarmEvent::getAccountId)) + .returns("2023-08-04T12:56:46.138+0000", from(CloudWatchCompositeAlarmEvent::getTime)) + .returns("us-east-1", from(CloudWatchCompositeAlarmEvent::getRegion)); + + AlarmData alarmData = event.getAlarmData(); + assertThat(alarmData).isNotNull(); + assertThat(alarmData) + .returns("CompositeDemo.Main", from(AlarmData::getAlarmName)); + + State state = alarmData.getState(); + assertThat(state).isNotNull(); + assertThat(state) + .returns("ALARM", from(State::getValue)) + .returns("arn:aws:cloudwatch:us-east-1:111122223333:alarm:CompositeDemo.FirstChild transitioned to ALARM at Friday 04 August, 2023 12:54:46 UTC", from(State::getReason)) + .returns("{\"triggeringAlarms\":[{\"arn\":\"arn:aws:cloudwatch:us-east-1:111122223333:alarm:CompositeDemo.FirstChild\",\"state\":{\"value\":\"ALARM\",\"timestamp\":\"2023-08-04T12:54:46.138+0000\"}}]}", from(State::getReasonData)) + .returns("2023-08-04T12:56:46.138+0000", from(State::getTimestamp)); + + PreviousState previousState = alarmData.getPreviousState(); + assertThat(previousState).isNotNull(); + assertThat(previousState) + .returns("ALARM", from(PreviousState::getValue)) + .returns("arn:aws:cloudwatch:us-east-1:111122223333:alarm:CompositeDemo.FirstChild transitioned to ALARM at Friday 04 August, 2023 12:54:46 UTC", from(PreviousState::getReason)) + .returns("{\"triggeringAlarms\":[{\"arn\":\"arn:aws:cloudwatch:us-east-1:111122223333:alarm:CompositeDemo.FirstChild\",\"state\":{\"value\":\"ALARM\",\"timestamp\":\"2023-08-04T12:54:46.138+0000\"}}]}", from(PreviousState::getReasonData)) + .returns("2023-08-04T12:54:46.138+0000", from(PreviousState::getTimestamp)) + .returns("WaitPeriod", from(PreviousState::getActionsSuppressedBy)) + .returns("Actions suppressed by WaitPeriod", from(PreviousState::getActionsSuppressedReason)); + + Configuration configuration = alarmData.getConfiguration(); + assertThat(configuration).isNotNull(); + assertThat(configuration) + .returns("ALARM(CompositeDemo.FirstChild) OR ALARM(CompositeDemo.SecondChild)", from(Configuration::getAlarmRule)) + .returns("CompositeDemo.ActionsSuppressor", from(Configuration::getActionsSuppressor)) + .returns(120, from(Configuration::getActionsSuppressorWaitPeriod)) + .returns(180, from(Configuration::getActionsSuppressorExtensionPeriod)); + } + + @Test + public void testCloudWatchMetricAlarmEvent() { + CloudWatchMetricAlarmEvent event = EventLoader.loadCloudWatchMetricAlarmEvent("cloudwatch_metric_alarm.json"); + assertThat(event).isNotNull(); + assertThat(event) + .returns("aws.cloudwatch", from(CloudWatchMetricAlarmEvent::getSource)) + .returns("arn:aws:cloudwatch:us-east-1:444455556666:alarm:lambda-demo-metric-alarm", from(CloudWatchMetricAlarmEvent::getAlarmArn)) + .returns("444455556666", from(CloudWatchMetricAlarmEvent::getAccountId)) + .returns("2023-08-04T12:36:15.490+0000", from(CloudWatchMetricAlarmEvent::getTime)) + .returns("us-east-1", from(CloudWatchMetricAlarmEvent::getRegion)); + + CloudWatchMetricAlarmEvent.AlarmData alarmData = event.getAlarmData(); + assertThat(alarmData).isNotNull(); + assertThat(alarmData) + .returns("lambda-demo-metric-alarm", from(CloudWatchMetricAlarmEvent.AlarmData::getAlarmName)); + + CloudWatchMetricAlarmEvent.State state = alarmData.getState(); + assertThat(state).isNotNull(); + assertThat(state) + .returns("ALARM", from(CloudWatchMetricAlarmEvent.State::getValue)) + .returns("test", from(CloudWatchMetricAlarmEvent.State::getReason)) + .returns("2023-08-04T12:36:15.490+0000", from(CloudWatchMetricAlarmEvent.State::getTimestamp)); + + CloudWatchMetricAlarmEvent.PreviousState previousState = alarmData.getPreviousState(); + assertThat(previousState).isNotNull(); + assertThat(previousState) + .returns("INSUFFICIENT_DATA", from(CloudWatchMetricAlarmEvent.PreviousState::getValue)) + .returns("Insufficient Data: 5 datapoints were unknown.", from(CloudWatchMetricAlarmEvent.PreviousState::getReason)) + .returns("{\"version\":\"1.0\",\"queryDate\":\"2023-08-04T12:31:29.591+0000\",\"statistic\":\"Average\",\"period\":60,\"recentDatapoints\":[],\"threshold\":5.0,\"evaluatedDatapoints\":[{\"timestamp\":\"2023-08-04T12:30:00.000+0000\"},{\"timestamp\":\"2023-08-04T12:29:00.000+0000\"},{\"timestamp\":\"2023-08-04T12:28:00.000+0000\"},{\"timestamp\":\"2023-08-04T12:27:00.000+0000\"},{\"timestamp\":\"2023-08-04T12:26:00.000+0000\"}]}", from(CloudWatchMetricAlarmEvent.PreviousState::getReasonData)) + .returns("2023-08-04T12:31:29.595+0000", from(CloudWatchMetricAlarmEvent.PreviousState::getTimestamp)); + + CloudWatchMetricAlarmEvent.Configuration configuration = alarmData.getConfiguration(); + assertThat(configuration).isNotNull(); + assertThat(configuration) + .returns("Metric Alarm to test Lambda actions", from(CloudWatchMetricAlarmEvent.Configuration::getDescription)); + + List metrics = configuration.getMetrics(); + assertThat(metrics).hasSize(1); + CloudWatchMetricAlarmEvent.Metric metric = metrics.get(0); + assertThat(metric) + .returns("1234e046-06f0-a3da-9534-EXAMPLEe4c", from(CloudWatchMetricAlarmEvent.Metric::getId)); + + CloudWatchMetricAlarmEvent.MetricStat metricStat = metric.getMetricStat(); + assertThat(metricStat).isNotNull(); + assertThat(metricStat) + .returns(60, from(CloudWatchMetricAlarmEvent.MetricStat::getPeriod)) + .returns("Average", from(CloudWatchMetricAlarmEvent.MetricStat::getStat)) + .returns("Percent", from(CloudWatchMetricAlarmEvent.MetricStat::getUnit)); + + CloudWatchMetricAlarmEvent.MetricDetail metricDetail = metricStat.getMetric(); + assertThat(metricDetail).isNotNull(); + assertThat(metricDetail) + .returns("AWS/Logs", from(CloudWatchMetricAlarmEvent.MetricDetail::getNamespace)) + .returns("CallCount", from(CloudWatchMetricAlarmEvent.MetricDetail::getName)); + + Map dimensions = metricDetail.getDimensions(); + assertThat(dimensions).isNotEmpty().hasSize(1); + assertThat(dimensions) + .contains(entry("InstanceId", "i-12345678")); + } } diff --git a/aws-lambda-java-tests/src/test/resources/cloudwatch_composite_alarm.json b/aws-lambda-java-tests/src/test/resources/cloudwatch_composite_alarm.json new file mode 100644 index 00000000..353d470a --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/cloudwatch_composite_alarm.json @@ -0,0 +1,30 @@ +{ + "source": "aws.cloudwatch", + "alarmArn": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:SuppressionDemo.Main", + "accountId": "111122223333", + "time": "2023-08-04T12:56:46.138+0000", + "region": "us-east-1", + "alarmData": { + "alarmName": "CompositeDemo.Main", + "state": { + "value": "ALARM", + "reason": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:CompositeDemo.FirstChild transitioned to ALARM at Friday 04 August, 2023 12:54:46 UTC", + "reasonData": "{\"triggeringAlarms\":[{\"arn\":\"arn:aws:cloudwatch:us-east-1:111122223333:alarm:CompositeDemo.FirstChild\",\"state\":{\"value\":\"ALARM\",\"timestamp\":\"2023-08-04T12:54:46.138+0000\"}}]}", + "timestamp": "2023-08-04T12:56:46.138+0000" + }, + "previousState": { + "value": "ALARM", + "reason": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:CompositeDemo.FirstChild transitioned to ALARM at Friday 04 August, 2023 12:54:46 UTC", + "reasonData": "{\"triggeringAlarms\":[{\"arn\":\"arn:aws:cloudwatch:us-east-1:111122223333:alarm:CompositeDemo.FirstChild\",\"state\":{\"value\":\"ALARM\",\"timestamp\":\"2023-08-04T12:54:46.138+0000\"}}]}", + "timestamp": "2023-08-04T12:54:46.138+0000", + "actionsSuppressedBy": "WaitPeriod", + "actionsSuppressedReason": "Actions suppressed by WaitPeriod" + }, + "configuration": { + "alarmRule": "ALARM(CompositeDemo.FirstChild) OR ALARM(CompositeDemo.SecondChild)", + "actionsSuppressor": "CompositeDemo.ActionsSuppressor", + "actionsSuppressorWaitPeriod": 120, + "actionsSuppressorExtensionPeriod": 180 + } + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/cloudwatch_metric_alarm.json b/aws-lambda-java-tests/src/test/resources/cloudwatch_metric_alarm.json new file mode 100644 index 00000000..61b4187b --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/cloudwatch_metric_alarm.json @@ -0,0 +1,42 @@ +{ + "source": "aws.cloudwatch", + "alarmArn": "arn:aws:cloudwatch:us-east-1:444455556666:alarm:lambda-demo-metric-alarm", + "accountId": "444455556666", + "time": "2023-08-04T12:36:15.490+0000", + "region": "us-east-1", + "alarmData": { + "alarmName": "lambda-demo-metric-alarm", + "state": { + "value": "ALARM", + "reason": "test", + "timestamp": "2023-08-04T12:36:15.490+0000" + }, + "previousState": { + "value": "INSUFFICIENT_DATA", + "reason": "Insufficient Data: 5 datapoints were unknown.", + "reasonData": "{\"version\":\"1.0\",\"queryDate\":\"2023-08-04T12:31:29.591+0000\",\"statistic\":\"Average\",\"period\":60,\"recentDatapoints\":[],\"threshold\":5.0,\"evaluatedDatapoints\":[{\"timestamp\":\"2023-08-04T12:30:00.000+0000\"},{\"timestamp\":\"2023-08-04T12:29:00.000+0000\"},{\"timestamp\":\"2023-08-04T12:28:00.000+0000\"},{\"timestamp\":\"2023-08-04T12:27:00.000+0000\"},{\"timestamp\":\"2023-08-04T12:26:00.000+0000\"}]}", + "timestamp": "2023-08-04T12:31:29.595+0000" + }, + "configuration": { + "description": "Metric Alarm to test Lambda actions", + "metrics": [ + { + "id": "1234e046-06f0-a3da-9534-EXAMPLEe4c", + "metricStat": { + "metric": { + "namespace": "AWS/Logs", + "name": "CallCount", + "dimensions": { + "InstanceId": "i-12345678" + } + }, + "period": 60, + "stat": "Average", + "unit": "Percent" + }, + "returnData": true + } + ] + } + } +} \ No newline at end of file From a59b548935c21e88101a179d18b8367e1f817d59 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 28 Jun 2024 18:31:15 +0100 Subject: [PATCH 2/4] release 2.5.1 (#487) --- .github/dependabot.yml | 7 + .github/workflows/aws-lambda-java-core.yml | 9 +- ...aws-lambda-java-events-sdk-transformer.yml | 7 +- .github/workflows/aws-lambda-java-events.yml | 7 +- .github/workflows/aws-lambda-java-log4j2.yml | 7 +- .../aws-lambda-java-serialization.yml | 7 +- .github/workflows/aws-lambda-java-tests.yml | 7 +- .github/workflows/repo-sync.yml | 4 + ...runtime-interface-client_merge_to_main.yml | 9 +- .../workflows/runtime-interface-client_pr.yml | 15 ++- .github/workflows/samples.yml | 3 +- .gitignore | 5 +- README.md | 2 +- .../Makefile | 19 ++- .../README.md | 4 +- .../RELEASE.CHANGELOG.md | 5 + .../pom.xml | 40 +++++- .../ric-dev-environment/publish_snapshot.sh | 6 +- .../services/lambda/crac/ContextImpl.java | 43 +++--- .../services/lambda/crac/DNSManager.java | 10 ++ .../lambda/runtime/api/client/UserFault.java | 21 ++- .../api/client/runtimeapi/JniHelper.java | 64 +++++++++ .../api/client/runtimeapi/NativeClient.java | 62 +-------- .../src/main/jni/Dockerfile.glibc | 9 +- .../src/main/jni/Dockerfile.musl | 9 +- .../src/main/jni/build-jni-lib.sh | 13 +- ...zonaws_services_lambda_crac_DNSManager.cpp | 27 ++++ ...mazonaws_services_lambda_crac_DNSManager.h | 19 +++ ...ime_api_client_runtimeapi_NativeClient.cpp | 22 +--- ...ntime_api_client_runtimeapi_NativeClient.h | 4 + .../src/main/jni/macro.h | 14 ++ .../services/lambda/crac/ContextImplTest.java | 21 ++- .../lambda/crac/DNSCacheManagerTest.java | 124 ++++++++++++++++++ .../runtime/api/client/UserFaultTest.java | 40 ++++++ .../codebuild/buildspec.os.alpine.yml | 2 +- .../codebuild/buildspec.os.amazoncorretto.yml | 2 +- .../codebuild/buildspec.os.amazonlinux.1.yml | 2 +- .../codebuild/buildspec.os.amazonlinux.2.yml | 2 +- .../test/integration/test-handler/pom.xml | 2 +- 39 files changed, 525 insertions(+), 150 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/DNSManager.java create mode 100644 aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/JniHelper.java create mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_crac_DNSManager.cpp create mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_crac_DNSManager.h create mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/macro.h create mode 100644 aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/crac/DNSCacheManagerTest.java diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..3722537a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" \ No newline at end of file diff --git a/.github/workflows/aws-lambda-java-core.yml b/.github/workflows/aws-lambda-java-core.yml index 0b553bbc..39ff1291 100644 --- a/.github/workflows/aws-lambda-java-core.yml +++ b/.github/workflows/aws-lambda-java-core.yml @@ -7,11 +7,12 @@ on: push: branches: [ main ] paths: - - 'aws-lambda-java-core/**' + - 'aws-lambda-java-core/**' pull_request: branches: [ '*' ] paths: - - 'aws-lambda-java-core/**' + - 'aws-lambda-java-core/**' + - '.github/workflows/aws-lambda-java-core.yml' jobs: build: @@ -21,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up JDK 1.8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 8 distribution: corretto @@ -38,3 +39,5 @@ jobs: - name: Run 'pr' target working-directory: ./aws-lambda-java-runtime-interface-client run: make pr + env: + IS_JAVA_8: true diff --git a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml index 679639d3..05a870ad 100644 --- a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml +++ b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml @@ -7,11 +7,12 @@ on: push: branches: [ main ] paths: - - 'aws-lambda-java-events-sdk-transformer/**' + - 'aws-lambda-java-events-sdk-transformer/**' pull_request: branches: [ '*' ] paths: - - 'aws-lambda-java-events-sdk-transformer/**' + - 'aws-lambda-java-events-sdk-transformer/**' + - '.github/workflows/aws-lambda-java-events-sdk-transformer.yml' jobs: build: @@ -21,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up JDK 1.8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 8 distribution: corretto diff --git a/.github/workflows/aws-lambda-java-events.yml b/.github/workflows/aws-lambda-java-events.yml index bdd01eb7..634d803e 100644 --- a/.github/workflows/aws-lambda-java-events.yml +++ b/.github/workflows/aws-lambda-java-events.yml @@ -7,11 +7,12 @@ on: push: branches: [ main ] paths: - - 'aws-lambda-java-events/**' + - 'aws-lambda-java-events/**' pull_request: branches: [ '*' ] paths: - - 'aws-lambda-java-events/**' + - 'aws-lambda-java-events/**' + - '.github/workflows/aws-lambda-java-events.yml' jobs: build: @@ -21,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up JDK 1.8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 8 distribution: corretto diff --git a/.github/workflows/aws-lambda-java-log4j2.yml b/.github/workflows/aws-lambda-java-log4j2.yml index 427c7536..ab86f200 100644 --- a/.github/workflows/aws-lambda-java-log4j2.yml +++ b/.github/workflows/aws-lambda-java-log4j2.yml @@ -7,11 +7,12 @@ on: push: branches: [ main ] paths: - - 'aws-lambda-java-log4j2/**' + - 'aws-lambda-java-log4j2/**' pull_request: branches: [ '*' ] paths: - - 'aws-lambda-java-log4j2/**' + - 'aws-lambda-java-log4j2/**' + - '.github/workflows/aws-lambda-java-log4j2.yml' jobs: build: @@ -21,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up JDK 1.8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 8 distribution: corretto diff --git a/.github/workflows/aws-lambda-java-serialization.yml b/.github/workflows/aws-lambda-java-serialization.yml index 9d71cbab..baa6052f 100644 --- a/.github/workflows/aws-lambda-java-serialization.yml +++ b/.github/workflows/aws-lambda-java-serialization.yml @@ -7,11 +7,12 @@ on: push: branches: [ main ] paths: - - 'aws-lambda-java-serialization/**' + - 'aws-lambda-java-serialization/**' pull_request: branches: [ '*' ] paths: - - 'aws-lambda-java-serialization/**' + - 'aws-lambda-java-serialization/**' + - '.github/workflows/aws-lambda-java-serialization.yml' jobs: build: @@ -21,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up JDK 1.8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 8 distribution: corretto diff --git a/.github/workflows/aws-lambda-java-tests.yml b/.github/workflows/aws-lambda-java-tests.yml index fc587e8e..bf17ed2a 100644 --- a/.github/workflows/aws-lambda-java-tests.yml +++ b/.github/workflows/aws-lambda-java-tests.yml @@ -7,11 +7,12 @@ on: push: branches: [ main ] paths: - - 'aws-lambda-java-tests/**' + - 'aws-lambda-java-tests/**' pull_request: branches: [ '*' ] paths: - - 'aws-lambda-java-tests/**' + - 'aws-lambda-java-tests/**' + - '.github/workflows/aws-lambda-java-tests.yml' jobs: build: @@ -21,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up JDK 1.8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 8 distribution: corretto diff --git a/.github/workflows/repo-sync.yml b/.github/workflows/repo-sync.yml index 91054f9e..c667bceb 100644 --- a/.github/workflows/repo-sync.yml +++ b/.github/workflows/repo-sync.yml @@ -3,6 +3,10 @@ name: Repo Sync on: schedule: - cron: "0 8 * * 1-5" # At 08:00 on every day-of-week from Monday through Friday + pull_request: + branches: [ '*' ] + paths: + - '.github/workflows/repo-sync.yml' workflow_dispatch: jobs: diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml index 1783f1cd..27b22fd7 100644 --- a/.github/workflows/runtime-interface-client_merge_to_main.yml +++ b/.github/workflows/runtime-interface-client_merge_to_main.yml @@ -15,6 +15,7 @@ on: branches: [ main ] paths: - 'aws-lambda-java-runtime-interface-client/**' + workflow_dispatch: jobs: @@ -29,13 +30,13 @@ jobs: - uses: actions/checkout@v3 - name: Set up JDK 1.8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 8 distribution: corretto - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 @@ -48,9 +49,11 @@ jobs: - name: Test Runtime Interface Client xplatform build - Run 'build' target working-directory: ./aws-lambda-java-runtime-interface-client run: make build + env: + IS_JAVA_8: true - name: Issue AWS credentials - uses: aws-actions/configure-aws-credentials@v1 + uses: aws-actions/configure-aws-credentials@v4 with: aws-region: ${{ secrets.AWS_REGION }} role-to-assume: ${{ secrets.AWS_ROLE }} diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index 7a7f602e..b935f4f7 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -7,7 +7,8 @@ on: pull_request: branches: [ '*' ] paths: - - 'aws-lambda-java-runtime-interface-client/**' + - 'aws-lambda-java-runtime-interface-client/**' + - '.github/workflows/runtime-interface-client_pr.yml' jobs: @@ -17,7 +18,7 @@ jobs: - uses: actions/checkout@v3 - name: Set up JDK 1.8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 8 distribution: corretto @@ -25,6 +26,8 @@ jobs: - name: Runtime Interface Client smoke tests - Run 'pr' target working-directory: ./aws-lambda-java-runtime-interface-client run: make pr + env: + IS_JAVA_8: true build: runs-on: ubuntu-latest @@ -32,13 +35,13 @@ jobs: - uses: actions/checkout@v3 - name: Set up JDK 1.8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 8 distribution: corretto - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 @@ -51,9 +54,11 @@ jobs: - name: Test Runtime Interface Client xplatform build - Run 'build' target working-directory: ./aws-lambda-java-runtime-interface-client run: make build + env: + IS_JAVA_8: true - name: Save the built jar - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: aws-lambda-java-runtime-interface-client path: ./aws-lambda-java-runtime-interface-client/target/aws-lambda-java-runtime-interface-client-*.jar diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index 2171ae78..cd665549 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -12,6 +12,7 @@ on: branches: [ '*' ] paths: - 'samples/kinesis-firehose-event-handler/**' + - '.github/workflows/samples.yml' jobs: build: @@ -21,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up JDK 1.8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 8 distribution: corretto diff --git a/.gitignore b/.gitignore index 2f2f0af4..371bed6b 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,7 @@ dependency-reduced-pom.xml .project # OSX -.DS_Store \ No newline at end of file +.DS_Store + +# snapshot process +aws-lambda-java-runtime-interface-client/pom.xml.versionsBackup diff --git a/README.md b/README.md index 445e35f9..94f842fb 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ The purpose of this package is to allow developers to deploy their applications com.amazonaws aws-lambda-java-runtime-interface-client - 2.5.0 + 2.5.1 ``` diff --git a/aws-lambda-java-runtime-interface-client/Makefile b/aws-lambda-java-runtime-interface-client/Makefile index e57daf3a..b3a20421 100644 --- a/aws-lambda-java-runtime-interface-client/Makefile +++ b/aws-lambda-java-runtime-interface-client/Makefile @@ -4,6 +4,13 @@ ARCHITECTURE := $(shell arch) ARCHITECTURE_ALIAS := $($(shell echo "$(ARCHITECTURE)_ALIAS")) ARCHITECTURE_ALIAS := $(or $(ARCHITECTURE_ALIAS),amd64) # on any other archs defaulting to amd64 +# Java 8 does not support passing some args (such add --add-opens) so we need to clear them +ifeq ($(IS_JAVA_8),true) + EXTRA_LOAD_ARG := -DargLineForReflectionTestOnly="" +else + EXTRA_LOAD_ARG := +endif + # This optional module exports MAVEN_REPO_URL, MAVEN_REPO_USERNAME and MAVEN_REPO_PASSWORD environment variables # making it possible to publish resulting artifacts to a codeartifact maven repository -include ric-dev-environment/codeartifact-repo.mk @@ -15,7 +22,7 @@ target: .PHONY: test test: - mvn test + mvn test $(EXTRA_LOAD_ARG) .PHONY: setup-codebuild-agent setup-codebuild-agent: @@ -44,11 +51,11 @@ pr: test test-smoke .PHONY: build build: - mvn clean install - mvn install -P linux-x86_64 - mvn install -P linux_musl-x86_64 - mvn install -P linux-aarch64 - mvn install -P linux_musl-aarch64 + mvn clean install $(EXTRA_LOAD_ARG) + mvn install -P linux-x86_64 $(EXTRA_LOAD_ARG) + mvn install -P linux_musl-x86_64 $(EXTRA_LOAD_ARG) + mvn install -P linux-aarch64 $(EXTRA_LOAD_ARG) + mvn install -P linux_musl-aarch64 $(EXTRA_LOAD_ARG) .PHONY: publish publish: diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md index 13f8658f..ae299c77 100644 --- a/aws-lambda-java-runtime-interface-client/README.md +++ b/aws-lambda-java-runtime-interface-client/README.md @@ -70,7 +70,7 @@ pom.xml com.amazonaws aws-lambda-java-runtime-interface-client - 2.5.0 + 2.5.1 @@ -160,7 +160,7 @@ platform-specific JAR by setting the ``. com.amazonaws aws-lambda-java-runtime-interface-client - 2.5.0 + 2.5.1 linux-x86_64 ``` diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md index 1fefd0e2..bb5794c2 100644 --- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md @@ -1,3 +1,8 @@ +### June 28, 2024 +`2.5.1` +- Runtime API client improvements: fix a DNS cache issue +- Runtime API client improvements: fix circular exception references causing stackOverflow + ### March 20, 2024 `2.5.0` - Runtime API client improvements ([#471](https://github.com/aws/aws-lambda-java-libs/pull/471)) diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 29d20c8d..a4f022a4 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.5.0 + 2.5.1 jar AWS Lambda Java Runtime Interface Client @@ -35,6 +35,8 @@ UTF-8 UTF-8 0.8.8 + 2.4 + 3.1.1 5.9.2 + --add-opens java.base/java.net=ALL-UNNAMED @@ -90,9 +97,22 @@ + + maven-install-plugin + org.apache.maven.plugins + ${maven-install-plugin.version} + + + maven-deploy-plugin + org.apache.maven.plugins + ${maven-deploy-plugin.version} + maven-surefire-plugin 3.0.0-M9 + + ${argLineForReflectionTestOnly} + org.junit.jupiter @@ -110,6 +130,24 @@ maven-antrun-plugin 1.7 + + build-jni-lib-for-tests + generate-test-sources + + run + + + + + + + + + + + + build-jni-lib prepare-package diff --git a/aws-lambda-java-runtime-interface-client/ric-dev-environment/publish_snapshot.sh b/aws-lambda-java-runtime-interface-client/ric-dev-environment/publish_snapshot.sh index cf5969e1..9d2f9837 100755 --- a/aws-lambda-java-runtime-interface-client/ric-dev-environment/publish_snapshot.sh +++ b/aws-lambda-java-runtime-interface-client/ric-dev-environment/publish_snapshot.sh @@ -18,6 +18,10 @@ else echo "Already -SNAPSHOT version" fi +# get the updated project version +snapshotProjectVersion=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) +echo "Updated project version is ${snapshotProjectVersion}" + CLASSIFIERS_ARRAY=("linux-x86_64" "linux_musl-x86_64" "linux-aarch_64" "linux_musl-aarch_64") for str in "${CLASSIFIERS_ARRAY[@]}"; do @@ -36,7 +40,7 @@ mvn -B -X -P ci-repo \ -DgroupId=com.amazonaws \ -DartifactId=aws-lambda-java-runtime-interface-client \ -Dpackaging=jar \ - -Dversion=$projectVersion \ + -Dversion=$snapshotProjectVersion \ -Dfile=./target/aws-lambda-java-runtime-interface-client-$projectVersion.jar \ -Dfiles=$FILES \ -Dclassifiers=$CLASSIFIERS \ diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/ContextImpl.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/ContextImpl.java index ac4bc648..87fb0ff2 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/ContextImpl.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/ContextImpl.java @@ -23,25 +23,9 @@ public class ContextImpl extends Context { @Override public synchronized void beforeCheckpoint(Context context) throws CheckpointException { - - List exceptionsThrown = new ArrayList<>(); - for (Resource resource : getCheckpointQueueReverseOrderOfRegistration()) { - try { - resource.beforeCheckpoint(this); - } catch (CheckpointException e) { - Collections.addAll(exceptionsThrown, e.getSuppressed()); - } catch (Exception e) { - exceptionsThrown.add(e); - } - } - - if (!exceptionsThrown.isEmpty()) { - CheckpointException checkpointException = new CheckpointException(); - for (Throwable t : exceptionsThrown) { - checkpointException.addSuppressed(t); - } - throw checkpointException; - } + executeBeforeCheckpointHooks(); + DNSManager.clearCache(); + System.gc(); } @Override @@ -87,4 +71,25 @@ private List getCheckpointQueueForwardOrderOfRegistration() { .map(Map.Entry::getKey) .collect(Collectors.toList()); } + + private void executeBeforeCheckpointHooks() throws CheckpointException { + List exceptionsThrown = new ArrayList<>(); + for (Resource resource : getCheckpointQueueReverseOrderOfRegistration()) { + try { + resource.beforeCheckpoint(this); + } catch (CheckpointException e) { + Collections.addAll(exceptionsThrown, e.getSuppressed()); + } catch (Exception e) { + exceptionsThrown.add(e); + } + } + + if (!exceptionsThrown.isEmpty()) { + CheckpointException checkpointException = new CheckpointException(); + for (Throwable t : exceptionsThrown) { + checkpointException.addSuppressed(t); + } + throw checkpointException; + } + } } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/DNSManager.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/DNSManager.java new file mode 100644 index 00000000..4c1a6cd5 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/DNSManager.java @@ -0,0 +1,10 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package com.amazonaws.services.lambda.crac; + +class DNSManager { + static native void clearCache(); +} \ No newline at end of file diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/UserFault.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/UserFault.java index bc95af57..fc8eb005 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/UserFault.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/UserFault.java @@ -4,6 +4,8 @@ import java.io.PrintWriter; import java.io.StringWriter; +import java.util.HashSet; +import java.util.Set; public final class UserFault extends RuntimeException { private static final long serialVersionUID = -479308856905162038L; @@ -65,6 +67,16 @@ public static String trace(Throwable t) { * the same object for convenience. */ public static T filterStackTrace(T t) { + return filterStackTrace(t, new HashSet<>(), new HashSet<>()); + } + + private static T filterStackTrace(T t, Set visited, Set visitedSuppressed) { + if (visited.contains(t)) { + return t; + } + + visited.add(t); + StackTraceElement[] trace = t.getStackTrace(); for (int i = 0; i < trace.length; i++) { if (trace[i].getClassName().startsWith(packagePrefix)) { @@ -78,12 +90,15 @@ public static T filterStackTrace(T t) { Throwable cause = t.getCause(); if (cause != null) { - filterStackTrace(cause); + filterStackTrace(cause, visited, visitedSuppressed); } Throwable[] suppressedExceptions = t.getSuppressed(); - for (Throwable suppressed : suppressedExceptions) { - filterStackTrace(suppressed); + for(Throwable suppressed: suppressedExceptions) { + if (!visitedSuppressed.contains(suppressed)) { + visitedSuppressed.add(suppressed); + filterStackTrace(suppressed, visited, visitedSuppressed); + } } return t; diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/JniHelper.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/JniHelper.java new file mode 100644 index 00000000..82586d27 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/JniHelper.java @@ -0,0 +1,64 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ +package com.amazonaws.services.lambda.runtime.api.client.runtimeapi; + +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.List; + +public class JniHelper { + + private static final String NATIVE_LIB_PATH = "/tmp/.libaws-lambda-jni.so"; + private static final String NATIVE_CLIENT_JNI_PROPERTY = "com.amazonaws.services.lambda.runtime.api.client.runtimeapi.NativeClient.JNI"; + + /** + * Unpacks JNI library from the JAR to a temporary location and tries to load it using System.load() + * Implementation based on AWS CRT + * (ref. ...) + * + * @param libsToTry - array of native libraries to try + */ + public static void load() { + String jniLib = System.getProperty(NATIVE_CLIENT_JNI_PROPERTY); + if (jniLib != null) { + System.load(jniLib); + } else { + String[] libsToTry = new String[]{ + "libaws-lambda-jni.linux-x86_64.so", + "libaws-lambda-jni.linux-aarch_64.so", + "libaws-lambda-jni.linux_musl-x86_64.so", + "libaws-lambda-jni.linux_musl-aarch_64.so" + }; + unpackAndLoad(libsToTry, NativeClient.class); + } + } + + private static void unpackAndLoad(String[] libsToTry, Class clazz) { + List errorMessages = new ArrayList<>(); + for (String libToTry : libsToTry) { + try (InputStream inputStream = clazz.getResourceAsStream( + Paths.get("/jni", libToTry).toString())) { + if (inputStream == null) { + throw new FileNotFoundException("Specified file not in the JAR: " + libToTry); + } + Files.copy(inputStream, Paths.get(NATIVE_LIB_PATH), StandardCopyOption.REPLACE_EXISTING); + System.load(NATIVE_LIB_PATH); + return; + } catch (UnsatisfiedLinkError | Exception e) { + errorMessages.add(e.getMessage()); + } + } + + for (int i = 0; i < libsToTry.length; ++i) { + System.err.println("Failed to load the native runtime interface client library " + libsToTry[i] + + ". Exception: " + errorMessages.get(i)); + } + System.exit(-1); + } +} diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/NativeClient.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/NativeClient.java index a311ff00..10e6bafd 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/NativeClient.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/NativeClient.java @@ -5,14 +5,7 @@ package com.amazonaws.services.lambda.runtime.api.client.runtimeapi; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest; - -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; -import java.util.ArrayList; -import java.util.List; +import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.JniHelper; import static com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeApiClientImpl.USER_AGENT; @@ -21,60 +14,11 @@ * interactions with the Runtime API. */ class NativeClient { - private static final String NATIVE_LIB_PATH = "/tmp/.libaws-lambda-jni.so"; - public static final String NATIVE_CLIENT_JNI_PROPERTY = "com.amazonaws.services.lambda.runtime.api.client.runtimeapi.NativeClient.JNI"; static void init() { - loadJNILib(); + JniHelper.load(); initializeClient(USER_AGENT.getBytes()); } - - private static void loadJNILib() { - String jniLib = System.getProperty(NATIVE_CLIENT_JNI_PROPERTY); - if (jniLib != null) { - System.load(jniLib); - } else { - String[] libsToTry = new String[]{ - "libaws-lambda-jni.linux-x86_64.so", - "libaws-lambda-jni.linux-aarch_64.so", - "libaws-lambda-jni.linux_musl-x86_64.so", - "libaws-lambda-jni.linux_musl-aarch_64.so" - }; - unpackAndLoadNativeLibrary(libsToTry); - } - } - - - /** - * Unpacks JNI library from the JAR to a temporary location and tries to load it using System.load() - * Implementation based on AWS CRT - * (ref. ...) - * - * @param libsToTry - array of native libraries to try - */ - static void unpackAndLoadNativeLibrary(String[] libsToTry) { - - List errorMessages = new ArrayList<>(); - for (String libToTry : libsToTry) { - try (InputStream inputStream = NativeClient.class.getResourceAsStream( - Paths.get("/jni", libToTry).toString())) { - if (inputStream == null) { - throw new FileNotFoundException("Specified file not in the JAR: " + libToTry); - } - Files.copy(inputStream, Paths.get(NATIVE_LIB_PATH), StandardCopyOption.REPLACE_EXISTING); - System.load(NATIVE_LIB_PATH); - return; - } catch (UnsatisfiedLinkError | Exception e) { - errorMessages.add(e.getMessage()); - } - } - - for (int i = 0; i < libsToTry.length; ++i) { - System.err.println("Failed to load the native runtime interface client library " + libsToTry[i] + - ". Exception: " + errorMessages.get(i)); - } - System.exit(-1); - } - + static native void initializeClient(byte[] userAgent); static native InvocationRequest next(); diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.glibc b/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.glibc index dd4fdb22..1cfcfbb1 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.glibc +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.glibc @@ -53,9 +53,16 @@ RUN /usr/bin/c++ -c \ -I${JAVA_HOME}/include/linux \ -I ./deps/artifacts/include \ com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp -o com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.o && \ + /usr/bin/c++ -c \ + -std=gnu++11 \ + -fPIC \ + -I${JAVA_HOME}/include \ + -I${JAVA_HOME}/include/linux \ + -I ./deps/artifacts/include \ + com_amazonaws_services_lambda_crac_DNSManager.cpp -o com_amazonaws_services_lambda_crac_DNSManager.o && \ /usr/bin/c++ -shared \ -std=gnu++11 \ - -o aws-lambda-runtime-interface-client.so com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.o \ + -o aws-lambda-runtime-interface-client.so com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.o com_amazonaws_services_lambda_crac_DNSManager.o \ -L ./deps/artifacts/lib64/ \ -L ./deps/artifacts/lib/ \ -laws-lambda-runtime \ diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.musl b/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.musl index e15f6adc..64725c14 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.musl +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.musl @@ -54,9 +54,16 @@ RUN /usr/bin/c++ -c \ -I${JAVA_HOME}/include/linux \ -I ./deps/artifacts/include \ com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp -o com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.o && \ + /usr/bin/c++ -c \ + -std=gnu++11 \ + -fPIC \ + -I${JAVA_HOME}/include \ + -I${JAVA_HOME}/include/linux \ + -I ./deps/artifacts/include \ + com_amazonaws_services_lambda_crac_DNSManager.cpp -o com_amazonaws_services_lambda_crac_DNSManager.o && \ /usr/bin/c++ -shared \ -std=gnu++11 \ - -o aws-lambda-runtime-interface-client.so com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.o \ + -o aws-lambda-runtime-interface-client.so com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.o com_amazonaws_services_lambda_crac_DNSManager.o \ -L ./deps/artifacts/lib/ \ -laws-lambda-runtime \ -lcurl \ diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh b/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh index 3b505e74..b7dbb5a8 100755 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh @@ -51,10 +51,21 @@ function build_for_libc_arch() { echo "multi-arch not requested, assuming this is a workaround to goofyness when docker buildx is enabled on Linux CI environments." echo "enabling docker buildx often updates the docker api version, so assuming that docker cli is also too old to use --output type=tar, so doing alternative build-tag-run approach" image_name="lambda-java-jni-lib-${libc_impl}-${arch}" + + # GitHub actions is using dockerx build under the hood. We need to pass --load option to be able to run the image + # This args is NOT part of the classic docker build command, so we need to check against a GitHub Action env var not to make local build crash. + if [[ "${GITHUB_RUN_ID:+isset}" == "isset" ]]; then + EXTRA_LOAD_ARG="--load" + else + EXTRA_LOAD_ARG="" + fi + docker build --platform="${docker_platform}" \ -t "${image_name}" \ -f "${SRC_DIR}/Dockerfile.${libc_impl}" \ - --build-arg CURL_VERSION=${CURL_VERSION} "${SRC_DIR}" + --build-arg CURL_VERSION=${CURL_VERSION} "${SRC_DIR}" ${EXTRA_LOAD_ARG} + + echo "Docker image has been successfully built" docker run --rm --entrypoint /bin/cat "${image_name}" \ /src/aws-lambda-runtime-interface-client.so > "${artifact}" diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_crac_DNSManager.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_crac_DNSManager.cpp new file mode 100644 index 00000000..ccf5481b --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_crac_DNSManager.cpp @@ -0,0 +1,27 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ +#include +#include "macro.h" +#include "com_amazonaws_services_lambda_crac_DNSManager.h" + +JNIEXPORT void JNICALL Java_com_amazonaws_services_lambda_crac_DNSManager_clearCache + (JNIEnv *env, jclass thisClass) { + jclass iNetAddressClass; + jclass concurrentMap; + jfieldID cacheFieldID; + jobject cacheObj; + jmethodID clearMethodID; + CHECK_EXCEPTION(env, iNetAddressClass = env->FindClass("java/net/InetAddress")); + CHECK_EXCEPTION(env, concurrentMap = env->FindClass("java/util/concurrent/ConcurrentMap")); + CHECK_EXCEPTION(env, cacheFieldID = env->GetStaticFieldID(iNetAddressClass, "cache", "Ljava/util/concurrent/ConcurrentMap;")); + CHECK_EXCEPTION(env, cacheObj = (jobject) env->GetStaticObjectField(iNetAddressClass, cacheFieldID)); + CHECK_EXCEPTION(env, clearMethodID = env->GetMethodID(concurrentMap, "clear", "()V")); + CHECK_EXCEPTION(env, env->CallVoidMethod(cacheObj, clearMethodID)); + return; + + ERROR: + // we need to fail silently here + env->ExceptionClear(); +} diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_crac_DNSManager.h b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_crac_DNSManager.h new file mode 100644 index 00000000..f26639ba --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_crac_DNSManager.h @@ -0,0 +1,19 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ +#include + +#ifndef _Included_com_amazonaws_services_lambda_crac_DNSManager +#define _Included_com_amazonaws_services_lambda_crac_DNSManager +#ifdef __cplusplus +extern "C" { +#endif + +JNIEXPORT void JNICALL Java_com_amazonaws_services_lambda_crac_DNSManager_clearCache + (JNIEnv *, jclass); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp index 02cfeb7e..db71b819 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp @@ -1,27 +1,13 @@ /* - * Copyright 2019-present Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ #include +#include "macro.h" #include "com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.h" #include "aws/lambda-runtime/runtime.h" #include "aws/lambda-runtime/version.h" -#define CHECK_EXCEPTION(env, expr) \ - expr; \ - if ((env)->ExceptionOccurred()) \ - goto ERROR; - static aws::lambda_runtime::runtime * CLIENT = nullptr; static jint JNI_VERSION = JNI_VERSION_1_8; diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.h b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.h index 28a6f444..27c63611 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.h +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.h @@ -1,3 +1,7 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ #include #ifndef _Included_com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/macro.h b/aws-lambda-java-runtime-interface-client/src/main/jni/macro.h new file mode 100644 index 00000000..df5759af --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/macro.h @@ -0,0 +1,14 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +#ifndef _Included_macros +#define _Included_macros + +#define CHECK_EXCEPTION(env, expr) \ + expr; \ + if ((env)->ExceptionOccurred()) \ + goto ERROR; + +#endif diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/crac/ContextImplTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/crac/ContextImplTest.java index b8166073..bad72ea2 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/crac/ContextImplTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/crac/ContextImplTest.java @@ -1,22 +1,35 @@ /* - * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. - */ - +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ package com.amazonaws.services.lambda.crac; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; import org.mockito.ArgumentMatchers; import org.mockito.InOrder; import org.mockito.Mockito; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.Mockito.doThrow; +import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.JniHelper; + +@DisabledOnOs(OS.MAC) public class ContextImplTest { private Resource throwsWithSuppressedException, noop, noop2, throwsException, throwCustomException; + @BeforeAll + public static void jniLoad() { + JniHelper.load(); + } + @BeforeEach public void setup() throws Exception { diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/crac/DNSCacheManagerTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/crac/DNSCacheManagerTest.java new file mode 100644 index 00000000..5eb6f749 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/crac/DNSCacheManagerTest.java @@ -0,0 +1,124 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package com.amazonaws.services.lambda.crac; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; + +import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.JniHelper; + +import java.util.Map; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.lang.reflect.Field; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +@DisabledOnOs(OS.MAC) +public class DNSCacheManagerTest { + + static String CACHE_FIELD_NAME = "cache"; + + // this should have no effect, as the DNS cache is cleared explicitly in these tests + static { + java.security.Security.setProperty("networkaddress.cache.ttl" , "10000"); + java.security.Security.setProperty("networkaddress.cache.negative.ttl" , "10000"); + } + + @BeforeAll + public static void jniLoad() { + JniHelper.load(); + } + + @BeforeEach + public void setup() { + Core.resetGlobalContext(); + DNSManager.clearCache(); + } + + static class StatefulResource implements Resource { + + int state = 0; + + @Override + public void afterRestore(Context context) { + state += 1; + } + + @Override + public void beforeCheckpoint(Context context) { + state += 2; + } + + int getValue() { + return state; + } + } + + @Test + public void positiveDnsCacheShouldBeEmpty() throws CheckpointException, RestoreException, UnknownHostException, ReflectiveOperationException { + int baselineDNSEntryCount = getDNSEntryCount(); + + StatefulResource resource = new StatefulResource(); + Core.getGlobalContext().register(resource); + + String[] hosts = {"www.stackoverflow.com", "www.amazon.com", "www.yahoo.com"}; + for(String singleHost : hosts) { + InetAddress address = InetAddress.getByName(singleHost); + } + // n hosts -> n DNS entries + assertEquals(hosts.length, getDNSEntryCount() - baselineDNSEntryCount); + + // this should call the native static method clearDNSCache + Core.getGlobalContext().beforeCheckpoint(null); + + // cache should be cleared + assertEquals(0, getDNSEntryCount()); + } + + @Test + public void negativeDnsCacheShouldBeEmpty() throws CheckpointException, RestoreException, UnknownHostException, ReflectiveOperationException { + int baselineDNSEntryCount = getDNSEntryCount(); + + StatefulResource resource = new StatefulResource(); + Core.getGlobalContext().register(resource); + + String invalidHost = "not.a.valid.host"; + try { + InetAddress address = InetAddress.getByName(invalidHost); + fail(); + } catch(UnknownHostException uhe) { + // this is actually fine + } + + // 1 host -> 1 DNS entry + assertEquals(1, getDNSEntryCount() - baselineDNSEntryCount); + + // this should the native static method clearDNSCache + Core.getGlobalContext().beforeCheckpoint(null); + + // cache should be cleared + assertEquals(0, getDNSEntryCount()); + } + + // helper functions to access the cache via reflection (see maven-surefire-plugin command args) + protected static Map getDNSCache() throws ReflectiveOperationException { + Class klass = InetAddress.class; + Field acf = klass.getDeclaredField(CACHE_FIELD_NAME); + acf.setAccessible(true); + Object addressCache = acf.get(null); + return (Map) acf.get(addressCache); + } + + protected static int getDNSEntryCount() throws ReflectiveOperationException { + Map cache = getDNSCache(); + return cache.size(); + } +} diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/UserFaultTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/UserFaultTest.java index 9e88024b..5a57e6e0 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/UserFaultTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/UserFaultTest.java @@ -84,4 +84,44 @@ public void testSuppressedExceptionsAreIncluded() { assertTrue(reportableUserFault.contains("Suppressed: java.lang.RuntimeException: error 2"), "Suppressed error 2 missing in reported UserFault"); } } + + @Test + public void testCircularExceptionReference() { + RuntimeException e1 = new RuntimeException(); + RuntimeException e2 = new RuntimeException(e1); + e1.initCause(e2); + + try { + throw e2; + } catch (Exception e) { + String stackTrace = UserFault.trace(e); + String expectedStackTrace = "java.lang.RuntimeException: java.lang.RuntimeException\n" + + "Caused by: java.lang.RuntimeException\n" + + "Caused by: [CIRCULAR REFERENCE: java.lang.RuntimeException: java.lang.RuntimeException]\n"; + + assertEquals(expectedStackTrace, stackTrace); + } + } + + @Test + public void testCircularSuppressedExceptionReference() { + RuntimeException e1 = new RuntimeException("Primary Exception"); + RuntimeException e2 = new RuntimeException(e1); + RuntimeException e3 = new RuntimeException("Exception with suppressed"); + + e1.addSuppressed(e2); + e3.addSuppressed(e2); + + try { + throw e3; + } catch (Exception e) { + String stackTrace = UserFault.trace(e); + String expectedStackTrace = "java.lang.RuntimeException: Exception with suppressed\n" + + "\tSuppressed: java.lang.RuntimeException: java.lang.RuntimeException: Primary Exception\n" + + "\tCaused by: java.lang.RuntimeException: Primary Exception\n" + + "\t\tSuppressed: [CIRCULAR REFERENCE: java.lang.RuntimeException: java.lang.RuntimeException: Primary Exception]\n"; + + assertEquals(expectedStackTrace, stackTrace); + } + } } diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.alpine.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.alpine.yml index 4f8caf39..cdc27a65 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.alpine.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.alpine.yml @@ -44,7 +44,7 @@ phases: - (cd aws-lambda-java-events && mvn install) # Install serialization (dependency of RIC) - (cd aws-lambda-java-serialization && mvn install) - - (cd aws-lambda-java-runtime-interface-client && mvn install) + - (cd aws-lambda-java-runtime-interface-client && mvn install -DargLineForReflectionTestOnly="") - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) - export IMAGE_TAG="java-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" - echo "Extracting and including Runtime Interface Emulator" diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazoncorretto.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazoncorretto.yml index fd45aabb..67dd7617 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazoncorretto.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazoncorretto.yml @@ -43,7 +43,7 @@ phases: - (cd aws-lambda-java-events && mvn install) # Install serialization (dependency of RIC) - (cd aws-lambda-java-serialization && mvn install) - - (cd aws-lambda-java-runtime-interface-client && mvn install) + - (cd aws-lambda-java-runtime-interface-client && mvn install -DargLineForReflectionTestOnly="") - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) - export IMAGE_TAG="java-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" - echo "Extracting and including Runtime Interface Emulator" diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.1.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.1.yml index 197e9724..04c486a8 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.1.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.1.yml @@ -38,7 +38,7 @@ phases: - (cd aws-lambda-java-events && mvn install) # Install serialization (dependency of RIC) - (cd aws-lambda-java-serialization && mvn install) - - (cd aws-lambda-java-runtime-interface-client && mvn install -DmultiArch=false) + - (cd aws-lambda-java-runtime-interface-client && mvn install -DmultiArch=false -DargLineForReflectionTestOnly="") - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) - export IMAGE_TAG="java-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" - echo "Extracting and including Runtime Interface Emulator" diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.2.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.2.yml index 75b816e8..8222bb41 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.2.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.2.yml @@ -42,7 +42,7 @@ phases: - (cd aws-lambda-java-events && mvn install) # Install serialization (dependency of RIC) - (cd aws-lambda-java-serialization && mvn install) - - (cd aws-lambda-java-runtime-interface-client && mvn install) + - (cd aws-lambda-java-runtime-interface-client && mvn install -DargLineForReflectionTestOnly="") - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) - export IMAGE_TAG="java-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" - echo "Extracting and including Runtime Interface Emulator" diff --git a/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml b/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml index dd69c8e0..40c79fe9 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml +++ b/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml @@ -15,7 +15,7 @@ com.amazonaws aws-lambda-java-runtime-interface-client - 2.5.0 + 2.5.1 From 6bf9473b94f9d4268535365bac2c4e6b6e17250f Mon Sep 17 00:00:00 2001 From: Shashank <48707265+ShashankAWS@users.noreply.github.com> Date: Wed, 10 Jul 2024 19:04:13 +0530 Subject: [PATCH 3/4] Added event class MskFirehoseEvent.java for Firehose Lambda transformation when MSK is the source (#490) * Create MskFirehoseEvent.java * Create MSKFirehoseResponse.java --- aws-lambda-java-events/README.md | 2 + .../runtime/events/MSKFirehoseEvent.java | 51 ++++++++++++++++ .../runtime/events/MSKFirehoseResponse.java | 61 +++++++++++++++++++ .../lambda/runtime/tests/EventLoader.java | 4 ++ .../lambda/runtime/tests/EventLoaderTest.java | 13 ++++ .../test/resources/msk_firehose_event.json | 18 ++++++ .../java/example/MSKFirehoseEventHandler.java | 39 ++++++++++++ .../example/MSKFirehoseEventHandlerTest.java | 32 ++++++++++ .../src/test/resources/event.json | 18 ++++++ 9 files changed, 238 insertions(+) create mode 100644 aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/MSKFirehoseEvent.java create mode 100644 aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/MSKFirehoseResponse.java create mode 100644 aws-lambda-java-tests/src/test/resources/msk_firehose_event.json create mode 100644 samples/msk-firehose-event-handler/src/main/java/example/MSKFirehoseEventHandler.java create mode 100644 samples/msk-firehose-event-handler/src/test/java/example/MSKFirehoseEventHandlerTest.java create mode 100644 samples/msk-firehose-event-handler/src/test/resources/event.json diff --git a/aws-lambda-java-events/README.md b/aws-lambda-java-events/README.md index c0d90d85..92bea0ea 100644 --- a/aws-lambda-java-events/README.md +++ b/aws-lambda-java-events/README.md @@ -46,6 +46,8 @@ * `KinesisFirehoseEvent` * `LambdaDestinationEvent` * `LexEvent` +* `MSKFirehoseEvent` +* `MSKFirehoseResponse` * `RabbitMQEvent` * `S3BatchEvent` * `S3BatchResponse` diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/MSKFirehoseEvent.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/MSKFirehoseEvent.java new file mode 100644 index 00000000..1af40ce4 --- /dev/null +++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/MSKFirehoseEvent.java @@ -0,0 +1,51 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package com.amazonaws.services.lambda.runtime.events; + +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder(setterPrefix = "with") +@NoArgsConstructor +@AllArgsConstructor + +public class MSKFirehoseEvent { + + private String invocationId; + + private String deliveryStreamArn; + + private String sourceMSKArn; + + private String region; + + private List records; + + @Data + @Builder(setterPrefix = "with") + @NoArgsConstructor + @AllArgsConstructor + public static class Record { + + private ByteBuffer kafkaRecordValue; + + private String recordId; + + private Long approximateArrivalEpoch; + + private Long approximateArrivalTimestamp; + + private Map mskRecordMetadata; + + } +} diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/MSKFirehoseResponse.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/MSKFirehoseResponse.java new file mode 100644 index 00000000..18b5aa13 --- /dev/null +++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/MSKFirehoseResponse.java @@ -0,0 +1,61 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package com.amazonaws.services.lambda.runtime.events; + +import java.nio.ByteBuffer; +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Response model for Amazon Data Firehose Lambda transformation with MSK as a source. + * [+] Amazon Data Firehose Data Transformation - Data Transformation and Status Model - ... + * OK : Indicates that processing of this item succeeded. + * ProcessingFailed : Indicate that the processing of this item failed. + * Dropped : Indicates that this item should be silently dropped + */ + +@Data +@Builder(setterPrefix = "with") +@NoArgsConstructor +@AllArgsConstructor + +public class MSKFirehoseResponse { + + public enum Result { + + /** + * Indicates that processing of this item succeeded. + */ + Ok, + + /** + * Indicate that the processing of this item failed + */ + ProcessingFailed, + + /** + * Indicates that this item should be silently dropped + */ + Dropped + } + public List records; + + @Data + @NoArgsConstructor + @Builder(setterPrefix = "with") + @AllArgsConstructor + + public static class Record { + public String recordId; + public Result result; + public ByteBuffer kafkaRecordValue; + + } +} diff --git a/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/EventLoader.java b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/EventLoader.java index 86886ea2..77812267 100644 --- a/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/EventLoader.java +++ b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/EventLoader.java @@ -97,6 +97,10 @@ public static LexEvent loadLexEvent(String filename) { return loadEvent(filename, LexEvent.class); } + public static MSKFirehoseEvent loadMSKFirehoseEvent(String filename) { + return loadEvent(filename, MSKFirehoseEvent.class); + } + public static S3Event loadS3Event(String filename) { return loadEvent(filename, S3Event.class); } diff --git a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java index 70da98a5..bcdf4ec0 100644 --- a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java +++ b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java @@ -147,6 +147,19 @@ public void testLoadKinesisFirehoseEvent() { assertThat(event.getRecords().get(0).getData().array()).asString().isEqualTo("Hello, this is a test 123."); } + @Test + public void testLoadMSKFirehoseEvent() { + MSKFirehoseEvent event = EventLoader.loadMSKFirehoseEvent("msk_firehose_event.json"); + + assertThat(event).isNotNull(); + assertThat(event.getSourceMSKArn()).isEqualTo("arn:aws:kafka:EXAMPLE"); + assertThat(event.getDeliveryStreamArn()).isEqualTo("arn:aws:firehose:EXAMPLE"); + assertThat(event.getRecords()).hasSize(1); + assertThat(event.getRecords().get(0).getKafkaRecordValue().array()).asString().isEqualTo("{\"Name\":\"Hello World\"}"); + assertThat(event.getRecords().get(0).getApproximateArrivalTimestamp()).asString().isEqualTo("1716369573887"); + assertThat(event.getRecords().get(0).getMskRecordMetadata()).asString().isEqualTo("{offset=0, partitionId=1, approximateArrivalTimestamp=1716369573887}"); + } + @Test public void testLoadS3Event() { S3Event event = EventLoader.loadS3Event("s3_event.json"); diff --git a/aws-lambda-java-tests/src/test/resources/msk_firehose_event.json b/aws-lambda-java-tests/src/test/resources/msk_firehose_event.json new file mode 100644 index 00000000..6b839912 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/msk_firehose_event.json @@ -0,0 +1,18 @@ +{ + "invocationId": "12345621-4787-0000-a418-36e56Example", + "sourceMSKArn": "arn:aws:kafka:EXAMPLE", + "deliveryStreamArn": "arn:aws:firehose:EXAMPLE", + "region": "us-east-1", + "records": [ + { + "recordId": "00000000000000000000000000000000000000000000000000000000000000", + "approximateArrivalTimestamp": 1716369573887, + "mskRecordMetadata": { + "offset": "0", + "partitionId": "1", + "approximateArrivalTimestamp": 1716369573887 + }, + "kafkaRecordValue": "eyJOYW1lIjoiSGVsbG8gV29ybGQifQ==" + } + ] +} diff --git a/samples/msk-firehose-event-handler/src/main/java/example/MSKFirehoseEventHandler.java b/samples/msk-firehose-event-handler/src/main/java/example/MSKFirehoseEventHandler.java new file mode 100644 index 00000000..f5e51349 --- /dev/null +++ b/samples/msk-firehose-event-handler/src/main/java/example/MSKFirehoseEventHandler.java @@ -0,0 +1,39 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package example; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.MSKFirehoseResponse; +import com.amazonaws.services.lambda.runtime.events.MSKFirehoseEvent; +import org.json.JSONObject; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +/** + * A sample MSKFirehoseEvent handler + * For more information see the developer guide - ... + */ +public class MSKFirehoseEventHandler implements RequestHandler { + + @Override + public MSKFirehoseResponse handleRequest(MSKFirehoseEvent MSKFirehoseEvent, Context context) { + List records = new ArrayList<>(); + + for (MSKFirehoseEvent.Record record : MSKFirehoseEvent.getRecords()) { + String recordData = new String(record.getKafkaRecordValue().array()); + // Your business logic + JSONObject jsonObject = new JSONObject(recordData); + records.add(new MSKFirehoseResponse.Record(record.getRecordId(), MSKFirehoseResponse.Result.Ok, encode(jsonObject.toString()))); + } + return new MSKFirehoseResponse(records); + } + private ByteBuffer encode(String content) { + return ByteBuffer.wrap(content.getBytes()); + } +} diff --git a/samples/msk-firehose-event-handler/src/test/java/example/MSKFirehoseEventHandlerTest.java b/samples/msk-firehose-event-handler/src/test/java/example/MSKFirehoseEventHandlerTest.java new file mode 100644 index 00000000..77223e51 --- /dev/null +++ b/samples/msk-firehose-event-handler/src/test/java/example/MSKFirehoseEventHandlerTest.java @@ -0,0 +1,32 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package example; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.tests.annotations.Event; +import com.amazonaws.services.lambda.runtime.events.MSKFirehoseEvent; +import com.amazonaws.services.lambda.runtime.events.MSKFirehoseResponse; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; + +import static java.nio.charset.StandardCharsets.UTF_8; + +public class MSKFirehoseEventHandlerTest { + + private Context context; // intentionally null as it's not used in the test + + @ParameterizedTest + @Event(value = "event.json", type = MSKFirehoseEvent.class) + public void testEventHandler(MSKFirehoseEvent event) { + MSKFirehoseEventHandler Sample = new MSKFirehoseEventHandler(); + MSKFirehoseResponse response = Sample.handleRequest(event, context); + + String expectedString = "{\"Name\":\"Hello World\"}"; + MSKFirehoseResponse.Record firstRecord = response.getRecords().get(0); + Assertions.assertEquals(expectedString, UTF_8.decode(firstRecord.getKafkaRecordValue()).toString()); + Assertions.assertEquals(MSKFirehoseResponse.Result.Ok, firstRecord.getResult()); + } +} diff --git a/samples/msk-firehose-event-handler/src/test/resources/event.json b/samples/msk-firehose-event-handler/src/test/resources/event.json new file mode 100644 index 00000000..91c4b420 --- /dev/null +++ b/samples/msk-firehose-event-handler/src/test/resources/event.json @@ -0,0 +1,18 @@ +{ + "invocationId": "12345621-4787-0000-a418-36e56Example", + "sourceMSKArn": "", + "deliveryStreamArn": "", + "region": "us-east-1", + "records": [ + { + "recordId": "00000000000000000000000000000000000000000000000000000000000000", + "approximateArrivalTimestamp": 1716369573887, + "mskRecordMetadata": { + "offset": "0", + "partitionId": "1", + "approximateArrivalTimestamp": 1716369573887 + }, + "kafkaRecordValue": "eyJOYW1lIjoiSGVsbG8gV29ybGQifQ==" + } + ] +} From f8a4ae1924bc8ce0183d4c51b781e2921d1f1047 Mon Sep 17 00:00:00 2001 From: Mark Sailes Date: Thu, 11 Jul 2024 11:08:44 +0100 Subject: [PATCH 4/4] Separated the events to two different Java classes Moved the tests to the test module --- .../amazonaws/services/lambda/runtime/tests/EventLoaderTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java index bcdf4ec0..4aa920f8 100644 --- a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java +++ b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java @@ -26,6 +26,7 @@ import com.amazonaws.services.lambda.runtime.events.KinesisFirehoseEvent; import com.amazonaws.services.lambda.runtime.events.LambdaDestinationEvent; import com.amazonaws.services.lambda.runtime.events.LexEvent; +import com.amazonaws.services.lambda.runtime.events.MSKFirehoseEvent; import com.amazonaws.services.lambda.runtime.events.RabbitMQEvent; import com.amazonaws.services.lambda.runtime.events.S3Event; import com.amazonaws.services.lambda.runtime.events.SNSEvent;