/* * 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.io.UnsupportedEncodingException; import java.lang.reflect.Field; import java.net.URI; import java.util.LinkedList; import java.util.List; import com.amazonaws.http.apache.client.impl.ConnectionManagerAwareHttpClient; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.HttpVersion; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.conn.HttpClientConnectionManager; import org.apache.http.message.BasicHttpResponse; import org.apache.http.message.BasicStatusLine; import org.apache.http.params.HttpParams; import org.apache.http.protocol.HttpContext; import org.junit.Assert; import com.amazonaws.AmazonClientException; import com.amazonaws.AmazonServiceException; import com.amazonaws.AmazonWebServiceRequest; import com.amazonaws.ClientConfiguration; import com.amazonaws.DefaultRequest; import com.amazonaws.Request; import com.amazonaws.http.AmazonHttpClient; import com.amazonaws.http.HttpResponseHandler; import com.amazonaws.util.StringInputStream; /** Some utility class and method for testing RetryCondition */ public class RetryPolicyTestBase { protected static ClientConfiguration clientConfiguration = new ClientConfiguration(); protected static final AmazonWebServiceRequest originalRequest = new TestAmazonWebServiceRequest(); protected static final HttpResponseHandler<AmazonServiceException> errorResponseHandler = new TestHttpResponseHandler(); /** * The retry condition and back-off strategy implementations that record all * the context data passed into shouldRetry and calculateSleepTime methods. */ protected static ContextDataCollectionRetryCondition retryCondition; protected static ContextDataCollectionBackoffStrategy backoffStrategy; public static void injectMockHttpClient(AmazonHttpClient amazonHttpClient, ConnectionManagerAwareHttpClient mockHttpClient) { try { Field f = AmazonHttpClient.class.getDeclaredField("httpClient"); f.setAccessible(true); f.set(amazonHttpClient, mockHttpClient); } catch (Exception e) { Assert.fail("Cannot inject the mock HttpClient object. " + e.getMessage()); } } @SuppressWarnings("rawtypes") public static Request<?> getSampleRequestWithRepeatableContent(AmazonWebServiceRequest amazonWebServiceRequest) { DefaultRequest<?> request = new DefaultRequest( amazonWebServiceRequest, "non-existent-service"); request.setEndpoint(URI.create("http://non-existent-service.amazonaws.com")); // StringInputStream#markSupported() returns true try { request.setContent(new StringInputStream("Some content that could be read for multiple times.")); } catch (UnsupportedEncodingException e) { Assert.fail("Unable to set up the request content"); } return request; } @SuppressWarnings("rawtypes") public static Request<?> getSampleRequestWithNonRepeatableContent(AmazonWebServiceRequest amazonWebServiceRequest) { DefaultRequest<?> request = new DefaultRequest( amazonWebServiceRequest, "non-existent-service"); request.setEndpoint(URI.create("http://non-existent-service.amazonaws.com")); // NonRepeatableInputStream#markSupported() returns false request.setContent(new NonRepeatableInputStream("Some content that could only be read once.")); return request; } public static class ContextDataCollectionRetryCondition extends ContextDataCollection implements RetryPolicy.RetryCondition { @Override public boolean shouldRetry(AmazonWebServiceRequest originalRequest, AmazonClientException exception, int retriesAttempted) { collect(originalRequest, exception, retriesAttempted); return true; } } public static class ContextDataCollectionBackoffStrategy extends ContextDataCollection implements RetryPolicy.BackoffStrategy { @Override public long delayBeforeNextRetry(AmazonWebServiceRequest originalRequest, AmazonClientException exception, int retriesAttempted) { collect(originalRequest, exception, retriesAttempted); return 0; // immediately retry to speed-up the test } } private static class ContextDataCollection { public List<AmazonWebServiceRequest> failedRequests = new LinkedList<AmazonWebServiceRequest>(); public List<AmazonClientException> exceptions = new LinkedList<AmazonClientException>(); public List<Integer> retriesAttemptedValues = new LinkedList<Integer>(); public void collect(AmazonWebServiceRequest originalRequest, AmazonClientException exception, int retriesAttempted) { failedRequests.add(originalRequest); exceptions.add(exception); retriesAttemptedValues.add(retriesAttempted); } } /** Verifies the RetryCondition has collected the expected context information. */ public static void verifyExpectedContextData(ContextDataCollection contextDataCollection, AmazonWebServiceRequest failedRequest, AmazonClientException expectedException, int expectedRetries) { Assert.assertEquals(expectedRetries, contextDataCollection.failedRequests.size()); Assert.assertEquals(expectedRetries, contextDataCollection.exceptions.size()); Assert.assertEquals(expectedRetries, contextDataCollection.retriesAttemptedValues.size()); if (expectedRetries > 0 ) { if (failedRequest != null) { // It should keep getting the same original request instance for (AmazonWebServiceRequest seenRequest : contextDataCollection.failedRequests) { Assert.assertTrue("seeRequest=" + seenRequest + ", failedRequest=" + failedRequest, seenRequest == failedRequest); } } // Verify the exceptions if (expectedException instanceof AmazonServiceException) { // It should get service exceptions with the expected error and status code AmazonServiceException ase = (AmazonServiceException)expectedException; for (AmazonClientException seenException : contextDataCollection.exceptions) { Assert.assertTrue(seenException instanceof AmazonServiceException); Assert.assertEquals(ase.getErrorCode(), ((AmazonServiceException)seenException).getErrorCode()); Assert.assertEquals(ase.getStatusCode(), ((AmazonServiceException)seenException).getStatusCode()); } } else if (expectedException != null) { // Client exceptions should have the same expected cause (the same // throwable instance from the mock HttpClient). Throwable expectedCause = expectedException.getCause(); for (AmazonClientException seenException : contextDataCollection.exceptions) { Assert.assertTrue(expectedCause == seenException.getCause()); } } // It should get "retriesAttempted" values starting from 0 int expectedRetriesAttempted = 0; for (int seenRetriesAttempted : contextDataCollection.retriesAttemptedValues) { Assert.assertEquals(expectedRetriesAttempted++, seenRetriesAttempted); } } } public static class TestAmazonWebServiceRequest extends AmazonWebServiceRequest {} /** * An error response handler implementation that simply * - keeps the status code * - sets the error code by the status text (which comes from the reason phrase in the low-level response) */ public static class TestHttpResponseHandler implements HttpResponseHandler<AmazonServiceException> { @Override public AmazonServiceException handle( com.amazonaws.http.HttpResponse response) throws Exception { AmazonServiceException ase = new AmazonServiceException("Fake service exception."); ase.setStatusCode(response.getStatusCode()); ase.setErrorCode(response.getStatusText()); return ase; } @Override public boolean needsConnectionLeftOpen() { return false; } } /** * A mock HttpClient implementation that does nothing but throws the * specified IOException or RuntimeException upon any call on execute(...) * method. */ public static class ThrowingExceptionHttpClient extends MockHttpClient { private final Throwable t; /** * @param t An IOException or RuntimeException object. */ public ThrowingExceptionHttpClient(Throwable t) { this.t = t; } @Override public HttpResponse execute(HttpUriRequest request) throws IOException, ClientProtocolException { if (t instanceof IOException) { throw (IOException)t; } else if (t instanceof RuntimeException){ throw (RuntimeException)t; } else { Assert.fail("The expected throwable should be either an IOException or RuntimeException."); return null; } } } /** * A mock HttpClient implementation that does nothing but directly returns a * BasicHttpResponse object with the specified status code upon any call on * execute(...) method. */ public static class ReturnServiceErrorHttpClient extends MockHttpClient { private final int statusCode; private final String reasonPhrase; /** * @param statusCode The status code to be included in the error response. */ public ReturnServiceErrorHttpClient(int statusCode, String reasonPhrase) { this.statusCode = statusCode; this.reasonPhrase = reasonPhrase; } @Override public HttpResponse execute(HttpUriRequest request) throws IOException, ClientProtocolException { return new BasicHttpResponse(new BasicStatusLine( HttpVersion.HTTP_1_1, statusCode, reasonPhrase)); } } /** A base abstract class for fake HttpClient implementations */ public static abstract class MockHttpClient implements ConnectionManagerAwareHttpClient { @Override public abstract HttpResponse execute(HttpUriRequest request) throws IOException, ClientProtocolException; @Override public HttpResponse execute(HttpUriRequest request, HttpContext context) throws IOException, ClientProtocolException { return execute(request); } /* * Unsupported operations. * These operations are not used by AmazonHttpClient */ @Override public HttpParams getParams() { return null; } @Override public ClientConnectionManager getConnectionManager() { return null; } @Override public HttpResponse execute(HttpHost target, HttpRequest request) throws IOException, ClientProtocolException { return null; } @Override public HttpResponse execute(HttpHost target, HttpRequest request, HttpContext context) throws IOException, ClientProtocolException { return null; } @Override public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler) throws IOException, ClientProtocolException { return null; } @Override public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler, HttpContext context) throws IOException, ClientProtocolException { return null; } @Override public <T> T execute(HttpHost target, HttpRequest request, ResponseHandler<? extends T> responseHandler) throws IOException, ClientProtocolException { return null; } @Override public <T> T execute(HttpHost target, HttpRequest request, ResponseHandler<? extends T> responseHandler, HttpContext context) throws IOException, ClientProtocolException { return null; } @Override public HttpClientConnectionManager getHttpClientConnectionManager() { return null; } } }