From 6cef3ebfdfd1bf58a6967a867cd97895b11cdc98 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Wed, 2 Nov 2022 11:15:29 +0100 Subject: [PATCH 01/13] add e2e tests for logging --- .../lambda/powertools/e2e/Function.java | 44 ++ powertools-e2e-tests/handlers/logging/pom.xml | 103 +++++ .../lambda/powertools/e2e/Function.java | 22 + .../amazon/lambda/powertools/e2e/Input.java | 27 ++ .../logging/src/main/resources/log4j2.xml | 16 + powertools-e2e-tests/pom.xml | 173 ++++++++ .../lambda/powertools/LoggingE2ETest.java | 72 ++++ .../powertools/testutils/Infrastructure.java | 408 ++++++++++++++++++ .../powertools/testutils/InvocationLogs.java | 68 +++ .../testutils/InvocationResult.java | 42 ++ .../powertools/testutils/JavaRuntime.java | 37 ++ .../src/test/resources/logback-test.xml | 20 + 12 files changed, 1032 insertions(+) create mode 100644 powertools-e2e-tests/handlers/idempotency/src/main/java/software/amazon/lambda/powertools/e2e/Function.java create mode 100644 powertools-e2e-tests/handlers/logging/pom.xml create mode 100644 powertools-e2e-tests/handlers/logging/src/main/java/software/amazon/lambda/powertools/e2e/Function.java create mode 100644 powertools-e2e-tests/handlers/logging/src/main/java/software/amazon/lambda/powertools/e2e/Input.java create mode 100644 powertools-e2e-tests/handlers/logging/src/main/resources/log4j2.xml create mode 100644 powertools-e2e-tests/pom.xml create mode 100644 powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ETest.java create mode 100644 powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/Infrastructure.java create mode 100644 powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/InvocationLogs.java create mode 100644 powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/InvocationResult.java create mode 100644 powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/JavaRuntime.java create mode 100644 powertools-e2e-tests/src/test/resources/logback-test.xml diff --git a/powertools-e2e-tests/handlers/idempotency/src/main/java/software/amazon/lambda/powertools/e2e/Function.java b/powertools-e2e-tests/handlers/idempotency/src/main/java/software/amazon/lambda/powertools/e2e/Function.java new file mode 100644 index 000000000..3111ea3c1 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency/src/main/java/software/amazon/lambda/powertools/e2e/Function.java @@ -0,0 +1,44 @@ +package software.amazon.lambda.powertools.e2e; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.lambda.powertools.idempotency.Idempotency; +import software.amazon.lambda.powertools.idempotency.IdempotencyConfig; +import software.amazon.lambda.powertools.idempotency.Idempotent; +import software.amazon.lambda.powertools.idempotency.persistence.DynamoDBPersistenceStore; + +import java.time.Instant; +import java.time.format.DateTimeFormatter; + + +public class Function implements RequestHandler { + + public Function() { + this(DynamoDbClient + .builder() + .httpClient(UrlConnectionHttpClient.builder().build()) + .region(Region.of(System.getenv("AWS_REGION"))) + .build()); + } + + public Function(DynamoDbClient client) { + Idempotency.config().withConfig( + IdempotencyConfig.builder() + .build()) + .withPersistenceStore( + DynamoDBPersistenceStore.builder() + .withDynamoDbClient(client) + .withTableName(System.getenv("IDEMPOTENCY_TABLE")) + .build() + ).configure(); + } + + @Idempotent + public String handleRequest(String input, Context context) { + DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE; + return dtf.format(Instant.now()); + } +} \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/logging/pom.xml b/powertools-e2e-tests/handlers/logging/pom.xml new file mode 100644 index 000000000..059cc5e31 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging/pom.xml @@ -0,0 +1,103 @@ + + 4.0.0 + + software.amazon.lambda + E2ELoggingFunction + 1.12.3 + jar + A sample Hello World using powertools logging + + + 2.19.0 + UTF-8 + 1.12.3 + + + + + software.amazon.lambda + powertools-logging + ${lambda.powertools.version} + + + com.amazonaws + aws-lambda-java-core + 1.2.1 + + + com.amazonaws + aws-lambda-java-events + 3.11.0 + + + + + + + + org.codehaus.mojo + aspectj-maven-plugin + 1.14.0 + + ${maven.compiler.source} + ${maven.compiler.target} + ${maven.compiler.target} + + + software.amazon.lambda + powertools-logging + + + + + + + compile + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + false + function + + + + package + + shade + + + + + + + + + + + + io.github.edwgiz + log4j-maven-shade-plugin-extensions + 2.17.2 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.10.1 + + ${maven.compiler.source} + ${maven.compiler.target} + + + + + diff --git a/powertools-e2e-tests/handlers/logging/src/main/java/software/amazon/lambda/powertools/e2e/Function.java b/powertools-e2e-tests/handlers/logging/src/main/java/software/amazon/lambda/powertools/e2e/Function.java new file mode 100644 index 000000000..5a9a87109 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging/src/main/java/software/amazon/lambda/powertools/e2e/Function.java @@ -0,0 +1,22 @@ +package software.amazon.lambda.powertools.e2e; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.logging.LoggingUtils; + +public class Function implements RequestHandler { + + private static final Logger LOG = LogManager.getLogger(Function.class); + + @Logging + public String handleRequest(Input input, Context context) { + + LoggingUtils.appendKeys(input.getKeys()); + LOG.info(input.getMessage()); + + return "OK"; + } +} \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/logging/src/main/java/software/amazon/lambda/powertools/e2e/Input.java b/powertools-e2e-tests/handlers/logging/src/main/java/software/amazon/lambda/powertools/e2e/Input.java new file mode 100644 index 000000000..83afbbd5a --- /dev/null +++ b/powertools-e2e-tests/handlers/logging/src/main/java/software/amazon/lambda/powertools/e2e/Input.java @@ -0,0 +1,27 @@ +package software.amazon.lambda.powertools.e2e; + +import java.util.Map; + +public class Input { + private String message; + private Map keys; + + public Input() { + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Map getKeys() { + return keys; + } + + public void setKeys(Map keys) { + this.keys = keys; + } +} diff --git a/powertools-e2e-tests/handlers/logging/src/main/resources/log4j2.xml b/powertools-e2e-tests/handlers/logging/src/main/resources/log4j2.xml new file mode 100644 index 000000000..8925f70b9 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/powertools-e2e-tests/pom.xml b/powertools-e2e-tests/pom.xml new file mode 100644 index 000000000..5264f18fa --- /dev/null +++ b/powertools-e2e-tests/pom.xml @@ -0,0 +1,173 @@ + + + 4.0.0 + + powertools-parent + software.amazon.lambda + 1.12.3 + + + powertools-e2e-tests + AWS Lambda Powertools for Java End-To-End Tests + + + 11 + 11 + 10.1.138 + 2.47.0 + + + + + + ch.qos.logback + logback-classic + 1.4.4 + + + + software.amazon.awssdk + lambda + ${aws.sdk.version} + test + + + + software.amazon.awssdk + cloudwatch + ${aws.sdk.version} + test + + + + software.amazon.awssdk + xray + ${aws.sdk.version} + test + + + + software.amazon.awssdk + url-connection-client + test + + + + org.junit.jupiter + junit-jupiter-api + test + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + org.assertj + assertj-core + test + + + + com.evanlennick + retry4j + 0.15.0 + test + + + + software.amazon.awscdk + aws-cdk-lib + ${cdk.version} + test + + + + software.constructs + constructs + ${constructs.version} + test + + + software.amazon.awssdk + s3 + ${aws.sdk.version} + test + + + software.amazon.awssdk + cloudformation + ${aws.sdk.version} + test + + + software.amazon.awssdk + sts + ${aws.sdk.version} + test + + + org.yaml + snakeyaml + 1.33 + test + + + org.aspectj + aspectjrt + compile + + + software.amazon.lambda + powertools-serialization + test + + + + + + it + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.3.0 + + + add-test-source + process-resources + + add-test-source + + + + src/it/java + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + 2.22.2 + + + + integration-test + verify + + + + + + + + + + \ No newline at end of file diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ETest.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ETest.java new file mode 100644 index 000000000..5fba73d6c --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ETest.java @@ -0,0 +1,72 @@ +package software.amazon.lambda.powertools; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import software.amazon.lambda.powertools.testutils.Infrastructure; +import software.amazon.lambda.powertools.testutils.InvocationResult; + +import java.util.HashMap; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; +import static software.amazon.lambda.powertools.testutils.InvocationLogs.Level.INFO; + +public class LoggingE2ETest { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + private static Infrastructure infrastructure; + + @BeforeAll + @Timeout(value = 5, unit = TimeUnit.MINUTES) + public static void setup() { + infrastructure = Infrastructure.builder() + .testName(LoggingE2ETest.class.getSimpleName()) + .pathToFunction("logging") + .environmentVariables(new HashMap<>() {{ + put("POWERTOOLS_LOG_LEVEL", "INFO"); + put("POWERTOOLS_SERVICE_NAME", LoggingE2ETest.class.getSimpleName()); + }} + ) + .build(); + infrastructure.deploy(); + } + + @AfterAll + public static void tearDown() { + if (infrastructure != null) + infrastructure.destroy(); + } + + @Test + public void test_logInfoWithAdditionalKeys() throws JsonProcessingException { + // GIVEN + String orderId = UUID.randomUUID().toString(); + String event = "{\"message\":\"New Order\", \"keys\":{\"orderId\":\"" + orderId +"\"}}"; + + // WHEN + InvocationResult invocationResult1 = infrastructure.invokeFunction(event); + InvocationResult invocationResult2 = infrastructure.invokeFunction(event); + + // THEN + String[] functionLogs = invocationResult1.getLogs().getFunctionLogs(INFO); + assertThat(functionLogs).hasSize(1); + + JsonNode jsonNode = objectMapper.readTree(functionLogs[0]); + assertThat(jsonNode.get("message").asText()).isEqualTo("New Order"); + assertThat(jsonNode.get("orderId").asText()).isEqualTo(orderId); + assertThat(jsonNode.get("coldStart").asBoolean()).isTrue(); + assertThat(jsonNode.get("function_request_id").asText()).isEqualTo(invocationResult1.getRequestId()); + + // second call should not be cold start + functionLogs = invocationResult2.getLogs().getFunctionLogs(INFO); + assertThat(functionLogs).hasSize(1); + jsonNode = objectMapper.readTree(functionLogs[0]); + assertThat(jsonNode.get("coldStart").asBoolean()).isFalse(); + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/Infrastructure.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/Infrastructure.java new file mode 100644 index 000000000..737c6badc --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/Infrastructure.java @@ -0,0 +1,408 @@ +package software.amazon.lambda.powertools.testutils; + +import com.evanlennick.retry4j.CallExecutor; +import com.evanlennick.retry4j.CallExecutorBuilder; +import com.evanlennick.retry4j.Status; +import com.evanlennick.retry4j.config.RetryConfig; +import com.evanlennick.retry4j.config.RetryConfigBuilder; +import com.fasterxml.jackson.databind.JsonNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.yaml.snakeyaml.Yaml; +import software.amazon.awscdk.Stack; +import software.amazon.awscdk.*; +import software.amazon.awscdk.cxapi.CloudAssembly; +import software.amazon.awscdk.services.dynamodb.Attribute; +import software.amazon.awscdk.services.dynamodb.AttributeType; +import software.amazon.awscdk.services.dynamodb.BillingMode; +import software.amazon.awscdk.services.dynamodb.Table; +import software.amazon.awscdk.services.lambda.Code; +import software.amazon.awscdk.services.lambda.Function; +import software.amazon.awscdk.services.lambda.Tracing; +import software.amazon.awscdk.services.logs.LogGroup; +import software.amazon.awscdk.services.logs.RetentionDays; +import software.amazon.awscdk.services.s3.assets.AssetOptions; +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.core.retry.RetryPolicy; +import software.amazon.awssdk.core.retry.backoff.EqualJitterBackoffStrategy; +import software.amazon.awssdk.core.waiters.WaiterResponse; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.cloudformation.CloudFormationClient; +import software.amazon.awssdk.services.cloudformation.model.*; +import software.amazon.awssdk.services.cloudwatch.CloudWatchClient; +import software.amazon.awssdk.services.cloudwatch.model.*; +import software.amazon.awssdk.services.lambda.LambdaClient; +import software.amazon.awssdk.services.lambda.model.InvokeRequest; +import software.amazon.awssdk.services.lambda.model.InvokeResponse; +import software.amazon.awssdk.services.lambda.model.LogType; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.ListObjectsV2Request; +import software.amazon.awssdk.services.s3.model.ListObjectsV2Response; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import software.amazon.awssdk.services.sts.StsClient; +import software.amazon.awssdk.services.xray.XRayClient; +import software.amazon.awssdk.services.xray.model.GetTraceSummariesRequest; +import software.amazon.awssdk.services.xray.model.GetTraceSummariesResponse; +import software.amazon.awssdk.services.xray.model.TimeRangeType; +import software.amazon.awssdk.utils.StringUtils; +import software.amazon.lambda.powertools.utilities.JsonConfig; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.time.Clock; +import java.time.Instant; +import java.util.*; +import java.util.concurrent.Callable; + +import static java.time.Duration.ofSeconds; +import static java.time.temporal.ChronoUnit.MINUTES; +import static java.util.Collections.singletonList; + +public class Infrastructure { + private static Logger LOG = LoggerFactory.getLogger(Infrastructure.class); + + private final String stackName; + private final boolean tracing; + private final Map envVar; + private final JavaRuntime runtime; + private final App app; + private final Stack stack; + private final long timeout; + private final String pathToFunction; + private final S3Client s3; + private final LambdaClient lambda; + private final CloudFormationClient cfn; + private final CloudWatchClient cloudwatch; + private final XRayClient xray; + private final Region region; + private final String account; + private final String idempotencyTable; + private String functionName; + private Object cfnTemplate; + private String cfnAssetDirectory; + private final SdkHttpClient httpClient; + + private Infrastructure(Builder builder) { + this.stackName = builder.stackName; + this.tracing = builder.tracing; + this.envVar = builder.environmentVariables; + this.runtime = builder.runtime; + this.timeout = builder.timeoutInSeconds; + this.pathToFunction = builder.pathToFunction; + this.idempotencyTable = builder.idemPotencyTable; + + this.app = new App(); + this.stack = createStackWithLambda(); + + this.synthesize(); + + this.httpClient = UrlConnectionHttpClient.builder().build(); + this.region = Region.of(System.getProperty("AWS_DEFAULT_REGION", "eu-west-1")); + this.account = StsClient.builder() + .httpClient(httpClient) + .region(region) + .build().getCallerIdentity().account(); + + s3 = S3Client.builder() + .httpClient(httpClient) + .region(region) + .build(); + lambda = LambdaClient.builder() + .httpClient(httpClient) + .region(region) + .build(); + cloudwatch = CloudWatchClient.builder() + .httpClient(httpClient) + .region(region) + .build(); + xray = XRayClient.builder() + .httpClient(httpClient) + .region(region) + .build(); + cfn = CloudFormationClient.builder() + .httpClient(httpClient) + .region(region) + .build(); + } + + public void deploy() { + uploadAssets(); + LOG.debug("Deploying '" + stackName + "' on account " + account); + cfn.createStack(CreateStackRequest.builder() + .stackName(stackName) + .templateBody(new Yaml().dump(cfnTemplate)) + .timeoutInMinutes(10) + .onFailure(OnFailure.ROLLBACK) + .capabilities(Capability.CAPABILITY_IAM) + .build()); + WaiterResponse waiterResponse = cfn.waiter().waitUntilStackCreateComplete(DescribeStacksRequest.builder().stackName(stackName).build()); + if (waiterResponse.matched().response().isPresent()) { + LOG.debug("Stack " + waiterResponse.matched().response().get().stacks().get(0).stackName() + " successfully deployed"); + } else { + throw new RuntimeException("Failed to create stack"); + } + } + + public void destroy() { + LOG.debug("Deleting '" + stackName + "' on account " + account); + cfn.deleteStack(DeleteStackRequest.builder().stackName(stackName).build()); + } + + public InvocationResult invokeFunction(String input) { + SdkBytes payload = SdkBytes.fromUtf8String(input); + + InvokeRequest request = InvokeRequest.builder() + .functionName(functionName) + .payload(payload) + .logType(LogType.TAIL) + .build(); + + Instant start = Instant.now(Clock.systemUTC()).truncatedTo(MINUTES); + InvokeResponse response = lambda.invoke(request); + Instant end = start.plus(1, MINUTES); + return new InvocationResult(response, start, end); + } + + public List getMetrics(Instant start, Instant end, int period, String namespace, String metricName, Map dimensions) { + List dimensionsList = new ArrayList<>(); + if (dimensions != null) + dimensions.forEach((key, value) -> dimensionsList.add(Dimension.builder().name(key).value(value).build())); + + Callable> callable = () -> { + LOG.debug("Get Metrics for namespace {}, start {}, end {}, metric {}, dimensions {}", namespace, start, end, metricName, dimensionsList); + GetMetricDataResponse metricData = cloudwatch.getMetricData(GetMetricDataRequest.builder() + .startTime(start) + .endTime(end) + .metricDataQueries(MetricDataQuery.builder() + .id(metricName.toLowerCase()) + .metricStat(MetricStat.builder() + .unit(StandardUnit.COUNT) + .metric(Metric.builder() + .namespace(namespace) + .metricName(metricName) + .dimensions(dimensionsList) + .build()) + .period(period) + .stat("Sum") + .build()) + .returnData(true) + .build()) + .build()); + List values = metricData.metricDataResults().get(0).values(); + if (values == null || values.isEmpty()) { + throw new Exception("No data found for metric " + metricName); + } + return values; + }; + + RetryConfig retryConfig = new RetryConfigBuilder() + .withMaxNumberOfTries(10) + .retryOnAnyException() + .withDelayBetweenTries(ofSeconds(2)) + .withRandomExponentialBackoff() + .build(); + CallExecutor> callExecutor = new CallExecutorBuilder>() + .config(retryConfig) + .afterFailedTryListener(s -> {LOG.warn(s.getLastExceptionThatCausedRetry().getMessage() + ", attempts: " + s.getTotalTries());}) + .build(); + Status> status = callExecutor.execute(callable); + return status.getResult(); + } + + public static Builder builder() { + return new Builder(); + } + + public String getFunctionName() { + return functionName; + } + + public static class Builder { + public long timeoutInSeconds = 30; + public String pathToFunction; + public String testName; + private String stackName; + private boolean tracing = false; + private JavaRuntime runtime = JavaRuntime.JAVA11; + private Map environmentVariables = new HashMap<>(); + private String idemPotencyTable; + + public Infrastructure build() { + Objects.requireNonNull(testName, "testName must not be null"); + + String uuid = UUID.randomUUID().toString().substring(0, 13); + stackName = testName + "-" + uuid; + + Objects.requireNonNull(pathToFunction, "pathToFunction must not be null"); + return new Infrastructure(this); + } + + public Builder testName(String testName) { + this.testName = testName; + return this; + } + + public Builder pathToFunction(String pathToFunction) { + this.pathToFunction = pathToFunction; + return this; + } + + public Builder tracing(boolean tracing) { + this.tracing = tracing; + return this; + } + + public Builder runtime(JavaRuntime runtime) { + this.runtime = runtime; + return this; + } + + public Builder idempotencyTable(String tableName) { + this.idemPotencyTable = tableName; + return this; + } + + public Builder environmentVariables(Map environmentVariables) { + this.environmentVariables = environmentVariables; + return this; + } + + public Builder timeoutInSeconds(long timeoutInSeconds) { + this.timeoutInSeconds = timeoutInSeconds; + return this; + } + } + + private Stack createStackWithLambda() { + Stack stack = new Stack(app, stackName); + List packagingInstruction = Arrays.asList( + "/bin/sh", + "-c", + "cd " + pathToFunction + + " && timeout -s SIGKILL 5m mvn clean install -ff " + + " -Dmaven.test.skip=true " + + " -Dmaven.resources.skip=true " + + " -Dmaven.compiler.source=" + runtime.getMvnProperty() + + " -Dmaven.compiler.target=" + runtime.getMvnProperty() + + " && cp /asset-input/" + pathToFunction + "/target/function.jar /asset-output/" + ); + + BundlingOptions.Builder builderOptions = BundlingOptions.builder() + .command(packagingInstruction) + .image(runtime.getCdkRuntime().getBundlingImage()) + .volumes(singletonList( + // Mount local .m2 repo to avoid download all the dependencies again inside the container + DockerVolume.builder() + .hostPath(System.getProperty("user.home") + "/.m2/") + .containerPath("/root/.m2/") + .build() + )) + .user("root") + .outputType(BundlingOutput.ARCHIVED); + + functionName = stackName + "-function"; + + Function function = Function.Builder + .create(stack, functionName) + .code(Code.fromAsset("handlers/", AssetOptions.builder() + .bundling(builderOptions + .command(packagingInstruction) + .build()) + .build())) + .functionName(functionName) + .handler("software.amazon.lambda.powertools.e2e.Function::handleRequest") + .memorySize(1024) + .timeout(Duration.seconds(timeout)) + .runtime(runtime.getCdkRuntime()) + .environment(envVar) + .tracing(tracing ? Tracing.ACTIVE : Tracing.DISABLED) + .build(); + + LogGroup.Builder + .create(stack, functionName + "-logs") + .logGroupName("/aws/lambda/" + functionName) + .retention(RetentionDays.ONE_DAY) + .removalPolicy(RemovalPolicy.DESTROY) + .build(); + + if (!StringUtils.isEmpty(idempotencyTable)) { + Table table = Table.Builder + .create(stack, "IdempotencyTable") + .billingMode(BillingMode.PAY_PER_REQUEST) + .removalPolicy(RemovalPolicy.DESTROY) + .partitionKey(Attribute.builder().name("id").type(AttributeType.STRING).build()) + .tableName(idempotencyTable) + .timeToLiveAttribute("expiration") + .build(); + table.grantReadWriteData(function); + } + + return stack; + } + + private void synthesize() { + CloudAssembly synth = app.synth(); + cfnTemplate = synth.getStackByName(stack.getStackName()).getTemplate(); + cfnAssetDirectory = synth.getDirectory(); + } + + private void uploadAssets() { + Map assets = findAssets(); + assets.forEach((objectKey, asset) -> { + if (!asset.assetPath.endsWith(".jar")) { + return; + } + ListObjectsV2Response objects = s3.listObjectsV2(ListObjectsV2Request.builder().bucket(asset.bucketName).build()); + if (objects.contents().stream().anyMatch(o -> o.key().equals(objectKey))) { + System.out.println("Asset already exists, skipping"); + return; + } + System.out.println("Uploading asset " + objectKey + " to " + asset.bucketName); + s3.putObject(PutObjectRequest.builder().bucket(asset.bucketName).key(objectKey).build(), Path.of(cfnAssetDirectory, asset.assetPath)); + }); + } + + private Map findAssets() { + Map assets = new HashMap<>(); + try { + JsonNode jsonNode = JsonConfig.get().getObjectMapper().readTree(new File(cfnAssetDirectory, stackName + ".assets.json")); + JsonNode files = jsonNode.get("files"); + files.iterator().forEachRemaining(file -> { + String assetPath = file.get("source").get("path").asText(); + String assetPackaging = file.get("source").get("packaging").asText(); + String bucketName = file.get("destinations").get("current_account-current_region").get("bucketName").asText(); + String objectKey = file.get("destinations").get("current_account-current_region").get("objectKey").asText(); + Asset asset = new Asset(assetPath, assetPackaging, bucketName.replace("${AWS::AccountId}", account).replace("${AWS::Region}", region.toString())); + assets.put(objectKey, asset); + }); + } catch (IOException e) { + throw new RuntimeException(e); + } + return assets; + } + + private static class Asset { + private final String assetPath; + private final String assetPackaging; + private final String bucketName; + + Asset(String assetPath, String assetPackaging, String bucketName) { + this.assetPath = assetPath; + this.assetPackaging = assetPackaging; + this.bucketName = bucketName; + } + + @Override + public String toString() { + return "Asset{" + + "assetPath='" + assetPath + '\'' + + ", assetPackaging='" + assetPackaging + '\'' + + ", bucketName='" + bucketName + '\'' + + '}'; + } + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/InvocationLogs.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/InvocationLogs.java new file mode 100644 index 000000000..0fa7d8a22 --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/InvocationLogs.java @@ -0,0 +1,68 @@ +package software.amazon.lambda.powertools.testutils; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Base64; +import java.util.stream.IntStream; + +public class InvocationLogs { + private String[] logs; + private String[] functionLogs; + + public InvocationLogs(String base64Logs, String requestId) { + String rawLogs = new String(Base64.getDecoder().decode(base64Logs), StandardCharsets.UTF_8); + this.logs = rawLogs.split("\n"); + + String start = String.format("START RequestId: %s", requestId); + String end = String.format("END RequestId: %s", requestId); + int startPos = IntStream.range(0, logs.length) + .filter(i -> logs[i].startsWith(start)) + .findFirst() + .orElse(-1); + int endPos = IntStream.range(0, logs.length) + .filter(i -> logs[i].equals(end)) + .findFirst() + .orElse(-1); + this.functionLogs = Arrays.copyOfRange(this.logs, startPos + 1, endPos); + } + + public String[] getAllLogs() { + return logs; + } + + /** + * Return only logs from function, exclude START, END, and REPORT and other elements generated by Lambda service + * @return only logs generated by the function + */ + public String[] getFunctionLogs() { + return this.functionLogs; + } + + public String[] getFunctionLogs(Level level) { + String[] filtered = getFunctionLogs(); + + return Arrays.stream(filtered).filter(log -> log.contains("\"level\":\""+level.getLevel()+"\"")).toArray(String[]::new); + } + + public enum Level { + DEBUG("DEBUG"), + INFO("INFO"), + WARN("WARN"), + ERROR("ERROR"); + + private final String level; + + Level(String lvl) { + this.level = lvl; + } + + public String getLevel() { + return level; + } + + @Override + public String toString() { + return level; + } + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/InvocationResult.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/InvocationResult.java new file mode 100644 index 000000000..a561d22c9 --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/InvocationResult.java @@ -0,0 +1,42 @@ +package software.amazon.lambda.powertools.testutils; + +import software.amazon.awssdk.services.lambda.model.InvokeResponse; + +import java.time.Instant; + +public class InvocationResult { + + private final InvocationLogs logs; + private final String result; + + private final String requestId; + private final Instant start; + private final Instant end; + + public InvocationResult(InvokeResponse response, Instant start, Instant end) { + requestId = response.responseMetadata().requestId(); + logs = new InvocationLogs(response.logResult(), requestId); + result = response.payload().asUtf8String(); + this.start = start; + this.end = end; + } + public InvocationLogs getLogs() { + return logs; + } + + public String getResult() { + return result; + } + + public String getRequestId() { + return requestId; + } + + public Instant getStart() { + return start; + } + + public Instant getEnd() { + return end; + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/JavaRuntime.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/JavaRuntime.java new file mode 100644 index 000000000..94ec13518 --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/JavaRuntime.java @@ -0,0 +1,37 @@ +package software.amazon.lambda.powertools.testutils; + +import software.amazon.awscdk.services.lambda.Runtime; + +public enum JavaRuntime { + JAVA8("java8", Runtime.JAVA_8, "1.8"), + JAVA8AL2("java8.al2", Runtime.JAVA_8_CORRETTO, "1.8"), + JAVA11("java11", Runtime.JAVA_11, "11"); + + private final String runtime; + private final Runtime cdkRuntime; + + private final String mvnProperty; + + JavaRuntime(String runtime, Runtime cdkRuntime, String mvnProperty) { + this.runtime = runtime; + this.cdkRuntime = cdkRuntime; + this.mvnProperty = mvnProperty; + } + + public Runtime getCdkRuntime() { + return cdkRuntime; + } + + public String getRuntime() { + return runtime; + } + + @Override + public String toString() { + return runtime; + } + + public String getMvnProperty() { + return mvnProperty; + } +} diff --git a/powertools-e2e-tests/src/test/resources/logback-test.xml b/powertools-e2e-tests/src/test/resources/logback-test.xml new file mode 100644 index 000000000..aac638007 --- /dev/null +++ b/powertools-e2e-tests/src/test/resources/logback-test.xml @@ -0,0 +1,20 @@ + + + + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + \ No newline at end of file From 0d60b9dda435a9fa9fa64293946e286820e0d90a Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Wed, 2 Nov 2022 11:16:15 +0100 Subject: [PATCH 02/13] add e2e tests for metrics --- powertools-e2e-tests/handlers/metrics/pom.xml | 103 ++++++++++++++++++ .../lambda/powertools/e2e/Function.java | 26 +++++ .../amazon/lambda/powertools/e2e/Input.java | 29 +++++ .../lambda/powertools/MetricsE2ETest.java | 65 +++++++++++ 4 files changed, 223 insertions(+) create mode 100644 powertools-e2e-tests/handlers/metrics/pom.xml create mode 100644 powertools-e2e-tests/handlers/metrics/src/main/java/software/amazon/lambda/powertools/e2e/Function.java create mode 100644 powertools-e2e-tests/handlers/metrics/src/main/java/software/amazon/lambda/powertools/e2e/Input.java create mode 100644 powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/MetricsE2ETest.java diff --git a/powertools-e2e-tests/handlers/metrics/pom.xml b/powertools-e2e-tests/handlers/metrics/pom.xml new file mode 100644 index 000000000..a4fa4e1ad --- /dev/null +++ b/powertools-e2e-tests/handlers/metrics/pom.xml @@ -0,0 +1,103 @@ + + 4.0.0 + + software.amazon.lambda + E2EMetricsFunction + 1.12.3 + jar + A sample Hello World using powertools metrics + + + 2.19.0 + UTF-8 + 1.12.3 + + + + + software.amazon.lambda + powertools-metrics + ${lambda.powertools.version} + + + com.amazonaws + aws-lambda-java-core + 1.2.1 + + + com.amazonaws + aws-lambda-java-events + 3.11.0 + + + + + + + + org.codehaus.mojo + aspectj-maven-plugin + 1.14.0 + + ${maven.compiler.source} + ${maven.compiler.target} + ${maven.compiler.target} + + + software.amazon.lambda + powertools-metrics + + + + + + + compile + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + false + function + + + + package + + shade + + + + + + + + + + + + io.github.edwgiz + log4j-maven-shade-plugin-extensions + 2.17.2 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.10.1 + + ${maven.compiler.source} + ${maven.compiler.target} + + + + + diff --git a/powertools-e2e-tests/handlers/metrics/src/main/java/software/amazon/lambda/powertools/e2e/Function.java b/powertools-e2e-tests/handlers/metrics/src/main/java/software/amazon/lambda/powertools/e2e/Function.java new file mode 100644 index 000000000..e643de9d5 --- /dev/null +++ b/powertools-e2e-tests/handlers/metrics/src/main/java/software/amazon/lambda/powertools/e2e/Function.java @@ -0,0 +1,26 @@ +package software.amazon.lambda.powertools.e2e; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger; +import software.amazon.cloudwatchlogs.emf.model.DimensionSet; +import software.amazon.cloudwatchlogs.emf.model.Unit; +import software.amazon.lambda.powertools.metrics.Metrics; +import software.amazon.lambda.powertools.metrics.MetricsUtils; + +public class Function implements RequestHandler { + + MetricsLogger metricsLogger = MetricsUtils.metricsLogger(); + + @Metrics(captureColdStart = true) + public String handleRequest(Input input, Context context) { + + DimensionSet dimensionSet = new DimensionSet(); + input.getDimensions().forEach((key, value) -> dimensionSet.addDimension(key, value)); + metricsLogger.putDimensions(dimensionSet); + + input.getMetrics().forEach((key, value) -> metricsLogger.putMetric(key, value, Unit.COUNT)); + + return "OK"; + } +} \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/metrics/src/main/java/software/amazon/lambda/powertools/e2e/Input.java b/powertools-e2e-tests/handlers/metrics/src/main/java/software/amazon/lambda/powertools/e2e/Input.java new file mode 100644 index 000000000..5ff8a7125 --- /dev/null +++ b/powertools-e2e-tests/handlers/metrics/src/main/java/software/amazon/lambda/powertools/e2e/Input.java @@ -0,0 +1,29 @@ +package software.amazon.lambda.powertools.e2e; + +import java.util.Map; + +public class Input { + private Map metrics; + + private Map dimensions; + + public Map getMetrics() { + return metrics; + } + + public void setMetrics(Map metrics) { + this.metrics = metrics; + } + + public Input() { + } + + + public Map getDimensions() { + return dimensions; + } + + public void setDimensions(Map dimensions) { + this.dimensions = dimensions; + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/MetricsE2ETest.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/MetricsE2ETest.java new file mode 100644 index 000000000..dd529bd29 --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/MetricsE2ETest.java @@ -0,0 +1,65 @@ +package software.amazon.lambda.powertools; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import software.amazon.lambda.powertools.testutils.Infrastructure; +import software.amazon.lambda.powertools.testutils.InvocationResult; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MetricsE2ETest { + private static Infrastructure infrastructure; + + private static final String namespace = "MetricsE2ENamespace_"+UUID.randomUUID(); + private static final String service = "MetricsE2EService_"+UUID.randomUUID(); + + @BeforeAll + @Timeout(value = 5, unit = TimeUnit.MINUTES) + public static void setup() { + infrastructure = Infrastructure.builder() + .testName(MetricsE2ETest.class.getSimpleName()) + .pathToFunction("metrics") + .environmentVariables(new HashMap<>() {{ + put("POWERTOOLS_METRICS_NAMESPACE", namespace); + put("POWERTOOLS_SERVICE_NAME", service); + }} + ) + .build(); + infrastructure.deploy(); + } + + @AfterAll + public static void tearDown() { + if (infrastructure != null) + infrastructure.destroy(); + } + + @Test + public void test_recordMetrics() { + // GIVEN + String event1 = "{ \"metrics\": {\"orders\": 1, \"products\": 4}, \"dimensions\": { \"Environment\": \"test\"} }"; + + // WHEN + InvocationResult invocationResult = infrastructure.invokeFunction(event1); + + // THEN + List coldStart = infrastructure.getMetrics(invocationResult.getStart(), invocationResult.getEnd(), 60, namespace, "ColdStart", new HashMap<>() {{ put("FunctionName", infrastructure.getFunctionName()); put("Service", service); }}); + assertThat(coldStart.get(0)).isEqualTo(1); + List orderMetrics = infrastructure.getMetrics(invocationResult.getStart(), invocationResult.getEnd(), 60, namespace, "orders", Collections.singletonMap("Environment", "test")); + assertThat(orderMetrics.get(0)).isEqualTo(1); + List productMetrics = infrastructure.getMetrics(invocationResult.getStart(), invocationResult.getEnd(), 60, namespace, "products", Collections.singletonMap("Environment", "test")); + assertThat(productMetrics.get(0)).isEqualTo(4); + orderMetrics = infrastructure.getMetrics(invocationResult.getStart(), invocationResult.getEnd(), 60, namespace, "orders", Collections.singletonMap("Service", service)); + assertThat(orderMetrics.get(0)).isEqualTo(1); + productMetrics = infrastructure.getMetrics(invocationResult.getStart(), invocationResult.getEnd(), 60, namespace, "products", Collections.singletonMap("Service", service)); + assertThat(productMetrics.get(0)).isEqualTo(4); + } +} From 40c8bc434262c7ac662390be7e82b21e4a003617 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Wed, 2 Nov 2022 11:16:50 +0100 Subject: [PATCH 03/13] add e2e tests for idempotency --- .../handlers/idempotency/pom.xml | 103 ++++++++++++++++++ .../lambda/powertools/e2e/Function.java | 10 +- .../amazon/lambda/powertools/e2e/Input.java | 20 ++++ .../lambda/powertools/IdempotencyE2ETest.java | 61 +++++++++++ 4 files changed, 191 insertions(+), 3 deletions(-) create mode 100644 powertools-e2e-tests/handlers/idempotency/pom.xml create mode 100644 powertools-e2e-tests/handlers/idempotency/src/main/java/software/amazon/lambda/powertools/e2e/Input.java create mode 100644 powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/IdempotencyE2ETest.java diff --git a/powertools-e2e-tests/handlers/idempotency/pom.xml b/powertools-e2e-tests/handlers/idempotency/pom.xml new file mode 100644 index 000000000..b47ebbfc8 --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency/pom.xml @@ -0,0 +1,103 @@ + + 4.0.0 + + software.amazon.lambda + E2EIdempotencyFunction + 1.12.3 + jar + A sample Hello World using powertools idempotency + + + 2.19.0 + UTF-8 + 1.12.3 + + + + + software.amazon.lambda + powertools-idempotency + ${lambda.powertools.version} + + + com.amazonaws + aws-lambda-java-core + 1.2.1 + + + com.amazonaws + aws-lambda-java-events + 3.11.0 + + + + + + + + org.codehaus.mojo + aspectj-maven-plugin + 1.14.0 + + ${maven.compiler.source} + ${maven.compiler.target} + ${maven.compiler.target} + + + software.amazon.lambda + powertools-idempotency + + + + + + + compile + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + false + function + + + + package + + shade + + + + + + + + + + + + io.github.edwgiz + log4j-maven-shade-plugin-extensions + 2.17.2 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.10.1 + + ${maven.compiler.source} + ${maven.compiler.target} + + + + + diff --git a/powertools-e2e-tests/handlers/idempotency/src/main/java/software/amazon/lambda/powertools/e2e/Function.java b/powertools-e2e-tests/handlers/idempotency/src/main/java/software/amazon/lambda/powertools/e2e/Function.java index 3111ea3c1..cc6eec4fa 100644 --- a/powertools-e2e-tests/handlers/idempotency/src/main/java/software/amazon/lambda/powertools/e2e/Function.java +++ b/powertools-e2e-tests/handlers/idempotency/src/main/java/software/amazon/lambda/powertools/e2e/Function.java @@ -10,11 +10,14 @@ import software.amazon.lambda.powertools.idempotency.Idempotent; import software.amazon.lambda.powertools.idempotency.persistence.DynamoDBPersistenceStore; +import java.time.Duration; import java.time.Instant; import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.TimeZone; -public class Function implements RequestHandler { +public class Function implements RequestHandler { public Function() { this(DynamoDbClient @@ -27,6 +30,7 @@ public Function() { public Function(DynamoDbClient client) { Idempotency.config().withConfig( IdempotencyConfig.builder() + .withExpiration(Duration.of(10, ChronoUnit.SECONDS)) .build()) .withPersistenceStore( DynamoDBPersistenceStore.builder() @@ -37,8 +41,8 @@ public Function(DynamoDbClient client) { } @Idempotent - public String handleRequest(String input, Context context) { - DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE; + public String handleRequest(Input input, Context context) { + DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE_TIME.withZone(TimeZone.getTimeZone("UTC").toZoneId()); return dtf.format(Instant.now()); } } \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/idempotency/src/main/java/software/amazon/lambda/powertools/e2e/Input.java b/powertools-e2e-tests/handlers/idempotency/src/main/java/software/amazon/lambda/powertools/e2e/Input.java new file mode 100644 index 000000000..c5c2a121e --- /dev/null +++ b/powertools-e2e-tests/handlers/idempotency/src/main/java/software/amazon/lambda/powertools/e2e/Input.java @@ -0,0 +1,20 @@ +package software.amazon.lambda.powertools.e2e; + +public class Input { + private String message; + + public Input(String message) { + this.message = message; + } + + public Input() { + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/IdempotencyE2ETest.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/IdempotencyE2ETest.java new file mode 100644 index 000000000..37713ba5c --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/IdempotencyE2ETest.java @@ -0,0 +1,61 @@ +package software.amazon.lambda.powertools; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import software.amazon.lambda.powertools.testutils.Infrastructure; +import software.amazon.lambda.powertools.testutils.InvocationResult; + +import java.time.Year; +import java.util.HashMap; +import java.util.concurrent.TimeUnit; + +public class IdempotencyE2ETest { + private static Infrastructure infrastructure; + + @BeforeAll + @Timeout(value = 5, unit = TimeUnit.MINUTES) + public static void setup() { + infrastructure = Infrastructure.builder() + .testName(IdempotencyE2ETest.class.getSimpleName()) + .pathToFunction("idempotency") + .idempotencyTable("idempo") + .environmentVariables(new HashMap<>() {{ + put("IDEMPOTENCY_TABLE", "idempo"); + }} + ) + .build(); + infrastructure.deploy(); + } + + @AfterAll + public static void tearDown() { + if (infrastructure != null) + infrastructure.destroy(); + } + + @Test + public void test_ttlNotExpired_sameResult_ttlExpired_differentResult() throws InterruptedException { + // GIVEN + String event = "{\"message\":\"TTL 10sec\"}"; + + // WHEN + // First invocation + InvocationResult result1 = infrastructure.invokeFunction(event); + + // Second invocation (should get same result) + InvocationResult result2 = infrastructure.invokeFunction(event); + + Thread.sleep(12000); + + // Third invocation (should get different result) + InvocationResult result3 = infrastructure.invokeFunction(event); + + // THEN + Assertions.assertThat(result1.getResult()).contains(Year.now().toString()); + Assertions.assertThat(result2.getResult()).isEqualTo(result1.getResult()); + Assertions.assertThat(result3.getResult()).isNotEqualTo(result2.getResult()); + } +} From 5a79635164bb9570417584b4ad1312cb30e81e97 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Thu, 3 Nov 2022 12:01:09 +0100 Subject: [PATCH 04/13] add e2e tests for tracing --- powertools-e2e-tests/handlers/tracing/pom.xml | 103 ++++++++++ .../lambda/powertools/e2e/Function.java | 40 ++++ .../amazon/lambda/powertools/e2e/Input.java | 17 ++ .../tracing/src/main/resources/log4j2.xml | 16 ++ powertools-e2e-tests/pom.xml | 29 +++ .../lambda/powertools/TracingE2ETest.java | 91 +++++++++ .../powertools/testutils/Infrastructure.java | 16 +- .../testutils/tracing/SegmentDocument.java | 88 +++++++++ .../powertools/testutils/tracing/Trace.java | 20 ++ .../testutils/tracing/TraceFetcher.java | 180 ++++++++++++++++++ 10 files changed, 586 insertions(+), 14 deletions(-) create mode 100644 powertools-e2e-tests/handlers/tracing/pom.xml create mode 100644 powertools-e2e-tests/handlers/tracing/src/main/java/software/amazon/lambda/powertools/e2e/Function.java create mode 100644 powertools-e2e-tests/handlers/tracing/src/main/java/software/amazon/lambda/powertools/e2e/Input.java create mode 100644 powertools-e2e-tests/handlers/tracing/src/main/resources/log4j2.xml create mode 100644 powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/TracingE2ETest.java create mode 100644 powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/SegmentDocument.java create mode 100644 powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/Trace.java create mode 100644 powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/TraceFetcher.java diff --git a/powertools-e2e-tests/handlers/tracing/pom.xml b/powertools-e2e-tests/handlers/tracing/pom.xml new file mode 100644 index 000000000..ddc22f4fd --- /dev/null +++ b/powertools-e2e-tests/handlers/tracing/pom.xml @@ -0,0 +1,103 @@ + + 4.0.0 + + software.amazon.lambda + E2ETracingFunction + 1.12.3 + jar + A sample Hello World using powertools tracing + + + 2.19.0 + UTF-8 + 1.12.3 + + + + + software.amazon.lambda + powertools-tracing + ${lambda.powertools.version} + + + com.amazonaws + aws-lambda-java-core + 1.2.1 + + + com.amazonaws + aws-lambda-java-events + 3.11.0 + + + + + + + + org.codehaus.mojo + aspectj-maven-plugin + 1.14.0 + + ${maven.compiler.source} + ${maven.compiler.target} + ${maven.compiler.target} + + + software.amazon.lambda + powertools-tracing + + + + + + + compile + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + false + function + + + + package + + shade + + + + + + + + + + + + io.github.edwgiz + log4j-maven-shade-plugin-extensions + 2.17.2 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.10.1 + + ${maven.compiler.source} + ${maven.compiler.target} + + + + + diff --git a/powertools-e2e-tests/handlers/tracing/src/main/java/software/amazon/lambda/powertools/e2e/Function.java b/powertools-e2e-tests/handlers/tracing/src/main/java/software/amazon/lambda/powertools/e2e/Function.java new file mode 100644 index 000000000..f7b2c5e5d --- /dev/null +++ b/powertools-e2e-tests/handlers/tracing/src/main/java/software/amazon/lambda/powertools/e2e/Function.java @@ -0,0 +1,40 @@ +package software.amazon.lambda.powertools.e2e; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import software.amazon.lambda.powertools.tracing.Tracing; +import software.amazon.lambda.powertools.tracing.TracingUtils; + +public class Function implements RequestHandler { + + @Tracing + public String handleRequest(Input input, Context context) { + try { + Thread.sleep(100); // simulate stuff + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + String message = buildMessage(input.getMessage(), context.getFunctionName()); + + TracingUtils.withSubsegment("internal_stuff", subsegment -> { + try { + Thread.sleep(100); // simulate stuff + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }); + + return message; + } + + @Tracing + private String buildMessage(String message, String funcName) { + TracingUtils.putAnnotation("message", message); + try { + Thread.sleep(150); // simulate other stuff + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + return String.format("%s (%s)", message, funcName); + } +} \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/tracing/src/main/java/software/amazon/lambda/powertools/e2e/Input.java b/powertools-e2e-tests/handlers/tracing/src/main/java/software/amazon/lambda/powertools/e2e/Input.java new file mode 100644 index 000000000..29cf618ba --- /dev/null +++ b/powertools-e2e-tests/handlers/tracing/src/main/java/software/amazon/lambda/powertools/e2e/Input.java @@ -0,0 +1,17 @@ +package software.amazon.lambda.powertools.e2e; + +public class Input { + private String message; + + public Input() { + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + +} diff --git a/powertools-e2e-tests/handlers/tracing/src/main/resources/log4j2.xml b/powertools-e2e-tests/handlers/tracing/src/main/resources/log4j2.xml new file mode 100644 index 000000000..8925f70b9 --- /dev/null +++ b/powertools-e2e-tests/handlers/tracing/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/powertools-e2e-tests/pom.xml b/powertools-e2e-tests/pom.xml index 5264f18fa..c93656698 100644 --- a/powertools-e2e-tests/pom.xml +++ b/powertools-e2e-tests/pom.xml @@ -17,9 +17,16 @@ 11 10.1.138 2.47.0 + 1.18.24 + + org.projectlombok + lombok + ${lombok.version} + provided + ch.qos.logback @@ -128,6 +135,28 @@ + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.10.1 + + ${maven.compiler.source} + ${maven.compiler.target} + + + org.projectlombok + lombok + ${lombok.version} + + + UTF-8 + + + + + it diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/TracingE2ETest.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/TracingE2ETest.java new file mode 100644 index 000000000..9fe63ad18 --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/TracingE2ETest.java @@ -0,0 +1,91 @@ +package software.amazon.lambda.powertools; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import software.amazon.lambda.powertools.testutils.Infrastructure; +import software.amazon.lambda.powertools.testutils.InvocationResult; +import software.amazon.lambda.powertools.testutils.tracing.SegmentDocument.SubSegment; +import software.amazon.lambda.powertools.testutils.tracing.Trace; +import software.amazon.lambda.powertools.testutils.tracing.TraceFetcher; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TracingE2ETest { + private static Infrastructure infrastructure; + private static final String service = "TracingE2EService_"+UUID.randomUUID(); // "TracingE2EService_e479fb27-422b-4107-9f8c-086c62e1cd12"; + + @BeforeAll + @Timeout(value = 5, unit = TimeUnit.MINUTES) + public static void setup() { + infrastructure = Infrastructure.builder() + .testName(TracingE2ETest.class.getSimpleName()) + .pathToFunction("tracing") + .tracing(true) + .environmentVariables(new HashMap<>() {{ + put("POWERTOOLS_SERVICE_NAME", service); + }} + ) + .build(); + infrastructure.deploy(); + } + + @AfterAll + public static void tearDown() { + if (infrastructure != null) + infrastructure.destroy(); + } + + @Test + public void test_tracing() { + // GIVEN + String message = "Hello World"; + String event = String.format("{\"message\":\"%s\"}", message); + String result = String.format("%s (%s)", message, infrastructure.getFunctionName()); + + // WHEN + InvocationResult invocationResult = infrastructure.invokeFunction(event); + + // THEN + Trace trace = TraceFetcher.builder() + .start(invocationResult.getStart()) + .end(invocationResult.getEnd()) +// .start(Instant.ofEpochSecond(1667468280)) +// .end(Instant.ofEpochSecond(1667468340)) + .functionName(infrastructure.getFunctionName()) +// .functionName("TracingE2ETest-744e0e5ba909-function") + .build() + .fetchTrace(); + + assertThat(trace.getSubsegments()).hasSize(1); + SubSegment handleRequest = trace.getSubsegments().get(0); + assertThat(handleRequest.getName()).isEqualTo("## handleRequest"); + assertThat(handleRequest.getAnnotations()).hasSize(2); + assertThat(handleRequest.getAnnotations().get("ColdStart")).isEqualTo(true); + assertThat(handleRequest.getAnnotations().get("Service")).isEqualTo(service); + assertThat(handleRequest.getMetadata()).hasSize(1); + Map metadata = (Map) handleRequest.getMetadata().get(service); + assertThat(metadata.get("handleRequest response")).isEqualTo(result); + assertThat(handleRequest.getSubsegments()).hasSize(2); + + SubSegment sub = handleRequest.getSubsegments().get(0); + assertThat(sub.getName()).isIn("## internal_stuff", "## buildMessage"); + + sub = handleRequest.getSubsegments().get(1); + assertThat(sub.getName()).isIn("## internal_stuff", "## buildMessage"); + + SubSegment buildMessage = handleRequest.getSubsegments().stream().filter(subSegment -> subSegment.getName().equals("## buildMessage")).findFirst().orElse(null); + assertThat(buildMessage).isNotNull(); + assertThat(buildMessage.getAnnotations()).hasSize(1); + assertThat(buildMessage.getAnnotations().get("message")).isEqualTo(message); + assertThat(buildMessage.getMetadata()).hasSize(1); + metadata = (Map) buildMessage.getMetadata().get(service); + assertThat(metadata.get("buildMessage response")).isEqualTo(result); + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/Infrastructure.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/Infrastructure.java index 737c6badc..d918bbbe2 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/Infrastructure.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/Infrastructure.java @@ -23,9 +23,6 @@ import software.amazon.awscdk.services.logs.RetentionDays; import software.amazon.awscdk.services.s3.assets.AssetOptions; import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; -import software.amazon.awssdk.core.retry.RetryPolicy; -import software.amazon.awssdk.core.retry.backoff.EqualJitterBackoffStrategy; import software.amazon.awssdk.core.waiters.WaiterResponse; import software.amazon.awssdk.http.SdkHttpClient; import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; @@ -43,10 +40,6 @@ import software.amazon.awssdk.services.s3.model.ListObjectsV2Response; import software.amazon.awssdk.services.s3.model.PutObjectRequest; import software.amazon.awssdk.services.sts.StsClient; -import software.amazon.awssdk.services.xray.XRayClient; -import software.amazon.awssdk.services.xray.model.GetTraceSummariesRequest; -import software.amazon.awssdk.services.xray.model.GetTraceSummariesResponse; -import software.amazon.awssdk.services.xray.model.TimeRangeType; import software.amazon.awssdk.utils.StringUtils; import software.amazon.lambda.powertools.utilities.JsonConfig; @@ -63,7 +56,7 @@ import static java.util.Collections.singletonList; public class Infrastructure { - private static Logger LOG = LoggerFactory.getLogger(Infrastructure.class); + private static final Logger LOG = LoggerFactory.getLogger(Infrastructure.class); private final String stackName; private final boolean tracing; @@ -77,7 +70,6 @@ public class Infrastructure { private final LambdaClient lambda; private final CloudFormationClient cfn; private final CloudWatchClient cloudwatch; - private final XRayClient xray; private final Region region; private final String account; private final String idempotencyTable; @@ -119,10 +111,6 @@ private Infrastructure(Builder builder) { .httpClient(httpClient) .region(region) .build(); - xray = XRayClient.builder() - .httpClient(httpClient) - .region(region) - .build(); cfn = CloudFormationClient.builder() .httpClient(httpClient) .region(region) @@ -234,7 +222,7 @@ public static class Builder { public Infrastructure build() { Objects.requireNonNull(testName, "testName must not be null"); - String uuid = UUID.randomUUID().toString().substring(0, 13); + String uuid = UUID.randomUUID().toString().replace("-", "").substring(0, 12); stackName = testName + "-" + uuid; Objects.requireNonNull(pathToFunction, "pathToFunction must not be null"); diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/SegmentDocument.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/SegmentDocument.java new file mode 100644 index 000000000..18fcc044a --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/SegmentDocument.java @@ -0,0 +1,88 @@ +package software.amazon.lambda.powertools.testutils.tracing; + +import com.fasterxml.jackson.annotation.JsonSetter; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@NoArgsConstructor +@Setter +@Getter +public class SegmentDocument { + private String id; + + @JsonSetter("trace_id") + private String traceId; + + private String name; + + @JsonSetter("start_time") + private long startTime; + + @JsonSetter("end_time") + private long endTime; + + private String origin; + + private Aws aws; + + private List subsegments = new ArrayList<>(); + + public Duration getDuration() { + return Duration.ofMillis(endTime - startTime); + } + + public boolean hasSubsegments() { + return !subsegments.isEmpty(); + } + + @NoArgsConstructor + @Setter + @Getter + public static class Aws{ + @JsonSetter("account_id") + private long accountId; + + @JsonSetter("function_arn") + private String functionArn; + + @JsonSetter("resource_names") + private String[] resourceNames; + } + + @NoArgsConstructor + @Setter + @Getter + public static class SubSegment{ + private String id; + + private String name; + + @JsonSetter("start_time") + private long startTime; + + @JsonSetter("end_time") + private long endTime; + + private List subsegments = new ArrayList<>(); + + private Map annotations; + + private Map metadata; + + private String namespace; + + public boolean hasSubsegments() { + return !subsegments.isEmpty(); + } + + public Duration getDuration() { + return Duration.ofMillis(endTime - startTime); + } + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/Trace.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/Trace.java new file mode 100644 index 000000000..cb6f59f04 --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/Trace.java @@ -0,0 +1,20 @@ +package software.amazon.lambda.powertools.testutils.tracing; + +import lombok.NoArgsConstructor; +import software.amazon.lambda.powertools.testutils.tracing.SegmentDocument.SubSegment; + +import java.util.ArrayList; +import java.util.List; + +@NoArgsConstructor +public class Trace { + private final List subsegments = new ArrayList<>(); + + public List getSubsegments() { + return subsegments; + } + + public void addSubSegment(SubSegment subSegment) { + subsegments.add(subSegment); + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/TraceFetcher.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/TraceFetcher.java new file mode 100644 index 000000000..d01174d92 --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/TraceFetcher.java @@ -0,0 +1,180 @@ +package software.amazon.lambda.powertools.testutils.tracing; + +import com.evanlennick.retry4j.CallExecutor; +import com.evanlennick.retry4j.CallExecutorBuilder; +import com.evanlennick.retry4j.Status; +import com.evanlennick.retry4j.config.RetryConfig; +import com.evanlennick.retry4j.config.RetryConfigBuilder; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.AllArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.xray.XRayClient; +import software.amazon.awssdk.services.xray.model.*; +import software.amazon.lambda.powertools.testutils.tracing.SegmentDocument.SubSegment; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.stream.Collectors; + +import static java.time.Duration.ofSeconds; + +@AllArgsConstructor +public class TraceFetcher { + + private static final ObjectMapper MAPPER = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + private static final Logger LOG = LoggerFactory.getLogger(TraceFetcher.class); + + private final Instant start; + private final Instant end; + private final String filterExpression; + private final List excludedSegments; + + public static Builder builder() { + return new Builder(); + } + + public Trace fetchTrace() { + Callable callable = () -> { + List traceIds = getTraceIds(); + return getTrace(traceIds); + }; + + RetryConfig retryConfig = new RetryConfigBuilder() + .withMaxNumberOfTries(10) + .retryOnAnyException() + .withDelayBetweenTries(ofSeconds(5)) + .withRandomExponentialBackoff() + .build(); + CallExecutor callExecutor = new CallExecutorBuilder() + .config(retryConfig) + .afterFailedTryListener(s -> {LOG.warn(s.getLastExceptionThatCausedRetry().getMessage() + ", attempts: " + s.getTotalTries());}) + .build(); + Status status = callExecutor.execute(callable); + return status.getResult(); + } + + private Trace getTrace(List traceIds) { + BatchGetTracesResponse tracesResponse = xray.batchGetTraces(BatchGetTracesRequest.builder() + .traceIds(traceIds) + .build()); + if (!tracesResponse.hasTraces()) { + throw new RuntimeException("No trace found"); + } + Trace traceRes = new Trace(); + tracesResponse.traces().forEach(trace -> { + if (trace.hasSegments()) { + trace.segments().forEach(segment -> { + try { + SegmentDocument document = MAPPER.readValue(segment.document(), SegmentDocument.class); + if (document.getOrigin().equals("AWS::Lambda::Function")) { + if (document.hasSubsegments()) { + getNestedSubSegments(document.getSubsegments(), traceRes, Collections.emptyList()); + } + } + } catch (JsonProcessingException e) { + LOG.error("Failed to parse segment document: " + e.getMessage()); + throw new RuntimeException(e); + } + }); + } + }); + return traceRes; + } + + private void getNestedSubSegments(List subsegments, Trace traceRes, List idsToIgnore) { + subsegments.forEach(subsegment -> { + List subSegmentIdsToIgnore = Collections.emptyList(); + if (!excludedSegments.contains(subsegment.getName()) && !idsToIgnore.contains(subsegment.getId())) { + traceRes.addSubSegment(subsegment); + if (subsegment.hasSubsegments()) { + subSegmentIdsToIgnore = subsegment.getSubsegments().stream().map(SubSegment::getId).collect(Collectors.toList()); + } + } + if (subsegment.hasSubsegments()) { + getNestedSubSegments(subsegment.getSubsegments(), traceRes, subSegmentIdsToIgnore); + } + }); + } + + private List getTraceIds() { + GetTraceSummariesResponse traceSummaries = xray.getTraceSummaries(GetTraceSummariesRequest.builder() + .startTime(start) + .endTime(end) + .timeRangeType(TimeRangeType.EVENT) + .sampling(false) + .filterExpression(filterExpression) + .build()); + if (!traceSummaries.hasTraceSummaries()) { + throw new RuntimeException("No trace id found"); + } + List traceIds = traceSummaries.traceSummaries().stream().map(TraceSummary::id).collect(Collectors.toList()); + if (traceIds.isEmpty()) { + throw new RuntimeException("No trace id found"); + } + return traceIds; + } + + private static final SdkHttpClient httpClient = UrlConnectionHttpClient.builder().build(); + private static final Region region = Region.of(System.getProperty("AWS_DEFAULT_REGION", "eu-west-1")); + private static final XRayClient xray = XRayClient.builder() + .httpClient(httpClient) + .region(region) + .build(); + + public static class Builder { + private Instant start; + private Instant end; + private String filterExpression; + private List excludedSegments = List.of("Initialization", "Invocation", "Overhead"); + + public TraceFetcher build() { + if (filterExpression == null) + throw new IllegalArgumentException("filterExpression or functionName is required"); + if (start == null) + throw new IllegalArgumentException("start is required"); + if (end == null) + end = start.plus(1, ChronoUnit.MINUTES); + LOG.debug("Looking for traces from {} to {} with filter {}", start, end, filterExpression); + return new TraceFetcher(start, end, filterExpression, excludedSegments); + } + + public Builder start(Instant start) { + this.start = start; + return this; + } + + public Builder end(Instant end) { + this.end = end; + return this; + } + + public Builder filterExpression(String filterExpression) { + this.filterExpression = filterExpression; + return this; + } + + /** + * "Initialization", "Invocation", "Overhead" are excluded by default + * @param excludedSegments + * @return + */ + public Builder excludeSegments(List excludedSegments) { + this.excludedSegments = excludedSegments; + return this; + } + + public Builder functionName(String functionName) { + this.filterExpression = String.format("service(id(name: \"%s\", type: \"AWS::Lambda::Function\"))", functionName); + return this; + } + } +} From 0dc66713633886aa1936e24e35425347f88abc3d Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Thu, 3 Nov 2022 13:18:58 +0100 Subject: [PATCH 05/13] refactoring --- powertools-e2e-tests/pom.xml | 15 -- .../lambda/powertools/IdempotencyE2ETest.java | 13 +- .../lambda/powertools/LoggingE2ETest.java | 13 +- .../lambda/powertools/MetricsE2ETest.java | 23 +-- .../lambda/powertools/TracingE2ETest.java | 18 +-- .../powertools/testutils/Infrastructure.java | 95 +---------- .../{ => lambda}/InvocationResult.java | 3 +- .../testutils/lambda/LambdaInvoker.java | 39 +++++ .../{ => logging}/InvocationLogs.java | 2 +- .../testutils/metrics/MetricsFetcher.java | 80 ++++++++++ .../testutils/tracing/SegmentDocument.java | 151 +++++++++++++++--- .../powertools/testutils/tracing/Trace.java | 5 +- .../testutils/tracing/TraceFetcher.java | 9 +- 13 files changed, 298 insertions(+), 168 deletions(-) rename powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/{ => lambda}/InvocationResult.java (87%) create mode 100644 powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/lambda/LambdaInvoker.java rename powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/{ => logging}/InvocationLogs.java (96%) create mode 100644 powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/metrics/MetricsFetcher.java diff --git a/powertools-e2e-tests/pom.xml b/powertools-e2e-tests/pom.xml index c93656698..797e1fe1c 100644 --- a/powertools-e2e-tests/pom.xml +++ b/powertools-e2e-tests/pom.xml @@ -17,17 +17,9 @@ 11 10.1.138 2.47.0 - 1.18.24 - - org.projectlombok - lombok - ${lombok.version} - provided - - ch.qos.logback logback-classic @@ -144,13 +136,6 @@ ${maven.compiler.source} ${maven.compiler.target} - - - org.projectlombok - lombok - ${lombok.version} - - UTF-8 diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/IdempotencyE2ETest.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/IdempotencyE2ETest.java index 37713ba5c..a7d95ee2d 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/IdempotencyE2ETest.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/IdempotencyE2ETest.java @@ -6,14 +6,17 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import software.amazon.lambda.powertools.testutils.Infrastructure; -import software.amazon.lambda.powertools.testutils.InvocationResult; +import software.amazon.lambda.powertools.testutils.lambda.InvocationResult; import java.time.Year; import java.util.HashMap; import java.util.concurrent.TimeUnit; +import static software.amazon.lambda.powertools.testutils.lambda.LambdaInvoker.invokeFunction; + public class IdempotencyE2ETest { private static Infrastructure infrastructure; + private static String functionName; @BeforeAll @Timeout(value = 5, unit = TimeUnit.MINUTES) @@ -27,7 +30,7 @@ public static void setup() { }} ) .build(); - infrastructure.deploy(); + functionName = infrastructure.deploy(); } @AfterAll @@ -43,15 +46,15 @@ public void test_ttlNotExpired_sameResult_ttlExpired_differentResult() throws In // WHEN // First invocation - InvocationResult result1 = infrastructure.invokeFunction(event); + InvocationResult result1 = invokeFunction(functionName, event); // Second invocation (should get same result) - InvocationResult result2 = infrastructure.invokeFunction(event); + InvocationResult result2 = invokeFunction(functionName, event); Thread.sleep(12000); // Third invocation (should get different result) - InvocationResult result3 = infrastructure.invokeFunction(event); + InvocationResult result3 = invokeFunction(functionName, event); // THEN Assertions.assertThat(result1.getResult()).contains(Year.now().toString()); diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ETest.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ETest.java index 5fba73d6c..a6c6bd47f 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ETest.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ETest.java @@ -8,19 +8,22 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import software.amazon.lambda.powertools.testutils.Infrastructure; -import software.amazon.lambda.powertools.testutils.InvocationResult; +import software.amazon.lambda.powertools.testutils.lambda.InvocationResult; import java.util.HashMap; import java.util.UUID; import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; -import static software.amazon.lambda.powertools.testutils.InvocationLogs.Level.INFO; +import static software.amazon.lambda.powertools.testutils.lambda.LambdaInvoker.invokeFunction; +import static software.amazon.lambda.powertools.testutils.logging.InvocationLogs.Level.INFO; public class LoggingE2ETest { private static final ObjectMapper objectMapper = new ObjectMapper(); + private static Infrastructure infrastructure; + private static String functionName; @BeforeAll @Timeout(value = 5, unit = TimeUnit.MINUTES) @@ -34,7 +37,7 @@ public static void setup() { }} ) .build(); - infrastructure.deploy(); + functionName = infrastructure.deploy(); } @AfterAll @@ -50,8 +53,8 @@ public void test_logInfoWithAdditionalKeys() throws JsonProcessingException { String event = "{\"message\":\"New Order\", \"keys\":{\"orderId\":\"" + orderId +"\"}}"; // WHEN - InvocationResult invocationResult1 = infrastructure.invokeFunction(event); - InvocationResult invocationResult2 = infrastructure.invokeFunction(event); + InvocationResult invocationResult1 = invokeFunction(functionName, event); + InvocationResult invocationResult2 = invokeFunction(functionName, event); // THEN String[] functionLogs = invocationResult1.getLogs().getFunctionLogs(INFO); diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/MetricsE2ETest.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/MetricsE2ETest.java index dd529bd29..0d7bf8223 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/MetricsE2ETest.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/MetricsE2ETest.java @@ -5,7 +5,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import software.amazon.lambda.powertools.testutils.Infrastructure; -import software.amazon.lambda.powertools.testutils.InvocationResult; +import software.amazon.lambda.powertools.testutils.lambda.InvocationResult; +import software.amazon.lambda.powertools.testutils.metrics.MetricsFetcher; import java.util.Collections; import java.util.HashMap; @@ -14,12 +15,13 @@ import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; +import static software.amazon.lambda.powertools.testutils.lambda.LambdaInvoker.invokeFunction; public class MetricsE2ETest { - private static Infrastructure infrastructure; - private static final String namespace = "MetricsE2ENamespace_"+UUID.randomUUID(); private static final String service = "MetricsE2EService_"+UUID.randomUUID(); + private static Infrastructure infrastructure; + private static String functionName; @BeforeAll @Timeout(value = 5, unit = TimeUnit.MINUTES) @@ -33,7 +35,7 @@ public static void setup() { }} ) .build(); - infrastructure.deploy(); + functionName = infrastructure.deploy(); } @AfterAll @@ -48,18 +50,19 @@ public void test_recordMetrics() { String event1 = "{ \"metrics\": {\"orders\": 1, \"products\": 4}, \"dimensions\": { \"Environment\": \"test\"} }"; // WHEN - InvocationResult invocationResult = infrastructure.invokeFunction(event1); + InvocationResult invocationResult = invokeFunction(functionName, event1); // THEN - List coldStart = infrastructure.getMetrics(invocationResult.getStart(), invocationResult.getEnd(), 60, namespace, "ColdStart", new HashMap<>() {{ put("FunctionName", infrastructure.getFunctionName()); put("Service", service); }}); + MetricsFetcher metricsFetcher = new MetricsFetcher(); + List coldStart = metricsFetcher.fetchMetrics(invocationResult.getStart(), invocationResult.getEnd(), 60, namespace, "ColdStart", new HashMap<>() {{ put("FunctionName", functionName); put("Service", service); }}); assertThat(coldStart.get(0)).isEqualTo(1); - List orderMetrics = infrastructure.getMetrics(invocationResult.getStart(), invocationResult.getEnd(), 60, namespace, "orders", Collections.singletonMap("Environment", "test")); + List orderMetrics = metricsFetcher.fetchMetrics(invocationResult.getStart(), invocationResult.getEnd(), 60, namespace, "orders", Collections.singletonMap("Environment", "test")); assertThat(orderMetrics.get(0)).isEqualTo(1); - List productMetrics = infrastructure.getMetrics(invocationResult.getStart(), invocationResult.getEnd(), 60, namespace, "products", Collections.singletonMap("Environment", "test")); + List productMetrics = metricsFetcher.fetchMetrics(invocationResult.getStart(), invocationResult.getEnd(), 60, namespace, "products", Collections.singletonMap("Environment", "test")); assertThat(productMetrics.get(0)).isEqualTo(4); - orderMetrics = infrastructure.getMetrics(invocationResult.getStart(), invocationResult.getEnd(), 60, namespace, "orders", Collections.singletonMap("Service", service)); + orderMetrics = metricsFetcher.fetchMetrics(invocationResult.getStart(), invocationResult.getEnd(), 60, namespace, "orders", Collections.singletonMap("Service", service)); assertThat(orderMetrics.get(0)).isEqualTo(1); - productMetrics = infrastructure.getMetrics(invocationResult.getStart(), invocationResult.getEnd(), 60, namespace, "products", Collections.singletonMap("Service", service)); + productMetrics = metricsFetcher.fetchMetrics(invocationResult.getStart(), invocationResult.getEnd(), 60, namespace, "products", Collections.singletonMap("Service", service)); assertThat(productMetrics.get(0)).isEqualTo(4); } } diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/TracingE2ETest.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/TracingE2ETest.java index 9fe63ad18..77c968bc7 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/TracingE2ETest.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/TracingE2ETest.java @@ -5,7 +5,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import software.amazon.lambda.powertools.testutils.Infrastructure; -import software.amazon.lambda.powertools.testutils.InvocationResult; +import software.amazon.lambda.powertools.testutils.lambda.InvocationResult; import software.amazon.lambda.powertools.testutils.tracing.SegmentDocument.SubSegment; import software.amazon.lambda.powertools.testutils.tracing.Trace; import software.amazon.lambda.powertools.testutils.tracing.TraceFetcher; @@ -16,11 +16,14 @@ import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; +import static software.amazon.lambda.powertools.testutils.lambda.LambdaInvoker.invokeFunction; public class TracingE2ETest { - private static Infrastructure infrastructure; private static final String service = "TracingE2EService_"+UUID.randomUUID(); // "TracingE2EService_e479fb27-422b-4107-9f8c-086c62e1cd12"; + private static Infrastructure infrastructure; + private static String functionName; + @BeforeAll @Timeout(value = 5, unit = TimeUnit.MINUTES) public static void setup() { @@ -33,7 +36,7 @@ public static void setup() { }} ) .build(); - infrastructure.deploy(); + functionName = infrastructure.deploy(); } @AfterAll @@ -47,19 +50,16 @@ public void test_tracing() { // GIVEN String message = "Hello World"; String event = String.format("{\"message\":\"%s\"}", message); - String result = String.format("%s (%s)", message, infrastructure.getFunctionName()); + String result = String.format("%s (%s)", message, functionName); // WHEN - InvocationResult invocationResult = infrastructure.invokeFunction(event); + InvocationResult invocationResult = invokeFunction(functionName, event); // THEN Trace trace = TraceFetcher.builder() .start(invocationResult.getStart()) .end(invocationResult.getEnd()) -// .start(Instant.ofEpochSecond(1667468280)) -// .end(Instant.ofEpochSecond(1667468340)) - .functionName(infrastructure.getFunctionName()) -// .functionName("TracingE2ETest-744e0e5ba909-function") + .functionName(functionName) .build() .fetchTrace(); diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/Infrastructure.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/Infrastructure.java index d918bbbe2..822f46c73 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/Infrastructure.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/Infrastructure.java @@ -1,10 +1,5 @@ package software.amazon.lambda.powertools.testutils; -import com.evanlennick.retry4j.CallExecutor; -import com.evanlennick.retry4j.CallExecutorBuilder; -import com.evanlennick.retry4j.Status; -import com.evanlennick.retry4j.config.RetryConfig; -import com.evanlennick.retry4j.config.RetryConfigBuilder; import com.fasterxml.jackson.databind.JsonNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,19 +17,12 @@ import software.amazon.awscdk.services.logs.LogGroup; import software.amazon.awscdk.services.logs.RetentionDays; import software.amazon.awscdk.services.s3.assets.AssetOptions; -import software.amazon.awssdk.core.SdkBytes; import software.amazon.awssdk.core.waiters.WaiterResponse; import software.amazon.awssdk.http.SdkHttpClient; import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.cloudformation.CloudFormationClient; import software.amazon.awssdk.services.cloudformation.model.*; -import software.amazon.awssdk.services.cloudwatch.CloudWatchClient; -import software.amazon.awssdk.services.cloudwatch.model.*; -import software.amazon.awssdk.services.lambda.LambdaClient; -import software.amazon.awssdk.services.lambda.model.InvokeRequest; -import software.amazon.awssdk.services.lambda.model.InvokeResponse; -import software.amazon.awssdk.services.lambda.model.LogType; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.ListObjectsV2Request; import software.amazon.awssdk.services.s3.model.ListObjectsV2Response; @@ -46,13 +34,8 @@ import java.io.File; import java.io.IOException; import java.nio.file.Path; -import java.time.Clock; -import java.time.Instant; import java.util.*; -import java.util.concurrent.Callable; -import static java.time.Duration.ofSeconds; -import static java.time.temporal.ChronoUnit.MINUTES; import static java.util.Collections.singletonList; public class Infrastructure { @@ -67,9 +50,7 @@ public class Infrastructure { private final long timeout; private final String pathToFunction; private final S3Client s3; - private final LambdaClient lambda; private final CloudFormationClient cfn; - private final CloudWatchClient cloudwatch; private final Region region; private final String account; private final String idempotencyTable; @@ -103,21 +84,13 @@ private Infrastructure(Builder builder) { .httpClient(httpClient) .region(region) .build(); - lambda = LambdaClient.builder() - .httpClient(httpClient) - .region(region) - .build(); - cloudwatch = CloudWatchClient.builder() - .httpClient(httpClient) - .region(region) - .build(); cfn = CloudFormationClient.builder() .httpClient(httpClient) .region(region) .build(); } - public void deploy() { + public String deploy() { uploadAssets(); LOG.debug("Deploying '" + stackName + "' on account " + account); cfn.createStack(CreateStackRequest.builder() @@ -133,6 +106,7 @@ public void deploy() { } else { throw new RuntimeException("Failed to create stack"); } + return functionName; } public void destroy() { @@ -140,75 +114,10 @@ public void destroy() { cfn.deleteStack(DeleteStackRequest.builder().stackName(stackName).build()); } - public InvocationResult invokeFunction(String input) { - SdkBytes payload = SdkBytes.fromUtf8String(input); - - InvokeRequest request = InvokeRequest.builder() - .functionName(functionName) - .payload(payload) - .logType(LogType.TAIL) - .build(); - - Instant start = Instant.now(Clock.systemUTC()).truncatedTo(MINUTES); - InvokeResponse response = lambda.invoke(request); - Instant end = start.plus(1, MINUTES); - return new InvocationResult(response, start, end); - } - - public List getMetrics(Instant start, Instant end, int period, String namespace, String metricName, Map dimensions) { - List dimensionsList = new ArrayList<>(); - if (dimensions != null) - dimensions.forEach((key, value) -> dimensionsList.add(Dimension.builder().name(key).value(value).build())); - - Callable> callable = () -> { - LOG.debug("Get Metrics for namespace {}, start {}, end {}, metric {}, dimensions {}", namespace, start, end, metricName, dimensionsList); - GetMetricDataResponse metricData = cloudwatch.getMetricData(GetMetricDataRequest.builder() - .startTime(start) - .endTime(end) - .metricDataQueries(MetricDataQuery.builder() - .id(metricName.toLowerCase()) - .metricStat(MetricStat.builder() - .unit(StandardUnit.COUNT) - .metric(Metric.builder() - .namespace(namespace) - .metricName(metricName) - .dimensions(dimensionsList) - .build()) - .period(period) - .stat("Sum") - .build()) - .returnData(true) - .build()) - .build()); - List values = metricData.metricDataResults().get(0).values(); - if (values == null || values.isEmpty()) { - throw new Exception("No data found for metric " + metricName); - } - return values; - }; - - RetryConfig retryConfig = new RetryConfigBuilder() - .withMaxNumberOfTries(10) - .retryOnAnyException() - .withDelayBetweenTries(ofSeconds(2)) - .withRandomExponentialBackoff() - .build(); - CallExecutor> callExecutor = new CallExecutorBuilder>() - .config(retryConfig) - .afterFailedTryListener(s -> {LOG.warn(s.getLastExceptionThatCausedRetry().getMessage() + ", attempts: " + s.getTotalTries());}) - .build(); - Status> status = callExecutor.execute(callable); - return status.getResult(); - } - public static Builder builder() { return new Builder(); } - public String getFunctionName() { - return functionName; - } - public static class Builder { public long timeoutInSeconds = 30; public String pathToFunction; diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/InvocationResult.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/lambda/InvocationResult.java similarity index 87% rename from powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/InvocationResult.java rename to powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/lambda/InvocationResult.java index a561d22c9..168fec71b 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/InvocationResult.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/lambda/InvocationResult.java @@ -1,6 +1,7 @@ -package software.amazon.lambda.powertools.testutils; +package software.amazon.lambda.powertools.testutils.lambda; import software.amazon.awssdk.services.lambda.model.InvokeResponse; +import software.amazon.lambda.powertools.testutils.logging.InvocationLogs; import java.time.Instant; diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/lambda/LambdaInvoker.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/lambda/LambdaInvoker.java new file mode 100644 index 000000000..ecde1042e --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/lambda/LambdaInvoker.java @@ -0,0 +1,39 @@ +package software.amazon.lambda.powertools.testutils.lambda; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.lambda.LambdaClient; +import software.amazon.awssdk.services.lambda.model.InvokeRequest; +import software.amazon.awssdk.services.lambda.model.InvokeResponse; +import software.amazon.awssdk.services.lambda.model.LogType; + +import java.time.Clock; +import java.time.Instant; + +import static java.time.temporal.ChronoUnit.MINUTES; + +public class LambdaInvoker { + private static final SdkHttpClient httpClient = UrlConnectionHttpClient.builder().build(); + private static final Region region = Region.of(System.getProperty("AWS_DEFAULT_REGION", "eu-west-1")); + private static final LambdaClient lambda = LambdaClient.builder() + .httpClient(httpClient) + .region(region) + .build(); + + public static InvocationResult invokeFunction(String functionName, String input) { + SdkBytes payload = SdkBytes.fromUtf8String(input); + + InvokeRequest request = InvokeRequest.builder() + .functionName(functionName) + .payload(payload) + .logType(LogType.TAIL) + .build(); + + Instant start = Instant.now(Clock.systemUTC()).truncatedTo(MINUTES); + InvokeResponse response = lambda.invoke(request); + Instant end = start.plus(1, MINUTES); + return new InvocationResult(response, start, end); + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/InvocationLogs.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/logging/InvocationLogs.java similarity index 96% rename from powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/InvocationLogs.java rename to powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/logging/InvocationLogs.java index 0fa7d8a22..bad4c4bcb 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/InvocationLogs.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/logging/InvocationLogs.java @@ -1,4 +1,4 @@ -package software.amazon.lambda.powertools.testutils; +package software.amazon.lambda.powertools.testutils.logging; import java.nio.charset.StandardCharsets; import java.util.Arrays; diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/metrics/MetricsFetcher.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/metrics/MetricsFetcher.java new file mode 100644 index 000000000..da44e4206 --- /dev/null +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/metrics/MetricsFetcher.java @@ -0,0 +1,80 @@ +package software.amazon.lambda.powertools.testutils.metrics; + +import com.evanlennick.retry4j.CallExecutor; +import com.evanlennick.retry4j.CallExecutorBuilder; +import com.evanlennick.retry4j.Status; +import com.evanlennick.retry4j.config.RetryConfig; +import com.evanlennick.retry4j.config.RetryConfigBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.cloudwatch.CloudWatchClient; +import software.amazon.awssdk.services.cloudwatch.model.*; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; + +import static java.time.Duration.ofSeconds; +public class MetricsFetcher { + private static final Logger LOG = LoggerFactory.getLogger(MetricsFetcher.class); + + private static final SdkHttpClient httpClient = UrlConnectionHttpClient.builder().build(); + private static final Region region = Region.of(System.getProperty("AWS_DEFAULT_REGION", "eu-west-1")); + private static final CloudWatchClient cloudwatch = CloudWatchClient.builder() + .httpClient(httpClient) + .region(region) + .build(); + + public List fetchMetrics(Instant start, Instant end, int period, String namespace, String metricName, Map dimensions) { + List dimensionsList = new ArrayList<>(); + if (dimensions != null) + dimensions.forEach((key, value) -> dimensionsList.add(Dimension.builder().name(key).value(value).build())); + + Callable> callable = () -> { + LOG.debug("Get Metrics for namespace {}, start {}, end {}, metric {}, dimensions {}", namespace, start, end, metricName, dimensionsList); + GetMetricDataResponse metricData = cloudwatch.getMetricData(GetMetricDataRequest.builder() + .startTime(start) + .endTime(end) + .metricDataQueries(MetricDataQuery.builder() + .id(metricName.toLowerCase()) + .metricStat(MetricStat.builder() + .unit(StandardUnit.COUNT) + .metric(Metric.builder() + .namespace(namespace) + .metricName(metricName) + .dimensions(dimensionsList) + .build()) + .period(period) + .stat("Sum") + .build()) + .returnData(true) + .build()) + .build()); + List values = metricData.metricDataResults().get(0).values(); + if (values == null || values.isEmpty()) { + throw new Exception("No data found for metric " + metricName); + } + return values; + }; + + RetryConfig retryConfig = new RetryConfigBuilder() + .withMaxNumberOfTries(10) + .retryOnAnyException() + .withDelayBetweenTries(ofSeconds(2)) + .withRandomExponentialBackoff() + .build(); + CallExecutor> callExecutor = new CallExecutorBuilder>() + .config(retryConfig) + .afterFailedTryListener(s -> { + LOG.warn(s.getLastExceptionThatCausedRetry().getMessage() + ", attempts: " + s.getTotalTries()); + }) + .build(); + Status> status = callExecutor.execute(callable); + return status.getResult(); + } +} diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/SegmentDocument.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/SegmentDocument.java index 18fcc044a..08f4bf7d8 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/SegmentDocument.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/SegmentDocument.java @@ -1,18 +1,12 @@ package software.amazon.lambda.powertools.testutils.tracing; import com.fasterxml.jackson.annotation.JsonSetter; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Map; -@NoArgsConstructor -@Setter -@Getter public class SegmentDocument { private String id; @@ -29,35 +23,75 @@ public class SegmentDocument { private String origin; - private Aws aws; - private List subsegments = new ArrayList<>(); - public Duration getDuration() { - return Duration.ofMillis(endTime - startTime); + public SegmentDocument() { } - public boolean hasSubsegments() { - return !subsegments.isEmpty(); + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTraceId() { + return traceId; } - @NoArgsConstructor - @Setter - @Getter - public static class Aws{ - @JsonSetter("account_id") - private long accountId; + public void setTraceId(String traceId) { + this.traceId = traceId; + } - @JsonSetter("function_arn") - private String functionArn; + public String getName() { + return name; + } - @JsonSetter("resource_names") - private String[] resourceNames; + public void setName(String name) { + this.name = name; + } + + public long getStartTime() { + return startTime; + } + + public void setStartTime(long startTime) { + this.startTime = startTime; + } + + public long getEndTime() { + return endTime; + } + + public void setEndTime(long endTime) { + this.endTime = endTime; + } + + public String getOrigin() { + return origin; + } + + public void setOrigin(String origin) { + this.origin = origin; + } + + public List getSubsegments() { + return subsegments; + } + + public void setSubsegments(List subsegments) { + this.subsegments = subsegments; + } + + public Duration getDuration() { + return Duration.ofMillis(endTime - startTime); + } + + public boolean hasSubsegments() { + return !subsegments.isEmpty(); } - @NoArgsConstructor - @Setter - @Getter public static class SubSegment{ private String id; @@ -77,6 +111,9 @@ public static class SubSegment{ private String namespace; + public SubSegment() { + } + public boolean hasSubsegments() { return !subsegments.isEmpty(); } @@ -84,5 +121,69 @@ public boolean hasSubsegments() { public Duration getDuration() { return Duration.ofMillis(endTime - startTime); } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getStartTime() { + return startTime; + } + + public void setStartTime(long startTime) { + this.startTime = startTime; + } + + public long getEndTime() { + return endTime; + } + + public void setEndTime(long endTime) { + this.endTime = endTime; + } + + public List getSubsegments() { + return subsegments; + } + + public void setSubsegments(List subsegments) { + this.subsegments = subsegments; + } + + public Map getAnnotations() { + return annotations; + } + + public void setAnnotations(Map annotations) { + this.annotations = annotations; + } + + public Map getMetadata() { + return metadata; + } + + public void setMetadata(Map metadata) { + this.metadata = metadata; + } + + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } } } diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/Trace.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/Trace.java index cb6f59f04..15026a9d1 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/Trace.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/Trace.java @@ -1,15 +1,16 @@ package software.amazon.lambda.powertools.testutils.tracing; -import lombok.NoArgsConstructor; import software.amazon.lambda.powertools.testutils.tracing.SegmentDocument.SubSegment; import java.util.ArrayList; import java.util.List; -@NoArgsConstructor public class Trace { private final List subsegments = new ArrayList<>(); + public Trace() { + } + public List getSubsegments() { return subsegments; } diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/TraceFetcher.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/TraceFetcher.java index d01174d92..4433876a1 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/TraceFetcher.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/TraceFetcher.java @@ -8,7 +8,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.AllArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import software.amazon.awssdk.http.SdkHttpClient; @@ -27,7 +26,6 @@ import static java.time.Duration.ofSeconds; -@AllArgsConstructor public class TraceFetcher { private static final ObjectMapper MAPPER = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); @@ -38,6 +36,13 @@ public class TraceFetcher { private final String filterExpression; private final List excludedSegments; + public TraceFetcher(Instant start, Instant end, String filterExpression, List excludedSegments) { + this.start = start; + this.end = end; + this.filterExpression = filterExpression; + this.excludedSegments = excludedSegments; + } + public static Builder builder() { return new Builder(); } From 8709ca0b0455e2cc6606a413b4ce80b7fa1a322f Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Thu, 3 Nov 2022 14:00:24 +0100 Subject: [PATCH 06/13] maven setup for e2e tests - rename to avoid execution during test phase - use a profile to execute them on demand only --- powertools-e2e-tests/README.md | 13 ++++++++++ powertools-e2e-tests/pom.xml | 26 +++++-------------- ...tencyE2ETest.java => IdempotencyE2ET.java} | 4 +-- .../{LoggingE2ETest.java => LoggingE2ET.java} | 6 ++--- .../{MetricsE2ETest.java => MetricsE2ET.java} | 4 +-- .../{TracingE2ETest.java => TracingE2ET.java} | 4 +-- 6 files changed, 28 insertions(+), 29 deletions(-) create mode 100644 powertools-e2e-tests/README.md rename powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/{IdempotencyE2ETest.java => IdempotencyE2ET.java} (95%) rename powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/{LoggingE2ETest.java => LoggingE2ET.java} (95%) rename powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/{MetricsE2ETest.java => MetricsE2ET.java} (97%) rename powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/{TracingE2ETest.java => TracingE2ET.java} (97%) diff --git a/powertools-e2e-tests/README.md b/powertools-e2e-tests/README.md new file mode 100644 index 000000000..ea1546025 --- /dev/null +++ b/powertools-e2e-tests/README.md @@ -0,0 +1,13 @@ +## End-to-end tests +This module is internal and meant to be used for end-to-end (E2E) testing of Lambda Powertools for Java. + +__Prerequisites__: an AWS account is needed as well as a local environment able to reach this account +([credentials](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/credentials.html)). + +To execute the E2E tests, use the following command: `mvn clean verify -Pe2e` + +### Under the hood +This module leverages the following components: +- AWS CDK to define the infrastructure and synthesize a CloudFormation template and the assets (lambda function packages) +- The AWS S3 SDK to push the assets on S3 +- The AWS CloudFormation SDK to deploy the template \ No newline at end of file diff --git a/powertools-e2e-tests/pom.xml b/powertools-e2e-tests/pom.xml index 797e1fe1c..b16758611 100644 --- a/powertools-e2e-tests/pom.xml +++ b/powertools-e2e-tests/pom.xml @@ -144,28 +144,9 @@ - it + e2e - - org.codehaus.mojo - build-helper-maven-plugin - 3.3.0 - - - add-test-source - process-resources - - add-test-source - - - - src/it/java - - - - - org.apache.maven.plugins maven-failsafe-plugin @@ -178,6 +159,11 @@ + + + **/*E2ET.java + + diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/IdempotencyE2ETest.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/IdempotencyE2ET.java similarity index 95% rename from powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/IdempotencyE2ETest.java rename to powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/IdempotencyE2ET.java index a7d95ee2d..900d47b3e 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/IdempotencyE2ETest.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/IdempotencyE2ET.java @@ -14,7 +14,7 @@ import static software.amazon.lambda.powertools.testutils.lambda.LambdaInvoker.invokeFunction; -public class IdempotencyE2ETest { +public class IdempotencyE2ET { private static Infrastructure infrastructure; private static String functionName; @@ -22,7 +22,7 @@ public class IdempotencyE2ETest { @Timeout(value = 5, unit = TimeUnit.MINUTES) public static void setup() { infrastructure = Infrastructure.builder() - .testName(IdempotencyE2ETest.class.getSimpleName()) + .testName(IdempotencyE2ET.class.getSimpleName()) .pathToFunction("idempotency") .idempotencyTable("idempo") .environmentVariables(new HashMap<>() {{ diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ETest.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ET.java similarity index 95% rename from powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ETest.java rename to powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ET.java index a6c6bd47f..16fb1c344 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ETest.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ET.java @@ -18,7 +18,7 @@ import static software.amazon.lambda.powertools.testutils.lambda.LambdaInvoker.invokeFunction; import static software.amazon.lambda.powertools.testutils.logging.InvocationLogs.Level.INFO; -public class LoggingE2ETest { +public class LoggingE2ET { private static final ObjectMapper objectMapper = new ObjectMapper(); @@ -29,11 +29,11 @@ public class LoggingE2ETest { @Timeout(value = 5, unit = TimeUnit.MINUTES) public static void setup() { infrastructure = Infrastructure.builder() - .testName(LoggingE2ETest.class.getSimpleName()) + .testName(LoggingE2ET.class.getSimpleName()) .pathToFunction("logging") .environmentVariables(new HashMap<>() {{ put("POWERTOOLS_LOG_LEVEL", "INFO"); - put("POWERTOOLS_SERVICE_NAME", LoggingE2ETest.class.getSimpleName()); + put("POWERTOOLS_SERVICE_NAME", LoggingE2ET.class.getSimpleName()); }} ) .build(); diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/MetricsE2ETest.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/MetricsE2ET.java similarity index 97% rename from powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/MetricsE2ETest.java rename to powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/MetricsE2ET.java index 0d7bf8223..c7668b66e 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/MetricsE2ETest.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/MetricsE2ET.java @@ -17,7 +17,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static software.amazon.lambda.powertools.testutils.lambda.LambdaInvoker.invokeFunction; -public class MetricsE2ETest { +public class MetricsE2ET { private static final String namespace = "MetricsE2ENamespace_"+UUID.randomUUID(); private static final String service = "MetricsE2EService_"+UUID.randomUUID(); private static Infrastructure infrastructure; @@ -27,7 +27,7 @@ public class MetricsE2ETest { @Timeout(value = 5, unit = TimeUnit.MINUTES) public static void setup() { infrastructure = Infrastructure.builder() - .testName(MetricsE2ETest.class.getSimpleName()) + .testName(MetricsE2ET.class.getSimpleName()) .pathToFunction("metrics") .environmentVariables(new HashMap<>() {{ put("POWERTOOLS_METRICS_NAMESPACE", namespace); diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/TracingE2ETest.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/TracingE2ET.java similarity index 97% rename from powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/TracingE2ETest.java rename to powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/TracingE2ET.java index 77c968bc7..1bb5b3c9c 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/TracingE2ETest.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/TracingE2ET.java @@ -18,7 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static software.amazon.lambda.powertools.testutils.lambda.LambdaInvoker.invokeFunction; -public class TracingE2ETest { +public class TracingE2ET { private static final String service = "TracingE2EService_"+UUID.randomUUID(); // "TracingE2EService_e479fb27-422b-4107-9f8c-086c62e1cd12"; private static Infrastructure infrastructure; @@ -28,7 +28,7 @@ public class TracingE2ETest { @Timeout(value = 5, unit = TimeUnit.MINUTES) public static void setup() { infrastructure = Infrastructure.builder() - .testName(TracingE2ETest.class.getSimpleName()) + .testName(TracingE2ET.class.getSimpleName()) .pathToFunction("tracing") .tracing(true) .environmentVariables(new HashMap<>() {{ From acbf7053f547c1d091b4fa17ba8b8c45716f0bf1 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Thu, 3 Nov 2022 17:36:01 +0100 Subject: [PATCH 07/13] clean poms and have a parent to simplify version bump --- .../handlers/idempotency/pom.xml | 21 +++---- powertools-e2e-tests/handlers/logging/pom.xml | 21 +++---- powertools-e2e-tests/handlers/metrics/pom.xml | 21 +++---- powertools-e2e-tests/handlers/pom.xml | 63 +++++++++++++++++++ powertools-e2e-tests/handlers/tracing/pom.xml | 21 +++---- 5 files changed, 95 insertions(+), 52 deletions(-) create mode 100644 powertools-e2e-tests/handlers/pom.xml diff --git a/powertools-e2e-tests/handlers/idempotency/pom.xml b/powertools-e2e-tests/handlers/idempotency/pom.xml index b47ebbfc8..f028cb089 100644 --- a/powertools-e2e-tests/handlers/idempotency/pom.xml +++ b/powertools-e2e-tests/handlers/idempotency/pom.xml @@ -2,33 +2,28 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 - software.amazon.lambda - E2EIdempotencyFunction - 1.12.3 - jar - A sample Hello World using powertools idempotency + + software.amazon.lambda + e2e-test-handlers-parent + 1.0.0 + - - 2.19.0 - UTF-8 - 1.12.3 - + e2e-test-handler-idempotency + jar + A Lambda function using powertools idempotency software.amazon.lambda powertools-idempotency - ${lambda.powertools.version} com.amazonaws aws-lambda-java-core - 1.2.1 com.amazonaws aws-lambda-java-events - 3.11.0 diff --git a/powertools-e2e-tests/handlers/logging/pom.xml b/powertools-e2e-tests/handlers/logging/pom.xml index 059cc5e31..6187d796e 100644 --- a/powertools-e2e-tests/handlers/logging/pom.xml +++ b/powertools-e2e-tests/handlers/logging/pom.xml @@ -2,33 +2,28 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 - software.amazon.lambda - E2ELoggingFunction - 1.12.3 - jar - A sample Hello World using powertools logging + + software.amazon.lambda + e2e-test-handlers-parent + 1.0.0 + - - 2.19.0 - UTF-8 - 1.12.3 - + e2e-test-handler-logging + jar + A Lambda function using powertools logging software.amazon.lambda powertools-logging - ${lambda.powertools.version} com.amazonaws aws-lambda-java-core - 1.2.1 com.amazonaws aws-lambda-java-events - 3.11.0 diff --git a/powertools-e2e-tests/handlers/metrics/pom.xml b/powertools-e2e-tests/handlers/metrics/pom.xml index a4fa4e1ad..0892c5047 100644 --- a/powertools-e2e-tests/handlers/metrics/pom.xml +++ b/powertools-e2e-tests/handlers/metrics/pom.xml @@ -2,33 +2,28 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 - software.amazon.lambda - E2EMetricsFunction - 1.12.3 - jar - A sample Hello World using powertools metrics + + software.amazon.lambda + e2e-test-handlers-parent + 1.0.0 + - - 2.19.0 - UTF-8 - 1.12.3 - + e2e-test-handler-metrics + jar + A Lambda function using powertools metrics software.amazon.lambda powertools-metrics - ${lambda.powertools.version} com.amazonaws aws-lambda-java-core - 1.2.1 com.amazonaws aws-lambda-java-events - 3.11.0 diff --git a/powertools-e2e-tests/handlers/pom.xml b/powertools-e2e-tests/handlers/pom.xml new file mode 100644 index 000000000..7c73d4bc6 --- /dev/null +++ b/powertools-e2e-tests/handlers/pom.xml @@ -0,0 +1,63 @@ + + 4.0.0 + + software.amazon.lambda + e2e-test-handlers-parent + 1.0.0 + pom + Handlers for End-to-End tests + Fake handlers that use Lambda Powertools for Java. + + + 1.12.3 + 1.2.1 + 3.11.0 + UTF-8 + 11 + 11 + + + + logging + tracing + metrics + idempotency + + + + + + software.amazon.lambda + powertools-logging + ${lambda.powertools.version} + + + software.amazon.lambda + powertools-tracing + ${lambda.powertools.version} + + + software.amazon.lambda + powertools-metrics + ${lambda.powertools.version} + + + software.amazon.lambda + powertools-idempotency + ${lambda.powertools.version} + + + com.amazonaws + aws-lambda-java-core + ${lambda.java.core} + + + com.amazonaws + aws-lambda-java-events + ${lambda.java.events} + + + + + diff --git a/powertools-e2e-tests/handlers/tracing/pom.xml b/powertools-e2e-tests/handlers/tracing/pom.xml index ddc22f4fd..fbd1ecbd1 100644 --- a/powertools-e2e-tests/handlers/tracing/pom.xml +++ b/powertools-e2e-tests/handlers/tracing/pom.xml @@ -2,33 +2,28 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 - software.amazon.lambda - E2ETracingFunction - 1.12.3 - jar - A sample Hello World using powertools tracing + + software.amazon.lambda + e2e-test-handlers-parent + 1.0.0 + - - 2.19.0 - UTF-8 - 1.12.3 - + e2e-test-handler-tracing + jar + A Lambda function using powertools tracing software.amazon.lambda powertools-tracing - ${lambda.powertools.version} com.amazonaws aws-lambda-java-core - 1.2.1 com.amazonaws aws-lambda-java-events - 3.11.0 From 1a69b2ad9cdd046ac6e9688e1996fdc1a374a94a Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Fri, 4 Nov 2022 11:25:03 +0100 Subject: [PATCH 08/13] add github action for e2e --- .github/workflows/build.yml | 3 +- .github/workflows/run-e2e-tests.yml | 46 +++++++++++++++++++ .../powertools/testutils/Infrastructure.java | 42 ++++++++++++----- 3 files changed, 78 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/run-e2e-tests.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3210dad84..d52217525 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -49,8 +49,7 @@ jobs: java: [8, 8.0.192, 11.0.x, 11.0.3, 12, 13, 15, 16, 17 ] name: Java ${{ matrix.java }} env: - OS: ${{ matrix.os }} - JAVA: ${{ matrix.java-version }} + JAVA: ${{ matrix.java }} AWS_REGION: eu-west-1 steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/run-e2e-tests.yml b/.github/workflows/run-e2e-tests.yml new file mode 100644 index 000000000..a1d7b205d --- /dev/null +++ b/.github/workflows/run-e2e-tests.yml @@ -0,0 +1,46 @@ +name: Run end-to-end tests + +on: + workflow_dispatch: + + push: + branches: [master] + paths: # add other modules when there are under e2e tests + - 'powertools-e2e-tests/**' + - 'powertools-core/**' + - 'powertools-serialization/**' + - 'powertools-logging/**' + - 'powertools-tracing/**' + - 'powertools-idempotency/**' + - 'powertools-metrics/**' + - 'pom.xml' + - '.github/workflows/**' + +jobs: + e2e: + runs-on: ubuntu-latest + strategy: + max-parallel: 2 + matrix: + java: [ 8, 11 ] + name: End-to-end tests ${{ matrix.java }} + env: + JAVA_VERSION: ${{ matrix.java }} + AWS_DEFAULT_REGION: eu-west-1 + permissions: + id-token: write # needed to interact with GitHub's OIDC Token endpoint. + contents: read + steps: + - uses: actions/checkout@v3 + - name: Setup java + uses: actions/setup-java@v2 + with: + distribution: 'corretto' + java-version: ${{ matrix.java }} + - name: Setup AWS credentials + uses: aws-actions/configure-aws-credentials@v1.6.1 + with: + role-to-assume: ${{ secrets.AWS_ROLE_ARN_TO_ASSUME }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} + - name: Run e2e test with Maven + run: mvn -Pe2e -B verify --file powertools-e2e-tests/pom.xml \ No newline at end of file diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/Infrastructure.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/Infrastructure.java index 822f46c73..2afd6a30a 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/Infrastructure.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/Infrastructure.java @@ -33,8 +33,10 @@ import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Path; import java.util.*; +import java.util.stream.Collectors; import static java.util.Collections.singletonList; @@ -92,7 +94,7 @@ private Infrastructure(Builder builder) { public String deploy() { uploadAssets(); - LOG.debug("Deploying '" + stackName + "' on account " + account); + LOG.info("Deploying '" + stackName + "' on account " + account); cfn.createStack(CreateStackRequest.builder() .stackName(stackName) .templateBody(new Yaml().dump(cfnTemplate)) @@ -102,7 +104,7 @@ public String deploy() { .build()); WaiterResponse waiterResponse = cfn.waiter().waitUntilStackCreateComplete(DescribeStacksRequest.builder().stackName(stackName).build()); if (waiterResponse.matched().response().isPresent()) { - LOG.debug("Stack " + waiterResponse.matched().response().get().stacks().get(0).stackName() + " successfully deployed"); + LOG.info("Stack " + waiterResponse.matched().response().get().stacks().get(0).stackName() + " successfully deployed"); } else { throw new RuntimeException("Failed to create stack"); } @@ -110,7 +112,7 @@ public String deploy() { } public void destroy() { - LOG.debug("Deleting '" + stackName + "' on account " + account); + LOG.info("Deleting '" + stackName + "' on account " + account); cfn.deleteStack(DeleteStackRequest.builder().stackName(stackName).build()); } @@ -124,10 +126,32 @@ public static class Builder { public String testName; private String stackName; private boolean tracing = false; - private JavaRuntime runtime = JavaRuntime.JAVA11; + private JavaRuntime runtime; private Map environmentVariables = new HashMap<>(); private String idemPotencyTable; + private Builder() { + getJavaRuntime(); + } + + /** + * Retrieve the java runtime to use for the lambda function. + */ + private void getJavaRuntime() { + String javaVersion = System.getenv("JAVA_VERSION"); // must be set in GitHub actions + if (javaVersion == null) { + throw new IllegalArgumentException("JAVA_VERSION is not set"); + } + if (javaVersion.startsWith("8")) { + runtime = JavaRuntime.JAVA8AL2; + } else if (javaVersion.startsWith("11")) { + runtime = JavaRuntime.JAVA11; + } else { + throw new IllegalArgumentException("Unsupported Java version " + javaVersion); + } + LOG.debug("Java Version set to {}, using runtime {}", javaVersion, runtime.getRuntime()); + } + public Infrastructure build() { Objects.requireNonNull(testName, "testName must not be null"); @@ -153,11 +177,6 @@ public Builder tracing(boolean tracing) { return this; } - public Builder runtime(JavaRuntime runtime) { - this.runtime = runtime; - return this; - } - public Builder idempotencyTable(String tableName) { this.idemPotencyTable = tableName; return this; @@ -203,6 +222,7 @@ private Stack createStackWithLambda() { functionName = stackName + "-function"; + LOG.debug("Building Lambda function with command "+ packagingInstruction.stream().collect(Collectors.joining(" ", "[", "]"))); Function function = Function.Builder .create(stack, functionName) .code(Code.fromAsset("handlers/", AssetOptions.builder() @@ -255,10 +275,10 @@ private void uploadAssets() { } ListObjectsV2Response objects = s3.listObjectsV2(ListObjectsV2Request.builder().bucket(asset.bucketName).build()); if (objects.contents().stream().anyMatch(o -> o.key().equals(objectKey))) { - System.out.println("Asset already exists, skipping"); + LOG.debug("Asset already exists, skipping"); return; } - System.out.println("Uploading asset " + objectKey + " to " + asset.bucketName); + LOG.info("Uploading asset " + objectKey + " to " + asset.bucketName); s3.putObject(PutObjectRequest.builder().bucket(asset.bucketName).key(objectKey).build(), Path.of(cfnAssetDirectory, asset.assetPath)); }); } From 6e5fadb295690a9a527d61b87fe1ddbf726186e6 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Fri, 4 Nov 2022 11:49:24 +0100 Subject: [PATCH 09/13] add maven cache to github actions --- .github/workflows/build.yml | 1 + .github/workflows/run-e2e-tests.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d52217525..83855525f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -58,6 +58,7 @@ jobs: with: distribution: 'zulu' java-version: ${{ matrix.java }} + cache: maven - name: Build with Maven run: mvn -Pbuild-without-spotbugs -B package --file pom.xml savepr: diff --git a/.github/workflows/run-e2e-tests.yml b/.github/workflows/run-e2e-tests.yml index a1d7b205d..d2bdf920f 100644 --- a/.github/workflows/run-e2e-tests.yml +++ b/.github/workflows/run-e2e-tests.yml @@ -37,6 +37,7 @@ jobs: with: distribution: 'corretto' java-version: ${{ matrix.java }} + cache: maven - name: Setup AWS credentials uses: aws-actions/configure-aws-credentials@v1.6.1 with: From 4c1b6d20237fc859ad3a694b0eea49fd8fa62235 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Mon, 7 Nov 2022 13:55:24 +0100 Subject: [PATCH 10/13] remove github actions e2e tests --- .github/workflows/run-e2e-tests.yml | 47 ----------------------------- 1 file changed, 47 deletions(-) delete mode 100644 .github/workflows/run-e2e-tests.yml diff --git a/.github/workflows/run-e2e-tests.yml b/.github/workflows/run-e2e-tests.yml deleted file mode 100644 index d2bdf920f..000000000 --- a/.github/workflows/run-e2e-tests.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Run end-to-end tests - -on: - workflow_dispatch: - - push: - branches: [master] - paths: # add other modules when there are under e2e tests - - 'powertools-e2e-tests/**' - - 'powertools-core/**' - - 'powertools-serialization/**' - - 'powertools-logging/**' - - 'powertools-tracing/**' - - 'powertools-idempotency/**' - - 'powertools-metrics/**' - - 'pom.xml' - - '.github/workflows/**' - -jobs: - e2e: - runs-on: ubuntu-latest - strategy: - max-parallel: 2 - matrix: - java: [ 8, 11 ] - name: End-to-end tests ${{ matrix.java }} - env: - JAVA_VERSION: ${{ matrix.java }} - AWS_DEFAULT_REGION: eu-west-1 - permissions: - id-token: write # needed to interact with GitHub's OIDC Token endpoint. - contents: read - steps: - - uses: actions/checkout@v3 - - name: Setup java - uses: actions/setup-java@v2 - with: - distribution: 'corretto' - java-version: ${{ matrix.java }} - cache: maven - - name: Setup AWS credentials - uses: aws-actions/configure-aws-credentials@v1.6.1 - with: - role-to-assume: ${{ secrets.AWS_ROLE_ARN_TO_ASSUME }} - aws-region: ${{ env.AWS_DEFAULT_REGION }} - - name: Run e2e test with Maven - run: mvn -Pe2e -B verify --file powertools-e2e-tests/pom.xml \ No newline at end of file From 841269e152f5026853a7b274ac6c4f60d65e3937 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Fri, 3 Mar 2023 18:04:40 +0100 Subject: [PATCH 11/13] cleaning poms --- pom.xml | 3 +- .../handlers/idempotency/pom.xml | 41 ------------ powertools-e2e-tests/handlers/logging/pom.xml | 41 ------------ powertools-e2e-tests/handlers/metrics/pom.xml | 41 ------------ powertools-e2e-tests/handlers/pom.xml | 62 ++++++++++++++++++- powertools-e2e-tests/handlers/tracing/pom.xml | 41 ------------ powertools-e2e-tests/pom.xml | 4 +- 7 files changed, 64 insertions(+), 169 deletions(-) diff --git a/pom.xml b/pom.xml index aca2162e1..85dff50aa 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,8 @@ powertools-validation powertools-test-suite powertools-cloudformation - powertools-idempotency + powertools-idempotency + powertools-e2e-tests examples diff --git a/powertools-e2e-tests/handlers/idempotency/pom.xml b/powertools-e2e-tests/handlers/idempotency/pom.xml index f028cb089..aa7870389 100644 --- a/powertools-e2e-tests/handlers/idempotency/pom.xml +++ b/powertools-e2e-tests/handlers/idempotency/pom.xml @@ -17,10 +17,6 @@ software.amazon.lambda powertools-idempotency - - com.amazonaws - aws-lambda-java-core - com.amazonaws aws-lambda-java-events @@ -33,7 +29,6 @@ org.codehaus.mojo aspectj-maven-plugin - 1.14.0 ${maven.compiler.source} ${maven.compiler.target} @@ -56,42 +51,6 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 - - false - function - - - - package - - shade - - - - - - - - - - - - io.github.edwgiz - log4j-maven-shade-plugin-extensions - 2.17.2 - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.10.1 - - ${maven.compiler.source} - ${maven.compiler.target} - diff --git a/powertools-e2e-tests/handlers/logging/pom.xml b/powertools-e2e-tests/handlers/logging/pom.xml index 6187d796e..a46702ca4 100644 --- a/powertools-e2e-tests/handlers/logging/pom.xml +++ b/powertools-e2e-tests/handlers/logging/pom.xml @@ -17,10 +17,6 @@ software.amazon.lambda powertools-logging - - com.amazonaws - aws-lambda-java-core - com.amazonaws aws-lambda-java-events @@ -33,7 +29,6 @@ org.codehaus.mojo aspectj-maven-plugin - 1.14.0 ${maven.compiler.source} ${maven.compiler.target} @@ -56,42 +51,6 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 - - false - function - - - - package - - shade - - - - - - - - - - - - io.github.edwgiz - log4j-maven-shade-plugin-extensions - 2.17.2 - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.10.1 - - ${maven.compiler.source} - ${maven.compiler.target} - diff --git a/powertools-e2e-tests/handlers/metrics/pom.xml b/powertools-e2e-tests/handlers/metrics/pom.xml index 0892c5047..e591f4966 100644 --- a/powertools-e2e-tests/handlers/metrics/pom.xml +++ b/powertools-e2e-tests/handlers/metrics/pom.xml @@ -17,10 +17,6 @@ software.amazon.lambda powertools-metrics - - com.amazonaws - aws-lambda-java-core - com.amazonaws aws-lambda-java-events @@ -33,7 +29,6 @@ org.codehaus.mojo aspectj-maven-plugin - 1.14.0 ${maven.compiler.source} ${maven.compiler.target} @@ -56,42 +51,6 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 - - false - function - - - - package - - shade - - - - - - - - - - - - io.github.edwgiz - log4j-maven-shade-plugin-extensions - 2.17.2 - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.10.1 - - ${maven.compiler.source} - ${maven.compiler.target} - diff --git a/powertools-e2e-tests/handlers/pom.xml b/powertools-e2e-tests/handlers/pom.xml index 7c73d4bc6..b82a9a0c9 100644 --- a/powertools-e2e-tests/handlers/pom.xml +++ b/powertools-e2e-tests/handlers/pom.xml @@ -10,12 +10,16 @@ Fake handlers that use Lambda Powertools for Java. - 1.12.3 - 1.2.1 - 3.11.0 + 1.14.0 UTF-8 11 11 + + 1.2.2 + 3.11.0 + 3.2.4 + 1.14.0 + 3.10.1 @@ -60,4 +64,56 @@ + + + + + org.apache.maven.plugins + maven-shade-plugin + ${maven.shade.version} + + false + function + + + + package + + shade + + + + + + + + + + + + io.github.edwgiz + log4j-maven-shade-plugin-extensions + 2.17.2 + + + + + org.codehaus.mojo + aspectj-maven-plugin + ${aspectj.version} + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.compiler.version} + + ${maven.compiler.source} + ${maven.compiler.target} + + + + + + diff --git a/powertools-e2e-tests/handlers/tracing/pom.xml b/powertools-e2e-tests/handlers/tracing/pom.xml index fbd1ecbd1..831669a3d 100644 --- a/powertools-e2e-tests/handlers/tracing/pom.xml +++ b/powertools-e2e-tests/handlers/tracing/pom.xml @@ -17,10 +17,6 @@ software.amazon.lambda powertools-tracing - - com.amazonaws - aws-lambda-java-core - com.amazonaws aws-lambda-java-events @@ -33,7 +29,6 @@ org.codehaus.mojo aspectj-maven-plugin - 1.14.0 ${maven.compiler.source} ${maven.compiler.target} @@ -56,42 +51,6 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 - - false - function - - - - package - - shade - - - - - - - - - - - - io.github.edwgiz - log4j-maven-shade-plugin-extensions - 2.17.2 - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.10.1 - - ${maven.compiler.source} - ${maven.compiler.target} - diff --git a/powertools-e2e-tests/pom.xml b/powertools-e2e-tests/pom.xml index b16758611..ba10d9c9e 100644 --- a/powertools-e2e-tests/pom.xml +++ b/powertools-e2e-tests/pom.xml @@ -6,13 +6,15 @@ powertools-parent software.amazon.lambda - 1.12.3 + 1.14.0 powertools-e2e-tests + AWS Lambda Powertools for Java library End-to-end tests AWS Lambda Powertools for Java End-To-End Tests + 11 11 10.1.138 From 7b81e22fb33f15278eed6465bae8f3a11b4918d3 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Fri, 3 Mar 2023 18:05:00 +0100 Subject: [PATCH 12/13] improve documentation --- powertools-e2e-tests/README.md | 7 ++-- .../powertools/testutils/Infrastructure.java | 32 ++++++++++++++++++- .../testutils/logging/InvocationLogs.java | 7 ++-- .../testutils/metrics/MetricsFetcher.java | 15 +++++++++ .../testutils/tracing/TraceFetcher.java | 24 ++++++++++++++ 5 files changed, 80 insertions(+), 5 deletions(-) diff --git a/powertools-e2e-tests/README.md b/powertools-e2e-tests/README.md index ea1546025..42fe9191b 100644 --- a/powertools-e2e-tests/README.md +++ b/powertools-e2e-tests/README.md @@ -1,10 +1,13 @@ ## End-to-end tests This module is internal and meant to be used for end-to-end (E2E) testing of Lambda Powertools for Java. -__Prerequisites__: an AWS account is needed as well as a local environment able to reach this account +__Prerequisites__: +- An AWS account is needed as well as a local environment able to reach this account ([credentials](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/credentials.html)). +- [Java 11+](https://docs.aws.amazon.com/corretto/latest/corretto-11-ug/downloads-list.html) +- [Docker](https://docs.docker.com/engine/install/) -To execute the E2E tests, use the following command: `mvn clean verify -Pe2e` +To execute the E2E tests, use the following command: `export JAVA_VERSION=11 && mvn clean verify -Pe2e` ### Under the hood This module leverages the following components: diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/Infrastructure.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/Infrastructure.java index 2afd6a30a..a1d17369e 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/Infrastructure.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/Infrastructure.java @@ -33,13 +33,22 @@ import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.nio.file.Path; import java.util.*; import java.util.stream.Collectors; import static java.util.Collections.singletonList; +/** + * This class is in charge of bootstrapping the infrastructure for the tests. + *
+ * Tests are actually run on AWS, so we need to provision Lambda functions, DynamoDB table (for Idempotency), + * CloudWatch log groups, ... + *
+ * It uses the Cloud Development Kit (CDK) to define required resources. The CDK stack is then synthesized to retrieve + * the CloudFormation templates and the assets (function jars). Assets are uploaded to S3 (with the SDK `PutObjectRequest`) + * and the CloudFormation stack is created (with the SDK `createStack`) + */ public class Infrastructure { private static final Logger LOG = LoggerFactory.getLogger(Infrastructure.class); @@ -92,6 +101,10 @@ private Infrastructure(Builder builder) { .build(); } + /** + * Use the CloudFormation SDK to create the stack + * @return the name of the function deployed part of the stack + */ public String deploy() { uploadAssets(); LOG.info("Deploying '" + stackName + "' on account " + account); @@ -111,6 +124,9 @@ public String deploy() { return functionName; } + /** + * Destroy the CloudFormation stack + */ public void destroy() { LOG.info("Deleting '" + stackName + "' on account " + account); cfn.deleteStack(DeleteStackRequest.builder().stackName(stackName).build()); @@ -193,6 +209,10 @@ public Builder timeoutInSeconds(long timeoutInSeconds) { } } + /** + * Build the CDK Stack containing the required resources (Lambda function, LogGroup, DDB Table) + * @return the CDK stack + */ private Stack createStackWithLambda() { Stack stack = new Stack(app, stackName); List packagingInstruction = Arrays.asList( @@ -261,12 +281,18 @@ private Stack createStackWithLambda() { return stack; } + /** + * cdk synth to retrieve the CloudFormation template and assets directory + */ private void synthesize() { CloudAssembly synth = app.synth(); cfnTemplate = synth.getStackByName(stack.getStackName()).getTemplate(); cfnAssetDirectory = synth.getDirectory(); } + /** + * Upload assets (mainly lambda function jars) to S3 + */ private void uploadAssets() { Map assets = findAssets(); assets.forEach((objectKey, asset) -> { @@ -283,6 +309,10 @@ private void uploadAssets() { }); } + /** + * Reading the cdk assets.json file to retrieve the list of assets to push to S3 + * @return a map of assets + */ private Map findAssets() { Map assets = new HashMap<>(); try { diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/logging/InvocationLogs.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/logging/InvocationLogs.java index bad4c4bcb..1ae1cfad7 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/logging/InvocationLogs.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/logging/InvocationLogs.java @@ -5,9 +5,12 @@ import java.util.Base64; import java.util.stream.IntStream; +/** + * Logs for a specific Lambda invocation + */ public class InvocationLogs { - private String[] logs; - private String[] functionLogs; + private final String[] logs; + private final String[] functionLogs; public InvocationLogs(String base64Logs, String requestId) { String rawLogs = new String(Base64.getDecoder().decode(base64Logs), StandardCharsets.UTF_8); diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/metrics/MetricsFetcher.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/metrics/MetricsFetcher.java index da44e4206..eb3cd63a4 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/metrics/MetricsFetcher.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/metrics/MetricsFetcher.java @@ -20,6 +20,10 @@ import java.util.concurrent.Callable; import static java.time.Duration.ofSeconds; + +/** + * Class in charge of retrieving the actual metrics of a Lambda execution on CloudWatch + */ public class MetricsFetcher { private static final Logger LOG = LoggerFactory.getLogger(MetricsFetcher.class); @@ -30,6 +34,17 @@ public class MetricsFetcher { .region(region) .build(); + /** + * Retrieve the metric values from start to end. Different parameters are required (see {@link CloudWatchClient#getMetricData} for more info). + * Use a retry mechanism as metrics may not be available instantaneously after a function runs. + * @param start + * @param end + * @param period + * @param namespace + * @param metricName + * @param dimensions + * @return + */ public List fetchMetrics(Instant start, Instant end, int period, String namespace, String metricName, Map dimensions) { List dimensionsList = new ArrayList<>(); if (dimensions != null) diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/TraceFetcher.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/TraceFetcher.java index 4433876a1..b26dbd0fe 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/TraceFetcher.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/TraceFetcher.java @@ -26,6 +26,9 @@ import static java.time.Duration.ofSeconds; +/** + * Class in charge of retrieving the actual traces of a Lambda execution on X-Ray + */ public class TraceFetcher { private static final ObjectMapper MAPPER = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); @@ -36,6 +39,12 @@ public class TraceFetcher { private final String filterExpression; private final List excludedSegments; + /** + * @param start beginning of the time slot to search in + * @param end end of the time slot to search in + * @param filterExpression eventual filter for the search + * @param excludedSegments list of segment to exclude from the search + */ public TraceFetcher(Instant start, Instant end, String filterExpression, List excludedSegments) { this.start = start; this.end = end; @@ -47,6 +56,12 @@ public static Builder builder() { return new Builder(); } + /** + * Retrieve the traces corresponding to a specific function during a specific time slot. + * Use a retry mechanism as traces may not be available instantaneously after a function runs. + * + * @return traces + */ public Trace fetchTrace() { Callable callable = () -> { List traceIds = getTraceIds(); @@ -67,6 +82,11 @@ public Trace fetchTrace() { return status.getResult(); } + /** + * Retrieve traces from trace ids. + * @param traceIds + * @return + */ private Trace getTrace(List traceIds) { BatchGetTracesResponse tracesResponse = xray.batchGetTraces(BatchGetTracesRequest.builder() .traceIds(traceIds) @@ -110,6 +130,10 @@ private void getNestedSubSegments(List subsegments, Trace traceRes, }); } + /** + * Use the X-Ray SDK to retrieve the trace ids corresponding to a specific function during a specific time slot + * @return a list of trace ids + */ private List getTraceIds() { GetTraceSummariesResponse traceSummaries = xray.getTraceSummaries(GetTraceSummariesRequest.builder() .startTime(start) From 09fd6eb9dfe96282f5341c2f3137f9800a3a3530 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Fri, 17 Mar 2023 13:47:37 +0100 Subject: [PATCH 13/13] jdk8 compatible --- powertools-e2e-tests/pom.xml | 4 ++-- .../lambda/powertools/IdempotencyE2ET.java | 7 ++----- .../amazon/lambda/powertools/LoggingE2ET.java | 14 ++++++++------ .../amazon/lambda/powertools/MetricsE2ET.java | 19 ++++++++++++------- .../amazon/lambda/powertools/TracingE2ET.java | 9 +++------ .../powertools/testutils/Infrastructure.java | 4 ++-- .../testutils/tracing/TraceFetcher.java | 3 ++- 7 files changed, 31 insertions(+), 29 deletions(-) diff --git a/powertools-e2e-tests/pom.xml b/powertools-e2e-tests/pom.xml index ba10d9c9e..77e9fa458 100644 --- a/powertools-e2e-tests/pom.xml +++ b/powertools-e2e-tests/pom.xml @@ -15,8 +15,8 @@ - 11 - 11 + 8 + 8 10.1.138 2.47.0 diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/IdempotencyE2ET.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/IdempotencyE2ET.java index 900d47b3e..4133f986f 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/IdempotencyE2ET.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/IdempotencyE2ET.java @@ -9,7 +9,7 @@ import software.amazon.lambda.powertools.testutils.lambda.InvocationResult; import java.time.Year; -import java.util.HashMap; +import java.util.Collections; import java.util.concurrent.TimeUnit; import static software.amazon.lambda.powertools.testutils.lambda.LambdaInvoker.invokeFunction; @@ -25,10 +25,7 @@ public static void setup() { .testName(IdempotencyE2ET.class.getSimpleName()) .pathToFunction("idempotency") .idempotencyTable("idempo") - .environmentVariables(new HashMap<>() {{ - put("IDEMPOTENCY_TABLE", "idempo"); - }} - ) + .environmentVariables(Collections.singletonMap("IDEMPOTENCY_TABLE", "idempo")) .build(); functionName = infrastructure.deploy(); } diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ET.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ET.java index 16fb1c344..15c5eb935 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ET.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ET.java @@ -10,9 +10,10 @@ import software.amazon.lambda.powertools.testutils.Infrastructure; import software.amazon.lambda.powertools.testutils.lambda.InvocationResult; -import java.util.HashMap; import java.util.UUID; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; import static software.amazon.lambda.powertools.testutils.lambda.LambdaInvoker.invokeFunction; @@ -31,11 +32,12 @@ public static void setup() { infrastructure = Infrastructure.builder() .testName(LoggingE2ET.class.getSimpleName()) .pathToFunction("logging") - .environmentVariables(new HashMap<>() {{ - put("POWERTOOLS_LOG_LEVEL", "INFO"); - put("POWERTOOLS_SERVICE_NAME", LoggingE2ET.class.getSimpleName()); - }} - ) + .environmentVariables( + Stream.of(new String[][]{ + {"POWERTOOLS_LOG_LEVEL", "INFO"}, + {"POWERTOOLS_SERVICE_NAME", LoggingE2ET.class.getSimpleName()} + }) + .collect(Collectors.toMap(data -> data[0], data -> data[1]))) .build(); functionName = infrastructure.deploy(); } diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/MetricsE2ET.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/MetricsE2ET.java index c7668b66e..4b8d7ea5a 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/MetricsE2ET.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/MetricsE2ET.java @@ -9,10 +9,11 @@ import software.amazon.lambda.powertools.testutils.metrics.MetricsFetcher; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; import static software.amazon.lambda.powertools.testutils.lambda.LambdaInvoker.invokeFunction; @@ -29,11 +30,12 @@ public static void setup() { infrastructure = Infrastructure.builder() .testName(MetricsE2ET.class.getSimpleName()) .pathToFunction("metrics") - .environmentVariables(new HashMap<>() {{ - put("POWERTOOLS_METRICS_NAMESPACE", namespace); - put("POWERTOOLS_SERVICE_NAME", service); - }} - ) + .environmentVariables( + Stream.of(new String[][]{ + {"POWERTOOLS_METRICS_NAMESPACE", namespace}, + {"POWERTOOLS_SERVICE_NAME", service} + }) + .collect(Collectors.toMap(data -> data[0], data -> data[1]))) .build(); functionName = infrastructure.deploy(); } @@ -54,7 +56,10 @@ public void test_recordMetrics() { // THEN MetricsFetcher metricsFetcher = new MetricsFetcher(); - List coldStart = metricsFetcher.fetchMetrics(invocationResult.getStart(), invocationResult.getEnd(), 60, namespace, "ColdStart", new HashMap<>() {{ put("FunctionName", functionName); put("Service", service); }}); + List coldStart = metricsFetcher.fetchMetrics(invocationResult.getStart(), invocationResult.getEnd(), 60, namespace, "ColdStart", Stream.of(new String[][]{ + {"FunctionName", functionName}, + {"Service", service}} + ).collect(Collectors.toMap(data -> data[0], data -> data[1]))); assertThat(coldStart.get(0)).isEqualTo(1); List orderMetrics = metricsFetcher.fetchMetrics(invocationResult.getStart(), invocationResult.getEnd(), 60, namespace, "orders", Collections.singletonMap("Environment", "test")); assertThat(orderMetrics.get(0)).isEqualTo(1); diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/TracingE2ET.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/TracingE2ET.java index 1bb5b3c9c..1f002fe60 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/TracingE2ET.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/TracingE2ET.java @@ -10,7 +10,7 @@ import software.amazon.lambda.powertools.testutils.tracing.Trace; import software.amazon.lambda.powertools.testutils.tracing.TraceFetcher; -import java.util.HashMap; +import java.util.Collections; import java.util.Map; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -19,7 +19,7 @@ import static software.amazon.lambda.powertools.testutils.lambda.LambdaInvoker.invokeFunction; public class TracingE2ET { - private static final String service = "TracingE2EService_"+UUID.randomUUID(); // "TracingE2EService_e479fb27-422b-4107-9f8c-086c62e1cd12"; + private static final String service = "TracingE2EService_" + UUID.randomUUID(); // "TracingE2EService_e479fb27-422b-4107-9f8c-086c62e1cd12"; private static Infrastructure infrastructure; private static String functionName; @@ -31,10 +31,7 @@ public static void setup() { .testName(TracingE2ET.class.getSimpleName()) .pathToFunction("tracing") .tracing(true) - .environmentVariables(new HashMap<>() {{ - put("POWERTOOLS_SERVICE_NAME", service); - }} - ) + .environmentVariables(Collections.singletonMap("POWERTOOLS_SERVICE_NAME", service)) .build(); functionName = infrastructure.deploy(); } diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/Infrastructure.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/Infrastructure.java index a1d17369e..f3659e5c7 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/Infrastructure.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/Infrastructure.java @@ -33,7 +33,7 @@ import java.io.File; import java.io.IOException; -import java.nio.file.Path; +import java.nio.file.Paths; import java.util.*; import java.util.stream.Collectors; @@ -305,7 +305,7 @@ private void uploadAssets() { return; } LOG.info("Uploading asset " + objectKey + " to " + asset.bucketName); - s3.putObject(PutObjectRequest.builder().bucket(asset.bucketName).key(objectKey).build(), Path.of(cfnAssetDirectory, asset.assetPath)); + s3.putObject(PutObjectRequest.builder().bucket(asset.bucketName).key(objectKey).build(), Paths.get(cfnAssetDirectory, asset.assetPath)); }); } diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/TraceFetcher.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/TraceFetcher.java index b26dbd0fe..e7cd13640 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/TraceFetcher.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/testutils/tracing/TraceFetcher.java @@ -19,6 +19,7 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; @@ -163,7 +164,7 @@ public static class Builder { private Instant start; private Instant end; private String filterExpression; - private List excludedSegments = List.of("Initialization", "Invocation", "Overhead"); + private List excludedSegments = Arrays.asList("Initialization", "Invocation", "Overhead"); public TraceFetcher build() { if (filterExpression == null)