Skip to content

Commit 839bbaa

Browse files
authored
Add business metric support for all flexible checksums (#6505)
* Add business metric support for all flexible checksums * Add changelog * Fixing checkstyle errors * Address PR feedback * Removing extra line * Review comments * Fixing failed tests
1 parent 008832c commit 839bbaa

File tree

5 files changed

+328
-3
lines changed

5 files changed

+328
-3
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "feature",
3+
"category": "AWS SDK for Java v2",
4+
"contributor": "",
5+
"description": "Adds business metrics for flexible checksum algorithms and configurations"
6+
}

core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/HttpChecksumStage.java

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@
4949
import software.amazon.awssdk.core.internal.http.RequestExecutionContext;
5050
import software.amazon.awssdk.core.internal.http.pipeline.MutableRequestToRequestPipeline;
5151
import software.amazon.awssdk.core.internal.io.AwsUnsignedChunkedEncodingInputStream;
52+
import software.amazon.awssdk.core.internal.useragent.BusinessMetricsUtils;
5253
import software.amazon.awssdk.core.internal.util.HttpChecksumUtils;
54+
import software.amazon.awssdk.core.useragent.BusinessMetricCollection;
5355
import software.amazon.awssdk.http.ContentStreamProvider;
5456
import software.amazon.awssdk.http.Header;
5557
import software.amazon.awssdk.http.SdkHttpFullRequest;
@@ -79,11 +81,16 @@ public SdkHttpFullRequest.Builder execute(SdkHttpFullRequest.Builder request, Re
7981

8082
ensurePayloadChecksumStorePresent(context.executionAttributes());
8183

84+
SdkHttpFullRequest.Builder result;
8285
if (sraSigningEnabled(context)) {
83-
return sraChecksum(request, context);
86+
result = sraChecksum(request, context);
87+
} else {
88+
result = legacyChecksum(request, context);
8489
}
8590

86-
return legacyChecksum(request, context);
91+
recordChecksumBusinessMetrics(context.executionAttributes());
92+
93+
return result;
8794
}
8895

8996
private SdkHttpFullRequest.Builder legacyChecksum(SdkHttpFullRequest.Builder request, RequestExecutionContext context) {
@@ -351,6 +358,29 @@ private PayloadChecksumStore getPayloadChecksumStore(ExecutionAttributes executi
351358
return executionAttributes.getAttribute(CHECKSUM_STORE);
352359
}
353360

361+
private void recordChecksumBusinessMetrics(ExecutionAttributes executionAttributes) {
362+
BusinessMetricCollection businessMetrics =
363+
executionAttributes.getAttribute(SdkInternalExecutionAttribute.BUSINESS_METRICS);
364+
365+
if (businessMetrics == null) {
366+
return;
367+
}
368+
369+
BusinessMetricsUtils.resolveRequestChecksumCalculationMetric(
370+
executionAttributes.getAttribute(SdkInternalExecutionAttribute.REQUEST_CHECKSUM_CALCULATION))
371+
.ifPresent(businessMetrics::addMetric);
372+
373+
BusinessMetricsUtils.resolveResponseChecksumValidationMetric(
374+
executionAttributes.getAttribute(SdkInternalExecutionAttribute.RESPONSE_CHECKSUM_VALIDATION))
375+
.ifPresent(businessMetrics::addMetric);
376+
377+
ChecksumSpecs checksumSpecs = executionAttributes.getAttribute(RESOLVED_CHECKSUM_SPECS);
378+
if (checksumSpecs != null && checksumSpecs.algorithmV2() != null) {
379+
BusinessMetricsUtils.resolveChecksumAlgorithmMetric(checksumSpecs.algorithmV2())
380+
.ifPresent(businessMetrics::addMetric);
381+
}
382+
}
383+
354384
static final class ChecksumCalculatingStreamProvider implements ContentStreamProvider {
355385
private final ContentStreamProvider underlyingInputStreamProvider;
356386
private final String checksumHeaderForTrailer;

core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/useragent/BusinessMetricsUtils.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717

1818
import java.util.Optional;
1919
import software.amazon.awssdk.annotations.SdkInternalApi;
20+
import software.amazon.awssdk.checksums.DefaultChecksumAlgorithm;
21+
import software.amazon.awssdk.checksums.spi.ChecksumAlgorithm;
22+
import software.amazon.awssdk.core.checksums.RequestChecksumCalculation;
23+
import software.amazon.awssdk.core.checksums.ResponseChecksumValidation;
2024
import software.amazon.awssdk.core.retry.RetryMode;
2125
import software.amazon.awssdk.core.retry.RetryPolicy;
2226
import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId;
@@ -55,4 +59,59 @@ public static Optional<String> resolveRetryMode(RetryPolicy retryPolicy, RetrySt
5559
}
5660
return Optional.empty();
5761
}
62+
63+
public static Optional<String> resolveRequestChecksumCalculationMetric(
64+
RequestChecksumCalculation requestChecksumCalculation) {
65+
if (requestChecksumCalculation == null) {
66+
return Optional.empty();
67+
}
68+
switch (requestChecksumCalculation) {
69+
case WHEN_SUPPORTED:
70+
return Optional.of(BusinessMetricFeatureId.FLEXIBLE_CHECKSUMS_REQ_WHEN_SUPPORTED.value());
71+
case WHEN_REQUIRED:
72+
return Optional.of(BusinessMetricFeatureId.FLEXIBLE_CHECKSUMS_REQ_WHEN_REQUIRED.value());
73+
default:
74+
return Optional.empty();
75+
}
76+
}
77+
78+
public static Optional<String> resolveResponseChecksumValidationMetric(
79+
ResponseChecksumValidation responseChecksumValidation) {
80+
if (responseChecksumValidation == null) {
81+
return Optional.empty();
82+
}
83+
switch (responseChecksumValidation) {
84+
case WHEN_SUPPORTED:
85+
return Optional.of(BusinessMetricFeatureId.FLEXIBLE_CHECKSUMS_RES_WHEN_SUPPORTED.value());
86+
case WHEN_REQUIRED:
87+
return Optional.of(BusinessMetricFeatureId.FLEXIBLE_CHECKSUMS_RES_WHEN_REQUIRED.value());
88+
default:
89+
return Optional.empty();
90+
}
91+
}
92+
93+
public static Optional<String> resolveChecksumAlgorithmMetric(ChecksumAlgorithm algorithm) {
94+
if (algorithm == null) {
95+
return Optional.empty();
96+
}
97+
98+
String algorithmId = algorithm.algorithmId();
99+
if (algorithmId.equals(DefaultChecksumAlgorithm.CRC32.algorithmId())) {
100+
return Optional.of(BusinessMetricFeatureId.FLEXIBLE_CHECKSUMS_REQ_CRC32.value());
101+
}
102+
if (algorithmId.equals(DefaultChecksumAlgorithm.CRC32C.algorithmId())) {
103+
return Optional.of(BusinessMetricFeatureId.FLEXIBLE_CHECKSUMS_REQ_CRC32C.value());
104+
}
105+
if (algorithmId.equals(DefaultChecksumAlgorithm.CRC64NVME.algorithmId())) {
106+
return Optional.of(BusinessMetricFeatureId.FLEXIBLE_CHECKSUMS_REQ_CRC64.value());
107+
}
108+
if (algorithmId.equals(DefaultChecksumAlgorithm.SHA1.algorithmId())) {
109+
return Optional.of(BusinessMetricFeatureId.FLEXIBLE_CHECKSUMS_REQ_SHA1.value());
110+
}
111+
if (algorithmId.equals(DefaultChecksumAlgorithm.SHA256.algorithmId())) {
112+
return Optional.of(BusinessMetricFeatureId.FLEXIBLE_CHECKSUMS_REQ_SHA256.value());
113+
}
114+
return Optional.empty();
115+
}
116+
58117
}

