Skip to content

Commit 50d97ac

Browse files
authored
Fix for issue #4720 , Cross Region enabled Clients created in US-EAST-1 will by internally disable global endpoint and do a regional endpoint call. (#4849)
* Fix for issue #4720 , Cross Region enabled Clients created in US-EAST-1 will by internally disable global endpoint and do a regional endpoint call. * Handled Zoe's comments
1 parent 9b1f20a commit 50d97ac

File tree

13 files changed

+170
-182
lines changed

13 files changed

+170
-182
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "bugfix",
3+
"category": "Amazon Simple Storage Service",
4+
"contributor": "",
5+
"description": "S3 client configured with crossRegionEnabled(true) will now use us-east-1 regional endpoint instead of the global endpoint. See [#4720](https://github.com/aws/aws-sdk-java-v2/issues/4720)."
6+
}

services/s3/src/it/java/software/amazon/awssdk/services/s3/crossregion/S3CrossRegionAsyncIntegrationTestBase.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
3131
import software.amazon.awssdk.services.s3.model.HeadBucketRequest;
3232
import software.amazon.awssdk.services.s3.model.HeadBucketResponse;
33+
import software.amazon.awssdk.services.s3.model.HeadObjectRequest;
34+
import software.amazon.awssdk.services.s3.model.HeadObjectResponse;
3335
import software.amazon.awssdk.services.s3.model.ListObjectsV2Request;
3436
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
3537
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
@@ -74,4 +76,9 @@ protected PutObjectResponse putAPICall(PutObjectRequest putObjectRequest, String
7476
protected ResponseBytes<GetObjectResponse> getAPICall(GetObjectRequest getObjectRequest) {
7577
return crossRegionS3Client.getObject(getObjectRequest, AsyncResponseTransformer.toBytes()).join();
7678
}
79+
80+
@Override
81+
protected HeadObjectResponse headObjectAPICall(HeadObjectRequest headObjectRequest) {
82+
return crossRegionS3Client.headObject(headObjectRequest).join();
83+
}
7784
}

services/s3/src/it/java/software/amazon/awssdk/services/s3/crossregion/S3CrossRegionIntegrationTestBase.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
3636
import software.amazon.awssdk.services.s3.model.HeadBucketRequest;
3737
import software.amazon.awssdk.services.s3.model.HeadBucketResponse;
38+
import software.amazon.awssdk.services.s3.model.HeadObjectRequest;
39+
import software.amazon.awssdk.services.s3.model.HeadObjectResponse;
3840
import software.amazon.awssdk.services.s3.model.ListObjectsV2Request;
3941
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
4042
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
@@ -58,6 +60,16 @@ void getApi_CrossRegionCall() {
5860
assertThat(new String(response.asByteArray())).isEqualTo("TEST_STRING");
5961
}
6062

