Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/next-release/feature-AWSSDKforJavav2-c120920.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "feature",
"category": "AWS SDK for Java v2",
"contributor": "",
"description": "Adds business metrics for flexible checksum algorithms and configurations"
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@
import software.amazon.awssdk.core.internal.http.RequestExecutionContext;
import software.amazon.awssdk.core.internal.http.pipeline.MutableRequestToRequestPipeline;
import software.amazon.awssdk.core.internal.io.AwsUnsignedChunkedEncodingInputStream;
import software.amazon.awssdk.core.internal.useragent.BusinessMetricsUtils;
import software.amazon.awssdk.core.internal.util.HttpChecksumUtils;
import software.amazon.awssdk.core.useragent.BusinessMetricCollection;
import software.amazon.awssdk.http.ContentStreamProvider;
import software.amazon.awssdk.http.Header;
import software.amazon.awssdk.http.SdkHttpFullRequest;
Expand Down Expand Up @@ -79,11 +81,16 @@ public SdkHttpFullRequest.Builder execute(SdkHttpFullRequest.Builder request, Re

ensurePayloadChecksumStorePresent(context.executionAttributes());

SdkHttpFullRequest.Builder result;
if (sraSigningEnabled(context)) {
return sraChecksum(request, context);
result = sraChecksum(request, context);
} else {
result = legacyChecksum(request, context);
}

return legacyChecksum(request, context);
recordChecksumBusinessMetrics(context.executionAttributes());

return result;
}

