diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 6f863ba558..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 @@ -127,7 +128,7 @@ affect you, if you are using the OpenTelemetry API only and not the SDK. - {pull ===== 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 attribute[*] for JMX pattern metrics (all metrics can now be generated with `object_name[*:type=*,name=*] attribute[*]`) - {pull}3376[#3376] 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 3d13b5f35c..a923707937 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 @@ -19,8 +19,8 @@ package co.elastic.apm.agent.awslambda.helper; import co.elastic.apm.agent.awslambda.MapTextHeaderGetter; -import co.elastic.apm.agent.tracer.GlobalTracer; import co.elastic.apm.agent.sdk.internal.util.PrivilegedActionUtils; +import co.elastic.apm.agent.tracer.GlobalTracer; import co.elastic.apm.agent.tracer.Tracer; import co.elastic.apm.agent.tracer.Transaction; import com.amazonaws.services.lambda.runtime.Context; @@ -28,7 +28,6 @@ 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, APIGatewayP if (null != rContext) { setApiGatewayContextData(transaction, rContext.getRequestId(), rContext.getApiId(), - getHost(apiGatewayRequest), rContext.getAccountId()); + getHost(apiGatewayRequest.getHeaders()), rContext.getAccountId()); } } @@ -149,4 +117,5 @@ protected String getStage(APIGatewayProxyRequestEvent event) { protected String getResourcePath(APIGatewayProxyRequestEvent event) { return event.getRequestContext().getResourcePath(); } + } diff --git a/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/APIGatewayProxyV2TransactionHelper.java b/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/APIGatewayProxyV2TransactionHelper.java index f912577f92..0ba8476c22 100644 --- a/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/APIGatewayProxyV2TransactionHelper.java +++ b/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/APIGatewayProxyV2TransactionHelper.java @@ -45,8 +45,8 @@ public static APIGatewayProxyV2TransactionHelper getInstance() { } @Override - protected Transaction doStartTransaction(APIGatewayV2HTTPEvent apiGatewayEvent, Context lambdaContext) { - Transaction transaction = tracer.startChildTransaction(apiGatewayEvent.getHeaders(), MapTextHeaderGetter.INSTANCE, PrivilegedActionUtils.getClassLoader(apiGatewayEvent.getClass())); + protected Transaction doStartTransaction(APIGatewayV2HTTPEvent apiGatewayEvent, Context lambdaContext) { + Transaction transaction = tracer.startChildTransaction(apiGatewayEvent.getHeaders(), MapTextHeaderGetter.INSTANCE, PrivilegedActionUtils.getClassLoader(apiGatewayEvent.getClass())); APIGatewayV2HTTPEvent.RequestContext requestContext = apiGatewayEvent.getRequestContext(); if (transaction != null) { @@ -60,12 +60,12 @@ protected Transaction doStartTransaction(APIGatewayV2HTTPEvent apiGatewayEvent, } @Override - public void captureOutputForTransaction(Transaction transaction, APIGatewayV2HTTPResponse responseEvent) { + public void captureOutputForTransaction(Transaction transaction, APIGatewayV2HTTPResponse responseEvent) { fillHttpResponseData(transaction, responseEvent.getHeaders(), responseEvent.getStatusCode()); } @Override - protected void setTransactionTriggerData(Transaction transaction, APIGatewayV2HTTPEvent apiGatewayRequest) { + protected void setTransactionTriggerData(Transaction transaction, APIGatewayV2HTTPEvent apiGatewayRequest) { super.setTransactionTriggerData(transaction, apiGatewayRequest); APIGatewayV2HTTPEvent.RequestContext rContext = apiGatewayRequest.getRequestContext(); setApiGatewayContextData(transaction, rContext.getRequestId(), rContext.getApiId(), 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 b72c423c79..ae4f268ac8 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 lambdaContex } 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 542eed68f6..b8e2f30e0c 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,6 +18,10 @@ */ package co.elastic.apm.agent.awslambda.helper; +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.ServiceOrigin; import co.elastic.apm.agent.tracer.Tracer; import co.elastic.apm.agent.tracer.Transaction; @@ -25,12 +29,7 @@ import co.elastic.apm.agent.tracer.metadata.Request; import co.elastic.apm.agent.tracer.metadata.Response; import co.elastic.apm.agent.tracer.util.ResultUtil; -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 com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent; import javax.annotation.Nullable; import java.nio.CharBuffer; @@ -68,6 +67,38 @@ protected void fillHttpRequestData(Transaction transaction, @Nullable String } } + @Nullable + protected String getHost(@Nullable Map headers) { + 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()) { + return null; + } + 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(); + } + + protected void fillHttpResponseData(Transaction transaction, @Nullable Map headers, int statusCode) { Response response = transaction.getContext().getResponse(); response.withFinished(true); @@ -82,7 +113,7 @@ protected void fillHttpResponseData(Transaction transaction, @Nullable Map { + @Nullable + private static ApplicationLoadBalancerRequestTransactionHelper INSTANCE; + + private ApplicationLoadBalancerRequestTransactionHelper(Tracer tracer) { + super(tracer); + } + + public static ApplicationLoadBalancerRequestTransactionHelper getInstance() { + if (INSTANCE == null) { + INSTANCE = new ApplicationLoadBalancerRequestTransactionHelper(GlobalTracer.get()); + } + 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"); + FaasTrigger faasTrigger = transaction.getFaas().getTrigger(); + faasTrigger.withType("http"); + faasTrigger.withRequestId(getHeader(loadBalancerRequestEvent, "x-amzn-trace-id")); + LoadBalancerElbTargetGroupArnMetadata metadata = parseMetadata(loadBalancerRequestEvent); + if (null != metadata) { + ServiceOrigin serviceOrigin = transaction.getContext().getServiceOrigin(); + serviceOrigin.withName(metadata.getTargetGroupName()); + serviceOrigin.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) { + if (null == event.getRequestContext()) { + return null; + } + 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; + } + + @Nullable + @Override + String getDomainName(ApplicationLoadBalancerRequestEvent apiGatewayRequest) { + 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..a87af8e85c --- /dev/null +++ b/apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/LoadBalancerElbTargetGroupArnMetadata.java @@ -0,0 +1,70 @@ +/* + * 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 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/AbstractLambdaTest.java b/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/AbstractLambdaTest.java index e196b30175..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 @@ -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; 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 6aa7f622b0..6006e85979 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 @@ -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) { @@ -210,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 d98e652551..4315ca4d3f 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 @@ -47,7 +47,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 @@ -151,34 +151,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) { @@ -212,19 +184,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 new file mode 100644 index 0000000000..5583ba1834 --- /dev/null +++ b/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/ApplicationLoadBalancerRequestEventLambdaTest.java @@ -0,0 +1,197 @@ +/* + * 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.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; +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 BaseGatewayLambdaTest { + + @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() { + 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; + } + + @Override + protected boolean supportsContextPropagation() { + return false; + } + + @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"); + } + + @Test + public void testCallWithNullRequestContext() { + ApplicationLoadBalancerRequestEvent requestEvent = createInput(); + requestEvent.setRequestContext(null); + + getFunction().handleRequest(requestEvent, 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()).isEqualTo("elb"); + + 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("http"); + assertThat(faas.getTrigger().getRequestId()).isEqualTo("Root=1-xxxxxxxxxxxxxx"); + } + + @Test + public void testTransactionNameWithUsePathAsName() { + doReturn(true).when(config.getConfig(WebConfiguration.class)).isUsePathAsName(); + getFunction().handleRequest(createInput(), context); + reporter.awaitTransactionCount(1); + reporter.awaitSpanCount(1); + assertThat(reporter.getFirstTransaction().getNameAsString()).isEqualTo("POST /toolz/api/v2.0/downloadPDF/PDF_2020-09-11_11-06-01.pdf"); + } + +} diff --git a/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/BaseGatewayLambdaTest.java b/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/BaseGatewayLambdaTest.java new file mode 100644 index 0000000000..22394fb6d6 --- /dev/null +++ b/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/BaseGatewayLambdaTest.java @@ -0,0 +1,75 @@ +/* + * 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.TestContext; +import co.elastic.apm.agent.impl.transaction.Faas; +import co.elastic.apm.agent.impl.transaction.Transaction; +import co.elastic.apm.agent.tracer.Outcome; +import org.junit.jupiter.api.Test; + +import java.util.Objects; + +import static org.assertj.core.api.Assertions.assertThat; + +public abstract class BaseGatewayLambdaTest 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); + } + +} 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..54dd61fa14 --- /dev/null +++ b/apm-agent-plugins/apm-awslambda-plugin/src/test/java/co/elastic/apm/agent/awslambda/lambdas/ApplicationLoadBalancerRequestLambdaFunction.java @@ -0,0 +1,52 @@ +/* + * 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.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; + } +}