63+
@Test
64+
void headObjectApi_CrossRegionCall() {
65+
s3.putObject(p -> p.bucket(bucketName()).checksumAlgorithm(ChecksumAlgorithm.CRC32).key(KEY), RequestBody.fromString(
66+
"TEST_STRING"));
67+
HeadObjectRequest headObjectRequest =
68+
HeadObjectRequest.builder().bucket(bucketName()).checksumMode(ChecksumMode.ENABLED).key(KEY).build();
69+
HeadObjectResponse response = headObjectAPICall(headObjectRequest);
70+
assertThat(response.contentLength()).isEqualTo("TEST_STRING".length());
71+
}
72+
6173
@Test
6274
void putApi_CrossRegionCall() {
6375
s3.putObject(p -> p.bucket(bucketName()).checksumAlgorithm(ChecksumAlgorithm.CRC32).key(KEY), RequestBody.fromString(
@@ -136,6 +148,7 @@ void headApi_CrossRegionCall() {
136148
protected abstract PutObjectResponse putAPICall(PutObjectRequest putObjectRequest, String testString);
137149

138150
protected abstract ResponseBytes<GetObjectResponse> getAPICall(GetObjectRequest getObjectRequest);
151+
protected abstract HeadObjectResponse headObjectAPICall(HeadObjectRequest headObjectRequest);
139152

140153
protected abstract String bucketName();
141154

services/s3/src/it/java/software/amazon/awssdk/services/s3/crossregion/S3CrossRegionSyncIntegrationTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
3737
import software.amazon.awssdk.services.s3.model.HeadBucketRequest;
3838
import software.amazon.awssdk.services.s3.model.HeadBucketResponse;
39+
import software.amazon.awssdk.services.s3.model.HeadObjectRequest;
40+
import software.amazon.awssdk.services.s3.model.HeadObjectResponse;
3941
import software.amazon.awssdk.services.s3.model.ListObjectsV2Request;
4042
import software.amazon.awssdk.services.s3.model.ListObjectsV2Response;
4143
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
@@ -101,6 +103,11 @@ protected ResponseBytes<GetObjectResponse> getAPICall(GetObjectRequest getObject
101103
return crossRegionS3Client.getObject(getObjectRequest, ResponseTransformer.toBytes());
102104
}
103105

106+
@Override
107+
protected HeadObjectResponse headObjectAPICall(HeadObjectRequest headObjectRequest) {
108+
return crossRegionS3Client.headObject(headObjectRequest);
109+
}
110+
104111
@Override
105112
protected String bucketName() {
106113
return BUCKET;

services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crossregion/S3CrossRegionAsyncClient.java

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,23 +52,17 @@ protected <T extends S3Request, ReturnT> CompletableFuture<ReturnT> invokeOperat
5252

5353
AwsRequestOverrideConfiguration overrideConfiguration = updateUserAgentInConfig(request);
5454
T userAgentUpdatedRequest = (T) request.toBuilder().overrideConfiguration(overrideConfiguration).build();
55-
5655
if (!bucket.isPresent()) {
5756
return operation.apply(userAgentUpdatedRequest);
5857
}
5958
String bucketName = bucket.get();
6059

6160
CompletableFuture<ReturnT> returnFuture = new CompletableFuture<>();
62-
CompletableFuture<ReturnT> apiOperationFuture = bucketToRegionCache.containsKey(bucketName) ?
63-
operation.apply(
64-
requestWithDecoratedEndpointProvider(
65-
userAgentUpdatedRequest,
66-
() -> bucketToRegionCache.get(bucketName),
67-
serviceClientConfiguration().endpointProvider().get()
68-
)
69-
) :
70-
operation.apply(userAgentUpdatedRequest);
71-
61+
CompletableFuture<ReturnT> apiOperationFuture = operation.apply(
62+
requestWithDecoratedEndpointProvider(userAgentUpdatedRequest,
63+
() -> bucketToRegionCache.get(bucketName),
64+
serviceClientConfiguration().endpointProvider().get())
65+
);
7266
apiOperationFuture.whenComplete(redirectToCrossRegionIfRedirectException(operation,
7367
userAgentUpdatedRequest,
7468
bucketName,

services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crossregion/S3CrossRegionSyncClient.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,10 @@ protected <T extends S3Request, ReturnT> ReturnT invokeOperation(T request, Func
6464
}
6565
String bucketName = bucketRequest.get();
6666
try {
67-
if (bucketToRegionCache.containsKey(bucketName)) {
68-
return operation.apply(
69-
requestWithDecoratedEndpointProvider(userAgentUpdatedRequest,
70-
() -> bucketToRegionCache.get(bucketName),
71-
serviceClientConfiguration().endpointProvider().get()));
72-
}
73-
return operation.apply(userAgentUpdatedRequest);
67+
return operation.apply(
68+
requestWithDecoratedEndpointProvider(userAgentUpdatedRequest,
69+
() -> bucketToRegionCache.get(bucketName),
70+
serviceClientConfiguration().endpointProvider().get()));
7471
} catch (S3Exception exception) {
7572
if (isS3RedirectException(exception)) {
7673
updateCacheFromRedirectException(exception, bucketName);

services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crossregion/endpointprovider/BucketEndpointProvider.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ public static BucketEndpointProvider create(S3EndpointProvider delegateEndPointP
4444
public CompletableFuture<Endpoint> resolveEndpoint(S3EndpointParams endpointParams) {
4545
Region crossRegion = regionSupplier.get();
4646
return delegateEndPointProvider.resolveEndpoint(
47-
crossRegion != null ? endpointParams.copy(c -> c.region(crossRegion)) : endpointParams);
47+
endpointParams.copy(c -> c.region(crossRegion == null ? endpointParams.region() : crossRegion)
48+
.useGlobalEndpoint(false)));
4849
}
4950
}
5051

services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crossregion/utils/CrossRegionUtils.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,13 @@
1717

1818

1919
import java.util.Arrays;
20-
import java.util.Collections;
2120
import java.util.List;
2221
import java.util.Optional;
2322
import java.util.concurrent.CompletionException;
2423
import java.util.function.Consumer;
2524
import java.util.function.Supplier;
2625
import software.amazon.awssdk.annotations.SdkInternalApi;
2726
import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration;
28-
import software.amazon.awssdk.awscore.exception.AwsErrorDetails;
2927
import software.amazon.awssdk.core.ApiName;
3028
import software.amazon.awssdk.endpoints.EndpointProvider;
3129
import software.amazon.awssdk.regions.Region;
@@ -41,7 +39,6 @@ public final class CrossRegionUtils {
4139
public static final String AMZ_BUCKET_REGION_HEADER = "x-amz-bucket-region";
4240
private static final List<Integer> REDIRECT_STATUS_CODES =
4341
Arrays.asList(REDIRECT_STATUS_CODE, TEMPORARY_REDIRECT_STATUS_CODE);
44-
private static final List<String> REDIRECT_ERROR_CODES = Collections.singletonList("AuthorizationHeaderMalformed");
4542
private static final ApiName API_NAME = ApiName.builder().version("cross-region").name("hll").build();
4643
private static final Consumer<AwsRequestOverrideConfiguration.Builder> USER_AGENT_APPLIER = b -> b.addApiName(API_NAME);
4744

@@ -65,12 +62,7 @@ private static boolean isRedirectError(S3Exception exceptionToBeChecked) {
6562
if (REDIRECT_STATUS_CODES.stream().anyMatch(status -> status.equals(exceptionToBeChecked.statusCode()))) {
6663
return true;
6764
}
68-
if (getBucketRegionFromException(exceptionToBeChecked).isPresent()) {
69-
return true;
70-
}
71-
AwsErrorDetails awsErrorDetails = exceptionToBeChecked.awsErrorDetails();
72-
return awsErrorDetails != null
73-
&& REDIRECT_ERROR_CODES.stream().anyMatch(code -> code.equals(awsErrorDetails.errorCode()));
65+
return getBucketRegionFromException(exceptionToBeChecked).isPresent();
7466
}
7567

7668
@SuppressWarnings("unchecked")

services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crossregion/S3CrossRegionAsyncClientRedirectTest.java

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -131,14 +131,6 @@ protected void verifyHeadBucketServiceCall(int times) {
131131
verify(mockDelegateAsyncClient, times(times)).headBucket(any(Consumer.class));
132132
}
133133

134-
@Override
135-
protected void stubApiWithAuthorizationHeaderMalformedError() {
136-
when(mockDelegateAsyncClient.listObjects(any(ListObjectsRequest.class)))
137-
.thenReturn(CompletableFutureUtils.failedFuture(
138-
new CompletionException(redirectException(400, null, "AuthorizationHeaderMalformed", null))))
139-
.thenReturn(CompletableFuture.completedFuture(ListObjectsResponse.builder().contents(S3_OBJECTS).build()));
140-
}
141-
142134
@Override
143135
protected void stubApiWithAuthorizationHeaderWithInternalSoftwareError() {
144136

@@ -150,16 +142,6 @@ protected void stubApiWithAuthorizationHeaderWithInternalSoftwareError() {
150142
"InternalError", null))));
151143
}
152144

153-
@Override
154-
protected void stubHeadBucketRedirectWithAuthorizationHeaderMalformed() {
155-
when(mockDelegateAsyncClient.headBucket(any(HeadBucketRequest.class)))
156-
.thenReturn(CompletableFutureUtils.failedFuture(
157-
new CompletionException(redirectException(400,CROSS_REGION.id(), "AuthorizationHeaderMalformed", null))));
158-
when(mockDelegateAsyncClient.headBucket(any(Consumer.class)))
159-
.thenReturn(CompletableFutureUtils.failedFuture(
160-
new CompletionException(redirectException(400,CROSS_REGION.id(), "AuthorizationHeaderMalformed", null))));
161-
}
162-
163145
@Override
164146
protected void verifyNoBucketCall() {
165147
assertThatExceptionOfType(CompletionException.class)

0 commit comments

Comments
 (0)