private SdkHttpFullRequest.Builder legacyChecksum(SdkHttpFullRequest.Builder request, RequestExecutionContext context) {
Expand Down Expand Up @@ -351,6 +358,27 @@ private PayloadChecksumStore getPayloadChecksumStore(ExecutionAttributes executi
return executionAttributes.getAttribute(CHECKSUM_STORE);
}

private void recordChecksumBusinessMetrics(ExecutionAttributes executionAttributes) {
BusinessMetricCollection businessMetrics =
executionAttributes.getAttribute(SdkInternalExecutionAttribute.BUSINESS_METRICS);

if (businessMetrics == null) {
return;
}

BusinessMetricsUtils.resolveRequestChecksumCalculationMetric(
executionAttributes.getAttribute(SdkInternalExecutionAttribute.REQUEST_CHECKSUM_CALCULATION))
.ifPresent(businessMetrics::addMetric);

BusinessMetricsUtils.resolveResponseChecksumValidationMetric(
executionAttributes.getAttribute(SdkInternalExecutionAttribute.RESPONSE_CHECKSUM_VALIDATION))
.ifPresent(businessMetrics::addMetric);

BusinessMetricsUtils.resolveChecksumSpecsMetric(
executionAttributes.getAttribute(RESOLVED_CHECKSUM_SPECS))
.ifPresent(businessMetrics::addMetric);
}

static final class ChecksumCalculatingStreamProvider implements ContentStreamProvider {
private final ContentStreamProvider underlyingInputStreamProvider;
private final String checksumHeaderForTrailer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@

import java.util.Optional;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.checksums.DefaultChecksumAlgorithm;
import software.amazon.awssdk.checksums.spi.ChecksumAlgorithm;
import software.amazon.awssdk.core.checksums.ChecksumSpecs;
import software.amazon.awssdk.core.checksums.RequestChecksumCalculation;
import software.amazon.awssdk.core.checksums.ResponseChecksumValidation;
import software.amazon.awssdk.core.retry.RetryMode;
import software.amazon.awssdk.core.retry.RetryPolicy;
import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId;
Expand Down Expand Up @@ -55,4 +60,57 @@ public static Optional<String> resolveRetryMode(RetryPolicy retryPolicy, RetrySt
}
return Optional.empty();
}

public static Optional<String> resolveRequestChecksumCalculationMetric(
RequestChecksumCalculation requestChecksumCalculation) {
if (requestChecksumCalculation == RequestChecksumCalculation.WHEN_SUPPORTED) {
return Optional.of(BusinessMetricFeatureId.FLEXIBLE_CHECKSUMS_REQ_WHEN_SUPPORTED.value());
}
if (requestChecksumCalculation == RequestChecksumCalculation.WHEN_REQUIRED) {
return Optional.of(BusinessMetricFeatureId.FLEXIBLE_CHECKSUMS_REQ_WHEN_REQUIRED.value());
}
return Optional.empty();
}

public static Optional<String> resolveResponseChecksumValidationMetric(
ResponseChecksumValidation responseChecksumValidation) {
if (responseChecksumValidation == ResponseChecksumValidation.WHEN_SUPPORTED) {
return Optional.of(BusinessMetricFeatureId.FLEXIBLE_CHECKSUMS_RES_WHEN_SUPPORTED.value());
}
if (responseChecksumValidation == ResponseChecksumValidation.WHEN_REQUIRED) {
return Optional.of(BusinessMetricFeatureId.FLEXIBLE_CHECKSUMS_RES_WHEN_REQUIRED.value());
}
return Optional.empty();
}

public static Optional<String> resolveChecksumAlgorithmMetric(ChecksumAlgorithm algorithm) {
if (algorithm == null) {
return Optional.empty();
}

String algorithmId = algorithm.algorithmId();
if (algorithmId.equals(DefaultChecksumAlgorithm.CRC32.algorithmId())) {
return Optional.of(BusinessMetricFeatureId.FLEXIBLE_CHECKSUMS_REQ_CRC32.value());
}
if (algorithmId.equals(DefaultChecksumAlgorithm.CRC32C.algorithmId())) {
return Optional.of(BusinessMetricFeatureId.FLEXIBLE_CHECKSUMS_REQ_CRC32C.value());
}
if (algorithmId.equals(DefaultChecksumAlgorithm.CRC64NVME.algorithmId())) {
return Optional.of(BusinessMetricFeatureId.FLEXIBLE_CHECKSUMS_REQ_CRC64.value());
}
if (algorithmId.equals(DefaultChecksumAlgorithm.SHA1.algorithmId())) {
return Optional.of(BusinessMetricFeatureId.FLEXIBLE_CHECKSUMS_REQ_SHA1.value());
}
if (algorithmId.equals(DefaultChecksumAlgorithm.SHA256.algorithmId())) {
return Optional.of(BusinessMetricFeatureId.FLEXIBLE_CHECKSUMS_REQ_SHA256.value());
}
return Optional.empty();
}

public static Optional<String> resolveChecksumSpecsMetric(ChecksumSpecs checksumSpecs) {
if (checksumSpecs != null && checksumSpecs.algorithmV2() != null) {
return resolveChecksumAlgorithmMetric(checksumSpecs.algorithmV2());
}
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
* Unsupported metrics (these will never be added): A,H
*/
@SdkProtectedApi
Expand All @@ -41,6 +41,15 @@ public enum BusinessMetricFeatureId {
ACCOUNT_ID_MODE_DISABLED("Q"),
ACCOUNT_ID_MODE_REQUIRED("R"),
RESOLVED_ACCOUNT_ID("T"),
FLEXIBLE_CHECKSUMS_REQ_CRC32("U"),
FLEXIBLE_CHECKSUMS_REQ_CRC32C("V"),
FLEXIBLE_CHECKSUMS_REQ_CRC64("W"),
FLEXIBLE_CHECKSUMS_REQ_SHA1("X"),
FLEXIBLE_CHECKSUMS_REQ_SHA256("Y"),
FLEXIBLE_CHECKSUMS_REQ_WHEN_SUPPORTED("Z"),
FLEXIBLE_CHECKSUMS_REQ_WHEN_REQUIRED("a"),
FLEXIBLE_CHECKSUMS_RES_WHEN_SUPPORTED("b"),
FLEXIBLE_CHECKSUMS_RES_WHEN_REQUIRED("c"),
DDB_MAPPER("d"),
BEARER_SERVICE_ENV_VARS("3"),
CREDENTIALS_CODE("e"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
/*
* 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 java.util.stream.Stream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.core.checksums.RequestChecksumCalculation;
import software.amazon.awssdk.core.checksums.ResponseChecksumValidation;
import software.amazon.awssdk.core.sync.RequestBody;
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.protocolrestjson.ProtocolRestJsonClient;
import software.amazon.awssdk.services.protocolrestjson.model.ChecksumAlgorithm;
import software.amazon.awssdk.testutils.service.http.MockSyncHttpClient;
import software.amazon.awssdk.utils.StringInputStream;

/**
* Test class to verify that flexible checksum business metrics are correctly included
* in the User-Agent header when checksum algorithms are used.
*/
class FlexibleChecksumBusinessMetricTest {
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_noChecksumConfigurationIsSet_defaultConfigMetricsAreAdded() {
ProtocolRestJsonClient client = ProtocolRestJsonClient.builder()
.region(Region.US_WEST_2)
.credentialsProvider(CREDENTIALS_PROVIDER)
.httpClient(mockHttpClient)
.build();

client.allTypes(r -> {});
String userAgent = getUserAgentFromLastRequest();

assertThat(userAgent).matches(METRIC_SEARCH_PATTERN.apply("Z"));
assertThat(userAgent).matches(METRIC_SEARCH_PATTERN.apply("b"));
assertThat(userAgent).doesNotMatch(METRIC_SEARCH_PATTERN.apply("a"));
assertThat(userAgent).doesNotMatch(METRIC_SEARCH_PATTERN.apply("c"));
}

@ParameterizedTest
@MethodSource("checksumAlgorithmTestCases")
void when_checksumAlgorithmIsUsed_correctMetricIsAdded(ChecksumAlgorithm algorithm, String expectedMetric) {
ProtocolRestJsonClient client = ProtocolRestJsonClient.builder()
.region(Region.US_WEST_2)
.credentialsProvider(CREDENTIALS_PROVIDER)
.httpClient(mockHttpClient)
.build();

client.putOperationWithChecksum(r -> r.checksumAlgorithm(algorithm),
RequestBody.fromString("test content"));

String userAgent = getUserAgentFromLastRequest();
assertThat(userAgent).matches(METRIC_SEARCH_PATTERN.apply(expectedMetric));
}

static Stream<Arguments> checksumAlgorithmTestCases() {
return Stream.of(
Arguments.of(ChecksumAlgorithm.CRC32, "U"),
Arguments.of(ChecksumAlgorithm.CRC32_C, "V"),
Arguments.of(ChecksumAlgorithm.CRC64_NVME, "W"),
Arguments.of(ChecksumAlgorithm.SHA1, "X"),
Arguments.of(ChecksumAlgorithm.SHA256, "Y")
);
}

@ParameterizedTest
@MethodSource("checksumConfigurationTestCases")
void when_checksumConfigurationIsSet_correctMetricIsAdded(RequestChecksumCalculation requestConfig,
ResponseChecksumValidation responseConfig,
String expectedRequestMetric,
String expectedResponseMetric) {
ProtocolRestJsonClient client = ProtocolRestJsonClient.builder()
.region(Region.US_WEST_2)
.credentialsProvider(CREDENTIALS_PROVIDER)
.httpClient(mockHttpClient)
.requestChecksumCalculation(requestConfig)
.responseChecksumValidation(responseConfig)
.build();

client.allTypes(r -> {});

String userAgent = getUserAgentFromLastRequest();
assertThat(userAgent).matches(METRIC_SEARCH_PATTERN.apply(expectedRequestMetric));
assertThat(userAgent).matches(METRIC_SEARCH_PATTERN.apply(expectedResponseMetric));
}

static Stream<Arguments> checksumConfigurationTestCases() {
return Stream.of(
Arguments.of(RequestChecksumCalculation.WHEN_SUPPORTED,
ResponseChecksumValidation.WHEN_SUPPORTED, "Z", "b"),
Arguments.of(RequestChecksumCalculation.WHEN_REQUIRED,
ResponseChecksumValidation.WHEN_REQUIRED, "a", "c"),
Arguments.of(RequestChecksumCalculation.WHEN_REQUIRED,
ResponseChecksumValidation.WHEN_SUPPORTED, "a", "b"),
Arguments.of(RequestChecksumCalculation.WHEN_SUPPORTED,
ResponseChecksumValidation.WHEN_REQUIRED, "Z", "c")
);
}

@ParameterizedTest
@MethodSource("checksumConfigurationWithAlgorithmTestCases")
void when_checksumConfigurationAndAlgorithmAreSet_correctMetricsAreAdded(
RequestChecksumCalculation requestConfig,
ResponseChecksumValidation responseConfig,
ChecksumAlgorithm algorithm,
String expectedRequestMetric,
String expectedResponseMetric,
String expectedAlgorithmMetric) {

ProtocolRestJsonClient client = ProtocolRestJsonClient.builder()
.region(Region.US_WEST_2)
.credentialsProvider(CREDENTIALS_PROVIDER)
.httpClient(mockHttpClient)
.requestChecksumCalculation(requestConfig)
.responseChecksumValidation(responseConfig)
.build();

client.putOperationWithChecksum(r -> r.checksumAlgorithm(algorithm),
RequestBody.fromString("test content"));

String userAgent = getUserAgentFromLastRequest();

assertThat(userAgent).matches(METRIC_SEARCH_PATTERN.apply(expectedRequestMetric));
assertThat(userAgent).matches(METRIC_SEARCH_PATTERN.apply(expectedResponseMetric));
assertThat(userAgent).matches(METRIC_SEARCH_PATTERN.apply(expectedAlgorithmMetric));
}

static Stream<Arguments> checksumConfigurationWithAlgorithmTestCases() {
return Stream.of(
Arguments.of(RequestChecksumCalculation.WHEN_SUPPORTED,
ResponseChecksumValidation.WHEN_SUPPORTED,
ChecksumAlgorithm.CRC32, "Z", "b", "U"),
Arguments.of(RequestChecksumCalculation.WHEN_SUPPORTED,
ResponseChecksumValidation.WHEN_SUPPORTED,
ChecksumAlgorithm.CRC32_C, "Z", "b", "V"),
Arguments.of(RequestChecksumCalculation.WHEN_SUPPORTED,
ResponseChecksumValidation.WHEN_SUPPORTED,
ChecksumAlgorithm.SHA256, "Z", "b", "Y"),

Arguments.of(RequestChecksumCalculation.WHEN_REQUIRED,
ResponseChecksumValidation.WHEN_REQUIRED,
ChecksumAlgorithm.CRC32, "a", "c", "U"),
Arguments.of(RequestChecksumCalculation.WHEN_REQUIRED,
ResponseChecksumValidation.WHEN_REQUIRED,
ChecksumAlgorithm.CRC64_NVME, "a", "c", "W"),
Arguments.of(RequestChecksumCalculation.WHEN_REQUIRED,
ResponseChecksumValidation.WHEN_REQUIRED,
ChecksumAlgorithm.SHA1, "a", "c", "X"),

Arguments.of(RequestChecksumCalculation.WHEN_REQUIRED,
ResponseChecksumValidation.WHEN_SUPPORTED,
ChecksumAlgorithm.CRC32_C, "a", "b", "V"),
Arguments.of(RequestChecksumCalculation.WHEN_REQUIRED,
ResponseChecksumValidation.WHEN_SUPPORTED,
ChecksumAlgorithm.SHA256, "a", "b", "Y"),

Arguments.of(RequestChecksumCalculation.WHEN_SUPPORTED,
ResponseChecksumValidation.WHEN_REQUIRED,
ChecksumAlgorithm.CRC64_NVME, "Z", "c", "W"),
Arguments.of(RequestChecksumCalculation.WHEN_SUPPORTED,
ResponseChecksumValidation.WHEN_REQUIRED,
ChecksumAlgorithm.SHA1, "Z", "c", "X")
);
}

private String getUserAgentFromLastRequest() {
SdkHttpRequest lastRequest = mockHttpClient.getLastRequest();
assertThat(lastRequest).isNotNull();

List<String> 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();
}
}
Loading