From 2ff68fc79ebca23a6a0cf60c80b8d307440e4b67 Mon Sep 17 00:00:00 2001 From: Andrew Gaul Date: Tue, 14 Jun 2016 21:35:19 -0700 Subject: [PATCH] JCLOUDS-1005: Retry on 500 and 503 errors This requires rewriting the URL for b2_upload_file and b2_upload_part requests. --- .../java/org/jclouds/b2/B2ApiMetadata.java | 2 + .../jclouds/b2/config/B2HttpApiModule.java | 7 ++ .../jclouds/b2/filters/B2RetryHandler.java | 90 +++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 providers/b2/src/main/java/org/jclouds/b2/filters/B2RetryHandler.java diff --git a/providers/b2/src/main/java/org/jclouds/b2/B2ApiMetadata.java b/providers/b2/src/main/java/org/jclouds/b2/B2ApiMetadata.java index ef2e715b09..b5aecaa894 100644 --- a/providers/b2/src/main/java/org/jclouds/b2/B2ApiMetadata.java +++ b/providers/b2/src/main/java/org/jclouds/b2/B2ApiMetadata.java @@ -48,6 +48,8 @@ public final class B2ApiMetadata extends BaseHttpApiMetadata { public static Properties defaultProperties() { Properties properties = BaseHttpApiMetadata.defaultProperties(); properties.setProperty(Constants.PROPERTY_SESSION_INTERVAL, String.valueOf(TimeUnit.HOURS.toSeconds(1))); + properties.setProperty(Constants.PROPERTY_IDEMPOTENT_METHODS, "DELETE,GET,HEAD,OPTIONS,POST,PUT"); + properties.setProperty(Constants.PROPERTY_RETRY_DELAY_START, String.valueOf(TimeUnit.SECONDS.toMillis(1))); return properties; } diff --git a/providers/b2/src/main/java/org/jclouds/b2/config/B2HttpApiModule.java b/providers/b2/src/main/java/org/jclouds/b2/config/B2HttpApiModule.java index 11d862440a..a30f9a428e 100644 --- a/providers/b2/src/main/java/org/jclouds/b2/config/B2HttpApiModule.java +++ b/providers/b2/src/main/java/org/jclouds/b2/config/B2HttpApiModule.java @@ -26,9 +26,11 @@ import org.jclouds.Constants; import org.jclouds.collect.Memoized; import org.jclouds.b2.B2Api; import org.jclouds.b2.domain.Authorization; +import org.jclouds.b2.filters.B2RetryHandler; import org.jclouds.b2.filters.RequestAuthorization; import org.jclouds.b2.handlers.ParseB2ErrorFromJsonContent; import org.jclouds.http.HttpErrorHandler; +import org.jclouds.http.HttpRetryHandler; import org.jclouds.http.annotation.ClientError; import org.jclouds.http.annotation.Redirection; import org.jclouds.http.annotation.ServerError; @@ -57,6 +59,11 @@ public final class B2HttpApiModule extends HttpApiModule { bind(HttpErrorHandler.class).annotatedWith(ServerError.class).to(ParseB2ErrorFromJsonContent.class); } + @Override + protected void bindRetryHandlers() { + bind(HttpRetryHandler.class).annotatedWith(ServerError.class).to(B2RetryHandler.class); + } + @Provides @Singleton static Supplier provideAuthorizationSupplier(final B2Api b2Api) { diff --git a/providers/b2/src/main/java/org/jclouds/b2/filters/B2RetryHandler.java b/providers/b2/src/main/java/org/jclouds/b2/filters/B2RetryHandler.java new file mode 100644 index 0000000000..e58d712bbe --- /dev/null +++ b/providers/b2/src/main/java/org/jclouds/b2/filters/B2RetryHandler.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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.b2.filters; + +import static org.jclouds.http.HttpUtils.closeClientButKeepContentStream; +import static org.jclouds.http.HttpUtils.releasePayload; + +import javax.annotation.Resource; +import javax.inject.Inject; + +import org.jclouds.b2.B2Api; +import org.jclouds.b2.domain.GetUploadPartResponse; +import org.jclouds.b2.domain.UploadUrlResponse; +import org.jclouds.http.HttpCommand; +import org.jclouds.http.HttpException; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpRequestFilter; +import org.jclouds.http.HttpResponse; +import org.jclouds.http.handlers.BackoffLimitedRetryHandler; +import org.jclouds.logging.Logger; + +import com.google.common.net.HttpHeaders; +import com.google.inject.Singleton; + +@Singleton +public final class B2RetryHandler extends BackoffLimitedRetryHandler implements HttpRequestFilter { + private final B2Api api; + + @Resource + private Logger logger = Logger.NULL; + + @Inject + B2RetryHandler(B2Api api) { + this.api = api; + } + + @Override + public HttpRequest filter(HttpRequest request) throws HttpException { + HttpRequest.Builder builder = request.toBuilder(); + + // B2 requires retrying on a different storage node for uploads + String path = request.getEndpoint().getPath(); + if (path.startsWith("/b2api/v1/b2_upload_file")) { + String bucketId = path.split("/")[4]; + UploadUrlResponse uploadUrl = api.getObjectApi().getUploadUrl(bucketId); + builder.endpoint(uploadUrl.uploadUrl()) + .replaceHeader(HttpHeaders.AUTHORIZATION, uploadUrl.authorizationToken()); + } else if (path.startsWith("/b2api/v1/b2_upload_part")) { + String fileId = path.split("/")[4]; + GetUploadPartResponse uploadUrl = api.getMultipartApi().getUploadPartUrl(fileId); + builder.endpoint(uploadUrl.uploadUrl()) + .replaceHeader(HttpHeaders.AUTHORIZATION, uploadUrl.authorizationToken()); + } + + return builder.build(); + } + + @Override + public boolean shouldRetryRequest(HttpCommand command, HttpResponse response) { + boolean retry = false; + try { + byte[] data = closeClientButKeepContentStream(response); + switch (response.getStatusCode()) { + case 500: + case 503: + retry = super.shouldRetryRequest(command, response); + break; + default: + break; + } + } finally { + releasePayload(response); + } + return retry; + } +}