diff --git a/.changes/next-release/feature-AWSSDKforJavav2-7c6c7a4.json b/.changes/next-release/feature-AWSSDKforJavav2-7c6c7a4.json new file mode 100644 index 000000000000..62044809461b --- /dev/null +++ b/.changes/next-release/feature-AWSSDKforJavav2-7c6c7a4.json @@ -0,0 +1,6 @@ +{ + "type": "feature", + "category": "AWS SDK for Java v2", + "contributor": "", + "description": "Add business metric support for Sigv4A auth scheme to track when an operation is called using Sigv4A signing" +} diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeInterceptorSpec.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeInterceptorSpec.java index 043bf74ba9d9..c4f9e768eaa3 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeInterceptorSpec.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeInterceptorSpec.java @@ -51,6 +51,7 @@ import software.amazon.awssdk.core.metrics.CoreMetric; import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.endpoints.EndpointProvider; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4aAuthScheme; import software.amazon.awssdk.http.auth.aws.signer.RegionSet; import software.amazon.awssdk.http.auth.scheme.BearerAuthScheme; import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; @@ -158,6 +159,22 @@ private MethodSpec generateBeforeExecution() { builder.addStatement("recordEnvironmentTokenBusinessMetric(selectedAuthScheme, " + "executionAttributes)"); } + + if (authSchemeSpecUtils.hasSigV4aSupport()) { + builder.beginControlFlow("if (selectedAuthScheme != null && " + + "selectedAuthScheme.authSchemeOption().schemeId().equals($T.SCHEME_ID) && " + + "!$T.isSignerOverridden(context.request(), executionAttributes))", + AwsV4aAuthScheme.class, + ClassName.get("software.amazon.awssdk.awscore.util", "SignerOverrideUtils")) + .addStatement("$T businessMetrics = executionAttributes.getAttribute($T.BUSINESS_METRICS)", + ClassName.get("software.amazon.awssdk.core.useragent", "BusinessMetricCollection"), + SdkInternalExecutionAttribute.class) + .beginControlFlow("if (businessMetrics != null)") + .addStatement("businessMetrics.addMetric($T.SIGV4A_SIGNING.value())", + BusinessMetricFeatureId.class) + .endControlFlow() + .endControlFlow(); + } return builder.build(); } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-interceptor.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-interceptor.java index 70b901338096..39c345eeb7b9 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-interceptor.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-interceptor.java @@ -10,6 +10,7 @@ import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.awscore.AwsExecutionAttribute; +import software.amazon.awssdk.awscore.util.SignerOverrideUtils; import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.SelectedAuthScheme; import software.amazon.awssdk.core.exception.SdkException; @@ -20,6 +21,9 @@ import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; import software.amazon.awssdk.core.internal.util.MetricUtils; import software.amazon.awssdk.core.metrics.CoreMetric; +import software.amazon.awssdk.core.useragent.BusinessMetricCollection; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4aAuthScheme; import software.amazon.awssdk.http.auth.aws.signer.RegionSet; import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; @@ -49,6 +53,14 @@ public void beforeExecution(Context.BeforeExecution context, ExecutionAttributes List authOptions = resolveAuthOptions(context, executionAttributes); SelectedAuthScheme selectedAuthScheme = selectAuthScheme(authOptions, executionAttributes); putSelectedAuthScheme(executionAttributes, selectedAuthScheme); + if (selectedAuthScheme != null && selectedAuthScheme.authSchemeOption().schemeId().equals(AwsV4aAuthScheme.SCHEME_ID) + && !SignerOverrideUtils.isSignerOverridden(context.request(), executionAttributes)) { + BusinessMetricCollection businessMetrics = executionAttributes + .getAttribute(SdkInternalExecutionAttribute.BUSINESS_METRICS); + if (businessMetrics != null) { + businessMetrics.addMetric(BusinessMetricFeatureId.SIGV4A_SIGNING.value()); + } + } } private List resolveAuthOptions(Context.BeforeExecution context, ExecutionAttributes executionAttributes) { diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-with-allowlist-auth-scheme-interceptor.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-with-allowlist-auth-scheme-interceptor.java index dcc6418d2e89..a17b61f81f53 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-with-allowlist-auth-scheme-interceptor.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-with-allowlist-auth-scheme-interceptor.java @@ -10,6 +10,7 @@ import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.awscore.AwsExecutionAttribute; +import software.amazon.awssdk.awscore.util.SignerOverrideUtils; import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.SelectedAuthScheme; import software.amazon.awssdk.core.exception.SdkException; @@ -20,7 +21,10 @@ import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; import software.amazon.awssdk.core.internal.util.MetricUtils; import software.amazon.awssdk.core.metrics.CoreMetric; +import software.amazon.awssdk.core.useragent.BusinessMetricCollection; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.endpoints.EndpointProvider; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4aAuthScheme; import software.amazon.awssdk.http.auth.aws.signer.RegionSet; import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; @@ -52,6 +56,14 @@ public void beforeExecution(Context.BeforeExecution context, ExecutionAttributes List authOptions = resolveAuthOptions(context, executionAttributes); SelectedAuthScheme selectedAuthScheme = selectAuthScheme(authOptions, executionAttributes); putSelectedAuthScheme(executionAttributes, selectedAuthScheme); + if (selectedAuthScheme != null && selectedAuthScheme.authSchemeOption().schemeId().equals(AwsV4aAuthScheme.SCHEME_ID) + && !SignerOverrideUtils.isSignerOverridden(context.request(), executionAttributes)) { + BusinessMetricCollection businessMetrics = executionAttributes + .getAttribute(SdkInternalExecutionAttribute.BUSINESS_METRICS); + if (businessMetrics != null) { + businessMetrics.addMetric(BusinessMetricFeatureId.SIGV4A_SIGNING.value()); + } + } } private List resolveAuthOptions(Context.BeforeExecution context, ExecutionAttributes executionAttributes) { @@ -102,7 +114,6 @@ private QueryAuthSchemeParams authSchemeParams(SdkRequest request, ExecutionAttr executionAttributes.getOptionalAttribute(AwsExecutionAttribute.AWS_SIGV4A_SIGNING_REGION_SET) .filter(regionSet -> !CollectionUtils.isNullOrEmpty(regionSet)) .ifPresent(nonEmptyRegionSet -> builder.regionSet(RegionSet.create(nonEmptyRegionSet))); - if (builder instanceof QueryEndpointResolverAware.Builder) { EndpointProvider endpointProvider = executionAttributes.getAttribute(SdkInternalExecutionAttribute.ENDPOINT_PROVIDER); if (endpointProvider instanceof QueryEndpointProvider) { diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-without-allowlist-auth-scheme-interceptor.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-without-allowlist-auth-scheme-interceptor.java index e9405f2fbb67..caef3f2c59ca 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-without-allowlist-auth-scheme-interceptor.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-without-allowlist-auth-scheme-interceptor.java @@ -10,6 +10,7 @@ import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.awscore.AwsExecutionAttribute; +import software.amazon.awssdk.awscore.util.SignerOverrideUtils; import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.SelectedAuthScheme; import software.amazon.awssdk.core.exception.SdkException; @@ -20,7 +21,10 @@ import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; import software.amazon.awssdk.core.internal.util.MetricUtils; import software.amazon.awssdk.core.metrics.CoreMetric; +import software.amazon.awssdk.core.useragent.BusinessMetricCollection; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.endpoints.EndpointProvider; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4aAuthScheme; import software.amazon.awssdk.http.auth.aws.signer.RegionSet; import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; @@ -52,6 +56,14 @@ public void beforeExecution(Context.BeforeExecution context, ExecutionAttributes List authOptions = resolveAuthOptions(context, executionAttributes); SelectedAuthScheme selectedAuthScheme = selectAuthScheme(authOptions, executionAttributes); putSelectedAuthScheme(executionAttributes, selectedAuthScheme); + if (selectedAuthScheme != null && selectedAuthScheme.authSchemeOption().schemeId().equals(AwsV4aAuthScheme.SCHEME_ID) + && !SignerOverrideUtils.isSignerOverridden(context.request(), executionAttributes)) { + BusinessMetricCollection businessMetrics = executionAttributes + .getAttribute(SdkInternalExecutionAttribute.BUSINESS_METRICS); + if (businessMetrics != null) { + businessMetrics.addMetric(BusinessMetricFeatureId.SIGV4A_SIGNING.value()); + } + } } private List resolveAuthOptions(Context.BeforeExecution context, ExecutionAttributes executionAttributes) { diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApplyUserAgentStage.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApplyUserAgentStage.java index 6cbb536ad43b..583634c7288c 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApplyUserAgentStage.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApplyUserAgentStage.java @@ -47,7 +47,7 @@ /** * A stage for adding the user agent header to the request, after retrieving the current string - * from execution attributes and adding any additional information. + * from execution attributes and adding any additional information. */ @SdkInternalApi public class ApplyUserAgentStage implements MutableRequestToRequestPipeline { @@ -152,11 +152,11 @@ private static Optional getBusinessMetricsString(ExecutionAttributes exe businessMetrics.merge(metricsFromApiNames); credentialProviderBusinessMetrics(executionAttributes).ifPresent(businessMetrics::merge); - + if (businessMetrics.recordedMetrics().isEmpty()) { return Optional.empty(); } - + return Optional.of(businessMetrics.asBoundedString()); } @@ -195,4 +195,4 @@ private Optional requestApiNames(List requestApiNames) { .append(apiName.version())); return Optional.of(concatenatedNames.toString()); } -} +} \ No newline at end of file diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/useragent/BusinessMetricFeatureId.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/useragent/BusinessMetricFeatureId.java index fe1af17823c2..59ac9f7ebd1e 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/useragent/BusinessMetricFeatureId.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/useragent/BusinessMetricFeatureId.java @@ -22,7 +22,7 @@ /** * An enum class representing a short form of identity providers to record in the UA string. * - * Unimplemented metrics: I,J,K,O,S,U-c + * Unimplemented metrics: I,J,K,O,U-c * Unsupported metrics (these will never be added): A,H */ @SdkProtectedApi @@ -40,6 +40,7 @@ public enum BusinessMetricFeatureId { ACCOUNT_ID_MODE_PREFERRED("P"), ACCOUNT_ID_MODE_DISABLED("Q"), ACCOUNT_ID_MODE_REQUIRED("R"), + SIGV4A_SIGNING("S"), RESOLVED_ACCOUNT_ID("T"), DDB_MAPPER("d"), BEARER_SERVICE_ENV_VARS("3"), diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/Sigv4aBusinessMetricUserAgentTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/Sigv4aBusinessMetricUserAgentTest.java new file mode 100644 index 000000000000..cd7239358536 --- /dev/null +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/Sigv4aBusinessMetricUserAgentTest.java @@ -0,0 +1,181 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.services; + +import static org.assertj.core.api.Assertions.assertThat; +import static software.amazon.awssdk.core.useragent.BusinessMetricCollection.METRIC_SEARCH_PATTERN; + +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.auth.signer.Aws4Signer; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; +import software.amazon.awssdk.http.AbortableInputStream; +import software.amazon.awssdk.http.HttpExecuteResponse; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.SdkHttpResponse; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.multiauth.MultiauthClient; +import software.amazon.awssdk.services.multiauth.auth.scheme.MultiauthAuthSchemeProvider; +import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonAsyncClient; +import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonClient; +import software.amazon.awssdk.services.sigv4aauth.Sigv4AauthAsyncClient; +import software.amazon.awssdk.services.sigv4aauth.Sigv4AauthClient; +import software.amazon.awssdk.testutils.service.http.MockAsyncHttpClient; +import software.amazon.awssdk.testutils.service.http.MockSyncHttpClient; +import software.amazon.awssdk.utils.StringInputStream; +import java.util.Arrays; + +/** + * Test class to verify that SIGV4A_SIGNING business metric is correctly included + * in the User-Agent header when operation called using Sigv4A signing. + */ +class Sigv4aBusinessMetricUserAgentTest { + private static final String USER_AGENT_HEADER_NAME = "User-Agent"; + private static final StaticCredentialsProvider CREDENTIALS_PROVIDER = + StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", "skid")); + + private MockSyncHttpClient mockHttpClient; + private MockAsyncHttpClient mockAsyncHttpClient; + + @BeforeEach + public void setup() { + mockHttpClient = new MockSyncHttpClient(); + mockHttpClient.stubNextResponse(mockResponse()); + + mockAsyncHttpClient = new MockAsyncHttpClient(); + mockAsyncHttpClient.stubNextResponse(mockResponse()); + } + + @Test + void when_sigv4aServiceIsUsed_correctMetricIsAdded() { + Sigv4AauthClient client = Sigv4AauthClient.builder() + .region(Region.US_WEST_2) + .credentialsProvider(CREDENTIALS_PROVIDER) + .httpClient(mockHttpClient) + .build(); + + client.simpleOperationWithNoEndpointParams(r -> r.stringMember("test")); + + String userAgent = getUserAgentFromLastRequest(); + assertThat(userAgent).matches(METRIC_SEARCH_PATTERN.apply("S")); + } + + @Test + void when_sigv4aServiceIsUsedAsync_correctMetricIsAdded() { + Sigv4AauthAsyncClient asyncClient = Sigv4AauthAsyncClient.builder() + .region(Region.US_WEST_2) + .credentialsProvider(CREDENTIALS_PROVIDER) + .httpClient(mockAsyncHttpClient) + .build(); + + asyncClient.simpleOperationWithNoEndpointParams(r -> r.stringMember("test")).join(); + + String userAgent = getUserAgentFromLastAsyncRequest(); + assertThat(userAgent).matches(METRIC_SEARCH_PATTERN.apply("S")); + } + + @Test + void when_regularServiceIsUsed_sigv4aMetricIsNotAdded() { + ProtocolRestJsonClient client = ProtocolRestJsonClient.builder() + .region(Region.US_WEST_2) + .credentialsProvider(CREDENTIALS_PROVIDER) + .httpClient(mockHttpClient) + .build(); + + client.allTypes(r -> {}); + + String userAgent = getUserAgentFromLastRequest(); + assertThat(userAgent).doesNotMatch(METRIC_SEARCH_PATTERN.apply("S")); + } + + @Test + void when_regularServiceIsUsedAsync_sigv4aMetricIsNotAdded() { + ProtocolRestJsonAsyncClient asyncClient = ProtocolRestJsonAsyncClient.builder() + .region(Region.US_WEST_2) + .credentialsProvider(CREDENTIALS_PROVIDER) + .httpClient(mockAsyncHttpClient) + .build(); + + asyncClient.allTypes(r -> {}).join(); + + String userAgent = getUserAgentFromLastAsyncRequest(); + assertThat(userAgent).doesNotMatch(METRIC_SEARCH_PATTERN.apply("S")); + } + + @Test + void when_signerIsOverridden_sigv4aMetricIsNotAdded() { + MultiauthClient client = MultiauthClient.builder() + .region(Region.US_WEST_2) + .credentialsProvider(CREDENTIALS_PROVIDER) + .overrideConfiguration(ClientOverrideConfiguration.builder() + .putAdvancedOption(SdkAdvancedClientOption.SIGNER, + Aws4Signer.create()) + .build()) + .httpClient(mockHttpClient) + .build(); + + client.multiAuthWithOnlySigv4aAndSigv4(r -> r.stringMember("test")); + String userAgent = getUserAgentFromLastRequest(); + + assertThat(userAgent).doesNotMatch(METRIC_SEARCH_PATTERN.apply("S")); + } + + @Test + void when_authSchemeProviderOverridesSigv4aOrder_sigv4IsSelected() { + MultiauthClient client = MultiauthClient.builder() + .region(Region.US_WEST_2) + .credentialsProvider(CREDENTIALS_PROVIDER) + .authSchemeProvider(MultiauthAuthSchemeProvider. + defaultProvider( + Arrays.asList("sigv4","sigv4a"))) + .httpClient(mockHttpClient) + .build(); + + client.multiAuthWithOnlySigv4aAndSigv4(r -> r.stringMember("test")); + String userAgent = getUserAgentFromLastRequest(); + + assertThat(userAgent).doesNotMatch(METRIC_SEARCH_PATTERN.apply("S")); + } + + private String getUserAgentFromLastRequest() { + SdkHttpRequest lastRequest = mockHttpClient.getLastRequest(); + assertThat(lastRequest).isNotNull(); + + List userAgentHeaders = lastRequest.headers().get(USER_AGENT_HEADER_NAME); + assertThat(userAgentHeaders).isNotNull().hasSize(1); + return userAgentHeaders.get(0); + } + + private String getUserAgentFromLastAsyncRequest() { + SdkHttpRequest lastRequest = mockAsyncHttpClient.getLastRequest(); + assertThat(lastRequest).isNotNull(); + + List userAgentHeaders = lastRequest.headers().get(USER_AGENT_HEADER_NAME); + assertThat(userAgentHeaders).isNotNull().hasSize(1); + return userAgentHeaders.get(0); + } + + private static HttpExecuteResponse mockResponse() { + return HttpExecuteResponse.builder() + .response(SdkHttpResponse.builder().statusCode(200).build()) + .responseBody(AbortableInputStream.create(new StringInputStream("{}"))) + .build(); + } +} diff --git a/test/crt-unavailable-tests/pom.xml b/test/crt-unavailable-tests/pom.xml index 47fd89736375..57b82faeebae 100644 --- a/test/crt-unavailable-tests/pom.xml +++ b/test/crt-unavailable-tests/pom.xml @@ -106,6 +106,12 @@ checksums ${awsjavasdk.version} + + software.amazon.awssdk + service-test-utils + ${awsjavasdk.version} + test + diff --git a/test/crt-unavailable-tests/src/main/resources/codegen-resources/multiauth/service-2.json b/test/crt-unavailable-tests/src/main/resources/codegen-resources/multiauth/service-2.json new file mode 100644 index 000000000000..3777e4192603 --- /dev/null +++ b/test/crt-unavailable-tests/src/main/resources/codegen-resources/multiauth/service-2.json @@ -0,0 +1,37 @@ +{ + "version":"2.0", + "metadata":{ + "apiVersion":"2016-03-11", + "endpointPrefix":"internalconfig", + "jsonVersion":"1.1", + "protocol":"rest-json", + "serviceAbbreviation":"AwsMultiAuthService", + "serviceFullName":"AWS Multi Auth Service", + "serviceId":"Multiauth", + "targetPrefix":"MultiAuth", + "auth":["aws.auth#sigv4a","aws.auth#sigv4"], + "timestampFormat":"unixTimestamp", + "uid":"restjson-2016-03-11" + }, + "operations":{ + "operationWithBothSigv4AndSigv4a":{ + "name":"operationWithBothSigv4AndSigv4a", + "http":{ + "method":"POST", + "requestUri":"/2016-03-11/operationWithBothSigv4AndSigv4a" + }, + "input":{"shape":"SampleRequest"} + } + }, + "shapes": { + "SampleRequest": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String" + } + } + }, + "String":{"type":"string"} + } +} diff --git a/test/crt-unavailable-tests/src/test/java/software/amazon/awssdk/services/Sigv4aCrtUnavailableBusinessMetricTest.java b/test/crt-unavailable-tests/src/test/java/software/amazon/awssdk/services/Sigv4aCrtUnavailableBusinessMetricTest.java new file mode 100644 index 000000000000..72d150f07948 --- /dev/null +++ b/test/crt-unavailable-tests/src/test/java/software/amazon/awssdk/services/Sigv4aCrtUnavailableBusinessMetricTest.java @@ -0,0 +1,83 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.services; + +import static org.assertj.core.api.Assertions.assertThat; +import static software.amazon.awssdk.core.useragent.BusinessMetricCollection.METRIC_SEARCH_PATTERN; + +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.http.AbortableInputStream; +import software.amazon.awssdk.http.HttpExecuteResponse; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.SdkHttpResponse; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.multiauth.MultiauthClient; +import software.amazon.awssdk.testutils.service.http.MockSyncHttpClient; +import software.amazon.awssdk.utils.StringInputStream; + +/** + * Test class to verify that SIGV4A_SIGNING business metric is NOT included + * in the User-Agent header when CRT is not available on the classpath. + */ +class Sigv4aCrtUnavailableBusinessMetricTest { + private static final String USER_AGENT_HEADER_NAME = "User-Agent"; + private static final StaticCredentialsProvider CREDENTIALS_PROVIDER = + StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", "skid")); + + private MockSyncHttpClient mockHttpClient; + + @BeforeEach + public void setup() { + mockHttpClient = new MockSyncHttpClient(); + mockHttpClient.stubNextResponse(mockResponse()); + } + + @Test + void when_crtUnavailable_sigv4aFallsBackToSigv4_noSigv4aMetric() { + + MultiauthClient client = MultiauthClient.builder() + .region(Region.US_WEST_2) + .credentialsProvider(CREDENTIALS_PROVIDER) + .httpClient(mockHttpClient) + .build(); + + client.operationWithBothSigv4AndSigv4a(r -> r.stringMember("test")); + + String userAgent = getUserAgentFromLastRequest(); + + assertThat(userAgent).doesNotMatch(METRIC_SEARCH_PATTERN.apply("S")); + } + + private String getUserAgentFromLastRequest() { + SdkHttpRequest lastRequest = mockHttpClient.getLastRequest(); + assertThat(lastRequest).isNotNull(); + + List userAgentHeaders = lastRequest.headers().get(USER_AGENT_HEADER_NAME); + assertThat(userAgentHeaders).isNotNull().hasSize(1); + return userAgentHeaders.get(0); + } + + private static HttpExecuteResponse mockResponse() { + return HttpExecuteResponse.builder() + .response(SdkHttpResponse.builder().statusCode(200).build()) + .responseBody(AbortableInputStream.create(new StringInputStream("{}"))) + .build(); + } +}