Update openstack-keystone RetryOnRenew to handle 408 errors with a BackoffLimitedRetryHandler

This commit is contained in:
Jeremy Daggett 2014-05-14 14:39:10 -07:00
parent 8d51ad6f87
commit 94459ba6e3
2 changed files with 54 additions and 10 deletions

View File

@ -22,11 +22,14 @@ import static org.jclouds.http.HttpUtils.releasePayload;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.inject.Named;
import org.jclouds.Constants;
import org.jclouds.domain.Credentials; import org.jclouds.domain.Credentials;
import org.jclouds.http.HttpCommand; import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpResponse; import org.jclouds.http.HttpResponse;
import org.jclouds.http.HttpRetryHandler; import org.jclouds.http.HttpRetryHandler;
import org.jclouds.http.handlers.BackoffLimitedRetryHandler;
import org.jclouds.logging.Logger; import org.jclouds.logging.Logger;
import org.jclouds.openstack.keystone.v2_0.domain.Access; import org.jclouds.openstack.keystone.v2_0.domain.Access;
import org.jclouds.openstack.v2_0.reference.AuthHeaders; import org.jclouds.openstack.v2_0.reference.AuthHeaders;
@ -43,8 +46,6 @@ import com.google.inject.Singleton;
/** /**
* This will parse and set an appropriate exception on the command object. * This will parse and set an appropriate exception on the command object.
* *
* @author Adrian Cole
* @author Zack Shoylev
*/ */
@Singleton @Singleton
public class RetryOnRenew implements HttpRetryHandler { public class RetryOnRenew implements HttpRetryHandler {
@ -52,13 +53,19 @@ public class RetryOnRenew implements HttpRetryHandler {
protected Logger logger = Logger.NULL; protected Logger logger = Logger.NULL;
@VisibleForTesting @VisibleForTesting
@Inject(optional = true)
@Named(Constants.PROPERTY_MAX_RETRIES)
static final int NUM_RETRIES = 5; static final int NUM_RETRIES = 5;
private final LoadingCache<Credentials, Access> authenticationResponseCache; private final LoadingCache<Credentials, Access> authenticationResponseCache;
private final BackoffLimitedRetryHandler backoffHandler;
@Inject @Inject
protected RetryOnRenew(LoadingCache<Credentials, Access> authenticationResponseCache) { protected RetryOnRenew(LoadingCache<Credentials, Access> authenticationResponseCache,
BackoffLimitedRetryHandler backoffHandler) {
this.authenticationResponseCache = authenticationResponseCache; this.authenticationResponseCache = authenticationResponseCache;
this.backoffHandler = backoffHandler;
} }
/* /*
@ -73,6 +80,7 @@ public class RetryOnRenew implements HttpRetryHandler {
@Override @Override
public boolean shouldRetryRequest(HttpCommand command, HttpResponse response) { public boolean shouldRetryRequest(HttpCommand command, HttpResponse response) {
boolean retry = false; // default
try { try {
switch (response.getStatusCode()) { switch (response.getStatusCode()) {
case 401: case 401:
@ -80,7 +88,7 @@ public class RetryOnRenew implements HttpRetryHandler {
Multimap<String, String> headers = command.getCurrentRequest().getHeaders(); Multimap<String, String> headers = command.getCurrentRequest().getHeaders();
if (headers != null && headers.containsKey(AuthHeaders.AUTH_USER) if (headers != null && headers.containsKey(AuthHeaders.AUTH_USER)
&& headers.containsKey(AuthHeaders.AUTH_KEY) && !headers.containsKey(AuthHeaders.AUTH_TOKEN)) { && headers.containsKey(AuthHeaders.AUTH_KEY) && !headers.containsKey(AuthHeaders.AUTH_TOKEN)) {
return false; retry = false;
} else { } else {
closeClientButKeepContentStream(response); closeClientButKeepContentStream(response);
// This is not an authentication request returning 401 // This is not an authentication request returning 401
@ -92,12 +100,12 @@ public class RetryOnRenew implements HttpRetryHandler {
logger.debug("invalidating authentication token - first time for %s", command); logger.debug("invalidating authentication token - first time for %s", command);
retryCountMap.put(command, 1); retryCountMap.put(command, 1);
authenticationResponseCache.invalidateAll(); authenticationResponseCache.invalidateAll();
return true; retry = true;
} else { } else {
// This request has failed before // This request has failed before
if (count + 1 >= NUM_RETRIES) { if (count + 1 >= NUM_RETRIES) {
logger.debug("too many 401s - giving up after: %s for %s", count, command); logger.debug("too many 401s - giving up after: %s for %s", count, command);
return false; retry = false;
} else { } else {
// Retry just in case // Retry just in case
logger.debug("invalidating authentication token - retry %s for %s", count, command); logger.debug("invalidating authentication token - retry %s for %s", count, command);
@ -105,12 +113,15 @@ public class RetryOnRenew implements HttpRetryHandler {
// Wait between retries // Wait between retries
authenticationResponseCache.invalidateAll(); authenticationResponseCache.invalidateAll();
Uninterruptibles.sleepUninterruptibly(5, TimeUnit.SECONDS); Uninterruptibles.sleepUninterruptibly(5, TimeUnit.SECONDS);
return true; retry = true;
} }
} }
} }
break;
case 408:
return backoffHandler.shouldRetryRequest(command, response);
} }
return false; return retry;
} finally { } finally {
releasePayload(response); releasePayload(response);
} }

View File

@ -28,6 +28,7 @@ import org.jclouds.domain.Credentials;
import org.jclouds.http.HttpCommand; import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse; import org.jclouds.http.HttpResponse;
import org.jclouds.http.handlers.BackoffLimitedRetryHandler;
import org.jclouds.io.Payloads; import org.jclouds.io.Payloads;
import org.jclouds.openstack.keystone.v2_0.domain.Access; import org.jclouds.openstack.keystone.v2_0.domain.Access;
import org.testng.annotations.Test; import org.testng.annotations.Test;
@ -48,6 +49,7 @@ public class RetryOnRenewTest {
HttpResponse response = createMock(HttpResponse.class); HttpResponse response = createMock(HttpResponse.class);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
LoadingCache<Credentials, Access> cache = createMock(LoadingCache.class); LoadingCache<Credentials, Access> cache = createMock(LoadingCache.class);
BackoffLimitedRetryHandler backoffHandler = createMock(BackoffLimitedRetryHandler.class);
expect(command.getCurrentRequest()).andReturn(request); expect(command.getCurrentRequest()).andReturn(request);
@ -60,8 +62,9 @@ public class RetryOnRenewTest {
replay(command); replay(command);
replay(response); replay(response);
replay(cache); replay(cache);
replay(backoffHandler);
RetryOnRenew retry = new RetryOnRenew(cache); RetryOnRenew retry = new RetryOnRenew(cache, backoffHandler);
assertTrue(retry.shouldRetryRequest(command, response)); assertTrue(retry.shouldRetryRequest(command, response));
@ -69,6 +72,7 @@ public class RetryOnRenewTest {
verify(response); verify(response);
verify(cache); verify(cache);
} }
@Test @Test
public void test401ShouldRetry4Times() { public void test401ShouldRetry4Times() {
HttpCommand command = createMock(HttpCommand.class); HttpCommand command = createMock(HttpCommand.class);
@ -77,6 +81,7 @@ public class RetryOnRenewTest {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
LoadingCache<Credentials, Access> cache = createMock(LoadingCache.class); LoadingCache<Credentials, Access> cache = createMock(LoadingCache.class);
BackoffLimitedRetryHandler backoffHandler = createMock(BackoffLimitedRetryHandler.class);
expect(command.getCurrentRequest()).andReturn(request).anyTimes(); expect(command.getCurrentRequest()).andReturn(request).anyTimes();
expect(request.getHeaders()).andStubReturn(null); expect(request.getHeaders()).andStubReturn(null);
@ -89,7 +94,7 @@ public class RetryOnRenewTest {
replay(command, request, response, cache); replay(command, request, response, cache);
RetryOnRenew retry = new RetryOnRenew(cache); RetryOnRenew retry = new RetryOnRenew(cache, backoffHandler);
for (int i = 0; i < RetryOnRenew.NUM_RETRIES - 1; ++i) { for (int i = 0; i < RetryOnRenew.NUM_RETRIES - 1; ++i) {
assertTrue(retry.shouldRetryRequest(command, response), "Expected retry to succeed"); assertTrue(retry.shouldRetryRequest(command, response), "Expected retry to succeed");
@ -99,4 +104,32 @@ public class RetryOnRenewTest {
verify(command, response, cache); verify(command, response, cache);
} }
@Test
public void test408ShouldRetry() {
HttpCommand command = createMock(HttpCommand.class);
HttpResponse response = createMock(HttpResponse.class);
@SuppressWarnings("unchecked")
LoadingCache<Credentials, Access> cache = createMock(LoadingCache.class);
BackoffLimitedRetryHandler backoffHandler = createMock(BackoffLimitedRetryHandler.class);
expect(response.getPayload()).andReturn(Payloads.newStringPayload(
"The server has waited too long for the request to be sent by the client.")).times(2);
expect(backoffHandler.shouldRetryRequest(command, response)).andReturn(true).once();
expect(response.getStatusCode()).andReturn(408).once();
replay(command);
replay(response);
replay(cache);
replay(backoffHandler);
RetryOnRenew retry = new RetryOnRenew(cache, backoffHandler);
assertTrue(retry.shouldRetryRequest(command, response));
verify(command);
verify(response);
verify(cache);
verify(backoffHandler);
}
} }