Skip to content

Commit 081679c

Browse files
John Viegasjoviegas
authored andcommitted
Wrap CrtS3RuntimeException to S3ServiceException
1 parent e770e19 commit 081679c

File tree

7 files changed

+426
-5
lines changed

7 files changed

+426
-5
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
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.transfer.s3;
17+
18+
import org.junit.AfterClass;
19+
import org.junit.Before;
20+
import org.junit.BeforeClass;
21+
import org.junit.Test;
22+
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
23+
import software.amazon.awssdk.services.s3.model.NoSuchBucketException;
24+
import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
25+
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
26+
import software.amazon.awssdk.testutils.RandomTempFile;
27+
import software.amazon.awssdk.transfer.s3.internal.S3CrtAsyncClient;
28+
29+
import java.io.IOException;
30+
import java.nio.file.Files;
31+
import java.nio.file.Paths;
32+
33+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
34+
import static software.amazon.awssdk.testutils.service.S3BucketUtils.temporaryBucketName;
35+
36+
public class CrtExceptionTransformationIntegrationTest extends S3IntegrationTestBase {
37+
38+
private static final String BUCKET = temporaryBucketName(CrtExceptionTransformationIntegrationTest.class);
39+
40+
private static final String KEY = "some-key";
41+
42+
private static final int OBJ_SIZE = 8 * 1024;
43+
private static RandomTempFile testFile;
44+
private S3TransferManager transferManager;
45+
private S3CrtAsyncClient s3Crt;
46+
47+
@BeforeClass
48+
public static void setupFixture() {
49+
createBucket(BUCKET);
50+
}
51+
52+
@AfterClass
53+
public static void tearDownFixture() {
54+
deleteBucketAndAllContents(BUCKET);
55+
}
56+
57+
@Before
58+
public void methodSetup() throws IOException {
59+
testFile = new RandomTempFile(BUCKET, OBJ_SIZE);
60+
s3Crt = S3CrtAsyncClient.builder()
61+
.credentialsProvider(CREDENTIALS_PROVIDER_CHAIN)
62+
.region(S3IntegrationTestBase.DEFAULT_REGION)
63+
.build();
64+
transferManager =
65+
S3TransferManager.builder()
66+
.s3ClientConfiguration(b -> b.credentialsProvider(CREDENTIALS_PROVIDER_CHAIN)
67+
.region(S3IntegrationTestBase.DEFAULT_REGION)
68+
.targetThroughputInGbps(20.0)
69+
.minimumPartSizeInBytes(1000L))
70+
.build();
71+
}
72+
73+
@Test
74+
public void getObjectNoSuchKey() throws IOException {
75+
String randomBaseDirectory = Files.createTempDirectory(getClass().getSimpleName()).toString();
76+
assertThatThrownBy(() -> s3Crt.getObject(GetObjectRequest.builder().bucket(BUCKET).key("randomKey").build(),
77+
Paths.get(randomBaseDirectory).resolve("testFile")).get())
78+
.hasCauseInstanceOf(NoSuchKeyException.class)
79+
.hasMessageContaining("software.amazon.awssdk.services.s3.model.NoSuchKeyException: The specified key does not exist");
80+
}
81+
82+
@Test
83+
public void getObjectNoSuchBucket() throws IOException {
84+
String randomBaseDirectory = Files.createTempDirectory(getClass().getSimpleName()).toString();
85+
assertThatThrownBy(() -> s3Crt.getObject(GetObjectRequest.builder().bucket("nonExistingTestBucket").key(KEY).build(),
86+
Paths.get(randomBaseDirectory).resolve("testFile")).get())
87+
.hasCauseInstanceOf(NoSuchBucketException.class)
88+
.hasMessageContaining("software.amazon.awssdk.services.s3.model.NoSuchBucketException: The specified bucket does not exist");
89+
}
90+
91+
@Test
92+
public void transferManagerDownloadObjectWithNoSuchKey() throws IOException {
93+
String randomBaseDirectory = Files.createTempDirectory(getClass().getSimpleName()).toString();
94+
assertThatThrownBy(() -> transferManager.download(DownloadRequest.builder()
95+
.getObjectRequest(GetObjectRequest.builder().bucket(BUCKET).key("randomKey").build())
96+
.destination(Paths.get(randomBaseDirectory).resolve("testFile"))
97+
.build()).completionFuture().join())
98+
.hasCauseInstanceOf(NoSuchKeyException.class)
99+
.hasMessageContaining("software.amazon.awssdk.services.s3.model.NoSuchKeyException: The specified key does not exist");
100+
}
101+
102+
@Test
103+
public void transferManagerDownloadObjectWithNoSuchBucket() throws IOException {
104+
String randomBaseDirectory = Files.createTempDirectory(getClass().getSimpleName()).toString();
105+
assertThatThrownBy(() -> transferManager.download(DownloadRequest.builder()
106+
.getObjectRequest(GetObjectRequest.builder().bucket("nonExistingTestBucket").key(KEY).build())
107+
.destination(Paths.get(randomBaseDirectory).resolve("testFile"))
108+
.build()).completionFuture().join())
109+
.hasCauseInstanceOf(NoSuchBucketException.class)
110+
.hasMessageContaining("software.amazon.awssdk.services.s3.model.NoSuchBucketException: The specified bucket does not exist");
111+
}
112+
113+
@Test
114+
public void putObjectNoSuchKey() throws IOException {
115+
String randomBaseDirectory = Files.createTempDirectory(getClass().getSimpleName()).toString();
116+
assertThatThrownBy(() -> s3Crt.getObject(GetObjectRequest.builder().bucket(BUCKET).key("someRandomKey").build(),
117+
Paths.get(randomBaseDirectory).resolve("testFile")).get())
118+
.hasCauseInstanceOf(NoSuchKeyException.class)
119+
.hasMessageContaining("software.amazon.awssdk.services.s3.model.NoSuchKeyException: The specified key does not exist");
120+
}
121+
122+
@Test
123+
public void putObjectNoSuchBucket() throws IOException {
124+
125+
String randomBaseDirectory = Files.createTempDirectory(getClass().getSimpleName()).toString();
126+
assertThatThrownBy(() -> s3Crt.getObject(GetObjectRequest.builder().bucket("nonExistingTestBucket").key(KEY).build(),
127+
Paths.get(randomBaseDirectory).resolve("testFile")).get())
128+
.hasCauseInstanceOf(NoSuchBucketException.class)
129+
.hasMessageContaining("software.amazon.awssdk.services.s3.model.NoSuchBucketException: The specified bucket does not exist");
130+
}
131+
132+
@Test
133+
public void transferManagerUploadObjectWithNoSuchObject() throws IOException{
134+
assertThatThrownBy(() -> transferManager.upload(UploadRequest.builder()
135+
.putObjectRequest(PutObjectRequest.builder().bucket("nonExistingTestBucket").key("someKey").build())
136+
.source(testFile.toPath())
137+
.build()).completionFuture().join())
138+
.hasCauseInstanceOf(NoSuchBucketException.class)
139+
.hasMessageContaining("software.amazon.awssdk.services.s3.model.NoSuchBucketException: The specified bucket does not exist");
140+
}
141+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
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.transfer.s3.internal;
17+
18+
import java.util.HashMap;
19+
import java.util.Map;
20+
import java.util.Optional;
21+
import software.amazon.awssdk.annotations.SdkInternalApi;
22+
import software.amazon.awssdk.awscore.exception.AwsErrorDetails;
23+
import software.amazon.awssdk.core.exception.SdkClientException;
24+
import software.amazon.awssdk.crt.s3.CrtS3RuntimeException;
25+
import software.amazon.awssdk.services.s3.model.BucketAlreadyExistsException;
26+
import software.amazon.awssdk.services.s3.model.BucketAlreadyOwnedByYouException;
27+
import software.amazon.awssdk.services.s3.model.InvalidObjectStateException;
28+
import software.amazon.awssdk.services.s3.model.NoSuchBucketException;
29+
import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
30+
import software.amazon.awssdk.services.s3.model.NoSuchUploadException;
31+
import software.amazon.awssdk.services.s3.model.ObjectAlreadyInActiveTierErrorException;
32+
import software.amazon.awssdk.services.s3.model.S3Exception;
33+
import software.amazon.awssdk.utils.StringUtils;
34+
35+
@SdkInternalApi
36+
public class CrtErrorHandler {
37+
38+
private final Map<String, S3Exception.Builder> s3ExceptionBuilderMap;
39+
40+
public CrtErrorHandler() {
41+
s3ExceptionBuilderMap = getS3ExceptionBuilderMap();
42+
}
43+
44+
/**
45+
* This class transform a crtTRunTimeException to the S3 service Exceptions.
46+
* CrtS3RuntimeException are the exceptions generated due to failures in CRTClient due to S3 Service errors.
47+
*
48+
* @param crtRuntimeException Exception that is thrown by CrtClient.
49+
* @return
50+
*/
51+
public Exception transformException(Exception crtRuntimeException) {
52+
Optional<CrtS3RuntimeException> crtS3RuntimeExceptionOptional = getCrtS3RuntimeException(crtRuntimeException);
53+
Exception exception = crtS3RuntimeExceptionOptional
54+
.filter(CrtErrorHandler::isErrorDetailsAvailable)
55+
.map(e -> getServiceSideException(e))
56+
.orElse(SdkClientException.create(crtRuntimeException.getMessage(), crtRuntimeException));
57+
return exception;
58+
}
59+
60+
private Exception getServiceSideException(CrtS3RuntimeException e) {
61+
if (s3ExceptionBuilderMap.get(e.getAwsErrorCode()) != null) {
62+
return s3ExceptionBuilderMap.get(e.getAwsErrorCode())
63+
.awsErrorDetails(
64+
AwsErrorDetails.builder().errorCode(e.getAwsErrorCode())
65+
.errorMessage(e.getAwsErrorMessage()).build())
66+
.cause(e)
67+
.message(e.getMessage())
68+
.statusCode(e.getStatusCode())
69+
.build();
70+
}
71+
return S3Exception.builder().statusCode(e.getStatusCode()).message(e.getMessage()).cause(e).build();
72+
}
73+
74+
/**
75+
* This method checks if the exception has the required details to transform to S3 Exception.
76+
* @param crtS3RuntimeException the exception that needs to be checked
77+
* @return true if exception has the required details.
78+
*/
79+
private static boolean isErrorDetailsAvailable(CrtS3RuntimeException crtS3RuntimeException) {
80+
return StringUtils.isNotBlank(crtS3RuntimeException.getAwsErrorCode());
81+
}
82+
83+
/**
84+
* Checks if the Exception or its cause is of CrtS3RuntimeException.
85+
* The S3 Service related exception are in the form of CrtS3RuntimeException.
86+
* @param crtRuntimeException
87+
* @return CrtS3RuntimeException else return empty,
88+
*/
89+
private Optional<CrtS3RuntimeException> getCrtS3RuntimeException(Exception crtRuntimeException) {
90+
if (crtRuntimeException instanceof CrtS3RuntimeException) {
91+
return Optional.of((CrtS3RuntimeException) crtRuntimeException);
92+
}
93+
Throwable cause = crtRuntimeException.getCause();
94+
if (cause instanceof CrtS3RuntimeException) {
95+
return Optional.of((CrtS3RuntimeException) cause);
96+
}
97+
return Optional.empty();
98+
}
99+
100+
101+
/**
102+
* Gets a Mapping of AWSErrorCode to its corresponding S3 Exception Builders.
103+
*
104+
* @return
105+
*/
106+
private Map<String, S3Exception.Builder> getS3ExceptionBuilderMap() {
107+
Map<String, S3Exception.Builder> s3ExceptionBuilderMap = new HashMap<>();
108+
s3ExceptionBuilderMap.put("ObjectAlreadyInActiveTierError", ObjectAlreadyInActiveTierErrorException.builder());
109+
s3ExceptionBuilderMap.put("NoSuchUpload", NoSuchUploadException.builder());
110+
s3ExceptionBuilderMap.put("BucketAlreadyExists", BucketAlreadyExistsException.builder());
111+
s3ExceptionBuilderMap.put("BucketAlreadyOwnedByYou", BucketAlreadyOwnedByYouException.builder());
112+
s3ExceptionBuilderMap.put("InvalidObjectState", InvalidObjectStateException.builder());
113+
s3ExceptionBuilderMap.put("NoSuchBucket", NoSuchBucketException.builder());
114+
s3ExceptionBuilderMap.put("NoSuchKey", NoSuchKeyException.builder());
115+
return s3ExceptionBuilderMap;
116+
}
117+
}

services-custom/s3-transfer-manager/src/main/java/software/amazon/awssdk/transfer/s3/internal/CrtResponseDataConsumerAdapter.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ public class CrtResponseDataConsumerAdapter<ReturnT> implements ResponseDataCons
3939
private final CompletableFuture<ReturnT> future;
4040
private final S3CrtDataPublisher publisher;
4141
private final ResponseHeadersHandler headerHandler;
42+
private final CrtErrorHandler errorHandler;
43+
4244

4345
public CrtResponseDataConsumerAdapter(AsyncResponseTransformer<GetObjectResponse, ReturnT> transformer) {
4446
this(transformer, new S3CrtDataPublisher(), new ResponseHeadersHandler());
@@ -52,6 +54,7 @@ public CrtResponseDataConsumerAdapter(AsyncResponseTransformer<GetObjectResponse
5254
this.future = transformer.prepare();
5355
this.publisher = s3CrtDataPublisher;
5456
this.headerHandler = headersHandler;
57+
this.errorHandler = new CrtErrorHandler();
5558
}
5659

5760
public CompletableFuture<ReturnT> transformerFuture() {
@@ -87,8 +90,9 @@ public void onResponseData(ByteBuffer byteBuffer) {
8790
@Override
8891
public void onException(CrtRuntimeException e) {
8992
log.debug(() -> "An error occurred ", e);
90-
transformer.exceptionOccurred(e);
91-
publisher.notifyError(e);
93+
Exception transformException = errorHandler.transformException(e);
94+
transformer.exceptionOccurred(transformException);
95+
publisher.notifyError(transformException);
9296
}
9397

9498
@Override

services-custom/s3-transfer-manager/src/main/java/software/amazon/awssdk/transfer/s3/internal/DefaultS3CrtAsyncClient.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
public final class DefaultS3CrtAsyncClient implements S3CrtAsyncClient {
3838
private final S3NativeClient s3NativeClient;
3939
private final S3NativeClientConfiguration configuration;
40+
private final CrtErrorHandler crtErrorHandler;
4041

4142
public DefaultS3CrtAsyncClient(DefaultS3CrtClientBuilder builder) {
4243
S3NativeClientConfiguration.Builder configBuilder =
@@ -58,13 +59,16 @@ public DefaultS3CrtAsyncClient(DefaultS3CrtClientBuilder builder) {
5859
configuration.partSizeBytes(),
5960
configuration.targetThroughputInGbps(),
6061
configuration.maxConcurrency());
62+
this.crtErrorHandler = new CrtErrorHandler();
6163
}
6264

6365
@SdkTestInternalApi
6466
DefaultS3CrtAsyncClient(S3NativeClientConfiguration configuration,
6567
S3NativeClient nativeClient) {
6668
this.configuration = configuration;
6769
this.s3NativeClient = nativeClient;
70+
this.crtErrorHandler = new CrtErrorHandler();
71+
6872
}
6973

7074
@Override
@@ -82,9 +86,20 @@ public <ReturnT> CompletableFuture<ReturnT> getObject(
8286
// Forward the cancellation to crtFuture to cancel the request
8387
CompletableFutureUtils.forwardExceptionTo(returnFuture, crtFuture);
8488

89+
8590
// Forward the exception from the CRT future to the return future in case
8691
// the adapter callback didn't get it
87-
CompletableFutureUtils.forwardExceptionTo(crtFuture, returnFuture);
92+
CompletableFutureUtils.forwardTransformedExceptionTo(crtFuture, returnFuture,
93+
t -> t instanceof Exception ? crtErrorHandler.transformException((Exception) t) : t);
94+
95+
returnFuture.whenComplete((r, t) -> {
96+
if (t == null) {
97+
returnFuture.complete(r);
98+
} else {
99+
returnFuture.completeExceptionally(t instanceof Exception
100+
? crtErrorHandler.transformException((Exception) t) : t);
101+
}
102+
});
88103

89104
CompletableFutureUtils.forwardResultTo(adapterFuture, returnFuture, configuration.futureCompletionExecutor());
90105

@@ -115,6 +130,15 @@ public CompletableFuture<PutObjectResponse> putObject(PutObjectRequest putObject
115130
crtFuture.thenApply(putObjectOutput -> S3CrtPojoConversion.fromCrtPutObjectOutput(
116131
putObjectOutput, httpResponseFuture.getNow(SdkHttpResponse.builder().build())));
117132

133+
executeFuture.whenComplete((r, t) -> {
134+
if (t == null) {
135+
returnFuture.complete(r);
136+
} else {
137+
returnFuture.completeExceptionally(t instanceof Exception
138+
? crtErrorHandler.transformException((Exception) t) : t);
139+
}
140+
});
141+
118142
CompletableFutureUtils.forwardResultTo(executeFuture, returnFuture, configuration.futureCompletionExecutor());
119143

120144
return CompletableFutureUtils.forwardExceptionTo(returnFuture, executeFuture);

0 commit comments

Comments
 (0)