mirror of https://github.com/apache/jclouds.git
decorating cacheloader only used in s3
This commit is contained in:
parent
45bb35971e
commit
f1819fe8b9
|
@ -18,6 +18,8 @@
|
||||||
*/
|
*/
|
||||||
package org.jclouds.s3.blobstore.config;
|
package org.jclouds.s3.blobstore.config;
|
||||||
|
|
||||||
|
import static com.google.inject.Scopes.SINGLETON;
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
@ -29,21 +31,19 @@ import org.jclouds.blobstore.attr.ConsistencyModel;
|
||||||
import org.jclouds.blobstore.config.BlobStoreMapModule;
|
import org.jclouds.blobstore.config.BlobStoreMapModule;
|
||||||
import org.jclouds.domain.Location;
|
import org.jclouds.domain.Location;
|
||||||
import org.jclouds.s3.S3AsyncClient;
|
import org.jclouds.s3.S3AsyncClient;
|
||||||
import org.jclouds.s3.S3Client;
|
|
||||||
import org.jclouds.s3.blobstore.S3AsyncBlobStore;
|
import org.jclouds.s3.blobstore.S3AsyncBlobStore;
|
||||||
import org.jclouds.s3.blobstore.S3BlobRequestSigner;
|
import org.jclouds.s3.blobstore.S3BlobRequestSigner;
|
||||||
import org.jclouds.s3.blobstore.S3BlobStore;
|
import org.jclouds.s3.blobstore.S3BlobStore;
|
||||||
import org.jclouds.s3.blobstore.S3BlobStoreContext;
|
import org.jclouds.s3.blobstore.S3BlobStoreContext;
|
||||||
import org.jclouds.s3.blobstore.functions.LocationFromBucketName;
|
import org.jclouds.s3.blobstore.functions.LocationFromBucketName;
|
||||||
|
import org.jclouds.s3.blobstore.internal.BackoffOnNotFoundWhenGetBucketACL;
|
||||||
import org.jclouds.s3.domain.AccessControlList;
|
import org.jclouds.s3.domain.AccessControlList;
|
||||||
|
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
import com.google.common.cache.CacheLoader;
|
|
||||||
import com.google.common.cache.LoadingCache;
|
import com.google.common.cache.LoadingCache;
|
||||||
import com.google.inject.AbstractModule;
|
import com.google.inject.AbstractModule;
|
||||||
import com.google.inject.Provides;
|
import com.google.inject.Provides;
|
||||||
import com.google.inject.Scopes;
|
|
||||||
import com.google.inject.TypeLiteral;
|
import com.google.inject.TypeLiteral;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -57,8 +57,8 @@ public class S3BlobStoreContextModule extends AbstractModule {
|
||||||
protected void configure() {
|
protected void configure() {
|
||||||
install(new BlobStoreMapModule());
|
install(new BlobStoreMapModule());
|
||||||
bind(ConsistencyModel.class).toInstance(ConsistencyModel.EVENTUAL);
|
bind(ConsistencyModel.class).toInstance(ConsistencyModel.EVENTUAL);
|
||||||
bind(AsyncBlobStore.class).to(S3AsyncBlobStore.class).in(Scopes.SINGLETON);
|
bind(AsyncBlobStore.class).to(S3AsyncBlobStore.class).in(SINGLETON);
|
||||||
bind(BlobStore.class).to(S3BlobStore.class).in(Scopes.SINGLETON);
|
bind(BlobStore.class).to(S3BlobStore.class).in(SINGLETON);
|
||||||
bind(new TypeLiteral<Function<String, Location>>() {
|
bind(new TypeLiteral<Function<String, Location>>() {
|
||||||
}).to(LocationFromBucketName.class);
|
}).to(LocationFromBucketName.class);
|
||||||
bindRequestSigner();
|
bindRequestSigner();
|
||||||
|
@ -68,21 +68,10 @@ public class S3BlobStoreContextModule extends AbstractModule {
|
||||||
bind(BlobRequestSigner.class).to(new TypeLiteral<S3BlobRequestSigner<S3AsyncClient>>() {
|
bind(BlobRequestSigner.class).to(new TypeLiteral<S3BlobRequestSigner<S3AsyncClient>>() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
protected LoadingCache<String, AccessControlList> bucketAcls(final S3Client client) {
|
protected LoadingCache<String, AccessControlList> bucketAcls(BackoffOnNotFoundWhenGetBucketACL loader) {
|
||||||
return CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.SECONDS).build(
|
return CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.SECONDS).build(loader);
|
||||||
new CacheLoader<String, AccessControlList>() {
|
|
||||||
@Override
|
|
||||||
public AccessControlList load(String bucketName) {
|
|
||||||
return client.getBucketACL(bucketName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "getBucketAcl()";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
/**
|
||||||
|
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. jclouds licenses this file
|
||||||
|
* to you 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://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License 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 org.jclouds.s3.blobstore.internal;
|
||||||
|
|
||||||
|
import static com.google.common.base.Throwables.propagate;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import org.jclouds.rest.ResourceNotFoundException;
|
||||||
|
import org.jclouds.s3.S3Client;
|
||||||
|
import org.jclouds.s3.domain.AccessControlList;
|
||||||
|
|
||||||
|
import com.google.common.annotations.Beta;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
|
||||||
|
|
||||||
|
@Beta
|
||||||
|
public class BackoffOnNotFoundWhenGetBucketACL extends CacheLoader<String, AccessControlList> {
|
||||||
|
private final S3Client client;
|
||||||
|
private final int maxTries = 5;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
BackoffOnNotFoundWhenGetBucketACL(S3Client client) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AccessControlList load(String bucketName) {
|
||||||
|
ResourceNotFoundException last = null;
|
||||||
|
for (int currentTries = 0; currentTries < maxTries; currentTries++) {
|
||||||
|
try {
|
||||||
|
return client.getBucketACL(bucketName);
|
||||||
|
} catch (ResourceNotFoundException e) {
|
||||||
|
imposeBackoffExponentialDelay(100l, 200l, 2, currentTries, maxTries);
|
||||||
|
last = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw last;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void imposeBackoffExponentialDelay(long period, long maxPeriod, int pow, int failureCount, int max) {
|
||||||
|
long delayMs = (long) (period * Math.pow(failureCount, pow));
|
||||||
|
delayMs = delayMs > maxPeriod ? maxPeriod : delayMs;
|
||||||
|
try {
|
||||||
|
Thread.sleep(delayMs);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw propagate(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "getBucketAcl()";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
/**
|
||||||
|
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. jclouds licenses this file
|
||||||
|
* to you 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://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License 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 org.jclouds.s3.blobstore.internal;
|
||||||
|
|
||||||
|
import static org.easymock.EasyMock.createMock;
|
||||||
|
import static org.easymock.EasyMock.expect;
|
||||||
|
import static org.easymock.EasyMock.replay;
|
||||||
|
import static org.easymock.EasyMock.verify;
|
||||||
|
import static org.testng.Assert.assertSame;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import org.jclouds.rest.ResourceNotFoundException;
|
||||||
|
import org.jclouds.s3.S3Client;
|
||||||
|
import org.jclouds.s3.domain.AccessControlList;
|
||||||
|
import org.testng.annotations.BeforeMethod;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.UncheckedExecutionException;
|
||||||
|
|
||||||
|
@Test(groups = "unit", singleThreaded = true, testName = "BackoffOnNotFoundWhenGetBucketACLTest")
|
||||||
|
public class BackoffOnNotFoundWhenGetBucketACLTest {
|
||||||
|
private S3Client mock;
|
||||||
|
|
||||||
|
@BeforeMethod
|
||||||
|
public void setUp() {
|
||||||
|
mock = createMock(S3Client.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMaxRetriesNotExceededReturnsValue() {
|
||||||
|
AccessControlList acl = createMock(AccessControlList.class);
|
||||||
|
|
||||||
|
int attempts = 5;
|
||||||
|
BackoffOnNotFoundWhenGetBucketACL backoff = new BackoffOnNotFoundWhenGetBucketACL(mock);
|
||||||
|
|
||||||
|
expect(mock.getBucketACL("foo")).andThrow(new ResourceNotFoundException()).times(attempts - 1);
|
||||||
|
expect(mock.getBucketACL("foo")).andReturn(acl);
|
||||||
|
|
||||||
|
replay(mock);
|
||||||
|
assertSame(backoff.load("foo"), acl);
|
||||||
|
verify(mock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expectedExceptions = ResourceNotFoundException.class)
|
||||||
|
void testMaxRetriesExceededThrowsException() {
|
||||||
|
int attempts = 5;
|
||||||
|
BackoffOnNotFoundWhenGetBucketACL backoff = new BackoffOnNotFoundWhenGetBucketACL(mock);
|
||||||
|
|
||||||
|
expect(mock.getBucketACL("foo")).andThrow(new ResourceNotFoundException()).times(attempts);
|
||||||
|
|
||||||
|
replay(mock);
|
||||||
|
backoff.load("foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expectedExceptions = UncheckedExecutionException.class)
|
||||||
|
void testDoesntCatchOtherExceptions() {
|
||||||
|
BackoffOnNotFoundWhenGetBucketACL backoff = new BackoffOnNotFoundWhenGetBucketACL(mock);
|
||||||
|
|
||||||
|
expect(mock.getBucketACL("foo")).andThrow(new UncheckedExecutionException(new TimeoutException()));
|
||||||
|
|
||||||
|
replay(mock);
|
||||||
|
backoff.load("foo");
|
||||||
|
verify(mock);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,58 +0,0 @@
|
||||||
/**
|
|
||||||
* Licensed to jclouds, Inc. (jclouds) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. jclouds licenses this file
|
|
||||||
* to you 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://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License 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 org.jclouds.cache;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import com.google.common.annotations.Beta;
|
|
||||||
import com.google.common.cache.CacheLoader;
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link CacheLoader} which forwards all its method calls to another {@link CacheLoader}.
|
|
||||||
* Subclasses should override one or more methods to modify the behavior of the backing cache as
|
|
||||||
* desired per the <a href="http://en.wikipedia.org/wiki/Decorator_pattern">decorator pattern</a>.
|
|
||||||
*
|
|
||||||
* @author Adrian Cole
|
|
||||||
* @since 1.5
|
|
||||||
*/
|
|
||||||
@Beta
|
|
||||||
public abstract class ForwardingCacheLoader<K, V> extends CacheLoader<K, V> {
|
|
||||||
|
|
||||||
/** Constructor for use by subclasses. */
|
|
||||||
protected ForwardingCacheLoader() {
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract CacheLoader<K, V> delegate();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public V load(K key) throws Exception {
|
|
||||||
return delegate().load(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ListenableFuture<V> reload(K key, V oldValue) throws Exception {
|
|
||||||
return delegate().reload(key, oldValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<K, V> loadAll(Iterable<? extends K> keys) throws Exception {
|
|
||||||
return delegate().loadAll(keys);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,196 +0,0 @@
|
||||||
/**
|
|
||||||
* Licensed to jclouds, Inc. (jclouds) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. jclouds licenses this file
|
|
||||||
* to you 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://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License 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 org.jclouds.cache;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
|
||||||
|
|
||||||
import org.jclouds.cache.internal.BackoffExponentiallyAndRetryOnThrowableCacheLoader;
|
|
||||||
|
|
||||||
import com.google.common.annotations.Beta;
|
|
||||||
import com.google.common.base.Objects;
|
|
||||||
import com.google.common.cache.CacheLoader;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* A decorator of {@link CacheLoader} instances having any combination of the following features:
|
|
||||||
*
|
|
||||||
* <ul>
|
|
||||||
* <li>exponential backoff based on a particular Throwable type
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* These features are all optional; cache loaders can be created using all or none of them. By
|
|
||||||
* default, the input cache loader is returned unaffected.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* Usage example:
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* @code
|
|
||||||
*
|
|
||||||
* CacheLoader<Key, Graph> loader = RetryingCacheLoaderDecorator.newDecorator()
|
|
||||||
* .on(ResourceNotFoundException.class).exponentiallyBackoff()
|
|
||||||
* .decorate(
|
|
||||||
* new CacheLoader<Key, Graph>() {
|
|
||||||
* public Graph load(Key key) throws AnyException {
|
|
||||||
* return createOnFlakeyConnection(key);
|
|
||||||
* }
|
|
||||||
* });}
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param <K>
|
|
||||||
* the base key type for all cache loaders created by this decorator
|
|
||||||
* @param <V>
|
|
||||||
* the base value type for all cache loaders created by this decorator
|
|
||||||
* @author Adrian Cole
|
|
||||||
* @since 1.5
|
|
||||||
*/
|
|
||||||
@Beta
|
|
||||||
public class RetryingCacheLoaderDecorator<K, V> {
|
|
||||||
protected RetryingCacheLoaderDecorator() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new {@code RetryingCacheLoaderDecorator} instance with default settings, and no
|
|
||||||
* retrying any kind.
|
|
||||||
*/
|
|
||||||
// we can resolve generic type during decorate, as opposed to here
|
|
||||||
public static RetryingCacheLoaderDecorator<Object, Object> newDecorator() {
|
|
||||||
return new RetryingCacheLoaderDecorator<Object, Object>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines the action to carry out on a particular throwable.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public OnThrowableBuilder<K, V> on(Class<? extends Throwable> retryableThrowable) {
|
|
||||||
return new OnThrowableBuilder<K, V>(retryableThrowable);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class OnThrowableBuilder<K, V> {
|
|
||||||
private Class<? extends Throwable> retryableThrowable;
|
|
||||||
|
|
||||||
private OnThrowableBuilder(Class<? extends Throwable> retryableThrowable) {
|
|
||||||
this.retryableThrowable = checkNotNull(retryableThrowable, "retryableThrowable");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For each attempt, exponentially backoff
|
|
||||||
*/
|
|
||||||
public BackoffExponentiallyAndRetryOnThrowableCacheLoaderDecorator<K, V> exponentiallyBackoff() {
|
|
||||||
return new BackoffExponentiallyAndRetryOnThrowableCacheLoaderDecorator<K, V>(retryableThrowable);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class BackoffExponentiallyAndRetryOnThrowableCacheLoaderDecorator<K, V> extends
|
|
||||||
RetryingCacheLoaderDecorator<K, V> {
|
|
||||||
private long periodMs = 100l;
|
|
||||||
private long maxPeriodMs = 200l;
|
|
||||||
private int maxTries = 5;
|
|
||||||
private final Class<? extends Throwable> retryableThrowable;
|
|
||||||
|
|
||||||
private BackoffExponentiallyAndRetryOnThrowableCacheLoaderDecorator(Class<? extends Throwable> retryableThrowable) {
|
|
||||||
this.retryableThrowable = checkNotNull(retryableThrowable, "retryableThrowable");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The initial period in milliseconds to delay between tries. with each try this period will
|
|
||||||
* increase exponentially.
|
|
||||||
* <p/>
|
|
||||||
* default: {@code 100}
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public BackoffExponentiallyAndRetryOnThrowableCacheLoaderDecorator<K, V> periodMs(long periodMs) {
|
|
||||||
checkArgument(periodMs > 1, "maxTries must be positive: %d", periodMs);
|
|
||||||
this.periodMs = periodMs;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The initial period in milliseconds to delay between tries. with each try this period will
|
|
||||||
* increase exponentially.
|
|
||||||
* <p/>
|
|
||||||
* default: {@code 200}
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public BackoffExponentiallyAndRetryOnThrowableCacheLoaderDecorator<K, V> maxPeriodMs(long maxPeriodMs) {
|
|
||||||
checkArgument(maxPeriodMs > periodMs, "maxPeriodMs must be equal to or greater than periodMs: %d %d",
|
|
||||||
maxPeriodMs, periodMs);
|
|
||||||
this.maxPeriodMs = maxPeriodMs;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The maximum attempts to try on the given exception type
|
|
||||||
* <p/>
|
|
||||||
* default: {@code 5}
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public BackoffExponentiallyAndRetryOnThrowableCacheLoaderDecorator<K, V> maxTries(int maxTries) {
|
|
||||||
checkArgument(maxTries > 1, "maxTries must be more than one: %d", maxTries);
|
|
||||||
this.maxTries = maxTries;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <K1 extends K, V1 extends V> CacheLoader<K1, V1> decorate(CacheLoader<K1, V1> loader) {
|
|
||||||
return new BackoffExponentiallyAndRetryOnThrowableCacheLoader<K1, V1>(retryableThrowable, periodMs,
|
|
||||||
maxPeriodMs, maxTries, super.decorate(loader));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Objects.ToStringHelper string() {
|
|
||||||
return Objects.toStringHelper(this).add("retryableThrowable", retryableThrowable).add("periodMs", periodMs).add("maxPeriodMs",
|
|
||||||
maxPeriodMs).add("maxTries", maxTries);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decorates a cacheloader, or returns the same value, if no retrying features were requested.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* This method does not alter the state of this {@code RetryingCacheLoaderDecorator} instance, so
|
|
||||||
* it can be invoked again to create multiple independent cache loaders.
|
|
||||||
*
|
|
||||||
* @param loader
|
|
||||||
* the cache loader used to obtain new values
|
|
||||||
* @return a cache loader having the requested features
|
|
||||||
*/
|
|
||||||
public <K1 extends K, V1 extends V> CacheLoader<K1, V1> decorate(CacheLoader<K1, V1> loader) {
|
|
||||||
return (CacheLoader<K1, V1>) loader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string representation for this RetryingCacheLoaderDecorator instance. The exact form
|
|
||||||
* of the returned string is not specified.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return string().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* append any state that should be considered in {@link #toString} here.
|
|
||||||
*/
|
|
||||||
protected Objects.ToStringHelper string() {
|
|
||||||
return Objects.toStringHelper(this);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,139 +0,0 @@
|
||||||
/**
|
|
||||||
* Licensed to jclouds, Inc. (jclouds) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. jclouds licenses this file
|
|
||||||
* to you 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://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License 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 org.jclouds.cache.internal;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
import org.jclouds.cache.ForwardingCacheLoader;
|
|
||||||
import org.jclouds.util.Throwables2;
|
|
||||||
|
|
||||||
import com.google.common.annotations.Beta;
|
|
||||||
import com.google.common.cache.CacheLoader;
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exponentially backs off, if we encounter an exception of the given type during any of the
|
|
||||||
* following methods:
|
|
||||||
* <ul>
|
|
||||||
* <li>load
|
|
||||||
* <li>reload
|
|
||||||
* <li>loadAll
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* @param <K>
|
|
||||||
* the key type of the cache loader
|
|
||||||
* @param <V>
|
|
||||||
* the value type of the cache loader
|
|
||||||
* @author Adrian Cole
|
|
||||||
* @since 1.5
|
|
||||||
*/
|
|
||||||
@Beta
|
|
||||||
public class BackoffExponentiallyAndRetryOnThrowableCacheLoader<K, V> extends ForwardingCacheLoader<K, V> {
|
|
||||||
private final Class<? extends Throwable> retryableThrowable;
|
|
||||||
private final long periodMs;
|
|
||||||
private final long maxPeriodMs;
|
|
||||||
private final int maxTries;
|
|
||||||
private final CacheLoader<K, V> loader;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param retryableThrowable
|
|
||||||
* the exception which we can retry
|
|
||||||
* @param periodMs
|
|
||||||
* initial period, which exponentially increases with each try, specified in
|
|
||||||
* milliseconds
|
|
||||||
* @param maxPeriodMs
|
|
||||||
* maximum period duration, specified in milliseconds
|
|
||||||
* @param maxTries
|
|
||||||
* maximum amount of tries
|
|
||||||
* @param loader
|
|
||||||
* the loader we are able to retry
|
|
||||||
*/
|
|
||||||
public BackoffExponentiallyAndRetryOnThrowableCacheLoader(Class<? extends Throwable> retryableThrowable, long periodMs,
|
|
||||||
long maxPeriodMs, int maxTries, CacheLoader<K, V> loader) {
|
|
||||||
this.retryableThrowable = checkNotNull(retryableThrowable, "retryableThrowable");
|
|
||||||
checkArgument(maxTries > 1, "maxTries must be more than one: %d", maxTries);
|
|
||||||
this.maxTries = maxTries;
|
|
||||||
checkArgument(periodMs > 0, "periodMs must be positive: %d", periodMs);
|
|
||||||
this.periodMs = periodMs;
|
|
||||||
checkArgument(maxPeriodMs > periodMs, "maxPeriodMs must be equal to or greater than periodMs: %d %d",
|
|
||||||
maxPeriodMs, periodMs);
|
|
||||||
this.maxPeriodMs = maxPeriodMs;
|
|
||||||
this.loader = checkNotNull(loader, "loader");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected CacheLoader<K, V> delegate() {
|
|
||||||
return loader;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: refactor into a better closure in java pattern, if one exists
|
|
||||||
@Override
|
|
||||||
public V load(final K key) throws Exception {
|
|
||||||
return backoffExponentiallyAndRetryOnThrowable(new Callable<V>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public V call() throws Exception {
|
|
||||||
try {
|
|
||||||
return BackoffExponentiallyAndRetryOnThrowableCacheLoader.super.load(key);
|
|
||||||
} catch (Exception e) {
|
|
||||||
TimeoutException te = Throwables2.getFirstThrowableOfType(e,
|
|
||||||
TimeoutException.class);
|
|
||||||
if (te != null) {
|
|
||||||
throw te;
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ListenableFuture<V> reload(final K key, final V oldValue) throws Exception {
|
|
||||||
return backoffExponentiallyAndRetryOnThrowable(new Callable<ListenableFuture<V>>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ListenableFuture<V> call() throws Exception {
|
|
||||||
return BackoffExponentiallyAndRetryOnThrowableCacheLoader.super.reload(key, oldValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<K, V> loadAll(final Iterable<? extends K> keys) throws Exception {
|
|
||||||
return backoffExponentiallyAndRetryOnThrowable(new Callable<Map<K, V>>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<K, V> call() throws Exception {
|
|
||||||
return BackoffExponentiallyAndRetryOnThrowableCacheLoader.super.loadAll(keys);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T> T backoffExponentiallyAndRetryOnThrowable(Callable<T> callable) throws Exception {
|
|
||||||
return new BackoffExponentiallyAndRetryOnThrowableCallable<T>(retryableThrowable, periodMs, maxPeriodMs,
|
|
||||||
maxTries, callable).call();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,99 +0,0 @@
|
||||||
/**
|
|
||||||
* Licensed to jclouds, Inc. (jclouds) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. jclouds licenses this file
|
|
||||||
* to you 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://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License 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 org.jclouds.cache.internal;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
|
||||||
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
|
|
||||||
import org.jclouds.util.Throwables2;
|
|
||||||
|
|
||||||
import com.google.common.annotations.Beta;
|
|
||||||
import com.google.common.base.Objects;
|
|
||||||
import com.google.common.base.Throwables;
|
|
||||||
import com.google.common.collect.ForwardingObject;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exponentially backs off, if we encounter an exception of the given type during
|
|
||||||
* {@link Callable#call}
|
|
||||||
*
|
|
||||||
* @author Adrian Cole
|
|
||||||
* @since 1.5
|
|
||||||
*/
|
|
||||||
@Beta
|
|
||||||
class BackoffExponentiallyAndRetryOnThrowableCallable<T> extends ForwardingObject implements Callable<T> {
|
|
||||||
private final Class<? extends Throwable> retryableThrowable;
|
|
||||||
private final long periodMs;
|
|
||||||
private final long maxPeriodMs;
|
|
||||||
private final int maxTries;
|
|
||||||
private final Callable<T> callable;
|
|
||||||
|
|
||||||
BackoffExponentiallyAndRetryOnThrowableCallable(Class<? extends Throwable> retryableThrowable, long periodMs,
|
|
||||||
long maxPeriodMs, int maxTries, Callable<T> callable) {
|
|
||||||
this.retryableThrowable = checkNotNull(retryableThrowable, "retryableThrowable");
|
|
||||||
checkArgument(maxTries > 1, "maxTries must be more than one: %d", maxTries);
|
|
||||||
this.maxTries = maxTries;
|
|
||||||
checkArgument(periodMs > 0, "periodMs must be positive: %d", periodMs);
|
|
||||||
this.periodMs = periodMs;
|
|
||||||
checkArgument(maxPeriodMs > periodMs, "maxPeriodMs must be equal to or greater than periodMs: %d %d",
|
|
||||||
maxPeriodMs, periodMs);
|
|
||||||
this.maxPeriodMs = maxPeriodMs;
|
|
||||||
this.callable = checkNotNull(callable, "callable");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Callable<T> delegate() {
|
|
||||||
return callable;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public T call() throws Exception {
|
|
||||||
Exception currentException = null;
|
|
||||||
for (int currentTries = 0; currentTries < maxTries; currentTries++) {
|
|
||||||
try {
|
|
||||||
return delegate().call();
|
|
||||||
} catch (Exception e) {
|
|
||||||
currentException = e;
|
|
||||||
if (Throwables2.getFirstThrowableOfType(e, retryableThrowable) != null) {
|
|
||||||
imposeBackoffExponentialDelay(periodMs, maxPeriodMs, 2, currentTries, maxTries);
|
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw currentException;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void imposeBackoffExponentialDelay(long period, long maxPeriod, int pow, int failureCount, int max) {
|
|
||||||
long delayMs = (long) (period * Math.pow(failureCount, pow));
|
|
||||||
delayMs = delayMs > maxPeriod ? maxPeriod : delayMs;
|
|
||||||
try {
|
|
||||||
Thread.sleep(delayMs);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Throwables.propagate(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return Objects.toStringHelper("").add("retryableThrowable", retryableThrowable).add("periodMs", periodMs).add(
|
|
||||||
"maxPeriodMs", maxPeriodMs).add("maxTries", maxTries).add("callable", callable).toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
/**
|
|
||||||
* Licensed to jclouds, Inc. (jclouds) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. jclouds licenses this file
|
|
||||||
* to you 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://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License 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 org.jclouds.cache;
|
|
||||||
|
|
||||||
import static org.easymock.EasyMock.createMockBuilder;
|
|
||||||
import static org.easymock.EasyMock.expect;
|
|
||||||
import static org.easymock.EasyMock.replay;
|
|
||||||
import static org.easymock.EasyMock.verify;
|
|
||||||
import static org.testng.Assert.assertEquals;
|
|
||||||
import static org.testng.Assert.assertSame;
|
|
||||||
|
|
||||||
import org.testng.annotations.BeforeMethod;
|
|
||||||
import org.testng.annotations.Test;
|
|
||||||
|
|
||||||
import com.google.common.cache.CacheLoader;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import com.google.common.util.concurrent.Futures;
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unit test for {@link ForwardingCacheLoader}.
|
|
||||||
*
|
|
||||||
* @author Adrian Cole
|
|
||||||
*/
|
|
||||||
@Test(testName = "ForwardingCacheLoaderTest", singleThreaded = true)
|
|
||||||
public class ForwardingCacheLoaderTest {
|
|
||||||
private CacheLoader<String, Boolean> forward;
|
|
||||||
private CacheLoader<String, Boolean> mock;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@BeforeMethod
|
|
||||||
public void setUp() {
|
|
||||||
// add mocked methods for default forwarded ones
|
|
||||||
mock = createMockBuilder(CacheLoader.class).addMockedMethods("loadAll", "reload").createMock();
|
|
||||||
forward = new ForwardingCacheLoader<String, Boolean>() {
|
|
||||||
@Override
|
|
||||||
protected CacheLoader<String, Boolean> delegate() {
|
|
||||||
return mock;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testLoad() throws Exception {
|
|
||||||
expect(mock.load("key")).andReturn(Boolean.TRUE);
|
|
||||||
replay(mock);
|
|
||||||
assertSame(Boolean.TRUE, forward.load("key"));
|
|
||||||
verify(mock);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReload() throws Exception {
|
|
||||||
ListenableFuture<Boolean> trueF = Futures.immediateFuture(true);
|
|
||||||
expect(mock.reload("key", false)).andReturn(trueF);
|
|
||||||
replay(mock);
|
|
||||||
assertSame(forward.reload("key", false), trueF);
|
|
||||||
verify(mock);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testLoadAll() throws Exception {
|
|
||||||
expect(mock.loadAll(ImmutableList.of("key"))).andReturn(ImmutableMap.of("key", Boolean.TRUE));
|
|
||||||
replay(mock);
|
|
||||||
assertEquals(ImmutableMap.of("key", Boolean.TRUE), forward.loadAll(ImmutableList.of("key")));
|
|
||||||
verify(mock);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,85 +0,0 @@
|
||||||
/**
|
|
||||||
* Licensed to jclouds, Inc. (jclouds) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. jclouds licenses this file
|
|
||||||
* to you 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://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License 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 org.jclouds.cache;
|
|
||||||
|
|
||||||
import static org.easymock.EasyMock.createMockBuilder;
|
|
||||||
import static org.easymock.EasyMock.expect;
|
|
||||||
import static org.easymock.EasyMock.replay;
|
|
||||||
import static org.easymock.EasyMock.verify;
|
|
||||||
import static org.testng.Assert.assertSame;
|
|
||||||
import static org.testng.Assert.fail;
|
|
||||||
|
|
||||||
import org.jclouds.rest.ResourceNotFoundException;
|
|
||||||
import org.testng.annotations.BeforeMethod;
|
|
||||||
import org.testng.annotations.Test;
|
|
||||||
|
|
||||||
import com.google.common.cache.CacheLoader;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unit tests for RetryingCacheLoaderDecorator.
|
|
||||||
*
|
|
||||||
* @author Adrian Cole
|
|
||||||
*/
|
|
||||||
@Test(testName = "ForwardingCacheLoaderTest", singleThreaded = true)
|
|
||||||
public class RetryingCacheLoaderDecoratorTest {
|
|
||||||
private CacheLoader<String, Boolean> mock;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@BeforeMethod
|
|
||||||
public void setUp() {
|
|
||||||
// add mocked methods for default forwarded ones
|
|
||||||
mock = createMockBuilder(CacheLoader.class).addMockedMethods("loadAll", "reload").createMock();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testNewDecoratorDecorateSameWhenNoParams() throws Exception {
|
|
||||||
assertSame(mock, RetryingCacheLoaderDecorator.newDecorator().decorate(mock));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testDefaultMaxTriesIs5() throws Exception {
|
|
||||||
CacheLoader<String, Boolean> backoff = RetryingCacheLoaderDecorator.newDecorator().on(
|
|
||||||
ResourceNotFoundException.class).exponentiallyBackoff().decorate(mock);
|
|
||||||
|
|
||||||
expect(mock.load("foo")).andThrow(new ResourceNotFoundException()).times(4);
|
|
||||||
expect(mock.load("foo")).andReturn(Boolean.TRUE);
|
|
||||||
|
|
||||||
replay(mock);
|
|
||||||
assertSame(backoff.load("foo"), Boolean.TRUE);
|
|
||||||
verify(mock);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testMaxRetriesExceededThrowsException() throws Exception {
|
|
||||||
CacheLoader<String, Boolean> backoff = RetryingCacheLoaderDecorator.newDecorator()
|
|
||||||
.on(ResourceNotFoundException.class).exponentiallyBackoff()
|
|
||||||
.decorate(mock);
|
|
||||||
|
|
||||||
expect(mock.load("foo")).andThrow(new ResourceNotFoundException()).times(5);
|
|
||||||
|
|
||||||
replay(mock);
|
|
||||||
try {
|
|
||||||
backoff.load("foo");
|
|
||||||
fail();
|
|
||||||
} catch (ResourceNotFoundException e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
verify(mock);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
/**
|
|
||||||
* Licensed to jclouds, Inc. (jclouds) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. jclouds licenses this file
|
|
||||||
* to you 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://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License 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 org.jclouds.cache.internal;
|
|
||||||
|
|
||||||
import static org.easymock.EasyMock.createMockBuilder;
|
|
||||||
import static org.easymock.EasyMock.expect;
|
|
||||||
import static org.easymock.EasyMock.replay;
|
|
||||||
import static org.easymock.EasyMock.verify;
|
|
||||||
import static org.testng.Assert.assertSame;
|
|
||||||
import static org.testng.Assert.fail;
|
|
||||||
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
import org.jclouds.rest.ResourceNotFoundException;
|
|
||||||
import org.testng.annotations.BeforeMethod;
|
|
||||||
import org.testng.annotations.Test;
|
|
||||||
|
|
||||||
import com.google.common.cache.CacheLoader;
|
|
||||||
|
|
||||||
@Test(groups = "unit", singleThreaded = true, testName = "BackoffExponentiallyAndRetryOnThrowableCacheLoaderTest")
|
|
||||||
public class BackoffExponentiallyAndRetryOnThrowableCacheLoaderTest {
|
|
||||||
private CacheLoader<String, Boolean> mock;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@BeforeMethod
|
|
||||||
public void setUp() {
|
|
||||||
// add mocked methods for default forwarded ones
|
|
||||||
mock = createMockBuilder(CacheLoader.class).addMockedMethods("loadAll", "reload").createMock();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testMaxRetriesNotExceededReturnsValue() throws Exception {
|
|
||||||
int attempts = 3;
|
|
||||||
BackoffExponentiallyAndRetryOnThrowableCacheLoader<String, Boolean> backoff = new BackoffExponentiallyAndRetryOnThrowableCacheLoader<String, Boolean>(
|
|
||||||
ResourceNotFoundException.class, 50l, 500l, attempts, mock);
|
|
||||||
|
|
||||||
expect(mock.load("foo")).andThrow(new ResourceNotFoundException()).times(attempts - 1);
|
|
||||||
expect(mock.load("foo")).andReturn(Boolean.TRUE);
|
|
||||||
|
|
||||||
replay(mock);
|
|
||||||
assertSame(backoff.load("foo"), Boolean.TRUE);
|
|
||||||
verify(mock);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testMaxRetriesExceededThrowsException() throws Exception {
|
|
||||||
int attempts = 3;
|
|
||||||
BackoffExponentiallyAndRetryOnThrowableCacheLoader<String, Boolean> backoff = new BackoffExponentiallyAndRetryOnThrowableCacheLoader<String, Boolean>(
|
|
||||||
ResourceNotFoundException.class, 50l, 500l, attempts, mock);
|
|
||||||
|
|
||||||
expect(mock.load("foo")).andThrow(new ResourceNotFoundException()).times(attempts);
|
|
||||||
|
|
||||||
replay(mock);
|
|
||||||
try {
|
|
||||||
backoff.load("foo");
|
|
||||||
fail();
|
|
||||||
} catch (ResourceNotFoundException e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
verify(mock);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testThrowsTimeoutException() throws Exception {
|
|
||||||
int attempts = 3;
|
|
||||||
BackoffExponentiallyAndRetryOnThrowableCacheLoader<String, Boolean> backoff = new BackoffExponentiallyAndRetryOnThrowableCacheLoader<String, Boolean>(
|
|
||||||
ResourceNotFoundException.class, 50l, 500l, attempts, mock);
|
|
||||||
|
|
||||||
expect(mock.load("foo")).andThrow(new ExecutionException(new TimeoutException()));
|
|
||||||
|
|
||||||
replay(mock);
|
|
||||||
try {
|
|
||||||
backoff.load("foo");
|
|
||||||
fail();
|
|
||||||
} catch (TimeoutException e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
verify(mock);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,75 +0,0 @@
|
||||||
/**
|
|
||||||
* Licensed to jclouds, Inc. (jclouds) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. jclouds licenses this file
|
|
||||||
* to you 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://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License 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 org.jclouds.cache.internal;
|
|
||||||
|
|
||||||
import static org.easymock.EasyMock.createMock;
|
|
||||||
import static org.easymock.EasyMock.expect;
|
|
||||||
import static org.easymock.EasyMock.replay;
|
|
||||||
import static org.easymock.EasyMock.verify;
|
|
||||||
import static org.testng.Assert.assertEquals;
|
|
||||||
import static org.testng.Assert.fail;
|
|
||||||
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
|
|
||||||
import org.jclouds.rest.ResourceNotFoundException;
|
|
||||||
import org.testng.annotations.BeforeMethod;
|
|
||||||
import org.testng.annotations.Test;
|
|
||||||
|
|
||||||
@Test(groups = "unit", testName = "BackoffExponentiallyAndRetryOnThrowableCallableTest", singleThreaded = true)
|
|
||||||
public class BackoffExponentiallyAndRetryOnThrowableCallableTest {
|
|
||||||
private Callable<String> mock;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@BeforeMethod
|
|
||||||
public void setUp() {
|
|
||||||
mock = createMock(Callable.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testMaxRetriesNotExceededReturnsValue() throws Exception {
|
|
||||||
int attempts = 3;
|
|
||||||
BackoffExponentiallyAndRetryOnThrowableCallable<String> backoff = new BackoffExponentiallyAndRetryOnThrowableCallable<String>(
|
|
||||||
ResourceNotFoundException.class, 50l, 500l, attempts, mock);
|
|
||||||
|
|
||||||
expect(mock.call()).andThrow(new ResourceNotFoundException()).times(attempts - 1);
|
|
||||||
expect(mock.call()).andReturn("foo");
|
|
||||||
|
|
||||||
replay(mock);
|
|
||||||
assertEquals(backoff.call(), "foo");
|
|
||||||
verify(mock);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testMaxRetriesExceededThrowsException() throws Exception {
|
|
||||||
int attempts = 3;
|
|
||||||
BackoffExponentiallyAndRetryOnThrowableCallable<String> backoff = new BackoffExponentiallyAndRetryOnThrowableCallable<String>(
|
|
||||||
ResourceNotFoundException.class, 50l, 500l, attempts, mock);
|
|
||||||
|
|
||||||
expect(mock.call()).andThrow(new ResourceNotFoundException()).times(attempts);
|
|
||||||
|
|
||||||
replay(mock);
|
|
||||||
try {
|
|
||||||
backoff.call();
|
|
||||||
fail();
|
|
||||||
} catch (ResourceNotFoundException e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
verify(mock);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,8 +18,6 @@
|
||||||
*/
|
*/
|
||||||
package org.jclouds.aws.s3.blobstore.config;
|
package org.jclouds.aws.s3.blobstore.config;
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.jclouds.aws.s3.AWSS3AsyncClient;
|
import org.jclouds.aws.s3.AWSS3AsyncClient;
|
||||||
import org.jclouds.aws.s3.blobstore.AWSS3AsyncBlobStore;
|
import org.jclouds.aws.s3.blobstore.AWSS3AsyncBlobStore;
|
||||||
import org.jclouds.aws.s3.blobstore.AWSS3BlobStore;
|
import org.jclouds.aws.s3.blobstore.AWSS3BlobStore;
|
||||||
|
@ -28,18 +26,11 @@ import org.jclouds.aws.s3.blobstore.strategy.MultipartUploadStrategy;
|
||||||
import org.jclouds.aws.s3.blobstore.strategy.internal.ParallelMultipartUploadStrategy;
|
import org.jclouds.aws.s3.blobstore.strategy.internal.ParallelMultipartUploadStrategy;
|
||||||
import org.jclouds.aws.s3.blobstore.strategy.internal.SequentialMultipartUploadStrategy;
|
import org.jclouds.aws.s3.blobstore.strategy.internal.SequentialMultipartUploadStrategy;
|
||||||
import org.jclouds.blobstore.BlobRequestSigner;
|
import org.jclouds.blobstore.BlobRequestSigner;
|
||||||
import org.jclouds.cache.RetryingCacheLoaderDecorator;
|
|
||||||
import org.jclouds.rest.ResourceNotFoundException;
|
|
||||||
import org.jclouds.s3.S3Client;
|
|
||||||
import org.jclouds.s3.blobstore.S3AsyncBlobStore;
|
import org.jclouds.s3.blobstore.S3AsyncBlobStore;
|
||||||
import org.jclouds.s3.blobstore.S3BlobRequestSigner;
|
import org.jclouds.s3.blobstore.S3BlobRequestSigner;
|
||||||
import org.jclouds.s3.blobstore.S3BlobStore;
|
import org.jclouds.s3.blobstore.S3BlobStore;
|
||||||
import org.jclouds.s3.blobstore.config.S3BlobStoreContextModule;
|
import org.jclouds.s3.blobstore.config.S3BlobStoreContextModule;
|
||||||
import org.jclouds.s3.domain.AccessControlList;
|
|
||||||
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
|
||||||
import com.google.common.cache.CacheLoader;
|
|
||||||
import com.google.common.cache.LoadingCache;
|
|
||||||
import com.google.inject.Scopes;
|
import com.google.inject.Scopes;
|
||||||
import com.google.inject.TypeLiteral;
|
import com.google.inject.TypeLiteral;
|
||||||
|
|
||||||
|
@ -64,23 +55,4 @@ public class AWSS3BlobStoreContextModule extends S3BlobStoreContextModule {
|
||||||
bind(BlobRequestSigner.class).to(new TypeLiteral<S3BlobRequestSigner<AWSS3AsyncClient>>() {
|
bind(BlobRequestSigner.class).to(new TypeLiteral<S3BlobRequestSigner<AWSS3AsyncClient>>() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected LoadingCache<String, AccessControlList> bucketAcls(final S3Client client) {
|
|
||||||
CacheLoader<String, AccessControlList> loader = RetryingCacheLoaderDecorator.newDecorator()
|
|
||||||
.on(ResourceNotFoundException.class).exponentiallyBackoff()
|
|
||||||
.decorate(
|
|
||||||
new CacheLoader<String, AccessControlList>() {
|
|
||||||
@Override
|
|
||||||
public AccessControlList load(String bucketName) {
|
|
||||||
return client.getBucketACL(bucketName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "getBucketAcl()";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.SECONDS).build(loader);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue