/* * Copyright 2010-2017 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 com.amazonaws.retry; import java.io.IOException; import java.util.Random; import java.util.UUID; import org.junit.Assert; import org.junit.Before; import org.junit.Test; 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; /** * 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(); private AmazonHttpClient testedClient; /** 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 testedClient = new AmazonHttpClient(clientConfiguration); } /** * 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.requestExecutionBuilder() .request(testedRepeatableRequest) .errorResponseHandler(errorResponseHandler) .executionContext(context) .execute(); 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.requestExecutionBuilder() .request(testedRepeatableRequest) .errorResponseHandler(errorResponseHandler) .executionContext(context) .execute(); 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.requestExecutionBuilder() .request(testedNonRepeatableRequest) .errorResponseHandler(errorResponseHandler) .executionContext(context) .execute(); 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, EXPECTED_RETRY_COUNT); verifyExpectedContextData(backoffStrategy, null, null, EXPECTED_RETRY_COUNT); 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 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.requestExecutionBuilder() .request(testedRepeatableRequest) .errorResponseHandler(errorResponseHandler) .executionContext(context) .execute(); Assert.fail("AmazonClientException is expected."); } catch (AmazonClientException ace) { Assert.assertTrue(simulatedIOException == ace.getCause()); } // Verifies that shouldRetry and calculateSleepTime are still called verifyExpectedContextData(retryCondition, null, null, EXPECTED_RETRY_COUNT); verifyExpectedContextData(backoffStrategy, null, null, EXPECTED_RETRY_COUNT); 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 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.requestExecutionBuilder() .request(testedRepeatableRequest) .errorResponseHandler(errorResponseHandler) .executionContext(context) .execute(); 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()); } }