From 0547c992fad0eab185654b54dfd04f2f2c83105a Mon Sep 17 00:00:00 2001 From: Nugusbayev Kanagat Date: Mon, 6 Nov 2023 22:00:28 +0600 Subject: [PATCH 1/8] added support to Elastic Load Balancer transactions --- .../APIGatewayProxyV1TransactionHelper.java | 42 +---- .../awslambda/helper/AWSEventsHelper.java | 7 + .../AbstractAPIGatewayTransactionHelper.java | 42 ++++- ...nLoadBalancerRequestTransactionHelper.java | 157 ++++++++++++++++++ ...LoadBalancerElbTargetGroupArnMetadata.java | 52 ++++++ ...ionLoadBalancerRequestEventLambdaTest.java | 157 ++++++++++++++++++ ...tionLoadBalancerRequestLambdaFunction.java | 34 ++++ 7 files changed, 450 insertions(+), 41 deletions(-) create mode 100644 apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/ApplicationLoadBalancerRequestTransactionHelper.java create mode 100644 apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/LoadBalancerElbTargetGroupArnMetadata.java create mode 100644 apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/ApplicationLoadBalancerRequestEventLambdaTest.java create mode 100644 apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/lambdas/ApplicationLoadBalancerRequestLambdaFunction.java diff --git a/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/APIGatewayProxyV1TransactionHelper.java b/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/APIGatewayProxyV1TransactionHelper.java index d2d4589d9c..b9b8d18d7b 100644 --- a/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/APIGatewayProxyV1TransactionHelper.java +++ b/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/APIGatewayProxyV1TransactionHelper.java @@ -20,15 +20,14 @@ import co.elastic.apm.agent.awslambda.MapTextHeaderGetter; import co.elastic.apm.agent.impl.ElasticApmTracer; -import co.elastic.apm.agent.tracer.GlobalTracer; import co.elastic.apm.agent.impl.transaction.Transaction; import co.elastic.apm.agent.sdk.internal.util.PrivilegedActionUtils; +import co.elastic.apm.agent.tracer.GlobalTracer; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; import javax.annotation.Nullable; -import java.util.Map; public class APIGatewayProxyV1TransactionHelper extends AbstractAPIGatewayTransactionHelper { @@ -49,48 +48,17 @@ public static APIGatewayProxyV1TransactionHelper getInstance() { @Override protected Transaction doStartTransaction(APIGatewayProxyRequestEvent apiGatewayEvent, Context lambdaContext) { Transaction transaction = tracer.startChildTransaction(apiGatewayEvent.getHeaders(), MapTextHeaderGetter.INSTANCE, PrivilegedActionUtils.getClassLoader(apiGatewayEvent.getClass())); - String host = getHost(apiGatewayEvent); if (null != transaction) { + String host = getHost(apiGatewayEvent.getHeaders()); + fillHttpRequestData(transaction, getHttpMethod(apiGatewayEvent), apiGatewayEvent.getHeaders(), host, - apiGatewayEvent.getRequestContext().getPath(), getQueryString(apiGatewayEvent), apiGatewayEvent.getBody()); + apiGatewayEvent.getRequestContext().getPath(), getQueryString(apiGatewayEvent.getQueryStringParameters()), apiGatewayEvent.getBody()); } return transaction; } - @Nullable - private String getHost(APIGatewayProxyRequestEvent apiGatewayEvent) { - String host = null; - if (null != apiGatewayEvent.getHeaders()) { - host = apiGatewayEvent.getHeaders().get("host"); - if (null == host) { - host = apiGatewayEvent.getHeaders().get("Host"); - } - } - return host; - } - - @Nullable - private String getQueryString(APIGatewayProxyRequestEvent apiGatewayEvent) { - Map queryParameters = apiGatewayEvent.getQueryStringParameters(); - if (null != queryParameters && !queryParameters.isEmpty()) { - StringBuilder queryString = new StringBuilder(); - int i = 0; - for (Map.Entry entry : apiGatewayEvent.getQueryStringParameters().entrySet()) { - if (i > 0) { - queryString.append('&'); - } - queryString.append(entry.getKey()); - queryString.append('='); - queryString.append(entry.getValue()); - i++; - } - return queryString.toString(); - } - return null; - } - @Override public void captureOutputForTransaction(Transaction transaction, APIGatewayProxyResponseEvent responseEvent) { Integer statusCode = responseEvent.getStatusCode(); @@ -107,7 +75,7 @@ protected void setTransactionTriggerData(Transaction transaction, APIGatewayProx if (null != rContext) { setApiGatewayContextData(transaction, rContext.getRequestId(), rContext.getApiId(), - getHost(apiGatewayRequest), rContext.getAccountId()); + getHost(apiGatewayRequest.getHeaders()), rContext.getAccountId()); } } diff --git a/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/AWSEventsHelper.java b/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/AWSEventsHelper.java index 9ec285ad99..5a3299268e 100644 --- a/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/AWSEventsHelper.java +++ b/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/AWSEventsHelper.java @@ -24,6 +24,8 @@ import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent; import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse; +import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent; +import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerResponseEvent; import com.amazonaws.services.lambda.runtime.events.S3Event; import com.amazonaws.services.lambda.runtime.events.SNSEvent; import com.amazonaws.services.lambda.runtime.events.SQSEvent; @@ -50,6 +52,9 @@ public static Transaction startTransaction(Object input, Context lambdaContext) } else if (input instanceof S3Event) { // S3 event trigger return S3TransactionHelper.getInstance().startTransaction((S3Event) input, lambdaContext); + } else if (input instanceof ApplicationLoadBalancerRequestEvent) { + // Load Balancer Request event trigger + return ApplicationLoadBalancerRequestTransactionHelper.getInstance().startTransaction((ApplicationLoadBalancerRequestEvent) input, lambdaContext); } return PlainTransactionHelper.getInstance().startTransaction(input, lambdaContext); } @@ -59,6 +64,8 @@ public static void finalizeTransaction(Transaction transaction, Object output, @ APIGatewayProxyV2TransactionHelper.getInstance().finalizeTransaction(transaction, (APIGatewayV2HTTPResponse) output, thrown); } else if (output instanceof APIGatewayProxyResponseEvent) { APIGatewayProxyV1TransactionHelper.getInstance().finalizeTransaction(transaction, (APIGatewayProxyResponseEvent) output, thrown); + } else if (output instanceof ApplicationLoadBalancerResponseEvent) { + ApplicationLoadBalancerRequestTransactionHelper.getInstance().finalizeTransaction(transaction, (ApplicationLoadBalancerResponseEvent) output, thrown); } else { // use PlainTransactionHelper for all triggers that do not expect an output PlainTransactionHelper.getInstance().finalizeTransaction(transaction, output, thrown); diff --git a/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/AbstractAPIGatewayTransactionHelper.java b/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/AbstractAPIGatewayTransactionHelper.java index 72c1d0eecb..150a072a1e 100644 --- a/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/AbstractAPIGatewayTransactionHelper.java +++ b/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/AbstractAPIGatewayTransactionHelper.java @@ -30,6 +30,7 @@ import co.elastic.apm.agent.sdk.logging.LoggerFactory; import co.elastic.apm.agent.tracer.AbstractSpan; import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; import javax.annotation.Nullable; import java.nio.CharBuffer; @@ -43,7 +44,7 @@ public abstract class AbstractAPIGatewayTransactionHelper extends AbstractLambdaTransactionHelper { private static final Logger logger = LoggerFactory.getLogger(AbstractAPIGatewayTransactionHelper.class); protected static final String TRANSACTION_TYPE = "request"; - private static final String CONTENT_TYPE_HEADER = "Content-Type"; + protected static final String CONTENT_TYPE_HEADER = "Content-Type"; private static final Set METHODS_WITH_BODY = new HashSet<>(Arrays.asList("POST", "PUT", "PATCH", "DELETE")); private static final String CONTENT_TYPE_FROM_URLENCODED = "application/x-www-form-urlencoded"; @@ -67,6 +68,39 @@ protected void fillHttpRequestData(Transaction transaction, @Nullable String htt } } + + @Nullable + protected String getHost(@Nullable Map headers) { + String host = null; + if (null != headers) { + host = headers.get("host"); + if (null == host) { + host = headers.get("Host"); + } + } + return host; + } + + @Nullable + protected String getQueryString(@Nullable Map queryParameters) { + if (null != queryParameters && !queryParameters.isEmpty()) { + StringBuilder queryString = new StringBuilder(); + int i = 0; + for (Map.Entry entry : queryParameters.entrySet()) { + if (i > 0) { + queryString.append('&'); + } + queryString.append(entry.getKey()); + queryString.append('='); + queryString.append(entry.getValue()); + i++; + } + return queryString.toString(); + } + return null; + } + + protected void fillHttpResponseData(Transaction transaction, @Nullable Map headers, int statusCode) { Response response = transaction.getContext().getResponse(); response.withFinished(true); @@ -80,7 +114,7 @@ protected void fillHttpResponseData(Transaction transaction, @Nullable Map headers) { + protected void setRequestHeaders(Transaction transaction, Map headers) { final Request req = transaction.getContext().getRequest(); if (transaction.isSampled() && isCaptureHeaders()) { for (Map.Entry headerEntry : headers.entrySet()) { diff --git a/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/ApplicationLoadBalancerRequestTransactionHelper.java b/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/ApplicationLoadBalancerRequestTransactionHelper.java new file mode 100644 index 0000000000..fa85443a69 --- /dev/null +++ b/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/ApplicationLoadBalancerRequestTransactionHelper.java @@ -0,0 +1,157 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package co.elastic.apm.agent.awslambda.helper; + +import co.elastic.apm.agent.awslambda.MapTextHeaderGetter; +import co.elastic.apm.agent.impl.ElasticApmTracer; +import co.elastic.apm.agent.impl.context.CloudOrigin; +import co.elastic.apm.agent.impl.context.Request; +import co.elastic.apm.agent.impl.transaction.Transaction; +import co.elastic.apm.agent.sdk.internal.util.PrivilegedActionUtils; +import co.elastic.apm.agent.tracer.GlobalTracer; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent; +import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerResponseEvent; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.nio.CharBuffer; +import java.util.Map; + +public class ApplicationLoadBalancerRequestTransactionHelper extends AbstractAPIGatewayTransactionHelper { + @Nullable + private static ApplicationLoadBalancerRequestTransactionHelper INSTANCE; + + private ApplicationLoadBalancerRequestTransactionHelper(ElasticApmTracer tracer) { + super(tracer); + } + + public static ApplicationLoadBalancerRequestTransactionHelper getInstance() { + if (INSTANCE == null) { + INSTANCE = new ApplicationLoadBalancerRequestTransactionHelper(GlobalTracer.get().require(ElasticApmTracer.class)); + } + return INSTANCE; + } + + @Override + protected Transaction doStartTransaction(ApplicationLoadBalancerRequestEvent loadBalancerRequestEvent, Context lambdaContext) { + Transaction transaction = tracer.startChildTransaction(loadBalancerRequestEvent.getHeaders(), MapTextHeaderGetter.INSTANCE, PrivilegedActionUtils.getClassLoader(loadBalancerRequestEvent.getClass())); + + if (transaction != null) { + String host = getHost(loadBalancerRequestEvent.getHeaders()); + super.fillHttpRequestData(transaction, loadBalancerRequestEvent.getHttpMethod(), loadBalancerRequestEvent.getHeaders(), host, + loadBalancerRequestEvent.getPath(), getQueryString(loadBalancerRequestEvent.getQueryStringParameters()), loadBalancerRequestEvent.getBody()); + } + + return transaction; + } + + @Override + public void captureOutputForTransaction(Transaction transaction, ApplicationLoadBalancerResponseEvent responseEvent) { + fillHttpResponseData(transaction, responseEvent.getHeaders(), responseEvent.getStatusCode()); + } + + @Override + protected void setTransactionTriggerData(Transaction transaction, ApplicationLoadBalancerRequestEvent loadBalancerRequestEvent) { + transaction.withType(TRANSACTION_TYPE); + CloudOrigin cloudOrigin = transaction.getContext().getCloudOrigin(); + cloudOrigin.withServiceName("elb"); + cloudOrigin.withProvider("aws"); + transaction.getFaas().getTrigger().withType("http"); + transaction.getFaas().getTrigger().withRequestId(getHeader(loadBalancerRequestEvent, "x-amzn-trace-id")); + LoadBalancerElbTargetGroupArnMetadata metadata = parseMetadata(loadBalancerRequestEvent); + if (null != metadata) { + transaction.getContext().getServiceOrigin().withName(metadata.getTargetGroupName()); + transaction.getContext().getServiceOrigin().withId(metadata.getTargetGroupArn()); + cloudOrigin.withAccountId(metadata.getAccountId()); + cloudOrigin.withRegion(metadata.getCloudRegion()); + } + } + + @Nullable + private String getHeader(@Nonnull ApplicationLoadBalancerRequestEvent loadBalancerRequestEvent, + @Nonnull String headerName) { + Map headers = loadBalancerRequestEvent.getHeaders(); + if (null == headers) { + return null; + } + return headers.get(headerName); + } + + @Nullable + private LoadBalancerElbTargetGroupArnMetadata parseMetadata(ApplicationLoadBalancerRequestEvent event) { + ApplicationLoadBalancerRequestEvent.Elb elb = event.getRequestContext().getElb(); + if (null == elb) { + return null; + } + String targetGroupArn = elb.getTargetGroupArn(); + if (null == targetGroupArn) { + return null; + } + LoadBalancerElbTargetGroupArnMetadata metadata = new LoadBalancerElbTargetGroupArnMetadata(targetGroupArn); + String[] arnParts = targetGroupArn.split(":"); + int arnPartsLength = arnParts.length; + if (arnPartsLength < 4) { + return metadata; + } + metadata.withCloudRegion(arnParts[3]); + if (arnPartsLength < 5) { + return metadata; + } + metadata.withAccountId(arnParts[4]); + if (arnPartsLength < 6) { + return metadata; + } + String targetGroup = arnParts[5]; + String[] targetGroupParts = targetGroup.split("/"); + if (targetGroupParts.length < 2) { + return metadata; + } + return metadata.withTargetGroupName(targetGroupParts[2]); + } + + @Override + protected String getApiGatewayVersion() { + throw new UnsupportedOperationException("Not supported by ELB"); + } + + @Nullable + @Override + protected String getHttpMethod(ApplicationLoadBalancerRequestEvent event) { + return event.getHttpMethod(); + } + + @Nullable + @Override + protected String getRequestContextPath(ApplicationLoadBalancerRequestEvent event) { + return event.getPath(); + } + + @Nullable + @Override + protected String getStage(ApplicationLoadBalancerRequestEvent event) { + throw new UnsupportedOperationException("Not supported by ELB"); + } + + @Nullable + @Override + protected String getResourcePath(ApplicationLoadBalancerRequestEvent event) { + return null; + } +} diff --git a/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/LoadBalancerElbTargetGroupArnMetadata.java b/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/LoadBalancerElbTargetGroupArnMetadata.java new file mode 100644 index 0000000000..e3ef019ca6 --- /dev/null +++ b/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/LoadBalancerElbTargetGroupArnMetadata.java @@ -0,0 +1,52 @@ +package co.elastic.apm.agent.awslambda.helper; + + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class LoadBalancerElbTargetGroupArnMetadata { + + private LoadBalancerElbTargetGroupArnMetadata() {} + public LoadBalancerElbTargetGroupArnMetadata(String targetGroupArn) { + this.targetGroupArn = targetGroupArn; + } + private String targetGroupArn; + private String cloudRegion; + private String accountId; + private String targetGroupName; + + public LoadBalancerElbTargetGroupArnMetadata withCloudRegion(String cloudRegion) { + this.cloudRegion = cloudRegion; + return this; + } + + @Nullable + public String getCloudRegion() { + return cloudRegion; + } + + public LoadBalancerElbTargetGroupArnMetadata withAccountId(String accountId) { + this.accountId = accountId; + return this; + } + + public LoadBalancerElbTargetGroupArnMetadata withTargetGroupName(String targetGroupName) { + this.targetGroupName = targetGroupName; + return this; + } + + @Nonnull + public String getTargetGroupArn() { + return targetGroupArn; + } + + @Nullable + public String getAccountId() { + return accountId; + } + + @Nullable + public String getTargetGroupName() { + return targetGroupName; + } +} diff --git a/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/ApplicationLoadBalancerRequestEventLambdaTest.java b/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/ApplicationLoadBalancerRequestEventLambdaTest.java new file mode 100644 index 0000000000..f3408ee61b --- /dev/null +++ b/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/ApplicationLoadBalancerRequestEventLambdaTest.java @@ -0,0 +1,157 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package co.elastic.apm.agent.awslambda; + +import co.elastic.apm.agent.awslambda.lambdas.AbstractFunction; +import co.elastic.apm.agent.awslambda.lambdas.ApiGatewayV1LambdaFunction; +import co.elastic.apm.agent.awslambda.lambdas.ApplicationLoadBalancerRequestLambdaFunction; +import co.elastic.apm.agent.awslambda.lambdas.TestContext; +import co.elastic.apm.agent.configuration.CoreConfiguration; +import co.elastic.apm.agent.impl.context.Request; +import co.elastic.apm.agent.impl.context.Response; +import co.elastic.apm.agent.impl.context.Url; +import co.elastic.apm.agent.impl.transaction.Faas; +import co.elastic.apm.agent.impl.transaction.Transaction; +import co.elastic.apm.agent.tracer.Outcome; +import co.elastic.apm.agent.tracer.metadata.PotentiallyMultiValuedMap; +import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent; +import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerResponseEvent; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import javax.annotation.Nonnull; +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doReturn; + +public class ApplicationLoadBalancerRequestEventLambdaTest extends AbstractLambdaTest { + + @BeforeAll + // Need to overwrite the beforeAll() method from parent, + // because we need to mock serverlessConfiguration BEFORE instrumentation is initialized! + public static synchronized void beforeAll() { + AbstractLambdaTest.initAllButInstrumentation(); + doReturn(ApplicationLoadBalancerRequestLambdaFunction.class.getName()).when(Objects.requireNonNull(serverlessConfiguration)).getAwsLambdaHandler(); + AbstractLambdaTest.initInstrumentation(); + } + + @Override + protected AbstractFunction createHandler() { + return new ApplicationLoadBalancerRequestLambdaFunction(); + } + + @Override + protected ApplicationLoadBalancerRequestEvent createInput() { + return createLoadBalancerRequestEvent(); + } + + @Override + protected boolean supportsContextPropagation() { + return false; + } + + @Nonnull + private ApplicationLoadBalancerRequestEvent createLoadBalancerRequestEvent() { + var event = new ApplicationLoadBalancerRequestEvent(); + event.setBody("blablablabody"); + event.setIsBase64Encoded(false); + var requestContext = new ApplicationLoadBalancerRequestEvent.RequestContext(); + var elb = new ApplicationLoadBalancerRequestEvent.Elb(); + elb.setTargetGroupArn("arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/lambda-279XGJDqGZ5rsrHC2Fjr/49e9d65c45c6791a"); + requestContext.setElb(elb); + event.setRequestContext(requestContext); + event.setHttpMethod("POST"); + event.setPath("/toolz/api/v2.0/downloadPDF/PDF_2020-09-11_11-06-01.pdf"); + event.setQueryStringParameters(Map.of("test%40key", "test%40value", "language", "en-DE")); + event.setHeaders(Map.of("accept-encoding", "gzip,deflate", + "connection", "Keep-Alive", + "host", "blabla.com", + "user-agent", "Apache-HttpClient/4.5.13 (Java/11.0.15)", + "x-amzn-trace-id", "Root=1-xxxxxxxxxxxxxx", + "x-forwarded-for", "199.99.99.999", + "x-forwarded-port", "443", + "x-forwarded-proto", "https")); + return event; + } + + + @Test + public void testBasicCall() { + doReturn(CoreConfiguration.EventType.ALL).when(config.getConfig(CoreConfiguration.class)).getCaptureBody(); + getFunction().handleRequest(createInput(), context); + reporter.awaitTransactionCount(1); + reporter.awaitSpanCount(1); + assertThat(reporter.getFirstSpan().getNameAsString()).isEqualTo("child-span"); + assertThat(reporter.getFirstSpan().getTransaction()).isEqualTo(reporter.getFirstTransaction()); + Transaction transaction = reporter.getFirstTransaction(); + assertThat(transaction.getNameAsString()).isEqualTo("FUNCTION_NAME"); + assertThat(transaction.getType()).isEqualTo("request"); + assertThat(transaction.getResult()).isEqualTo("HTTP 2xx"); + assertThat(transaction.getOutcome()).isEqualTo(Outcome.SUCCESS); + assertThat(reporter.getPartialTransactions()).containsExactly(transaction); + + Request request = transaction.getContext().getRequest(); + assertThat(request.getMethod()).isEqualTo(HTTP_METHOD); + assertThat(request.getBody()).isNull(); + assertThat(request.getHttpVersion()).isNull(); + + Url url = request.getUrl(); + assertThat(url.getHostname()).isEqualTo("blabla.com"); + assertThat(url.getPort()).isEqualTo(443); + assertThat(url.getPathname()).isEqualTo("/toolz/api/v2.0/downloadPDF/PDF_2020-09-11_11-06-01.pdf"); + assertThat(url.getSearch()).contains("test%40key=test%40value"); + assertThat(url.getSearch()).contains(Arrays.asList("language=en-DE", "test%40key=test%40value")); + assertThat(url.getProtocol()).isEqualTo("https"); + String baseUrl = "https://" + "blabla.com" + "/toolz/api/v2.0/downloadPDF/PDF_2020-09-11_11-06-01.pdf" + "?"; + assertThat(url.getFull().toString()).containsAnyOf(baseUrl + "test%40key=test%40value&language=en-DE", + baseUrl + "language=en-DE&test%40key=test%40value"); + + assertThat(request.getHeaders()).isNotNull(); + PotentiallyMultiValuedMap headers = request.getHeaders(); + assertThat(headers.get("connection")).isEqualTo("Keep-Alive"); + assertThat(headers.get("accept-encoding")).isEqualTo("gzip,deflate"); + + Response response = transaction.getContext().getResponse(); + assertThat(response.getStatusCode()).isEqualTo(ApiGatewayV1LambdaFunction.EXPECTED_STATUS_CODE); + assertThat(response.getHeaders()).isNotNull(); + assertThat(response.getHeaders().get(ApiGatewayV1LambdaFunction.EXPECTED_RESPONSE_HEADER_1_KEY)).isEqualTo(ApiGatewayV1LambdaFunction.EXPECTED_RESPONSE_HEADER_1_VALUE); + assertThat(response.getHeaders().get(ApiGatewayV1LambdaFunction.EXPECTED_RESPONSE_HEADER_2_KEY)).isEqualTo(ApiGatewayV1LambdaFunction.EXPECTED_RESPONSE_HEADER_2_VALUE); + + assertThat(transaction.getContext().getCloudOrigin()).isNotNull(); + assertThat(transaction.getContext().getCloudOrigin().getProvider()).isEqualTo("aws"); + assertThat(transaction.getContext().getCloudOrigin().getServiceName()).isEqualTo("elb"); + assertThat(transaction.getContext().getCloudOrigin().getAccountId()).isEqualTo("123456789012"); + assertThat(transaction.getContext().getCloudOrigin().getRegion()).isEqualTo("us-east-2"); + + assertThat(transaction.getContext().getServiceOrigin().hasContent()).isTrue(); + assertThat(transaction.getContext().getServiceOrigin().getName().toString()).isEqualTo("49e9d65c45c6791a"); + assertThat(transaction.getContext().getServiceOrigin().getId()).isEqualTo("arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/lambda-279XGJDqGZ5rsrHC2Fjr/49e9d65c45c6791a"); + assertThat(transaction.getContext().getServiceOrigin().getVersion()).isNull(); + + Faas faas = transaction.getFaas(); + assertThat(faas.getExecution()).isEqualTo(TestContext.AWS_REQUEST_ID); + assertThat(faas.getId()).isEqualTo(TestContext.FUNCTION_ARN); + assertThat(faas.getTrigger().getType()).isEqualTo("http"); + assertThat(faas.getTrigger().getRequestId()).isEqualTo("Root=1-xxxxxxxxxxxxxx"); + } + +} diff --git a/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/lambdas/ApplicationLoadBalancerRequestLambdaFunction.java b/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/lambdas/ApplicationLoadBalancerRequestLambdaFunction.java new file mode 100644 index 0000000000..9718bd0b1f --- /dev/null +++ b/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/lambdas/ApplicationLoadBalancerRequestLambdaFunction.java @@ -0,0 +1,34 @@ +package co.elastic.apm.agent.awslambda.lambdas; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent; +import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerResponseEvent; + +import java.util.Map; + +public class ApplicationLoadBalancerRequestLambdaFunction extends AbstractFunction { + public static final String EXPECTED_BODY = "This is some body"; + public static final String EXPECTED_RESPONSE_HEADER_1_KEY = "EXPECTED_HEADER_1_KEY"; + public static final String EXPECTED_RESPONSE_HEADER_1_VALUE = "EXPECTED_HEADER_1_VALUE"; + public static final String EXPECTED_RESPONSE_HEADER_2_KEY = "EXPECTED_HEADER_2_KEY"; + public static final String EXPECTED_RESPONSE_HEADER_2_VALUE = "EXPECTED_HEADER_2_VALUE"; + public static final int EXPECTED_STATUS_CODE = 202; + public static final int ERROR_STATUS_CODE = 505; + + @Override + public ApplicationLoadBalancerResponseEvent handleRequest(ApplicationLoadBalancerRequestEvent applicationLoadBalancerRequestEvent, Context context) { + createChildSpan(); + + ApplicationLoadBalancerResponseEvent response = new ApplicationLoadBalancerResponseEvent(); + response.setBody(EXPECTED_BODY); + response.setHeaders(Map.of(EXPECTED_RESPONSE_HEADER_1_KEY, EXPECTED_RESPONSE_HEADER_1_VALUE, EXPECTED_RESPONSE_HEADER_2_KEY, EXPECTED_RESPONSE_HEADER_2_VALUE)); + + if (((TestContext) context).shouldSetErrorStatusCode()) { + response.setStatusCode(ERROR_STATUS_CODE); + } else { + response.setStatusCode(EXPECTED_STATUS_CODE); + } + raiseException(context); + return response; + } +} From ae94d0a2ff35a1413c3259afc6d6947ee59fa4b8 Mon Sep 17 00:00:00 2001 From: Nugusbayev Kanagat Date: Tue, 7 Nov 2023 16:22:10 +0600 Subject: [PATCH 2/8] moved test with null input to base test class --- .../agent/awslambda/AbstractLambdaTest.java | 29 +++++++++++++++++++ .../awslambda/ApiGatewayV1LambdaTest.java | 28 ------------------ .../awslambda/ApiGatewayV2LambdaTest.java | 28 ------------------ 3 files changed, 29 insertions(+), 56 deletions(-) diff --git a/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/AbstractLambdaTest.java b/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/AbstractLambdaTest.java index e196b30175..3443c28e2d 100644 --- a/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/AbstractLambdaTest.java +++ b/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/AbstractLambdaTest.java @@ -27,6 +27,7 @@ import co.elastic.apm.agent.configuration.SpyConfiguration; import co.elastic.apm.agent.impl.metadata.MetaDataMock; import co.elastic.apm.agent.impl.stacktrace.StacktraceConfiguration; +import co.elastic.apm.agent.impl.transaction.Faas; import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.TraceState; import co.elastic.apm.agent.impl.transaction.Transaction; @@ -171,6 +172,34 @@ public void initTests() { function = createHandler(); } + @Test + public void testCallWithNullInput() { + getFunction().handleRequest(null, context); + + reporter.awaitTransactionCount(1); + reporter.awaitSpanCount(1); + assertThat(reporter.getFirstSpan().getNameAsString()).isEqualTo("child-span"); + assertThat(reporter.getFirstSpan().getTransaction()).isEqualTo(reporter.getFirstTransaction()); + Transaction transaction = reporter.getFirstTransaction(); + assertThat(transaction.getNameAsString()).isEqualTo(TestContext.FUNCTION_NAME); + assertThat(transaction.getType()).isEqualTo("request"); + assertThat(transaction.getResult()).isEqualTo("HTTP 2xx"); + + assertThat(transaction.getContext().getCloudOrigin()).isNotNull(); + assertThat(transaction.getContext().getCloudOrigin().getProvider()).isEqualTo("aws"); + assertThat(transaction.getContext().getCloudOrigin().getServiceName()).isNull(); + assertThat(transaction.getContext().getCloudOrigin().getRegion()).isNull(); + assertThat(transaction.getContext().getCloudOrigin().getAccountId()).isNull(); + + assertThat(transaction.getContext().getServiceOrigin().hasContent()).isFalse(); + + Faas faas = transaction.getFaas(); + assertThat(faas.getExecution()).isEqualTo(TestContext.AWS_REQUEST_ID); + + assertThat(faas.getTrigger().getType()).isEqualTo("other"); + assertThat(faas.getTrigger().getRequestId()).isNull(); + } + @Test public void testCallWithHandlerError() { Objects.requireNonNull(context).raiseException(); diff --git a/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/ApiGatewayV1LambdaTest.java b/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/ApiGatewayV1LambdaTest.java index c6e0be6e30..cf92777547 100644 --- a/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/ApiGatewayV1LambdaTest.java +++ b/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/ApiGatewayV1LambdaTest.java @@ -144,34 +144,6 @@ public void testBasicCall() { assertThat(faas.getTrigger().getRequestId()).isEqualTo(API_GATEWAY_REQUEST_ID); } - @Test - public void testCallWithNullInput() { - getFunction().handleRequest(null, context); - - reporter.awaitTransactionCount(1); - reporter.awaitSpanCount(1); - assertThat(reporter.getFirstSpan().getNameAsString()).isEqualTo("child-span"); - assertThat(reporter.getFirstSpan().getTransaction()).isEqualTo(reporter.getFirstTransaction()); - Transaction transaction = reporter.getFirstTransaction(); - assertThat(transaction.getNameAsString()).isEqualTo(TestContext.FUNCTION_NAME); - assertThat(transaction.getType()).isEqualTo("request"); - assertThat(transaction.getResult()).isEqualTo("HTTP 2xx"); - - assertThat(transaction.getContext().getCloudOrigin()).isNotNull(); - assertThat(transaction.getContext().getCloudOrigin().getProvider()).isEqualTo("aws"); - assertThat(transaction.getContext().getCloudOrigin().getServiceName()).isNull(); - assertThat(transaction.getContext().getCloudOrigin().getRegion()).isNull(); - assertThat(transaction.getContext().getCloudOrigin().getAccountId()).isNull(); - - assertThat(transaction.getContext().getServiceOrigin().hasContent()).isFalse(); - - Faas faas = transaction.getFaas(); - assertThat(faas.getExecution()).isEqualTo(TestContext.AWS_REQUEST_ID); - - assertThat(faas.getTrigger().getType()).isEqualTo("other"); - assertThat(faas.getTrigger().getRequestId()).isNull(); - } - @ParameterizedTest @ValueSource(booleans = {true, false}) public void testCallWithNullRequestContext(boolean isObjectNull) { diff --git a/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/ApiGatewayV2LambdaTest.java b/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/ApiGatewayV2LambdaTest.java index d2a7cd73b4..7857b48582 100644 --- a/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/ApiGatewayV2LambdaTest.java +++ b/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/ApiGatewayV2LambdaTest.java @@ -150,34 +150,6 @@ public void testBasicCall() { assertThat(faas.getTrigger().getRequestId()).isEqualTo(API_GATEWAY_REQUEST_ID); } - @Test - public void testCallWithNullInput() { - getFunction().handleRequest(null, context); - - reporter.awaitTransactionCount(1); - reporter.awaitSpanCount(1); - assertThat(reporter.getFirstSpan().getNameAsString()).isEqualTo("child-span"); - assertThat(reporter.getFirstSpan().getTransaction()).isEqualTo(reporter.getFirstTransaction()); - Transaction transaction = reporter.getFirstTransaction(); - assertThat(transaction.getNameAsString()).isEqualTo(TestContext.FUNCTION_NAME); - assertThat(transaction.getType()).isEqualTo("request"); - assertThat(transaction.getResult()).isEqualTo("HTTP 2xx"); - - assertThat(transaction.getContext().getCloudOrigin()).isNotNull(); - assertThat(transaction.getContext().getCloudOrigin().getProvider()).isEqualTo("aws"); - assertThat(transaction.getContext().getCloudOrigin().getServiceName()).isNull(); - assertThat(transaction.getContext().getCloudOrigin().getRegion()).isNull(); - assertThat(transaction.getContext().getCloudOrigin().getAccountId()).isNull(); - - assertThat(transaction.getContext().getServiceOrigin().hasContent()).isFalse(); - - Faas faas = transaction.getFaas(); - assertThat(faas.getExecution()).isEqualTo(TestContext.AWS_REQUEST_ID); - - assertThat(faas.getTrigger().getType()).isEqualTo("other"); - assertThat(faas.getTrigger().getRequestId()).isNull(); - } - @ParameterizedTest @ValueSource(booleans = {true, false}) public void testCallWithNullRequestContext(boolean isObjectNull) { From ef500c028cf600d7c79ef5f48c143c330c4884cd Mon Sep 17 00:00:00 2001 From: Nugusbayev Kanagat Date: Tue, 7 Nov 2023 17:39:52 +0600 Subject: [PATCH 3/8] added check on null in parse metadata method. cover more tests --- ...nLoadBalancerRequestTransactionHelper.java | 3 + .../agent/awslambda/AbstractLambdaTest.java | 28 --------- .../awslambda/ApiGatewayV1LambdaTest.java | 15 +---- .../awslambda/ApiGatewayV2LambdaTest.java | 15 +---- ...ionLoadBalancerRequestEventLambdaTest.java | 62 +++++++++++++++---- .../awslambda/BaseGatewayLambdaTest.java | 57 +++++++++++++++++ 6 files changed, 113 insertions(+), 67 deletions(-) create mode 100644 apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/BaseGatewayLambdaTest.java diff --git a/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/ApplicationLoadBalancerRequestTransactionHelper.java b/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/ApplicationLoadBalancerRequestTransactionHelper.java index fa85443a69..714b6cf71e 100644 --- a/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/ApplicationLoadBalancerRequestTransactionHelper.java +++ b/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/ApplicationLoadBalancerRequestTransactionHelper.java @@ -96,6 +96,9 @@ private String getHeader(@Nonnull ApplicationLoadBalancerRequestEvent loadBalanc @Nullable private LoadBalancerElbTargetGroupArnMetadata parseMetadata(ApplicationLoadBalancerRequestEvent event) { + if (null == event.getRequestContext()) { + return null; + } ApplicationLoadBalancerRequestEvent.Elb elb = event.getRequestContext().getElb(); if (null == elb) { return null; diff --git a/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/AbstractLambdaTest.java b/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/AbstractLambdaTest.java index 3443c28e2d..9722136ae1 100644 --- a/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/AbstractLambdaTest.java +++ b/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/AbstractLambdaTest.java @@ -172,34 +172,6 @@ public void initTests() { function = createHandler(); } - @Test - public void testCallWithNullInput() { - getFunction().handleRequest(null, context); - - reporter.awaitTransactionCount(1); - reporter.awaitSpanCount(1); - assertThat(reporter.getFirstSpan().getNameAsString()).isEqualTo("child-span"); - assertThat(reporter.getFirstSpan().getTransaction()).isEqualTo(reporter.getFirstTransaction()); - Transaction transaction = reporter.getFirstTransaction(); - assertThat(transaction.getNameAsString()).isEqualTo(TestContext.FUNCTION_NAME); - assertThat(transaction.getType()).isEqualTo("request"); - assertThat(transaction.getResult()).isEqualTo("HTTP 2xx"); - - assertThat(transaction.getContext().getCloudOrigin()).isNotNull(); - assertThat(transaction.getContext().getCloudOrigin().getProvider()).isEqualTo("aws"); - assertThat(transaction.getContext().getCloudOrigin().getServiceName()).isNull(); - assertThat(transaction.getContext().getCloudOrigin().getRegion()).isNull(); - assertThat(transaction.getContext().getCloudOrigin().getAccountId()).isNull(); - - assertThat(transaction.getContext().getServiceOrigin().hasContent()).isFalse(); - - Faas faas = transaction.getFaas(); - assertThat(faas.getExecution()).isEqualTo(TestContext.AWS_REQUEST_ID); - - assertThat(faas.getTrigger().getType()).isEqualTo("other"); - assertThat(faas.getTrigger().getRequestId()).isNull(); - } - @Test public void testCallWithHandlerError() { Objects.requireNonNull(context).raiseException(); diff --git a/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/ApiGatewayV1LambdaTest.java b/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/ApiGatewayV1LambdaTest.java index cf92777547..573c264560 100644 --- a/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/ApiGatewayV1LambdaTest.java +++ b/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/ApiGatewayV1LambdaTest.java @@ -45,7 +45,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.doReturn; -public class ApiGatewayV1LambdaTest extends AbstractLambdaTest { +public class ApiGatewayV1LambdaTest extends BaseGatewayLambdaTest { @BeforeAll @BeforeClass @@ -182,19 +182,6 @@ public void testCallWithNullRequestContext(boolean isObjectNull) { assertThat(faas.getTrigger().getRequestId()).isNull(); } - @Test - public void testCallWithHErrorStatusCode() { - Objects.requireNonNull(context).setErrorStatusCode(); - getFunction().handleRequest(createInput(), context); - reporter.awaitTransactionCount(1); - reporter.awaitSpanCount(1); - assertThat(reporter.getFirstSpan().getNameAsString()).isEqualTo("child-span"); - assertThat(reporter.getFirstSpan().getTransaction()).isEqualTo(reporter.getFirstTransaction()); - Transaction transaction = reporter.getFirstTransaction(); - assertThat(transaction.getResult()).isEqualTo("HTTP 5xx"); - assertThat(transaction.getOutcome()).isEqualTo(Outcome.FAILURE); - } - @Test public void testTransactionNameForRestApiSpecificRoute() { getFunction().handleRequest(createInput("PUT", "/prod/test/12345", "/test", "prod"), context); diff --git a/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/ApiGatewayV2LambdaTest.java b/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/ApiGatewayV2LambdaTest.java index 7857b48582..78da993e8d 100644 --- a/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/ApiGatewayV2LambdaTest.java +++ b/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/ApiGatewayV2LambdaTest.java @@ -46,7 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.doReturn; -public class ApiGatewayV2LambdaTest extends AbstractLambdaTest { +public class ApiGatewayV2LambdaTest extends BaseGatewayLambdaTest { @BeforeAll @BeforeClass @@ -183,19 +183,6 @@ public void testCallWithNullRequestContext(boolean isObjectNull) { assertThat(faas.getTrigger().getRequestId()).isNull(); } - @Test - public void testCallWithHErrorStatusCode() { - Objects.requireNonNull(context).setErrorStatusCode(); - getFunction().handleRequest(createInput(), context); - reporter.awaitTransactionCount(1); - reporter.awaitSpanCount(1); - assertThat(reporter.getFirstSpan().getNameAsString()).isEqualTo("child-span"); - assertThat(reporter.getFirstSpan().getTransaction()).isEqualTo(reporter.getFirstTransaction()); - Transaction transaction = reporter.getFirstTransaction(); - assertThat(transaction.getResult()).isEqualTo("HTTP 5xx"); - assertThat(transaction.getOutcome()).isEqualTo(Outcome.FAILURE); - } - @Test public void testTransactionNameForRestApiSpecificRoute() { getFunction().handleRequest(createInput("PUT", "/prod/test", "ANY /test", "prod"), context); diff --git a/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/ApplicationLoadBalancerRequestEventLambdaTest.java b/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/ApplicationLoadBalancerRequestEventLambdaTest.java index f3408ee61b..5583ba1834 100644 --- a/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/ApplicationLoadBalancerRequestEventLambdaTest.java +++ b/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/ApplicationLoadBalancerRequestEventLambdaTest.java @@ -29,11 +29,15 @@ import co.elastic.apm.agent.impl.transaction.Faas; import co.elastic.apm.agent.impl.transaction.Transaction; import co.elastic.apm.agent.tracer.Outcome; +import co.elastic.apm.agent.tracer.configuration.WebConfiguration; import co.elastic.apm.agent.tracer.metadata.PotentiallyMultiValuedMap; +import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent; import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent; import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerResponseEvent; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import javax.annotation.Nonnull; import java.util.Arrays; @@ -43,7 +47,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.doReturn; -public class ApplicationLoadBalancerRequestEventLambdaTest extends AbstractLambdaTest { +public class ApplicationLoadBalancerRequestEventLambdaTest extends BaseGatewayLambdaTest { @BeforeAll // Need to overwrite the beforeAll() method from parent, @@ -61,16 +65,6 @@ protected AbstractFunction extends AbstractLambdaTest { + + @Test + public void testCallWithNullInput() { + getFunction().handleRequest(null, context); + + reporter.awaitTransactionCount(1); + reporter.awaitSpanCount(1); + assertThat(reporter.getFirstSpan().getNameAsString()).isEqualTo("child-span"); + assertThat(reporter.getFirstSpan().getTransaction()).isEqualTo(reporter.getFirstTransaction()); + Transaction transaction = reporter.getFirstTransaction(); + assertThat(transaction.getNameAsString()).isEqualTo(TestContext.FUNCTION_NAME); + assertThat(transaction.getType()).isEqualTo("request"); + assertThat(transaction.getResult()).isEqualTo("HTTP 2xx"); + + assertThat(transaction.getContext().getCloudOrigin()).isNotNull(); + assertThat(transaction.getContext().getCloudOrigin().getProvider()).isEqualTo("aws"); + assertThat(transaction.getContext().getCloudOrigin().getServiceName()).isNull(); + assertThat(transaction.getContext().getCloudOrigin().getRegion()).isNull(); + assertThat(transaction.getContext().getCloudOrigin().getAccountId()).isNull(); + + assertThat(transaction.getContext().getServiceOrigin().hasContent()).isFalse(); + + Faas faas = transaction.getFaas(); + assertThat(faas.getExecution()).isEqualTo(TestContext.AWS_REQUEST_ID); + + assertThat(faas.getTrigger().getType()).isEqualTo("other"); + assertThat(faas.getTrigger().getRequestId()).isNull(); + } + + + @Test + public void testCallWithErrorStatusCode() { + Objects.requireNonNull(context).setErrorStatusCode(); + getFunction().handleRequest(createInput(), context); + reporter.awaitTransactionCount(1); + reporter.awaitSpanCount(1); + assertThat(reporter.getFirstSpan().getNameAsString()).isEqualTo("child-span"); + assertThat(reporter.getFirstSpan().getTransaction()).isEqualTo(reporter.getFirstTransaction()); + Transaction transaction = reporter.getFirstTransaction(); + assertThat(transaction.getResult()).isEqualTo("HTTP 5xx"); + assertThat(transaction.getOutcome()).isEqualTo(Outcome.FAILURE); + } + +} From f249ddec256fcb172961272a26213796e8a1553f Mon Sep 17 00:00:00 2001 From: Nugusbayev Kanagat Date: Tue, 7 Nov 2023 18:35:24 +0600 Subject: [PATCH 4/8] added entry to changelog --- CHANGELOG.asciidoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 83c2f288bb..e0ef0b7e37 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -35,8 +35,9 @@ Use subheadings with the "=====" level for adding notes for unreleased changes: ===== Features * Added protection against invalid timestamps provided by manual instrumentation - {pull}3363[#3363] * Added support for AWS SDK 2.21 - {pull}3373[#3373] -* Capture bucket and object key to Lambda transaction as OTel attributes - `aws.s3.bueckt`, `aws.s3.key` - {pull}3364[#3364] +* Capture bucket and object key to Lambda transaction as OTel attributes - `aws.s3.bucket`, `aws.s3.key` - {pull}3364[#3364] * Added `context_propagation_only` configuration option - {pull}3358[#3358] +* Added lambda support for ELB triggers {pull}#3411[#3411] [float] ===== Bug fixes From 1bd7be3d34ac17cb481b27f6f1239519b670f976 Mon Sep 17 00:00:00 2001 From: Nugusbayev Kanagat Date: Tue, 7 Nov 2023 18:40:15 +0600 Subject: [PATCH 5/8] minor polish --- .../helper/AbstractAPIGatewayTransactionHelper.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/AbstractAPIGatewayTransactionHelper.java b/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/AbstractAPIGatewayTransactionHelper.java index 150a072a1e..c2332359d8 100644 --- a/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/AbstractAPIGatewayTransactionHelper.java +++ b/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/AbstractAPIGatewayTransactionHelper.java @@ -44,7 +44,7 @@ public abstract class AbstractAPIGatewayTransactionHelper extends AbstractLambdaTransactionHelper { private static final Logger logger = LoggerFactory.getLogger(AbstractAPIGatewayTransactionHelper.class); protected static final String TRANSACTION_TYPE = "request"; - protected static final String CONTENT_TYPE_HEADER = "Content-Type"; + private static final String CONTENT_TYPE_HEADER = "Content-Type"; private static final Set METHODS_WITH_BODY = new HashSet<>(Arrays.asList("POST", "PUT", "PATCH", "DELETE")); private static final String CONTENT_TYPE_FROM_URLENCODED = "application/x-www-form-urlencoded"; @@ -114,7 +114,7 @@ protected void fillHttpResponseData(Transaction transaction, @Nullable Map headers) { + private void setRequestHeaders(Transaction transaction, Map headers) { final Request req = transaction.getContext().getRequest(); if (transaction.isSampled() && isCaptureHeaders()) { for (Map.Entry headerEntry : headers.entrySet()) { From 4d90e5a14c962464e61c5a9726d9e75f72afc864 Mon Sep 17 00:00:00 2001 From: Nugusbayev Kanagat Date: Tue, 7 Nov 2023 18:45:44 +0600 Subject: [PATCH 6/8] minor polish --- ...licationLoadBalancerRequestTransactionHelper.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/ApplicationLoadBalancerRequestTransactionHelper.java b/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/ApplicationLoadBalancerRequestTransactionHelper.java index 714b6cf71e..0c0ad28e06 100644 --- a/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/ApplicationLoadBalancerRequestTransactionHelper.java +++ b/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/ApplicationLoadBalancerRequestTransactionHelper.java @@ -22,6 +22,8 @@ import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.context.CloudOrigin; import co.elastic.apm.agent.impl.context.Request; +import co.elastic.apm.agent.impl.context.ServiceOrigin; +import co.elastic.apm.agent.impl.transaction.FaasTrigger; import co.elastic.apm.agent.impl.transaction.Transaction; import co.elastic.apm.agent.sdk.internal.util.PrivilegedActionUtils; import co.elastic.apm.agent.tracer.GlobalTracer; @@ -73,12 +75,14 @@ protected void setTransactionTriggerData(Transaction transaction, ApplicationLoa CloudOrigin cloudOrigin = transaction.getContext().getCloudOrigin(); cloudOrigin.withServiceName("elb"); cloudOrigin.withProvider("aws"); - transaction.getFaas().getTrigger().withType("http"); - transaction.getFaas().getTrigger().withRequestId(getHeader(loadBalancerRequestEvent, "x-amzn-trace-id")); + FaasTrigger faasTrigger = transaction.getFaas().getTrigger(); + faasTrigger.withType("http"); + faasTrigger.withRequestId(getHeader(loadBalancerRequestEvent, "x-amzn-trace-id")); LoadBalancerElbTargetGroupArnMetadata metadata = parseMetadata(loadBalancerRequestEvent); if (null != metadata) { - transaction.getContext().getServiceOrigin().withName(metadata.getTargetGroupName()); - transaction.getContext().getServiceOrigin().withId(metadata.getTargetGroupArn()); + ServiceOrigin serviceOrigin = transaction.getContext().getServiceOrigin(); + serviceOrigin.withName(metadata.getTargetGroupName()); + serviceOrigin.withId(metadata.getTargetGroupArn()); cloudOrigin.withAccountId(metadata.getAccountId()); cloudOrigin.withRegion(metadata.getCloudRegion()); } From 48869646fda6ea9701ba52b36ff218ab2edf08be Mon Sep 17 00:00:00 2001 From: Nugusbayev Kanagat Date: Tue, 7 Nov 2023 18:50:08 +0600 Subject: [PATCH 7/8] minor polish --- .../AbstractAPIGatewayTransactionHelper.java | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/AbstractAPIGatewayTransactionHelper.java b/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/AbstractAPIGatewayTransactionHelper.java index c2332359d8..4ec78e5a2a 100644 --- a/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/AbstractAPIGatewayTransactionHelper.java +++ b/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/AbstractAPIGatewayTransactionHelper.java @@ -18,19 +18,18 @@ */ package co.elastic.apm.agent.awslambda.helper; +import co.elastic.apm.agent.common.util.WildcardMatcher; import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.context.CloudOrigin; import co.elastic.apm.agent.impl.context.Request; import co.elastic.apm.agent.impl.context.Response; import co.elastic.apm.agent.impl.context.ServiceOrigin; -import co.elastic.apm.agent.tracer.util.ResultUtil; import co.elastic.apm.agent.impl.transaction.Transaction; -import co.elastic.apm.agent.common.util.WildcardMatcher; import co.elastic.apm.agent.sdk.logging.Logger; import co.elastic.apm.agent.sdk.logging.LoggerFactory; import co.elastic.apm.agent.tracer.AbstractSpan; +import co.elastic.apm.agent.tracer.util.ResultUtil; import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; import javax.annotation.Nullable; import java.nio.CharBuffer; @@ -71,33 +70,33 @@ protected void fillHttpRequestData(Transaction transaction, @Nullable String htt @Nullable protected String getHost(@Nullable Map headers) { - String host = null; - if (null != headers) { - host = headers.get("host"); - if (null == host) { - host = headers.get("Host"); - } + if (null == headers) { + return null; + } + String host = headers.get("host"); + if (null == host) { + host = headers.get("Host"); } return host; } @Nullable protected String getQueryString(@Nullable Map queryParameters) { - if (null != queryParameters && !queryParameters.isEmpty()) { - StringBuilder queryString = new StringBuilder(); - int i = 0; - for (Map.Entry entry : queryParameters.entrySet()) { - if (i > 0) { - queryString.append('&'); - } - queryString.append(entry.getKey()); - queryString.append('='); - queryString.append(entry.getValue()); - i++; + if (null == queryParameters || queryParameters.isEmpty()) { + return null; + } + StringBuilder queryString = new StringBuilder(); + int i = 0; + for (Map.Entry entry : queryParameters.entrySet()) { + if (i > 0) { + queryString.append('&'); } - return queryString.toString(); + queryString.append(entry.getKey()); + queryString.append('='); + queryString.append(entry.getValue()); + i++; } - return null; + return queryString.toString(); } @@ -115,7 +114,7 @@ protected void fillHttpResponseData(Transaction transaction, @Nullable Map Date: Wed, 13 Mar 2024 11:28:36 +0100 Subject: [PATCH 8/8] fix changelog --- CHANGELOG.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index fcb52b7502..35349f1556 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -38,6 +38,7 @@ Use subheadings with the "=====" level for adding notes for unreleased changes: [float] ===== Features * Differentiate Lambda URLs from API Gateway in AWS Lambda integration - {pull}3417[#3417] +* Added lambda support for ELB triggers {pull}#3411[#3411] [[release-notes-1.x]] === Java Agent version 1.x @@ -96,7 +97,6 @@ Use subheadings with the "=====" level for adding notes for unreleased changes: [float] ===== Features * Added support for OpenTelementry Attributes db.statement and db.user - {pull}3475[#3475] -* Added lambda support for ELB triggers {pull}#3411[#3411] [float] ===== Bug fixes