Skip to content

Commit a47f90d

Browse files
committed
Add business metric support for all flexible checksums
1 parent 3343d58 commit a47f90d

File tree

4 files changed

+305
-1
lines changed

4 files changed

+305
-1
lines changed

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@
3232
import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
3333
import software.amazon.awssdk.core.client.config.SdkClientOption;
3434
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
35+
import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute;
3536
import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute;
3637
import software.amazon.awssdk.core.internal.http.HttpClientDependencies;
3738
import software.amazon.awssdk.core.internal.http.RequestExecutionContext;
3839
import software.amazon.awssdk.core.internal.http.pipeline.MutableRequestToRequestPipeline;
40+
import software.amazon.awssdk.core.internal.useragent.BusinessMetricsUtils;
3941
import software.amazon.awssdk.core.useragent.AdditionalMetadata;
4042
import software.amazon.awssdk.core.useragent.BusinessMetricCollection;
4143
import software.amazon.awssdk.http.SdkHttpFullRequest;
@@ -151,6 +153,8 @@ private static Optional<String> getBusinessMetricsString(ExecutionAttributes exe
151153
}
152154
businessMetrics.merge(metricsFromApiNames);
153155

156+
ChecksumBusinessMetrics(executionAttributes, businessMetrics);
157+
154158
credentialProviderBusinessMetrics(executionAttributes).ifPresent(businessMetrics::merge);
155159