core/sdk-core/src/main/java/software/amazon/awssdk/core/useragent/BusinessMetricFeatureId.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
/**
2323
* An enum class representing a short form of identity providers to record in the UA string.
2424
*
25-
* Unimplemented metrics: I,J,K,O,U-c
25+
* Unimplemented metrics: I,J,K,O
2626
* Unsupported metrics (these will never be added): A,H
2727
*/
2828
@SdkProtectedApi
@@ -42,6 +42,15 @@ public enum BusinessMetricFeatureId {
4242
ACCOUNT_ID_MODE_REQUIRED("R"),
4343
SIGV4A_SIGNING("S"),
4444
RESOLVED_ACCOUNT_ID("T"),
45+
FLEXIBLE_CHECKSUMS_REQ_CRC32("U"),
46+
FLEXIBLE_CHECKSUMS_REQ_CRC32C("V"),
47+
FLEXIBLE_CHECKSUMS_REQ_CRC64("W"),
48+
FLEXIBLE_CHECKSUMS_REQ_SHA1("X"),
49+
FLEXIBLE_CHECKSUMS_REQ_SHA256("Y"),
50+
FLEXIBLE_CHECKSUMS_REQ_WHEN_SUPPORTED("Z"),
51+
FLEXIBLE_CHECKSUMS_REQ_WHEN_REQUIRED("a"),
52+
FLEXIBLE_CHECKSUMS_RES_WHEN_SUPPORTED("b"),
53+
FLEXIBLE_CHECKSUMS_RES_WHEN_REQUIRED("c"),
4554
DDB_MAPPER("d"),
4655
BEARER_SERVICE_ENV_VARS("3"),
4756
CREDENTIALS_CODE("e"),
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.services;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static software.amazon.awssdk.core.useragent.BusinessMetricCollection.METRIC_SEARCH_PATTERN;
20+
21+
import java.util.List;
22+
import java.util.stream.Stream;
23+
import org.junit.jupiter.api.BeforeEach;
24+
import org.junit.jupiter.api.Test;
25+
import org.junit.jupiter.params.ParameterizedTest;
26+
import org.junit.jupiter.params.provider.Arguments;
27+
import org.junit.jupiter.params.provider.MethodSource;
28+
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
29+
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
30+
import software.amazon.awssdk.core.checksums.RequestChecksumCalculation;
31+
import software.amazon.awssdk.core.checksums.ResponseChecksumValidation;
32+
import software.amazon.awssdk.core.sync.RequestBody;
33+
import software.amazon.awssdk.http.AbortableInputStream;
34+
import software.amazon.awssdk.http.HttpExecuteResponse;
35+
import software.amazon.awssdk.http.SdkHttpRequest;
36+
import software.amazon.awssdk.http.SdkHttpResponse;
37+
import software.amazon.awssdk.regions.Region;
38+
import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonClient;
39+
import software.amazon.awssdk.services.protocolrestjson.model.ChecksumAlgorithm;
40+
import software.amazon.awssdk.testutils.service.http.MockSyncHttpClient;
41+
import software.amazon.awssdk.utils.StringInputStream;
42+
43+
/**
44+
* Test class to verify that flexible checksum business metrics are correctly included
45+
* in the User-Agent header when checksum algorithms are used.
46+
*/
47+
class FlexibleChecksumBusinessMetricTest {
48+
private static final String USER_AGENT_HEADER_NAME = "User-Agent";
49+
private static final StaticCredentialsProvider CREDENTIALS_PROVIDER =
50+
StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", "skid"));
51+
52+
private MockSyncHttpClient mockHttpClient;
53+
54+
@BeforeEach
55+
public void setup() {
56+
mockHttpClient = new MockSyncHttpClient();
57+
mockHttpClient.stubNextResponse(mockResponse());
58+
}
59+
60+
@Test
61+
void when_noChecksumConfigurationIsSet_defaultConfigMetricsAreAdded() {
62+
ProtocolRestJsonClient client = ProtocolRestJsonClient.builder()
63+
.region(Region.US_WEST_2)
64+
.credentialsProvider(CREDENTIALS_PROVIDER)
65+
.httpClient(mockHttpClient)
66+
.build();
67+
68+
client.allTypes(r -> {});
69+
String userAgent = getUserAgentFromLastRequest();
70+
71+
assertThat(userAgent)
72+
.matches(METRIC_SEARCH_PATTERN.apply("Z"))
73+
.matches(METRIC_SEARCH_PATTERN.apply("b"))
74+
.doesNotMatch(METRIC_SEARCH_PATTERN.apply("a"))
75+
.doesNotMatch(METRIC_SEARCH_PATTERN.apply("c"));
76+
}
77+
78+
@ParameterizedTest
79+
@MethodSource("checksumAlgorithmTestCases")
80+
void when_checksumAlgorithmIsUsed_correctMetricIsAdded(ChecksumAlgorithm algorithm, String expectedMetric) {
81+
ProtocolRestJsonClient client = ProtocolRestJsonClient.builder()
82+
.region(Region.US_WEST_2)
83+
.credentialsProvider(CREDENTIALS_PROVIDER)
84+
.httpClient(mockHttpClient)
85+
.build();
86+
87+
client.putOperationWithChecksum(r -> r.checksumAlgorithm(algorithm),
88+
RequestBody.fromString("test content"));
89+
90+
String userAgent = getUserAgentFromLastRequest();
91+
assertThat(userAgent).matches(METRIC_SEARCH_PATTERN.apply(expectedMetric));
92+
}
93+
94+
static Stream<Arguments> checksumAlgorithmTestCases() {
95+
return Stream.of(
96+
Arguments.of(ChecksumAlgorithm.CRC32, "U"),
97+
Arguments.of(ChecksumAlgorithm.CRC32_C, "V"),
98+
Arguments.of(ChecksumAlgorithm.CRC64_NVME, "W"),
99+
Arguments.of(ChecksumAlgorithm.SHA1, "X"),
100+
Arguments.of(ChecksumAlgorithm.SHA256, "Y")
101+
);
102+
}
103+
104+
@ParameterizedTest
105+
@MethodSource("checksumConfigurationTestCases")
106+
void when_checksumConfigurationIsSet_correctMetricIsAdded(RequestChecksumCalculation requestConfig,
107+
ResponseChecksumValidation responseConfig,
108+
String expectedRequestMetric,
109+
String expectedResponseMetric) {
110+
ProtocolRestJsonClient client = ProtocolRestJsonClient.builder()
111+
.region(Region.US_WEST_2)
112+
.credentialsProvider(CREDENTIALS_PROVIDER)
113+
.httpClient(mockHttpClient)
114+
.requestChecksumCalculation(requestConfig)
115+
.responseChecksumValidation(responseConfig)
116+
.build();
117+
118+
client.allTypes(r -> {});
119+
120+
String userAgent = getUserAgentFromLastRequest();
121+
assertThat(userAgent)
122+
.matches(METRIC_SEARCH_PATTERN.apply(expectedRequestMetric))
123+
.matches(METRIC_SEARCH_PATTERN.apply(expectedResponseMetric));
124+
}
125+
126+
static Stream<Arguments> checksumConfigurationTestCases() {
127+
return Stream.of(
128+
Arguments.of(RequestChecksumCalculation.WHEN_SUPPORTED,
129+
ResponseChecksumValidation.WHEN_SUPPORTED, "Z", "b"),
130+
Arguments.of(RequestChecksumCalculation.WHEN_REQUIRED,
131+
ResponseChecksumValidation.WHEN_REQUIRED, "a", "c"),
132+
Arguments.of(RequestChecksumCalculation.WHEN_REQUIRED,
133+
ResponseChecksumValidation.WHEN_SUPPORTED, "a", "b"),
134+
Arguments.of(RequestChecksumCalculation.WHEN_SUPPORTED,
135+
ResponseChecksumValidation.WHEN_REQUIRED, "Z", "c")
136+
);
137+
}
138+
139+
@ParameterizedTest
140+
@MethodSource("checksumConfigurationWithAlgorithmTestCases")
141+
void when_checksumConfigurationAndAlgorithmAreSet_correctMetricsAreAdded(
142+
RequestChecksumCalculation requestConfig,
143+
ResponseChecksumValidation responseConfig,
144+
ChecksumAlgorithm algorithm,
145+
String expectedRequestMetric,
146+
String expectedResponseMetric,
147+
String expectedAlgorithmMetric) {
148+
149+
ProtocolRestJsonClient client = ProtocolRestJsonClient.builder()
150+
.region(Region.US_WEST_2)
151+
.credentialsProvider(CREDENTIALS_PROVIDER)
152+
.httpClient(mockHttpClient)
153+
.requestChecksumCalculation(requestConfig)
154+
.responseChecksumValidation(responseConfig)
155+
.build();
156+
157+
client.putOperationWithChecksum(r -> r.checksumAlgorithm(algorithm),
158+
RequestBody.fromString("test content"));
159+
160+
String userAgent = getUserAgentFromLastRequest();
161+
162+
assertThat(userAgent)
163+
.matches(METRIC_SEARCH_PATTERN.apply(expectedRequestMetric))
164+
.matches(METRIC_SEARCH_PATTERN.apply(expectedResponseMetric))
165+
.matches(METRIC_SEARCH_PATTERN.apply(expectedAlgorithmMetric));
166+
}
167+
168+
static Stream<Arguments> checksumConfigurationWithAlgorithmTestCases() {
169+
return Stream.of(
170+
Arguments.of(RequestChecksumCalculation.WHEN_SUPPORTED,
171+
ResponseChecksumValidation.WHEN_SUPPORTED,
172+
ChecksumAlgorithm.CRC32, "Z", "b", "U"),
173+
Arguments.of(RequestChecksumCalculation.WHEN_SUPPORTED,
174+
ResponseChecksumValidation.WHEN_SUPPORTED,
175+
ChecksumAlgorithm.CRC32_C, "Z", "b", "V"),
176+
Arguments.of(RequestChecksumCalculation.WHEN_SUPPORTED,
177+
ResponseChecksumValidation.WHEN_SUPPORTED,
178+
ChecksumAlgorithm.SHA256, "Z", "b", "Y"),
179+
180+
Arguments.of(RequestChecksumCalculation.WHEN_REQUIRED,
181+
ResponseChecksumValidation.WHEN_REQUIRED,
182+
ChecksumAlgorithm.CRC32, "a", "c", "U"),
183+
Arguments.of(RequestChecksumCalculation.WHEN_REQUIRED,
184+
ResponseChecksumValidation.WHEN_REQUIRED,
185+
ChecksumAlgorithm.CRC64_NVME, "a", "c", "W"),
186+
Arguments.of(RequestChecksumCalculation.WHEN_REQUIRED,
187+
ResponseChecksumValidation.WHEN_REQUIRED,
188+
ChecksumAlgorithm.SHA1, "a", "c", "X"),
189+
190+
Arguments.of(RequestChecksumCalculation.WHEN_REQUIRED,
191+
ResponseChecksumValidation.WHEN_SUPPORTED,
192+
ChecksumAlgorithm.CRC32_C, "a", "b", "V"),
193+
Arguments.of(RequestChecksumCalculation.WHEN_REQUIRED,
194+
ResponseChecksumValidation.WHEN_SUPPORTED,
195+
ChecksumAlgorithm.SHA256, "a", "b", "Y"),
196+
197+
Arguments.of(RequestChecksumCalculation.WHEN_SUPPORTED,
198+
ResponseChecksumValidation.WHEN_REQUIRED,
199+
ChecksumAlgorithm.CRC64_NVME, "Z", "c", "W"),
200+
Arguments.of(RequestChecksumCalculation.WHEN_SUPPORTED,
201+
ResponseChecksumValidation.WHEN_REQUIRED,
202+
ChecksumAlgorithm.SHA1, "Z", "c", "X")
203+
);
204+
}
205+
206+
private String getUserAgentFromLastRequest() {
207+
SdkHttpRequest lastRequest = mockHttpClient.getLastRequest();
208+
assertThat(lastRequest).isNotNull();
209+
210+
List<String> userAgentHeaders = lastRequest.headers().get(USER_AGENT_HEADER_NAME);
211+
assertThat(userAgentHeaders).isNotNull().hasSize(1);
212+
return userAgentHeaders.get(0);
213+
}
214+
215+
private static HttpExecuteResponse mockResponse() {
216+
return HttpExecuteResponse.builder()
217+
.response(SdkHttpResponse.builder().statusCode(200).build())
218+
.responseBody(AbortableInputStream.create(new StringInputStream("{}")))
219+
.build();
220+
}
221+
}

0 commit comments

Comments
 (0)