Skip to content

Commit e0c2f8b

Browse files
committed
Add business metric support for Sigv4A auth scheme
1 parent 6144c8a commit e0c2f8b

File tree

7 files changed

+178
-2
lines changed

7 files changed

+178
-2
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": "Add business metric support for Sigv4A auth scheme to track when an operation is called using Sigv4A signing"
6+
}

codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeInterceptorSpec.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,16 @@ private MethodSpec generateBeforeExecution() {
158158
builder.addStatement("recordEnvironmentTokenBusinessMetric(selectedAuthScheme, "
159159
+ "executionAttributes)");
160160
}
161+
162+
if (authSchemeSpecUtils.hasSigV4aSupport()) {
163+
builder.beginControlFlow("if (selectedAuthScheme != null && "
164+
+ "selectedAuthScheme.authSchemeOption().schemeId().equals($S))",
165+
"aws.auth#sigv4a")
166+
.addStatement("executionAttributes.getAttribute($T.BUSINESS_METRICS)"
167+
+ ".addMetric($T.SIGV4A_SIGNING.value())",
168+
SdkInternalExecutionAttribute.class, BusinessMetricFeatureId.class)
169+
.endControlFlow();
170+
}
161171
return builder.build();
162172
}
163173

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-interceptor.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute;
2121
import software.amazon.awssdk.core.internal.util.MetricUtils;
2222
import software.amazon.awssdk.core.metrics.CoreMetric;
23+
import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId;
2324
import software.amazon.awssdk.http.auth.aws.signer.RegionSet;
2425
import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme;
2526
import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption;
@@ -49,6 +50,10 @@ public void beforeExecution(Context.BeforeExecution context, ExecutionAttributes
4950
List<AuthSchemeOption> authOptions = resolveAuthOptions(context, executionAttributes);
5051
SelectedAuthScheme<? extends Identity> selectedAuthScheme = selectAuthScheme(authOptions, executionAttributes);
5152
putSelectedAuthScheme(executionAttributes, selectedAuthScheme);
53+
if (selectedAuthScheme != null && selectedAuthScheme.authSchemeOption().schemeId().equals("aws.auth#sigv4a")) {
54+
executionAttributes.getAttribute(SdkInternalExecutionAttribute.BUSINESS_METRICS).addMetric(
55+
BusinessMetricFeatureId.SIGV4A_SIGNING.value());
56+
}
5257
}
5358

