Skip to content

Wrap CrtS3RuntimeException to SdkServiceException #2558

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.awssdk.transfer.s3.internal;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.awscore.exception.AwsErrorDetails;
import software.amazon.awssdk.core.exception.SdkClientException;
import software.amazon.awssdk.core.exception.SdkServiceException;
import software.amazon.awssdk.crt.s3.CrtS3RuntimeException;
import software.amazon.awssdk.services.s3.model.BucketAlreadyExistsException;
import software.amazon.awssdk.services.s3.model.BucketAlreadyOwnedByYouException;
import software.amazon.awssdk.services.s3.model.InvalidObjectStateException;
import software.amazon.awssdk.services.s3.model.NoSuchBucketException;
import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
import software.amazon.awssdk.services.s3.model.NoSuchUploadException;
import software.amazon.awssdk.services.s3.model.ObjectAlreadyInActiveTierErrorException;
import software.amazon.awssdk.services.s3.model.S3Exception;
import software.amazon.awssdk.utils.StringUtils;

@SdkInternalApi
public class CrtErrorHandler {

private final Map<String, S3Exception.Builder> s3ExceptionBuilderMap;

public CrtErrorHandler() {
s3ExceptionBuilderMap = getS3ExceptionBuilderMap();
}

/**
* This class transform a crtTRunTimeException to the S3 service Exceptions.
* CrtS3RuntimeException are the exceptions generated due to failures in CRTClient due to S3 Service errors.
*
* @param crtRuntimeException Exception that is thrown by CrtClient.
* @return
*/
public Exception transformException(Exception crtRuntimeException) {
Optional<CrtS3RuntimeException> crtS3RuntimeExceptionOptional = getCrtS3RuntimeException(crtRuntimeException);
return crtS3RuntimeExceptionOptional
.filter(CrtErrorHandler::isErrorDetailsAvailable)
.map(e -> getServiceSideException(e))
.orElse(SdkClientException.create(crtRuntimeException.getMessage(), crtRuntimeException));
}

private Exception getServiceSideException(CrtS3RuntimeException e) {
if (s3ExceptionBuilderMap.get(e.getAwsErrorCode()) != null) {
return s3ExceptionBuilderMap.get(e.getAwsErrorCode())
.awsErrorDetails(
AwsErrorDetails.builder().errorCode(e.getAwsErrorCode())
.errorMessage(e.getAwsErrorMessage()).build())
.cause(e)
.message(e.getMessage())
.statusCode(e.getStatusCode())
.build();
}
return SdkServiceException.builder().statusCode(e.getStatusCode()).message(e.getMessage()).cause(e).build();
}

/**
* This method checks if the exception has the required details to transform to S3 Exception.
* @param crtS3RuntimeException the exception that needs to be checked
* @return true if exception has the required details.
*/
private static boolean isErrorDetailsAvailable(CrtS3RuntimeException crtS3RuntimeException) {
return StringUtils.isNotBlank(crtS3RuntimeException.getAwsErrorCode());
}

/**
* Checks if the Exception or its cause is of CrtS3RuntimeException.
* The S3 Service related exception are in the form of CrtS3RuntimeException.
* @param crtRuntimeException
* @return CrtS3RuntimeException else return empty,
*/
private Optional<CrtS3RuntimeException> getCrtS3RuntimeException(Exception crtRuntimeException) {
if (crtRuntimeException instanceof CrtS3RuntimeException) {
return Optional.of((CrtS3RuntimeException) crtRuntimeException);
}
Throwable cause = crtRuntimeException.getCause();
if (cause instanceof CrtS3RuntimeException) {
return Optional.of((CrtS3RuntimeException) cause);
}
return Optional.empty();
}


/**
* Gets a Mapping of AWSErrorCode to its corresponding S3 Exception Builders.
*
* @return
*/
private Map<String, S3Exception.Builder> getS3ExceptionBuilderMap() {
Map<String, S3Exception.Builder> s3ExceptionBuilderMap = new HashMap<>();
s3ExceptionBuilderMap.put("ObjectAlreadyInActiveTierError", ObjectAlreadyInActiveTierErrorException.builder());
s3ExceptionBuilderMap.put("NoSuchUpload", NoSuchUploadException.builder());
s3ExceptionBuilderMap.put("BucketAlreadyExists", BucketAlreadyExistsException.builder());
s3ExceptionBuilderMap.put("BucketAlreadyOwnedByYou", BucketAlreadyOwnedByYouException.builder());
s3ExceptionBuilderMap.put("InvalidObjectState", InvalidObjectStateException.builder());
s3ExceptionBuilderMap.put("NoSuchBucket", NoSuchBucketException.builder());
s3ExceptionBuilderMap.put("NoSuchKey", NoSuchKeyException.builder());
return s3ExceptionBuilderMap;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public class CrtResponseDataConsumerAdapter<ReturnT> implements ResponseDataCons
private final CompletableFuture<ReturnT> future;
private final S3CrtDataPublisher publisher;
private final ResponseHeadersHandler headerHandler;
private final CrtErrorHandler errorHandler;

public CrtResponseDataConsumerAdapter(AsyncResponseTransformer<GetObjectResponse, ReturnT> transformer) {
this(transformer, new S3CrtDataPublisher(), new ResponseHeadersHandler());
Expand All @@ -52,6 +53,7 @@ public CrtResponseDataConsumerAdapter(AsyncResponseTransformer<GetObjectResponse
this.future = transformer.prepare();
this.publisher = s3CrtDataPublisher;
this.headerHandler = headersHandler;
this.errorHandler = new CrtErrorHandler();
}

public CompletableFuture<ReturnT> transformerFuture() {
Expand Down Expand Up @@ -87,8 +89,9 @@ public void onResponseData(ByteBuffer byteBuffer) {
@Override
public void onException(CrtRuntimeException e) {
log.debug(() -> "An error occurred ", e);
transformer.exceptionOccurred(e);
publisher.notifyError(e);
Exception transformException = errorHandler.transformException(e);
transformer.exceptionOccurred(transformException);
publisher.notifyError(transformException);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
public final class DefaultS3CrtAsyncClient implements S3CrtAsyncClient {
private final S3NativeClient s3NativeClient;
private final S3NativeClientConfiguration configuration;
private final CrtErrorHandler crtErrorHandler;

public DefaultS3CrtAsyncClient(DefaultS3CrtClientBuilder builder) {
S3NativeClientConfiguration.Builder configBuilder =
Expand All @@ -56,13 +57,15 @@ public DefaultS3CrtAsyncClient(DefaultS3CrtClientBuilder builder) {
configuration.partSizeBytes(),
configuration.targetThroughputInGbps(),
configuration.maxConcurrency());
crtErrorHandler = new CrtErrorHandler();
}

@SdkTestInternalApi
DefaultS3CrtAsyncClient(S3NativeClientConfiguration configuration,
S3NativeClient nativeClient) {
this.configuration = configuration;
this.s3NativeClient = nativeClient;
crtErrorHandler = new CrtErrorHandler();
}

@Override
Expand Down Expand Up @@ -99,6 +102,7 @@ public <ReturnT> CompletableFuture<ReturnT> getObject(
@Override
public CompletableFuture<PutObjectResponse> putObject(PutObjectRequest putObjectRequest, AsyncRequestBody requestBody) {
com.amazonaws.s3.model.PutObjectRequest adaptedRequest = S3CrtPojoConversion.toCrtPutObjectRequest(putObjectRequest);
CompletableFuture<PutObjectResponse> returnFuture = new CompletableFuture<>();

if (adaptedRequest.contentLength() == null && requestBody.contentLength().isPresent()) {
adaptedRequest = adaptedRequest.toBuilder()
Expand All @@ -116,7 +120,17 @@ public CompletableFuture<PutObjectResponse> putObject(PutObjectRequest putObject
crtFuture.thenApply(putObjectOutput -> S3CrtPojoConversion.fromCrtPutObjectOutput(
putObjectOutput, httpResponseFuture.getNow(SdkHttpResponse.builder().build())));

return CompletableFutureUtils.forwardExceptionTo(executeFuture, crtFuture);
CompletableFutureUtils.forwardExceptionTo(executeFuture, crtFuture);
executeFuture.whenComplete((r, t) -> {
if (t == null) {
returnFuture.complete(r);
} else {
returnFuture.completeExceptionally(t instanceof Exception
? crtErrorHandler.transformException((Exception) t) : t);
}
// TODO: Offload to returnFuture completion thread
});
return CompletableFutureUtils.forwardExceptionTo(returnFuture, executeFuture);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.awssdk.transfer.s3.internal;

import static org.mockito.Mockito.when;

import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import software.amazon.awssdk.core.exception.SdkClientException;
import software.amazon.awssdk.core.exception.SdkServiceException;
import software.amazon.awssdk.crt.CrtRuntimeException;
import software.amazon.awssdk.crt.s3.CrtS3RuntimeException;
import software.amazon.awssdk.services.s3.model.BucketAlreadyExistsException;
import software.amazon.awssdk.services.s3.model.InvalidObjectStateException;

@RunWith(MockitoJUnitRunner.class)
public class CrtErrorHandlerTest {

@Mock
private CrtS3RuntimeException mockCrtS3RuntimeException;

@Test
public void crtS3ExceptionAreTransformed(){
CrtErrorHandler crtErrorHandler = new CrtErrorHandler();
when(mockCrtS3RuntimeException.getAwsErrorCode()).thenReturn("BucketAlreadyExists");
when(mockCrtS3RuntimeException.getAwsErrorMessage()).thenReturn("Bucket Already Exists");
when(mockCrtS3RuntimeException.getStatusCode()).thenReturn(404);
Exception transformException = crtErrorHandler.transformException(mockCrtS3RuntimeException);
Assertions.assertThat(transformException).isInstanceOf(BucketAlreadyExistsException.class);
Assertions.assertThat(transformException.getMessage()).contains("Bucket Already Exists");
}

@Test
public void nonCrtS3ExceptionAreNotTransformed(){
CrtErrorHandler crtErrorHandler = new CrtErrorHandler();
Exception transformException = crtErrorHandler.transformException(new CrtRuntimeException("AWS_ERROR"));
Assertions.assertThat(transformException).isInstanceOf(SdkClientException.class);
}


@Test
public void crtS3ExceptionAreTransformedWhenExceptionIsInCause(){
CrtErrorHandler crtErrorHandler = new CrtErrorHandler();
when(mockCrtS3RuntimeException.getAwsErrorCode()).thenReturn("InvalidObjectState");
when(mockCrtS3RuntimeException.getAwsErrorMessage()).thenReturn("Invalid Object State");
when(mockCrtS3RuntimeException.getStatusCode()).thenReturn(404);
final Exception transformException = crtErrorHandler.transformException(new Exception("Some Exception", mockCrtS3RuntimeException));

System.out.println("transformException " +transformException);

Assertions.assertThat(transformException).isInstanceOf(InvalidObjectStateException.class);
Assertions.assertThat(transformException.getMessage()).contains("Invalid Object State");
Assertions.assertThat(transformException.getCause()).isInstanceOf(CrtS3RuntimeException.class);
}

@Test
public void nonCrtS3ExceptionAreNotTransformedWhenExceptionIsInCause(){
CrtErrorHandler crtErrorHandler = new CrtErrorHandler();
final Exception crtRuntimeException = new Exception("Some Exception", new CrtRuntimeException("AWS_ERROR"));
Exception transformException = crtErrorHandler.transformException(
crtRuntimeException);
Assertions.assertThat(transformException).isNotInstanceOf(CrtRuntimeException.class);
Assertions.assertThat(transformException).isInstanceOf(SdkClientException.class);
Assertions.assertThat(transformException.getMessage()).isEqualTo("Some Exception");
Assertions.assertThat(transformException.getCause()).isEqualTo(crtRuntimeException);
}

@Test
public void crtS3ExceptionWithErrorCodeNodeNotInS3Model() {
CrtErrorHandler crtErrorHandler = new CrtErrorHandler();
when(mockCrtS3RuntimeException.getAwsErrorCode()).thenReturn("NewS3ExceptionFromCrt");
when(mockCrtS3RuntimeException.getAwsErrorMessage()).thenReturn("New S3 Exception From Crt");
when(mockCrtS3RuntimeException.getStatusCode()).thenReturn(404);
Exception transformException = crtErrorHandler.transformException(mockCrtS3RuntimeException);
Assertions.assertThat(transformException).isInstanceOf(SdkServiceException.class);
Assertions.assertThat(transformException.getCause()).isEqualTo(mockCrtS3RuntimeException);
Assertions.assertThat(transformException.getMessage()).isEqualTo(mockCrtS3RuntimeException.getMessage());
Assertions.assertThat(((SdkServiceException)transformException).statusCode())
.isEqualTo(mockCrtS3RuntimeException.getStatusCode());
}

@Test
public void crtS3ExceptionInCauseWithErrorCodeNodeNotInS3Model() {
CrtErrorHandler crtErrorHandler = new CrtErrorHandler();
when(mockCrtS3RuntimeException.getAwsErrorCode()).thenReturn("NewS3ExceptionFromCrt");
when(mockCrtS3RuntimeException.getAwsErrorMessage()).thenReturn("New S3 Exception From Crt");
when(mockCrtS3RuntimeException.getStatusCode()).thenReturn(404);
final Exception crtRuntimeException = new Exception(mockCrtS3RuntimeException);
Exception transformException = crtErrorHandler.transformException(crtRuntimeException);
Assertions.assertThat(transformException).isInstanceOf(SdkServiceException.class);
Assertions.assertThat(transformException.getCause()).isEqualTo(mockCrtS3RuntimeException);
Assertions.assertThat(transformException.getMessage()).isEqualTo(mockCrtS3RuntimeException.getMessage());
Assertions.assertThat(((SdkServiceException) transformException).statusCode()).isEqualTo(404);
}
}
Loading