/*
* Copyright 2010-2015 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.
* You may obtain a copy of the License at:
*
* http://aws.amazon.com/apache2.0
*
* 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 com.amazonaws.retry;
import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.Request;
import com.amazonaws.http.AmazonHttpClient;
import com.amazonaws.http.ExecutionContext;
import com.amazonaws.util.AWSRequestMetrics;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.Random;
import java.util.UUID;
/**
* Tests that {@link AmazonHttpClient#executeHelper()} method passes the correct
* context information into the configured RetryPolicy.
*/
public class AmazonHttpClientRetryPolicyTest extends RetryPolicyTestBase {
private static final int EXPECTED_RETRY_COUNT = 5;
private static final Random random = new Random();
/** Reset the RetryPolicy and restart collecting context data */
@Before
public void resetContextData() {
retryCondition = new ContextDataCollectionRetryCondition();
backoffStrategy = new ContextDataCollectionBackoffStrategy();
// Reset the RetryPolicy
clientConfiguration.setRetryPolicy(
new RetryPolicy(retryCondition,
backoffStrategy,
EXPECTED_RETRY_COUNT, // max error retry
false)); // ignore the maxErrorRetry in
// ClientConfiguration level
}
/**
* Tests AmazonHttpClient's behavior upon simulated service exceptions when
* the request payload is repeatable.
*/
@Test
public void testServiceExceptionHandling() {
int random500StatusCode = 500 + random.nextInt(100);
String randomErrorCode = UUID.randomUUID().toString();
// A mock HttpClient that always returns the specified status and error
// code.
injectMockHttpClient(testedClient, new ReturnServiceErrorHttpClient(random500StatusCode,
randomErrorCode));
// The ExecutionContext should collect the expected RequestCount
ExecutionContext context = new ExecutionContext(true);
Request<?> testedRepeatableRequest = getSampleRequestWithRepeatableContent(originalRequest);
// It should keep retrying until it reaches the max retry limit and
// throws the simulated ASE.
AmazonServiceException expectedServiceException = null;
try {
testedClient.execute(testedRepeatableRequest,
null,
errorResponseHandler,
context);
Assert.fail("AmazonServiceException is expected.");
} catch (AmazonServiceException ase) {
// We should see the original service exception
Assert.assertEquals(random500StatusCode, ase.getStatusCode());
Assert.assertEquals(randomErrorCode, ase.getErrorCode());
expectedServiceException = ase;
}
// Verifies that the correct information was passed into the
// RetryCondition and BackoffStrategy
verifyExpectedContextData(retryCondition,
originalRequest,
expectedServiceException,
EXPECTED_RETRY_COUNT);
verifyExpectedContextData(backoffStrategy,
originalRequest,
expectedServiceException,
EXPECTED_RETRY_COUNT);
// We also want to check the RequestCount metric is correctly captured.
Assert.assertEquals(
EXPECTED_RETRY_COUNT + 1, // request count = retries + 1
context.getAwsRequestMetrics()
.getTimingInfo()
.getCounter(AWSRequestMetrics.Field.RequestCount.toString()).intValue());
}
/**
* Tests AmazonHttpClient's behavior upon simulated IOException during
* executing the http request when the request payload is repeatable.
*/
@Test
public void testIOExceptioinHandling() {
// A mock HttpClient that always throws the specified IOException object
IOException simulatedIOException = new IOException("fake IOException");
injectMockHttpClient(testedClient, new ThrowingExceptionHttpClient(simulatedIOException));
// The ExecutionContext should collect the expected RequestCount
ExecutionContext context = new ExecutionContext(true);
Request<?> testedRepeatableRequest = getSampleRequestWithRepeatableContent(originalRequest);
// It should keep retrying until it reaches the max retry limit and
// throws the an ACE containing the simulated IOException.
AmazonClientException expectedClientException = null;
try {
testedClient.execute(testedRepeatableRequest,
null,
errorResponseHandler,
context);
Assert.fail("AmazonClientException is expected.");
} catch (AmazonClientException ace) {
Assert.assertTrue(simulatedIOException == ace.getCause());
expectedClientException = ace;
}
// Verifies that the correct information was passed into the
// RetryCondition and BackoffStrategy
verifyExpectedContextData(retryCondition,
originalRequest,
expectedClientException,
EXPECTED_RETRY_COUNT);
verifyExpectedContextData(backoffStrategy,
originalRequest,
expectedClientException,
EXPECTED_RETRY_COUNT);
// We also want to check the RequestCount metric is correctly captured.
Assert.assertEquals(
EXPECTED_RETRY_COUNT + 1, // request count = retries + 1
context.getAwsRequestMetrics()
.getTimingInfo()
.getCounter(AWSRequestMetrics.Field.RequestCount.toString()).intValue());
}
/**
* Tests AmazonHttpClient's behavior upon simulated service exceptions when
* the request payload is not repeatable.
*/
@Test
public void testServiceExceptionHandlingWithNonRepeatableRequestContent() {
int random500StatusCode = 500 + random.nextInt(100);
String randomErrorCode = UUID.randomUUID().toString();
// A mock HttpClient that always returns the specified status and error
// code.
injectMockHttpClient(testedClient, new ReturnServiceErrorHttpClient(random500StatusCode,
randomErrorCode));
// The ExecutionContext should collect the expected RequestCount
ExecutionContext context = new ExecutionContext(true);
// A non-repeatable request
Request<?> testedNonRepeatableRequest = getSampleRequestWithNonRepeatableContent(originalRequest);
// It should fail directly and throw the ASE, without consulting the
// custom shouldRetry(..) method.
try {
testedClient.execute(testedNonRepeatableRequest,
null,
errorResponseHandler,
context);
Assert.fail("AmazonServiceException is expected.");
} catch (AmazonServiceException ase) {
Assert.assertEquals(random500StatusCode, ase.getStatusCode());
Assert.assertEquals(randomErrorCode, ase.getErrorCode());
}
// Verifies that shouldRetry and calculateSleepTime were never called
verifyExpectedContextData(retryCondition,
null,
null,
0);
verifyExpectedContextData(backoffStrategy,
null,
null,
0);
// The captured RequestCount should be 1
Assert.assertEquals(
1,
context.getAwsRequestMetrics()
.getTimingInfo()
.getCounter(AWSRequestMetrics.Field.RequestCount.toString()).intValue());
}
/**
* Tests AmazonHttpClient's behavior upon simulated IOException when the
* request payload is not repeatable.
*/
@Test
public void testIOExceptionHandlingWithNonRepeatableRequestContent() {
// A mock HttpClient that always throws the specified IOException object
IOException simulatedIOException = new IOException("fake IOException");
injectMockHttpClient(testedClient, new ThrowingExceptionHttpClient(simulatedIOException));
// The ExecutionContext should collect the expected RequestCount
ExecutionContext context = new ExecutionContext(true);
// A non-repeatable request
Request<?> testedRepeatableRequest = getSampleRequestWithNonRepeatableContent(originalRequest);
// It should fail directly and throw an ACE containing the simulated
// IOException, without consulting the
// custom shouldRetry(..) method.
try {
testedClient.execute(testedRepeatableRequest,
null,
errorResponseHandler,
context);
Assert.fail("AmazonClientException is expected.");
} catch (AmazonClientException ace) {
Assert.assertTrue(simulatedIOException == ace.getCause());
}
// Verifies that shouldRetry and calculateSleepTime were never called
verifyExpectedContextData(retryCondition,
null,
null,
0);
verifyExpectedContextData(backoffStrategy,
null,
null,
0);
// The captured RequestCount should be 1
Assert.assertEquals(
1,
context.getAwsRequestMetrics()
.getTimingInfo()
.getCounter(AWSRequestMetrics.Field.RequestCount.toString()).intValue());
}
/**
* Tests AmazonHttpClient's behavior upon simulated RuntimeException (which
* should be handled as an unexpected failure and not retried).
*/
@Test
public void testUnexpectedFailureHandling() {
// A mock HttpClient that always throws an NPE
NullPointerException simulatedNPE = new NullPointerException("fake NullPointerException");
injectMockHttpClient(testedClient, new ThrowingExceptionHttpClient(simulatedNPE));
// The ExecutionContext should collect the expected RequestCount
ExecutionContext context = new ExecutionContext(true);
Request<?> testedRepeatableRequest = getSampleRequestWithRepeatableContent(originalRequest);
// It should fail directly and throw the simulated NPE, without
// consulting the custom shouldRetry(..) method.
try {
testedClient.execute(testedRepeatableRequest,
null,
errorResponseHandler,
context);
Assert.fail("AmazonClientException is expected.");
} catch (NullPointerException npe) {
Assert.assertTrue(simulatedNPE == npe);
}
// Verifies that shouldRetry and calculateSleepTime were never called
verifyExpectedContextData(retryCondition,
null,
null,
0);
verifyExpectedContextData(backoffStrategy,
null,
null,
0);
// The captured RequestCount should be 1
Assert.assertEquals(
1,
context.getAwsRequestMetrics()
.getTimingInfo()
.getCounter(AWSRequestMetrics.Field.RequestCount.toString()).intValue());
}
}