5459
private List<AuthSchemeOption> resolveAuthOptions(Context.BeforeExecution context, ExecutionAttributes executionAttributes) {

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-with-allowlist-auth-scheme-interceptor.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute;
2121
import software.amazon.awssdk.core.internal.util.MetricUtils;
2222
import software.amazon.awssdk.core.metrics.CoreMetric;
23+
import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId;
2324
import software.amazon.awssdk.endpoints.EndpointProvider;
2425
import software.amazon.awssdk.http.auth.aws.signer.RegionSet;
2526
import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme;
@@ -52,6 +53,10 @@ public void beforeExecution(Context.BeforeExecution context, ExecutionAttributes
5253
List<AuthSchemeOption> authOptions = resolveAuthOptions(context, executionAttributes);
5354
SelectedAuthScheme<? extends Identity> selectedAuthScheme = selectAuthScheme(authOptions, executionAttributes);
5455
putSelectedAuthScheme(executionAttributes, selectedAuthScheme);
56+
if (selectedAuthScheme != null && selectedAuthScheme.authSchemeOption().schemeId().equals("aws.auth#sigv4a")) {
57+
executionAttributes.getAttribute(SdkInternalExecutionAttribute.BUSINESS_METRICS).addMetric(
58+
BusinessMetricFeatureId.SIGV4A_SIGNING.value());
59+
}
5560
}
5661

5762
private List<AuthSchemeOption> resolveAuthOptions(Context.BeforeExecution context, ExecutionAttributes executionAttributes) {
@@ -102,7 +107,6 @@ private QueryAuthSchemeParams authSchemeParams(SdkRequest request, ExecutionAttr
102107
executionAttributes.getOptionalAttribute(AwsExecutionAttribute.AWS_SIGV4A_SIGNING_REGION_SET)
103108
.filter(regionSet -> !CollectionUtils.isNullOrEmpty(regionSet))
104109
.ifPresent(nonEmptyRegionSet -> builder.regionSet(RegionSet.create(nonEmptyRegionSet)));
105-
106110
if (builder instanceof QueryEndpointResolverAware.Builder) {
107111
EndpointProvider endpointProvider = executionAttributes.getAttribute(SdkInternalExecutionAttribute.ENDPOINT_PROVIDER);
108112
if (endpointProvider instanceof QueryEndpointProvider) {

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-without-allowlist-auth-scheme-interceptor.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute;
2121
import software.amazon.awssdk.core.internal.util.MetricUtils;
2222
import software.amazon.awssdk.core.metrics.CoreMetric;
23+
import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId;
2324
import software.amazon.awssdk.endpoints.EndpointProvider;
2425
import software.amazon.awssdk.http.auth.aws.signer.RegionSet;
2526
import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme;
@@ -52,6 +53,10 @@ public void beforeExecution(Context.BeforeExecution context, ExecutionAttributes
5253
List<AuthSchemeOption> authOptions = resolveAuthOptions(context, executionAttributes);
5354
SelectedAuthScheme<? extends Identity> selectedAuthScheme = selectAuthScheme(authOptions, executionAttributes);
5455
putSelectedAuthScheme(executionAttributes, selectedAuthScheme);
56+
if (selectedAuthScheme != null && selectedAuthScheme.authSchemeOption().schemeId().equals("aws.auth#sigv4a")) {
57+
executionAttributes.getAttribute(SdkInternalExecutionAttribute.BUSINESS_METRICS).addMetric(
58+
BusinessMetricFeatureId.SIGV4A_SIGNING.value());
59+
}
5560
}
5661

5762
private List<AuthSchemeOption> resolveAuthOptions(Context.BeforeExecution context, ExecutionAttributes executionAttributes) {

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

Lines changed: 2 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,M,O,S,U-c
25+
* Unimplemented metrics: I,J,K,M,O,U-c
2626
* Unsupported metrics (these will never be added): A,H
2727
*/
2828
@SdkProtectedApi
@@ -39,6 +39,7 @@ public enum BusinessMetricFeatureId {
3939
ACCOUNT_ID_MODE_PREFERRED("P"),
4040
ACCOUNT_ID_MODE_DISABLED("Q"),
4141
ACCOUNT_ID_MODE_REQUIRED("R"),
42+
SIGV4A_SIGNING("S"),
4243
RESOLVED_ACCOUNT_ID("T"),
4344
DDB_MAPPER("d"),
4445
BEARER_SERVICE_ENV_VARS("3"),
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
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 org.junit.jupiter.api.BeforeEach;
23+
import org.junit.jupiter.api.Test;
24+
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
25+
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
26+
import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId;
27+
import software.amazon.awssdk.http.AbortableInputStream;
28+
import software.amazon.awssdk.http.HttpExecuteResponse;
29+
import software.amazon.awssdk.http.SdkHttpRequest;
30+
import software.amazon.awssdk.http.SdkHttpResponse;
31+
import software.amazon.awssdk.regions.Region;
32+
import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonAsyncClient;
33+
import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonClient;
34+
import software.amazon.awssdk.services.sigv4aauth.Sigv4AauthAsyncClient;
35+
import software.amazon.awssdk.services.sigv4aauth.Sigv4AauthClient;
36+
import software.amazon.awssdk.testutils.service.http.MockAsyncHttpClient;
37+
import software.amazon.awssdk.testutils.service.http.MockSyncHttpClient;
38+
import software.amazon.awssdk.utils.StringInputStream;
39+
40+
/**
41+
* Test class to verify that SIGV4A_SIGNING business metric is correctly included
42+
* in the User-Agent header when operation called using Sigv4A signing.
43+
*/
44+
class Sigv4aBusinessMetricUserAgentTest {
45+
private static final String USER_AGENT_HEADER_NAME = "User-Agent";
46+
private static final StaticCredentialsProvider CREDENTIALS_PROVIDER =
47+
StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", "skid"));
48+
49+
private MockSyncHttpClient mockHttpClient;
50+
private MockAsyncHttpClient mockAsyncHttpClient;
51+
52+
@BeforeEach
53+
public void setup() {
54+
mockHttpClient = new MockSyncHttpClient();
55+
mockHttpClient.stubNextResponse(mockResponse());
56+
57+
mockAsyncHttpClient = new MockAsyncHttpClient();
58+
mockAsyncHttpClient.stubNextResponse(mockResponse());
59+
}
60+
61+
@Test
62+
void when_sigv4aServiceIsUsed_correctMetricIsAdded() {
63+
Sigv4AauthClient client = Sigv4AauthClient.builder()
64+
.region(Region.US_WEST_2)
65+
.credentialsProvider(CREDENTIALS_PROVIDER)
66+
.httpClient(mockHttpClient)
67+
.build();
68+
69+
client.simpleOperationWithNoEndpointParams(r -> r.stringMember("test"));
70+
71+
String userAgent = getUserAgentFromLastRequest();
72+
System.out.println("SigV4a service User-Agent: " + userAgent);
73+
assertThat(userAgent).matches(METRIC_SEARCH_PATTERN.apply(BusinessMetricFeatureId.SIGV4A_SIGNING.value()));
74+
}
75+
76+
@Test
77+
void when_sigv4aServiceIsUsedAsync_correctMetricIsAdded() {
78+
Sigv4AauthAsyncClient asyncClient = Sigv4AauthAsyncClient.builder()
79+
.region(Region.US_WEST_2)
80+
.credentialsProvider(CREDENTIALS_PROVIDER)
81+
.httpClient(mockAsyncHttpClient)
82+
.build();
83+
84+
asyncClient.simpleOperationWithNoEndpointParams(r -> r.stringMember("test")).join();
85+
86+
String userAgent = getUserAgentFromLastAsyncRequest();
87+
System.out.println("SigV4a async service User-Agent: " + userAgent);
88+
assertThat(userAgent).matches(METRIC_SEARCH_PATTERN.apply(BusinessMetricFeatureId.SIGV4A_SIGNING.value()));
89+
}
90+
91+
@Test
92+
void when_regularServiceIsUsed_sigv4aMetricIsNotAdded() {
93+
ProtocolRestJsonClient client = ProtocolRestJsonClient.builder()
94+
.region(Region.US_WEST_2)
95+
.credentialsProvider(CREDENTIALS_PROVIDER)
96+
.httpClient(mockHttpClient)
97+
.build();
98+
99+
client.allTypes(r -> {});
100+
101+
String userAgent = getUserAgentFromLastRequest();
102+
System.out.println("Regular service User-Agent: " + userAgent);
103+
assertThat(userAgent).doesNotMatch(METRIC_SEARCH_PATTERN.apply(BusinessMetricFeatureId.SIGV4A_SIGNING.value()));
104+
}
105+
106+
@Test
107+
void when_regularServiceIsUsedAsync_sigv4aMetricIsNotAdded() {
108+
ProtocolRestJsonAsyncClient asyncClient = ProtocolRestJsonAsyncClient.builder()
109+
.region(Region.US_WEST_2)
110+
.credentialsProvider(CREDENTIALS_PROVIDER)
111+
.httpClient(mockAsyncHttpClient)
112+
.build();
113+
114+
asyncClient.allTypes(r -> {}).join();
115+
116+
String userAgent = getUserAgentFromLastAsyncRequest();
117+
System.out.println("Regular async service User-Agent: " + userAgent);
118+
assertThat(userAgent).doesNotMatch(METRIC_SEARCH_PATTERN.apply(BusinessMetricFeatureId.SIGV4A_SIGNING.value()));
119+
}
120+
121+
private String getUserAgentFromLastRequest() {
122+
SdkHttpRequest lastRequest = mockHttpClient.getLastRequest();
123+
assertThat(lastRequest).isNotNull();
124+
125+
List<String> userAgentHeaders = lastRequest.headers().get(USER_AGENT_HEADER_NAME);
126+
assertThat(userAgentHeaders).isNotNull().hasSize(1);
127+
return userAgentHeaders.get(0);
128+
}
129+
130+
private String getUserAgentFromLastAsyncRequest() {
131+
SdkHttpRequest lastRequest = mockAsyncHttpClient.getLastRequest();
132+
assertThat(lastRequest).isNotNull();
133+
134+
List<String> userAgentHeaders = lastRequest.headers().get(USER_AGENT_HEADER_NAME);
135+
assertThat(userAgentHeaders).isNotNull().hasSize(1);
136+
return userAgentHeaders.get(0);
137+
}
138+
139+
private static HttpExecuteResponse mockResponse() {
140+
return HttpExecuteResponse.builder()
141+
.response(SdkHttpResponse.builder().statusCode(200).build())
142+
.responseBody(AbortableInputStream.create(new StringInputStream("{}")))
143+
.build();
144+
}
145+
}

0 commit comments

Comments
 (0)