From 57cfddcdd08d123f03746e0932d54ccee270f262 Mon Sep 17 00:00:00 2001 From: Raphael Philipe Mendes da Silva Date: Wed, 12 Jul 2023 10:58:46 -0700 Subject: [PATCH] Fix http attributes of AWS SDK V2 instrumentation (#8931) Signed-off-by: Raphael Silva Co-authored-by: Jean Bisutti --- .../aws-sdk-2.2/javaagent/build.gradle.kts | 2 +- .../src/test/java/QueryProtocolModelTest.java | 25 ++++ .../library-autoconfigure/build.gradle.kts | 1 - .../aws-sdk-2.2/library/build.gradle.kts | 3 +- .../v2_2/TracingExecutionInterceptor.java | 10 +- .../awssdk/v2_2/QueryProtocolModelTest.java | 28 +++++ .../aws-sdk-2.2/testing/build.gradle.kts | 1 + .../awssdk/v2_2/AbstractAws2ClientTest.groovy | 91 +++++++++------ .../v2_2/AbstractAws2SqsTracingTest.groovy | 3 + .../v2_2/AbstractQueryProtocolModelTest.java | 110 ++++++++++++++++++ 10 files changed, 229 insertions(+), 45 deletions(-) create mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/test/java/QueryProtocolModelTest.java create mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/QueryProtocolModelTest.java create mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractQueryProtocolModelTest.java diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts index e3e6e2d697..0aa4f3625c 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts @@ -81,6 +81,7 @@ dependencies { testLibrary("software.amazon.awssdk:s3:2.2.0") testLibrary("software.amazon.awssdk:sqs:2.2.0") testLibrary("software.amazon.awssdk:sns:2.2.0") + testLibrary("software.amazon.awssdk:ses:2.2.0") } tasks { @@ -95,7 +96,6 @@ tasks { } withType().configureEach { - systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) // TODO run tests both with and without experimental span attributes systemProperty("otel.instrumentation.aws-sdk.experimental-span-attributes", "true") } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/test/java/QueryProtocolModelTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/test/java/QueryProtocolModelTest.java new file mode 100644 index 0000000000..d86c1a49d4 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/test/java/QueryProtocolModelTest.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import io.opentelemetry.instrumentation.awssdk.v2_2.AbstractQueryProtocolModelTest; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; + +class QueryProtocolModelTest extends AbstractQueryProtocolModelTest { + @RegisterExtension + private final AgentInstrumentationExtension testing = AgentInstrumentationExtension.create(); + + @Override + protected ClientOverrideConfiguration.Builder createClientOverrideConfigurationBuilder() { + return ClientOverrideConfiguration.builder(); + } + + @Override + protected InstrumentationExtension getTesting() { + return testing; + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library-autoconfigure/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/library-autoconfigure/build.gradle.kts index 3c4d6758fb..4c6d8aacf0 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library-autoconfigure/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library-autoconfigure/build.gradle.kts @@ -23,7 +23,6 @@ dependencies { tasks { test { - systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) systemProperty("otel.instrumentation.aws-sdk.experimental-span-attributes", true) systemProperty("otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging", true) } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts index 9f47f29964..f50dd96dcf 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts @@ -18,6 +18,7 @@ dependencies { testLibrary("software.amazon.awssdk:kinesis:2.2.0") testLibrary("software.amazon.awssdk:rds:2.2.0") testLibrary("software.amazon.awssdk:s3:2.2.0") + testLibrary("software.amazon.awssdk:ses:2.2.0") } testing { @@ -42,8 +43,6 @@ testing { tasks { withType { - systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) - // NB: If you'd like to change these, there is some cleanup work to be done, as most tests ignore this and // set the value directly (the "library" does not normally query it, only library-autoconfigure) systemProperty("otel.instrumentation.aws-sdk.experimental-span-attributes", true) diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java index b5850350f6..c344596f4a 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java @@ -137,9 +137,13 @@ final class TracingExecutionInterceptor implements ExecutionInterceptor { } @Override - public void afterMarshalling( - Context.AfterMarshalling context, ExecutionAttributes executionAttributes) { - + public void beforeTransmission( + Context.BeforeTransmission context, ExecutionAttributes executionAttributes) { + // In beforeTransmission we get access to the finalized http request, including modifications + // performed by other interceptors and the message signature. + // It is unlikely that further modifications are performed by the http client performing the + // request given that this would require the signature to be regenerated. + // // Since we merge the HTTP attributes into an already started span instead of creating a // full child span, we have to do some dirty work here. // diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/QueryProtocolModelTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/QueryProtocolModelTest.java new file mode 100644 index 0000000000..d5ab73e541 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/QueryProtocolModelTest.java @@ -0,0 +1,28 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v2_2; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; + +class QueryProtocolModelTest extends AbstractQueryProtocolModelTest { + @RegisterExtension + public final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @Override + protected ClientOverrideConfiguration.Builder createClientOverrideConfigurationBuilder() { + return ClientOverrideConfiguration.builder() + .addExecutionInterceptor( + AwsSdkTelemetry.builder(testing.getOpenTelemetry()).build().newExecutionInterceptor()); + } + + @Override + protected InstrumentationExtension getTesting() { + return testing; + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/testing/build.gradle.kts index 4a77380569..2a7f14d14b 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/build.gradle.kts @@ -18,6 +18,7 @@ dependencies { compileOnly("software.amazon.awssdk:s3:2.2.0") compileOnly("software.amazon.awssdk:sqs:2.2.0") compileOnly("software.amazon.awssdk:sns:2.2.0") + compileOnly("software.amazon.awssdk:ses:2.2.0") // needed for SQS - using emq directly as localstack references emq v0.15.7 ie WITHOUT AWS trace header propagation implementation("org.elasticmq:elasticmq-rest-sqs_2.12:1.0.0") diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy index a193d167cb..518ccedf3e 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy @@ -52,13 +52,18 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { "Cannot check Sqs.SendMessage here due to hard-coded MD5.") } + // Force localhost instead of relying on mock server because using ip is yet another corner case of the virtual + // bucket changes introduced by aws sdk v2.18.0. When using IP, there is no way to prefix the hostname with the + // bucket name as label. + def clientUri = URI.create("http://localhost:${server.httpPort()}") + def "send #operation request with builder #builder.class.getName() mocked response"() { assumeSupportedConfig(service, operation) setup: configureSdkClient(builder) def client = builder - .endpointOverride(server.httpUri()) + .endpointOverride(clientUri) .region(Region.AP_NORTHEAST_1) .credentialsProvider(CREDENTIALS_PROVIDER) .build() @@ -80,9 +85,18 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { kind operation != "SendMessage" ? CLIENT : PRODUCER hasNoParent() attributes { - "$SemanticAttributes.NET_PEER_NAME" "127.0.0.1" + if (service == "S3") { + // Starting with AWS SDK V2 2.18.0, the s3 sdk will prefix the hostname with the bucket name in case + // the bucket name is a valid DNS label, even in the case that we are using an endpoint override. + // Previously the sdk was only doing that if endpoint had "s3" as label in the FQDN. + // Our test assert both cases so that we don't need to know what version is being tested. + "$SemanticAttributes.NET_PEER_NAME" { it == "somebucket.localhost" || it == "localhost" } + "$SemanticAttributes.HTTP_URL" { it.startsWith("http://somebucket.localhost:${server.httpPort()}") || it.startsWith("http://localhost:${server.httpPort()}/somebucket") } + } else { + "$SemanticAttributes.NET_PEER_NAME" "localhost" + "$SemanticAttributes.HTTP_URL" { it.startsWith("http://localhost:${server.httpPort()}") } + } "$SemanticAttributes.NET_PEER_PORT" server.httpPort() - "$SemanticAttributes.HTTP_URL" { it.startsWith("${server.httpUri()}${path}") } "$SemanticAttributes.HTTP_METHOD" "$method" "$SemanticAttributes.HTTP_STATUS_CODE" 200 "$SemanticAttributes.USER_AGENT_ORIGINAL" { it.startsWith("aws-sdk-java/") } @@ -111,17 +125,17 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { request.request().headers().get("traceparent") == null where: - service | operation | method | path | requestId | builder | call | body - "S3" | "CreateBucket" | "PUT" | path("somebucket") | "UNKNOWN" | S3Client.builder() | { c -> c.createBucket(CreateBucketRequest.builder().bucket("somebucket").build()) } | "" - "S3" | "GetObject" | "GET" | path("somebucket", "somekey") | "UNKNOWN" | S3Client.builder() | { c -> c.getObject(GetObjectRequest.builder().bucket("somebucket").key("somekey").build()) } | "" - "Kinesis" | "DeleteStream" | "POST" | "" | "UNKNOWN" | KinesisClient.builder() | { c -> c.deleteStream(DeleteStreamRequest.builder().streamName("somestream").build()) } | "" - "Sqs" | "CreateQueue" | "POST" | "" | "7a62c49f-347e-4fc4-9331-6e8e7a96aa73" | SqsClient.builder() | { c -> c.createQueue(CreateQueueRequest.builder().queueName("somequeue").build()) } | """ + service | operation | method | requestId | builder | call | body + "S3" | "CreateBucket" | "PUT" | "UNKNOWN" | S3Client.builder() | { c -> c.createBucket(CreateBucketRequest.builder().bucket("somebucket").build()) } | "" + "S3" | "GetObject" | "GET" | "UNKNOWN" | S3Client.builder() | { c -> c.getObject(GetObjectRequest.builder().bucket("somebucket").key("somekey").build()) } | "" + "Kinesis" | "DeleteStream" | "POST" | "UNKNOWN" | KinesisClient.builder() | { c -> c.deleteStream(DeleteStreamRequest.builder().streamName("somestream").build()) } | "" + "Sqs" | "CreateQueue" | "POST" | "7a62c49f-347e-4fc4-9331-6e8e7a96aa73" | SqsClient.builder() | { c -> c.createQueue(CreateQueueRequest.builder().queueName("somequeue").build()) } | """ https://queue.amazonaws.com/123456789012/MyQueue 7a62c49f-347e-4fc4-9331-6e8e7a96aa73 """ - "Sqs" | "SendMessage" | "POST" | "" | "27daac76-34dd-47df-bd01-1f6e873584a0" | SqsClient.builder() | { c -> c.sendMessage(SendMessageRequest.builder().queueUrl("someurl").messageBody("").build()) } | """ + "Sqs" | "SendMessage" | "POST" | "27daac76-34dd-47df-bd01-1f6e873584a0" | SqsClient.builder() | { c -> c.sendMessage(SendMessageRequest.builder().queueUrl("someurl").messageBody("").build()) } | """ d41d8cd98f00b204e9800998ecf8427e @@ -131,14 +145,14 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { 27daac76-34dd-47df-bd01-1f6e873584a0 """ - "Ec2" | "AllocateAddress" | "POST" | "" | "59dbff89-35bd-4eac-99ed-be587EXAMPLE" | Ec2Client.builder() | { c -> c.allocateAddress() } | """ + "Ec2" | "AllocateAddress" | "POST" | "59dbff89-35bd-4eac-99ed-be587EXAMPLE" | Ec2Client.builder() | { c -> c.allocateAddress() } | """ 59dbff89-35bd-4eac-99ed-be587EXAMPLE 192.0.2.1 standard """ - "Rds" | "DeleteOptionGroup" | "POST" | "" | "0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99" | RdsClient.builder() | { c -> c.deleteOptionGroup(DeleteOptionGroupRequest.builder().build()) } | """ + "Rds" | "DeleteOptionGroup" | "POST" | "0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99" | RdsClient.builder() | { c -> c.deleteOptionGroup(DeleteOptionGroupRequest.builder().build()) } | """ 0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99 @@ -150,7 +164,7 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { setup: configureSdkClient(builder) def client = builder - .endpointOverride(server.httpUri()) + .endpointOverride(clientUri) .region(Region.AP_NORTHEAST_1) .credentialsProvider(CREDENTIALS_PROVIDER) .build() @@ -171,9 +185,18 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { kind operation != "SendMessage" ? CLIENT : PRODUCER hasNoParent() attributes { - "$SemanticAttributes.NET_PEER_NAME" "127.0.0.1" + if (service == "S3") { + // Starting with AWS SDK V2 2.18.0, the s3 sdk will prefix the hostname with the bucket name in case + // the bucket name is a valid DNS label, even in the case that we are using an endpoint override. + // Previously the sdk was only doing that if endpoint had "s3" as label in the FQDN. + // Our test assert both cases so that we don't need to know what version is being tested. + "$SemanticAttributes.NET_PEER_NAME" { it == "somebucket.localhost" || it == "localhost" } + "$SemanticAttributes.HTTP_URL" { it.startsWith("http://somebucket.localhost:${server.httpPort()}") || it.startsWith("http://localhost:${server.httpPort()}") } + } else { + "$SemanticAttributes.NET_PEER_NAME" "localhost" + "$SemanticAttributes.HTTP_URL" "http://localhost:${server.httpPort()}" + } "$SemanticAttributes.NET_PEER_PORT" server.httpPort() - "$SemanticAttributes.HTTP_URL" { it.startsWith("${server.httpUri()}${path}") } "$SemanticAttributes.HTTP_METHOD" "$method" "$SemanticAttributes.HTTP_STATUS_CODE" 200 "$SemanticAttributes.USER_AGENT_ORIGINAL" { it.startsWith("aws-sdk-java/") } @@ -213,18 +236,18 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { } where: - service | operation | method | path | requestId | builder | call | body - "S3" | "CreateBucket" | "PUT" | path("somebucket") | "UNKNOWN" | S3AsyncClient.builder() | { c -> c.createBucket(CreateBucketRequest.builder().bucket("somebucket").build()) } | "" - "S3" | "GetObject" | "GET" | path("somebucket", "somekey") | "UNKNOWN" | S3AsyncClient.builder() | { c -> c.getObject(GetObjectRequest.builder().bucket("somebucket").key("somekey").build(), AsyncResponseTransformer.toBytes()) } | "1234567890" + service | operation | method | requestId | builder | call | body + "S3" | "CreateBucket" | "PUT" | "UNKNOWN" | S3AsyncClient.builder() | { c -> c.createBucket(CreateBucketRequest.builder().bucket("somebucket").build()) } | "" + "S3" | "GetObject" | "GET" | "UNKNOWN" | S3AsyncClient.builder() | { c -> c.getObject(GetObjectRequest.builder().bucket("somebucket").key("somekey").build(), AsyncResponseTransformer.toBytes()) } | "1234567890" // Kinesis seems to expect an http2 response which is incompatible with our test server. // "Kinesis" | "DeleteStream" | "POST" | "/" | "UNKNOWN" | KinesisAsyncClient.builder() | { c -> c.deleteStream(DeleteStreamRequest.builder().streamName("somestream").build()) } | "" - "Sqs" | "CreateQueue" | "POST" | "" | "7a62c49f-347e-4fc4-9331-6e8e7a96aa73" | SqsAsyncClient.builder() | { c -> c.createQueue(CreateQueueRequest.builder().queueName("somequeue").build()) } | """ + "Sqs" | "CreateQueue" | "POST" | "7a62c49f-347e-4fc4-9331-6e8e7a96aa73" | SqsAsyncClient.builder() | { c -> c.createQueue(CreateQueueRequest.builder().queueName("somequeue").build()) } | """ https://queue.amazonaws.com/123456789012/MyQueue 7a62c49f-347e-4fc4-9331-6e8e7a96aa73 """ - "Sqs" | "SendMessage" | "POST" | "" | "27daac76-34dd-47df-bd01-1f6e873584a0" | SqsAsyncClient.builder() | { c -> c.sendMessage(SendMessageRequest.builder().queueUrl("someurl").messageBody("").build()) } | """ + "Sqs" | "SendMessage" | "POST" | "27daac76-34dd-47df-bd01-1f6e873584a0" | SqsAsyncClient.builder() | { c -> c.sendMessage(SendMessageRequest.builder().queueUrl("someurl").messageBody("").build()) } | """ d41d8cd98f00b204e9800998ecf8427e @@ -234,19 +257,19 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { 27daac76-34dd-47df-bd01-1f6e873584a0 """ - "Ec2" | "AllocateAddress" | "POST" | "" | "59dbff89-35bd-4eac-99ed-be587EXAMPLE" | Ec2AsyncClient.builder() | { c -> c.allocateAddress() } | """ + "Ec2" | "AllocateAddress" | "POST" | "59dbff89-35bd-4eac-99ed-be587EXAMPLE" | Ec2AsyncClient.builder() | { c -> c.allocateAddress() } | """ 59dbff89-35bd-4eac-99ed-be587EXAMPLE 192.0.2.1 standard """ - "Rds" | "DeleteOptionGroup" | "POST" | "" | "0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99" | RdsAsyncClient.builder() | { c -> c.deleteOptionGroup(DeleteOptionGroupRequest.builder().build()) } | """ + "Rds" | "DeleteOptionGroup" | "POST" | "0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99" | RdsAsyncClient.builder() | { c -> c.deleteOptionGroup(DeleteOptionGroupRequest.builder().build()) } | """ 0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99 """ - "Sns" | "Publish" | "POST" | "" | "f187a3c1-376f-11df-8963-01868b7c937a" | SnsAsyncClient.builder() | { SnsAsyncClient c -> c.publish(r -> r.message("hello")) } | """ + "Sns" | "Publish" | "POST" | "f187a3c1-376f-11df-8963-01868b7c937a" | SnsAsyncClient.builder() | { SnsAsyncClient c -> c.publish(r -> r.message("hello")) } | """ 94f20ce6-13c5-43a0-9a9e-ca52d816e90b @@ -270,7 +293,7 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { .overrideConfiguration(createOverrideConfigurationBuilder() .retryPolicy(RetryPolicy.builder().numRetries(1).build()) .build()) - .endpointOverride(server.httpUri()) + .endpointOverride(clientUri) .region(Region.AP_NORTHEAST_1) .credentialsProvider(CREDENTIALS_PROVIDER) .httpClientBuilder(ApacheHttpClient.builder().socketTimeout(Duration.ofMillis(50))) @@ -282,7 +305,6 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { then: thrown SdkClientException - def path = path("somebucket", "somekey") assertTraces(1) { trace(0, 1) { span(0) { @@ -292,13 +314,18 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { errorEvent SdkClientException, "Unable to execute HTTP request: Read timed out" hasNoParent() attributes { - "$SemanticAttributes.NET_PEER_NAME" "127.0.0.1" + // Starting with AWS SDK V2 2.18.0, the s3 sdk will prefix the hostname with the bucket name in case + // the bucket name is a valid DNS label, even in the case that we are using an endpoint override. + // Previously the sdk was only doing that if endpoint had "s3" as label in the FQDN. + // Our test assert both cases so that we don't need to know what version is being tested. + "$SemanticAttributes.NET_PEER_NAME" { it == "somebucket.localhost" || it == "localhost" } + "$SemanticAttributes.HTTP_URL" { it == "http://somebucket.localhost:${server.httpPort()}/somekey" || it == "http://localhost:${server.httpPort()}/somebucket/somekey" } "$SemanticAttributes.NET_PEER_PORT" server.httpPort() - "$SemanticAttributes.HTTP_URL" "${server.httpUri()}${path}" "$SemanticAttributes.HTTP_METHOD" "GET" "$SemanticAttributes.RPC_SYSTEM" "aws-api" "$SemanticAttributes.RPC_SERVICE" "S3" "$SemanticAttributes.RPC_METHOD" "GetObject" + "$SemanticAttributes.USER_AGENT_ORIGINAL" String "aws.agent" "java-aws-sdk" "aws.bucket.name" "somebucket" } @@ -306,16 +333,4 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { } } } - - static String path(String bucket, String path = null) { - def result = "" - // since 2.18.0 bucket name is not present in request path - if (!Boolean.getBoolean("testLatestDeps") && !bucket.isEmpty()) { - result = "/" + bucket - } - if (path != null && !path.isEmpty()) { - result += "/" + path - } - return result - } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.groovy index a3529957b6..27b8e7e3f2 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.groovy @@ -171,6 +171,7 @@ abstract class AbstractAws2SqsTracingTest extends InstrumentationSpecification { "http.method" "POST" "http.status_code" 200 "http.url" { it.startsWith("http://localhost:$sqsPort") } + "$SemanticAttributes.USER_AGENT_ORIGINAL" String "net.peer.name" "localhost" "net.peer.port" sqsPort "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" { it == null || it instanceof Long } @@ -310,6 +311,7 @@ abstract class AbstractAws2SqsTracingTest extends InstrumentationSpecification { "http.method" "POST" "http.status_code" 200 "http.url" { it.startsWith("http://localhost:$sqsPort") } + "$SemanticAttributes.USER_AGENT_ORIGINAL" String "net.peer.name" "localhost" "net.peer.port" sqsPort "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" { it == null || it instanceof Long } @@ -364,6 +366,7 @@ abstract class AbstractAws2SqsTracingTest extends InstrumentationSpecification { "http.status_code" 200 "http.url" { it.startsWith("http://localhost:$sqsPort") } "net.peer.name" "localhost" + "$SemanticAttributes.USER_AGENT_ORIGINAL" String "net.peer.port" sqsPort "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" { it == null || it instanceof Long } "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" { it == null || it instanceof Long } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractQueryProtocolModelTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractQueryProtocolModelTest.java new file mode 100644 index 0000000000..37274a7a96 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractQueryProtocolModelTest.java @@ -0,0 +1,110 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v2_2; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_URL; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.testing.internal.armeria.common.HttpResponse; +import io.opentelemetry.testing.internal.armeria.common.HttpStatus; +import io.opentelemetry.testing.internal.armeria.common.MediaType; +import io.opentelemetry.testing.internal.armeria.testing.junit5.server.mock.MockWebServerExtension; +import java.net.URI; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.ses.SesClient; +import software.amazon.awssdk.services.ses.model.Body; +import software.amazon.awssdk.services.ses.model.Content; +import software.amazon.awssdk.services.ses.model.Destination; +import software.amazon.awssdk.services.ses.model.Message; +import software.amazon.awssdk.services.ses.model.SendEmailRequest; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public abstract class AbstractQueryProtocolModelTest { + private final MockWebServerExtension server = new MockWebServerExtension(); + + @BeforeAll + public void setup() { + server.start(); + } + + @AfterAll + public void end() { + server.stop(); + } + + @BeforeEach + public void setupEach() { + server.beforeTestExecution(null); + } + + protected abstract ClientOverrideConfiguration.Builder createClientOverrideConfigurationBuilder(); + + protected abstract InstrumentationExtension getTesting(); + + @Test + void testClientWithQueryProtocolModel() { + server.enqueue( + HttpResponse.of( + HttpStatus.OK, + MediaType.PLAIN_TEXT_UTF_8, + "12345")); + SesClient ses = + SesClient.builder() + .endpointOverride(server.httpUri()) + .credentialsProvider( + StaticCredentialsProvider.create(AwsBasicCredentials.create("foo", "bar"))) + .overrideConfiguration(createClientOverrideConfigurationBuilder().build()) + .region(Region.US_WEST_2) + .build(); + + Destination destination = Destination.builder().toAddresses("dest@test.com").build(); + Content content = Content.builder().data("content").build(); + Content sub = Content.builder().data("subject").build(); + Body body = Body.builder().html(content).build(); + Message msg = Message.builder().subject(sub).body(body).build(); + SendEmailRequest emailRequest = + SendEmailRequest.builder() + .destination(destination) + .message(msg) + .source("source@test.com") + .build(); + + ses.sendEmail(emailRequest); + + getTesting() + .waitAndAssertTraces( + trace -> { + trace.hasSpansSatisfyingExactly( + span -> { + span.hasKind(SpanKind.CLIENT); + span.hasAttributesSatisfying( + attributes -> { + assertThat(attributes) + .hasEntrySatisfying( + HTTP_URL, + entry -> { + assertThat(entry) + .satisfies( + value -> { + URI uri = URI.create(value); + assertThat(uri.getQuery()).isNull(); + }); + }); + }); + }); + }); + } +}