156160
if (businessMetrics.recordedMetrics().isEmpty()) {
@@ -160,6 +164,21 @@ private static Optional<String> getBusinessMetricsString(ExecutionAttributes exe
160164
return Optional.of(businessMetrics.asBoundedString());
161165
}
162166

167+
private static void ChecksumBusinessMetrics(ExecutionAttributes executionAttributes,
168+
BusinessMetricCollection businessMetrics) {
169+
BusinessMetricsUtils.resolveRequestChecksumCalculationMetric(
170+
executionAttributes.getAttribute(SdkInternalExecutionAttribute.REQUEST_CHECKSUM_CALCULATION))
171+
.ifPresent(businessMetrics::addMetric);
172+
173+
BusinessMetricsUtils.resolveResponseChecksumValidationMetric(
174+
executionAttributes.getAttribute(SdkInternalExecutionAttribute.RESPONSE_CHECKSUM_VALIDATION))
175+
.ifPresent(businessMetrics::addMetric);
176+
177+
BusinessMetricsUtils.resolveChecksumSpecsMetric(
178+
executionAttributes.getAttribute(SdkExecutionAttribute.RESOLVED_CHECKSUM_SPECS))
179+
.ifPresent(businessMetrics::addMetric);
180+
}
181+
163182
private static Optional<Collection<String>> credentialProviderBusinessMetrics(
164183
ExecutionAttributes executionAttributes) {
165184
return Optional.ofNullable(

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

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
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.ChecksumSpecs;
23+
import software.amazon.awssdk.core.checksums.RequestChecksumCalculation;
24+
import software.amazon.awssdk.core.checksums.ResponseChecksumValidation;
2025
import software.amazon.awssdk.core.retry.RetryMode;
2126
import software.amazon.awssdk.core.retry.RetryPolicy;
2227
import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId;
@@ -55,4 +60,57 @@ public static Optional<String> resolveRetryMode(RetryPolicy retryPolicy, RetrySt
5560
}
5661
return Optional.empty();
5762
}
63+
64+
public static Optional<String> resolveRequestChecksumCalculationMetric(
65+
RequestChecksumCalculation requestChecksumCalculation) {
66+
if (requestChecksumCalculation == RequestChecksumCalculation.WHEN_SUPPORTED) {
67+
return Optional.of(BusinessMetricFeatureId.FLEXIBLE_CHECKSUMS_REQ_WHEN_SUPPORTED.value());
68+
}
69+
if (requestChecksumCalculation == RequestChecksumCalculation.WHEN_REQUIRED) {
70+
return Optional.of(BusinessMetricFeatureId.FLEXIBLE_CHECKSUMS_REQ_WHEN_REQUIRED.value());
71+
}
72+
return Optional.empty();
73+
}
74+
75+
public static Optional<String> resolveResponseChecksumValidationMetric(
76+
ResponseChecksumValidation responseChecksumValidation) {
77+
if (responseChecksumValidation == ResponseChecksumValidation.WHEN_SUPPORTED) {
78+
return Optional.of(BusinessMetricFeatureId.FLEXIBLE_CHECKSUMS_RES_WHEN_SUPPORTED.value());
79+
}
80+
if (responseChecksumValidation == ResponseChecksumValidation.WHEN_REQUIRED) {
81+
return Optional.of(BusinessMetricFeatureId.FLEXIBLE_CHECKSUMS_RES_WHEN_REQUIRED.value());
82+
}
83+
return Optional.empty();
84+
}
85+
86+
public static Optional<String> resolveChecksumAlgorithmMetric(ChecksumAlgorithm algorithm) {
87+
if (algorithm == null) {
88+
return Optional.empty();
89+
}
90+
91+
String algorithmId = algorithm.algorithmId();
92+
if (algorithmId.equals(DefaultChecksumAlgorithm.CRC32.algorithmId())) {
93+
return Optional.of(BusinessMetricFeatureId.FLEXIBLE_CHECKSUMS_REQ_CRC32.value());
94+
}
95+
if (algorithmId.equals(DefaultChecksumAlgorithm.CRC32C.algorithmId())) {
96+
return Optional.of(BusinessMetricFeatureId.FLEXIBLE_CHECKSUMS_REQ_CRC32C.value());
97+
}
98+
if (algorithmId.equals(DefaultChecksumAlgorithm.CRC64NVME.algorithmId())) {
99+
return Optional.of(BusinessMetricFeatureId.FLEXIBLE_CHECKSUMS_REQ_CRC64.value());
100+
}
101+
if (algorithmId.equals(DefaultChecksumAlgorithm.SHA1.algorithmId())) {
102+
return Optional.of(BusinessMetricFeatureId.FLEXIBLE_CHECKSUMS_REQ_SHA1.value());
103+
}
104+
if (algorithmId.equals(DefaultChecksumAlgorithm.SHA256.algorithmId())) {
105+
return Optional.of(BusinessMetricFeatureId.FLEXIBLE_CHECKSUMS_REQ_SHA256.value());
106+
}
107+
return Optional.empty();
108+
}
109+
110+
public static Optional<String> resolveChecksumSpecsMetric(ChecksumSpecs checksumSpecs) {
111+
if (checksumSpecs != null && checksumSpecs.algorithmV2() != null) {
112+
return resolveChecksumAlgorithmMetric(checksumSpecs.algorithmV2());
113+
}
114+
return Optional.empty();
115+
}
58116
}

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,S,U-c
25+
* Unimplemented metrics: I,J,K,O
2626
* Unsupported metrics (these will never be added): A,H
2727
*/
2828
@SdkProtectedApi
@@ -41,6 +41,15 @@ public enum BusinessMetricFeatureId {
4141
ACCOUNT_ID_MODE_DISABLED("Q"),
4242
ACCOUNT_ID_MODE_REQUIRED("R"),
4343
RESOLVED_ACCOUNT_ID("T"),
44+
FLEXIBLE_CHECKSUMS_REQ_CRC32("U"),
45+
FLEXIBLE_CHECKSUMS_REQ_CRC32C("V"),
46+
FLEXIBLE_CHECKSUMS_REQ_CRC64("W"),
47+
FLEXIBLE_CHECKSUMS_REQ_SHA1("X"),
48+
FLEXIBLE_CHECKSUMS_REQ_SHA256("Y"),
49+
FLEXIBLE_CHECKSUMS_REQ_WHEN_SUPPORTED("Z"),
50+
FLEXIBLE_CHECKSUMS_REQ_WHEN_REQUIRED("a"),
51+
FLEXIBLE_CHECKSUMS_RES_WHEN_SUPPORTED("b"),
52+
FLEXIBLE_CHECKSUMS_RES_WHEN_REQUIRED("c"),
4453
DDB_MAPPER("d"),
4554
BEARER_SERVICE_ENV_VARS("3"),
4655
CREDENTIALS_CODE("e"),
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
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).matches(METRIC_SEARCH_PATTERN.apply("Z"));
72+
assertThat(userAgent).matches(METRIC_SEARCH_PATTERN.apply("b"));
73+
assertThat(userAgent).doesNotMatch(METRIC_SEARCH_PATTERN.apply("a"));
74+
assertThat(userAgent).doesNotMatch(METRIC_SEARCH_PATTERN.apply("c"));
75+
}
76+
77+
@ParameterizedTest
78+
@MethodSource("checksumAlgorithmTestCases")
79+
void when_checksumAlgorithmIsUsed_correctMetricIsAdded(ChecksumAlgorithm algorithm, String expectedMetric) {
80+
ProtocolRestJsonClient client = ProtocolRestJsonClient.builder()
81+
.region(Region.US_WEST_2)
82+
.credentialsProvider(CREDENTIALS_PROVIDER)
83+
.httpClient(mockHttpClient)
84+
.build();
85+
86+
client.putOperationWithChecksum(r -> r.checksumAlgorithm(algorithm),
87+
RequestBody.fromString("test content"));
88+
89+
String userAgent = getUserAgentFromLastRequest();
90+
assertThat(userAgent).matches(METRIC_SEARCH_PATTERN.apply(expectedMetric));
91+
}
92+
93+
static Stream<Arguments> checksumAlgorithmTestCases() {
94+
return Stream.of(
95+
Arguments.of(ChecksumAlgorithm.CRC32, "U"),
96+
Arguments.of(ChecksumAlgorithm.CRC32_C, "V"),
97+
Arguments.of(ChecksumAlgorithm.CRC64_NVME, "W"),
98+
Arguments.of(ChecksumAlgorithm.SHA1, "X"),
99+
Arguments.of(ChecksumAlgorithm.SHA256, "Y")
100+
);
101+
}
102+
103+
@ParameterizedTest
104+
@MethodSource("checksumConfigurationTestCases")
105+
void when_checksumConfigurationIsSet_correctMetricIsAdded(RequestChecksumCalculation requestConfig,
106+
ResponseChecksumValidation responseConfig,
107+
String expectedRequestMetric,
108+
String expectedResponseMetric) {
109+
ProtocolRestJsonClient client = ProtocolRestJsonClient.builder()
110+
.region(Region.US_WEST_2)
111+
.credentialsProvider(CREDENTIALS_PROVIDER)
112+
.httpClient(mockHttpClient)
113+
.requestChecksumCalculation(requestConfig)
114+
.responseChecksumValidation(responseConfig)
115+
.build();
116+
117+
client.allTypes(r -> {});
118+
119+
String userAgent = getUserAgentFromLastRequest();
120+
assertThat(userAgent).matches(METRIC_SEARCH_PATTERN.apply(expectedRequestMetric));
121+
assertThat(userAgent).matches(METRIC_SEARCH_PATTERN.apply(expectedResponseMetric));
122+
}
123+
124+
static Stream<Arguments> checksumConfigurationTestCases() {
125+
return Stream.of(
126+
Arguments.of(RequestChecksumCalculation.WHEN_SUPPORTED,
127+
ResponseChecksumValidation.WHEN_SUPPORTED, "Z", "b"),
128+
Arguments.of(RequestChecksumCalculation.WHEN_REQUIRED,
129+
ResponseChecksumValidation.WHEN_REQUIRED, "a", "c"),
130+
Arguments.of(RequestChecksumCalculation.WHEN_REQUIRED,
131+
ResponseChecksumValidation.WHEN_SUPPORTED, "a", "b"),
132+
Arguments.of(RequestChecksumCalculation.WHEN_SUPPORTED,
133+
ResponseChecksumValidation.WHEN_REQUIRED, "Z", "c")
134+
);
135+
}
136+
137+
@ParameterizedTest
138+
@MethodSource("checksumConfigurationWithAlgorithmTestCases")
139+
void when_checksumConfigurationAndAlgorithmAreSet_correctMetricsAreAdded(
140+
RequestChecksumCalculation requestConfig,
141+
ResponseChecksumValidation responseConfig,
142+
ChecksumAlgorithm algorithm,
143+
String expectedRequestMetric,
144+
String expectedResponseMetric,
145+
String expectedAlgorithmMetric) {
146+
147+
ProtocolRestJsonClient client = ProtocolRestJsonClient.builder()
148+
.region(Region.US_WEST_2)
149+
.credentialsProvider(CREDENTIALS_PROVIDER)
150+
.httpClient(mockHttpClient)
151+
.requestChecksumCalculation(requestConfig)
152+
.responseChecksumValidation(responseConfig)
153+
.build();
154+
155+
client.putOperationWithChecksum(r -> r.checksumAlgorithm(algorithm),
156+
RequestBody.fromString("test content"));
157+
158+
String userAgent = getUserAgentFromLastRequest();
159+
160+
assertThat(userAgent).matches(METRIC_SEARCH_PATTERN.apply(expectedRequestMetric));
161+
assertThat(userAgent).matches(METRIC_SEARCH_PATTERN.apply(expectedResponseMetric));
162+
assertThat(userAgent).matches(METRIC_SEARCH_PATTERN.apply(expectedAlgorithmMetric));
163+
}
164+
165+
static Stream<Arguments> checksumConfigurationWithAlgorithmTestCases() {
166+
return Stream.of(
167+
Arguments.of(RequestChecksumCalculation.WHEN_SUPPORTED,
168+
ResponseChecksumValidation.WHEN_SUPPORTED,
169+
ChecksumAlgorithm.CRC32, "Z", "b", "U"),
170+
Arguments.of(RequestChecksumCalculation.WHEN_SUPPORTED,
171+
ResponseChecksumValidation.WHEN_SUPPORTED,
172+
ChecksumAlgorithm.CRC32_C, "Z", "b", "V"),
173+
Arguments.of(RequestChecksumCalculation.WHEN_SUPPORTED,
174+
ResponseChecksumValidation.WHEN_SUPPORTED,
175+
ChecksumAlgorithm.SHA256, "Z", "b", "Y"),
176+
177+
Arguments.of(RequestChecksumCalculation.WHEN_REQUIRED,
178+
ResponseChecksumValidation.WHEN_REQUIRED,
179+
ChecksumAlgorithm.CRC32, "a", "c", "U"),
180+
Arguments.of(RequestChecksumCalculation.WHEN_REQUIRED,
181+
ResponseChecksumValidation.WHEN_REQUIRED,
182+
ChecksumAlgorithm.CRC64_NVME, "a", "c", "W"),
183+
Arguments.of(RequestChecksumCalculation.WHEN_REQUIRED,
184+
ResponseChecksumValidation.WHEN_REQUIRED,
185+
ChecksumAlgorithm.SHA1, "a", "c", "X"),
186+
187+
Arguments.of(RequestChecksumCalculation.WHEN_REQUIRED,
188+
ResponseChecksumValidation.WHEN_SUPPORTED,
189+
ChecksumAlgorithm.CRC32_C, "a", "b", "V"),
190+
Arguments.of(RequestChecksumCalculation.WHEN_REQUIRED,
191+
ResponseChecksumValidation.WHEN_SUPPORTED,
192+
ChecksumAlgorithm.SHA256, "a", "b", "Y"),
193+
194+
Arguments.of(RequestChecksumCalculation.WHEN_SUPPORTED,
195+
ResponseChecksumValidation.WHEN_REQUIRED,
196+
ChecksumAlgorithm.CRC64_NVME, "Z", "c", "W"),
197+
Arguments.of(RequestChecksumCalculation.WHEN_SUPPORTED,
198+
ResponseChecksumValidation.WHEN_REQUIRED,
199+
ChecksumAlgorithm.SHA1, "Z", "c", "X")
200+
);
201+
}
202+
203+
private String getUserAgentFromLastRequest() {
204+
SdkHttpRequest lastRequest = mockHttpClient.getLastRequest();
205+
assertThat(lastRequest).isNotNull();
206+
207+
List<String> userAgentHeaders = lastRequest.headers().get(USER_AGENT_HEADER_NAME);
208+
assertThat(userAgentHeaders).isNotNull().hasSize(1);
209+
return userAgentHeaders.get(0);
210+
}
211+
212+
private static HttpExecuteResponse mockResponse() {
213+
return HttpExecuteResponse.builder()
214+
.response(SdkHttpResponse.builder().statusCode(200).build())
215+
.responseBody(AbortableInputStream.create(new StringInputStream("{}")))
216+
.build();
217+
}
218+
}

0 commit comments

Comments
 (0)