From 70b01b748bb41208c2ec7a82e1cc30b421681cbc Mon Sep 17 00:00:00 2001 From: Timur Sadykov Date: Tue, 28 Sep 2021 12:50:32 -0700 Subject: [PATCH 01/34] feat: update retries and implement Retryable --- .../java/com/google/auth/Credentials.java | 9 +- .../com/google/auth/GoogleAuthException.java | 114 ++++++++++++++++++ .../java/com/google/auth/RetryStatus.java | 44 +++++++ .../java/com/google/auth/Retryable.java | 37 ++++++ credentials/pom.xml | 10 ++ oauth2_http/pom.xml | 2 + 6 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 credentials/java/com/google/auth/GoogleAuthException.java create mode 100644 credentials/java/com/google/auth/RetryStatus.java create mode 100644 credentials/java/com/google/auth/Retryable.java diff --git a/credentials/java/com/google/auth/Credentials.java b/credentials/java/com/google/auth/Credentials.java index c52303a48..806dda157 100644 --- a/credentials/java/com/google/auth/Credentials.java +++ b/credentials/java/com/google/auth/Credentials.java @@ -38,6 +38,8 @@ import java.util.Map; import java.util.concurrent.Executor; +import com.google.api.client.http.HttpResponseException; + /** Represents an abstract authorized identity instance. */ public abstract class Credentials implements Serializable { @@ -111,7 +113,12 @@ protected final void blockingGetToCallback(URI uri, RequestMetadataCallback call try { result = getRequestMetadata(uri); } catch (Throwable e) { - callback.onFailure(e); + if (e instanceof HttpResponseException) { + HttpResponseException httpException = (HttpResponseException) e; + callback.onFailure(new GoogleAuthException(httpException.getStatusCode(), httpException.getRetryCount(), e)); + } + + callback.onFailure(new GoogleAuthException(e)); return; } callback.onSuccess(result); diff --git a/credentials/java/com/google/auth/GoogleAuthException.java b/credentials/java/com/google/auth/GoogleAuthException.java new file mode 100644 index 000000000..5678757f2 --- /dev/null +++ b/credentials/java/com/google/auth/GoogleAuthException.java @@ -0,0 +1,114 @@ +/* + * Copyright 2021 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.auth; + +import java.io.IOException; + +/** + * Base class for the standard Auth error response. + * It extends a default exception while keeping Json response format + */ +public class GoogleAuthException extends IOException implements Retryable { + + private final RetryStatus retryStatus; + + /** + * Constructor with all parameters + * + * @param responseStatus + * A response status from the related HTTP request + * + * @param retryCount + * A number of retries performed + * + * @param message + * The detail message (which is saved for later retrieval + * by the {@link #getMessage()} method) + * + * @param cause + * The cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A null value is permitted, + * and indicates that the cause is nonexistent or unknown.) + * + */ + public GoogleAuthException(int responseStatus, int retryCount, String message, Throwable cause) { + super(message, cause); + retryStatus = getRetryStatus(responseStatus, retryCount); + } + + /** + * Constructor with message defaulted to the cause + * + * @param responseStatus A response status from the related HTTP request + * @param retryCount A number of retries performed + */ + public GoogleAuthException(int responseStatus, int retryCount, Throwable cause) { + super(cause); + retryStatus = getRetryStatus(responseStatus, retryCount); + } + + /** + * Constructor without retry info + * + * @param cause + * The cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A null value is permitted, + * and indicates that the cause is nonexistent or unknown.) + */ + public GoogleAuthException(Throwable cause) { + super(cause); + retryStatus = RetryStatus.NON_RETRYABLE; + } + + /** + * Returns retry status of the error + * @return + */ + @Override + public RetryStatus getRetryStatus() { + return retryStatus; + } + + /** + * Calculates retry status based on HTTP response status and number of performed retries + * @param responseStatus A response status from the related HTTP request + * @param retryCount A number of retries performed + * @return a retry status + */ + private RetryStatus getRetryStatus(int responseStatus, int retryCount) { + if (responseStatus == 500 || responseStatus == 503) { + return retryCount > 0 ? RetryStatus.RETRIED : RetryStatus.RETRYABLE; + } else { + return RetryStatus.NON_RETRYABLE; + } + } +} diff --git a/credentials/java/com/google/auth/RetryStatus.java b/credentials/java/com/google/auth/RetryStatus.java new file mode 100644 index 000000000..c2f34402a --- /dev/null +++ b/credentials/java/com/google/auth/RetryStatus.java @@ -0,0 +1,44 @@ +/* + * Copyright 2021 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + package com.google.auth; + + /** + * Enum of possible retriable error states + * RETRYABLE - the error may be retried. No retries were attempted. + * NON_RETRYABLE - the error must not be retried. + * RETRIED - at least one retry has been performed, further retries not recommended. + */ +public enum RetryStatus { + RETRYABLE, + NON_RETRYABLE, + RETRIED +} diff --git a/credentials/java/com/google/auth/Retryable.java b/credentials/java/com/google/auth/Retryable.java new file mode 100644 index 000000000..e50838d5b --- /dev/null +++ b/credentials/java/com/google/auth/Retryable.java @@ -0,0 +1,37 @@ +/* + * Copyright 2021 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.auth; + +// an interface to identify retryable errors +public interface Retryable { + RetryStatus getRetryStatus(); +} \ No newline at end of file diff --git a/credentials/pom.xml b/credentials/pom.xml index f2cabaa81..871d01d71 100644 --- a/credentials/pom.xml +++ b/credentials/pom.xml @@ -54,6 +54,16 @@ junit test + + com.google.api-client + google-api-client + 1.32.1 + + + com.google.http-client + google-http-client + 1.40.1-SNAPSHOT + diff --git a/oauth2_http/pom.xml b/oauth2_http/pom.xml index 3f216ad6d..b3c9b871a 100644 --- a/oauth2_http/pom.xml +++ b/oauth2_http/pom.xml @@ -129,10 +129,12 @@ com.google.http-client google-http-client + 1.40.1-SNAPSHOT com.google.http-client google-http-client-gson + 1.39.2 com.google.guava From b9bbc1f43ee0eb6ab9e48d224cf9e172daf313df Mon Sep 17 00:00:00 2001 From: Timur Sadykov Date: Wed, 29 Sep 2021 13:28:20 -0700 Subject: [PATCH 02/34] fix: retry logic fix --- credentials/java/com/google/auth/Retryable.java | 2 +- .../auth/oauth2/ServiceAccountCredentials.java | 16 ++++++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/credentials/java/com/google/auth/Retryable.java b/credentials/java/com/google/auth/Retryable.java index e50838d5b..f8ebc5205 100644 --- a/credentials/java/com/google/auth/Retryable.java +++ b/credentials/java/com/google/auth/Retryable.java @@ -34,4 +34,4 @@ // an interface to identify retryable errors public interface Retryable { RetryStatus getRetryStatus(); -} \ No newline at end of file +} diff --git a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java index 27d67f809..bfe53c3f5 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java @@ -97,6 +97,7 @@ public class ServiceAccountCredentials extends GoogleCredentials private static final String PARSE_ERROR_PREFIX = "Error parsing token refresh response. "; private static final int TWELVE_HOURS_IN_SECONDS = 43200; private static final int DEFAULT_LIFETIME_IN_SECONDS = 3600; + private static final int DEFAULT_NUMBER_OF_RETRIES = 3; private final String clientId; private final String clientEmail; @@ -576,24 +577,19 @@ public AccessToken refreshAccessToken() throws IOException { HttpRequestFactory requestFactory = transportFactory.create().createRequestFactory(); HttpRequest request = requestFactory.buildPostRequest(new GenericUrl(tokenServerUri), content); + request.setNumberOfRetries(DEFAULT_NUMBER_OF_RETRIES); request.setParser(new JsonObjectParser(jsonFactory)); - request.setIOExceptionHandler(new HttpBackOffIOExceptionHandler(new ExponentialBackOff())); request.setUnsuccessfulResponseHandler( new HttpBackOffUnsuccessfulResponseHandler(new ExponentialBackOff()) .setBackOffRequired( new BackOffRequired() { public boolean isRequired(HttpResponse response) { int code = response.getStatusCode(); - return ( - // Server error --- includes timeout errors, which use 500 instead of 408 - code / 100 == 5 - // Forbidden error --- for historical reasons, used for rate_limit_exceeded - // errors instead of 429, but there currently seems no robust automatic way - // to - // distinguish these cases: see - // https://github.com/google/google-api-java-client/issues/662 - || code == 403); + + // Server errors --- includes timeout errors, which use 500 instead of 408 + // Other 5xx codes are either not used or retries are unlickely to succeed + return code == 500 || code == 503; } })); From 11c9ffa897054fed29d57cdf8c68f80924c618c7 Mon Sep 17 00:00:00 2001 From: Timur Sadykov Date: Thu, 7 Oct 2021 04:08:12 -0700 Subject: [PATCH 03/34] feat: add RetryStrategy setting to GoogleCredential and ServiceAccountCredentials --- .../java/com/google/auth/Credentials.java | 14 +++- .../com/google/auth/GoogleAuthException.java | 30 ++------ .../google/auth/oauth2/GoogleCredentials.java | 17 ++++- .../com/google/auth/oauth2/RetryStrategy.java | 43 ++++++++++++ .../oauth2/ServiceAccountCredentials.java | 68 ++++++++++++++++--- 5 files changed, 136 insertions(+), 36 deletions(-) create mode 100644 oauth2_http/java/com/google/auth/oauth2/RetryStrategy.java diff --git a/credentials/java/com/google/auth/Credentials.java b/credentials/java/com/google/auth/Credentials.java index 806dda157..05f4532f1 100644 --- a/credentials/java/com/google/auth/Credentials.java +++ b/credentials/java/com/google/auth/Credentials.java @@ -115,7 +115,8 @@ protected final void blockingGetToCallback(URI uri, RequestMetadataCallback call } catch (Throwable e) { if (e instanceof HttpResponseException) { HttpResponseException httpException = (HttpResponseException) e; - callback.onFailure(new GoogleAuthException(httpException.getStatusCode(), httpException.getRetryCount(), e)); + RetryStatus retryStatus = getRetryStatus(httpException.getStatusCode(), httpException.getRetryCount()); + callback.onFailure(new GoogleAuthException(retryStatus, e)); } callback.onFailure(new GoogleAuthException(e)); @@ -124,6 +125,17 @@ protected final void blockingGetToCallback(URI uri, RequestMetadataCallback call callback.onSuccess(result); } + /** + * Calculates default retry status based on HTTP response status and number of performed retries + * By default, an error is considered a non-retryable, unless retries are configured in the calling credential + * @param responseStatus A response status from the related HTTP request + * @param retryCount A number of retries performed + * @return {@code RetryStatus.RETRIED} if any retries performed, {@code RetryStatus.NON_RETRYABLE} otherwise + */ + protected RetryStatus getRetryStatus(int responseStatus, int retryCount) { + return retryCount > 0 ? RetryStatus.RETRIED : RetryStatus.NON_RETRYABLE; + } + /** * Get the current request metadata in a blocking manner, refreshing tokens if required. * diff --git a/credentials/java/com/google/auth/GoogleAuthException.java b/credentials/java/com/google/auth/GoogleAuthException.java index 5678757f2..9e18ca180 100644 --- a/credentials/java/com/google/auth/GoogleAuthException.java +++ b/credentials/java/com/google/auth/GoogleAuthException.java @@ -44,11 +44,8 @@ public class GoogleAuthException extends IOException implements Retryable { /** * Constructor with all parameters * - * @param responseStatus - * A response status from the related HTTP request - * - * @param retryCount - * A number of retries performed + * @param retryStatus + * A retry status for the related HTTP request * * @param message * The detail message (which is saved for later retrieval @@ -58,11 +55,10 @@ public class GoogleAuthException extends IOException implements Retryable { * The cause (which is saved for later retrieval by the * {@link #getCause()} method). (A null value is permitted, * and indicates that the cause is nonexistent or unknown.) - * */ - public GoogleAuthException(int responseStatus, int retryCount, String message, Throwable cause) { + public GoogleAuthException(RetryStatus retryStatus, String message, Throwable cause) { super(message, cause); - retryStatus = getRetryStatus(responseStatus, retryCount); + this.retryStatus = retryStatus; } /** @@ -71,9 +67,9 @@ public GoogleAuthException(int responseStatus, int retryCount, String message, T * @param responseStatus A response status from the related HTTP request * @param retryCount A number of retries performed */ - public GoogleAuthException(int responseStatus, int retryCount, Throwable cause) { + public GoogleAuthException(RetryStatus retryStatus, Throwable cause) { super(cause); - retryStatus = getRetryStatus(responseStatus, retryCount); + this.retryStatus = retryStatus; } /** @@ -97,18 +93,4 @@ public GoogleAuthException(Throwable cause) { public RetryStatus getRetryStatus() { return retryStatus; } - - /** - * Calculates retry status based on HTTP response status and number of performed retries - * @param responseStatus A response status from the related HTTP request - * @param retryCount A number of retries performed - * @return a retry status - */ - private RetryStatus getRetryStatus(int responseStatus, int retryCount) { - if (responseStatus == 500 || responseStatus == 503) { - return retryCount > 0 ? RetryStatus.RETRIED : RetryStatus.RETRYABLE; - } else { - return RetryStatus.NON_RETRYABLE; - } - } } diff --git a/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java b/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java index af7cc8ecd..26d57d77e 100644 --- a/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java @@ -232,7 +232,7 @@ public boolean createScopedRequired() { } /** - * If the credentials support scopes, creates a copy of the the identity with the specified + * If the credentials support scopes, creates a copy of the identity with the specified * scopes; otherwise, returns the same instance. * * @param scopes Collection of scopes to request. @@ -243,7 +243,7 @@ public GoogleCredentials createScoped(Collection scopes) { } /** - * If the credentials support scopes, creates a copy of the the identity with the specified scopes + * If the credentials support scopes, creates a copy of the identity with the specified scopes * and default scopes; otherwise, returns the same instance. This is mainly used by client * libraries. * @@ -257,7 +257,7 @@ public GoogleCredentials createScoped( } /** - * If the credentials support scopes, creates a copy of the the identity with the specified + * If the credentials support scopes, creates a copy of the identity with the specified * scopes; otherwise, returns the same instance. * * @param scopes Collection of scopes to request. @@ -267,6 +267,17 @@ public GoogleCredentials createScoped(String... scopes) { return createScoped(ImmutableList.copyOf(scopes)); } + /** + * If the credentials support automatic retries, creates a copy of the identity with + * the provided retry strategy + * + * @param retryStrategy a retry strategy setting + * @return GoogleCredentials with the new retry strategy setting. + */ + public GoogleCredentials createWithCustomRetryStrategy(RetryStrategy retryStrategy) { + return this; + } + /** * If the credentials support domain-wide delegation, creates a copy of the identity so that it * impersonates the specified user; otherwise, returns the same instance. diff --git a/oauth2_http/java/com/google/auth/oauth2/RetryStrategy.java b/oauth2_http/java/com/google/auth/oauth2/RetryStrategy.java new file mode 100644 index 000000000..d323be66b --- /dev/null +++ b/oauth2_http/java/com/google/auth/oauth2/RetryStrategy.java @@ -0,0 +1,43 @@ +/* + * Copyright 2021 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + package com.google.auth.oauth2; + + /** + * Enum of possible retryable strategies + * NONE - retries disabled + * DEFAULT - exponential backoff on {@code RetryStatus.RETRYABLE} + * See more details {@link link to AIP 4117} + */ +public enum RetryStrategy { + NONE, + DEFAULT +} diff --git a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java index bfe53c3f5..3021ec53e 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java @@ -34,7 +34,6 @@ import static com.google.common.base.MoreObjects.firstNonNull; import com.google.api.client.http.GenericUrl; -import com.google.api.client.http.HttpBackOffIOExceptionHandler; import com.google.api.client.http.HttpBackOffUnsuccessfulResponseHandler; import com.google.api.client.http.HttpBackOffUnsuccessfulResponseHandler.BackOffRequired; import com.google.api.client.http.HttpRequest; @@ -54,6 +53,7 @@ import com.google.api.client.util.Preconditions; import com.google.api.client.util.SecurityUtils; import com.google.auth.RequestMetadataCallback; +import com.google.auth.RetryStatus; import com.google.auth.ServiceAccountSigner; import com.google.auth.http.HttpTransportFactory; import com.google.common.annotations.VisibleForTesting; @@ -98,6 +98,8 @@ public class ServiceAccountCredentials extends GoogleCredentials private static final int TWELVE_HOURS_IN_SECONDS = 43200; private static final int DEFAULT_LIFETIME_IN_SECONDS = 3600; private static final int DEFAULT_NUMBER_OF_RETRIES = 3; + private static final int INITIAL_RETRY_INTERVAL_MILLIS = 1000; + private static final double RETRY_RANDOMIZATION_FACTOR = 0.1; private final String clientId; private final String clientEmail; @@ -112,6 +114,7 @@ public class ServiceAccountCredentials extends GoogleCredentials private final String quotaProjectId; private final int lifetime; private final boolean useJwtAccessWithScope; + private final RetryStrategy retryStrategy; private transient HttpTransportFactory transportFactory; @@ -150,7 +153,8 @@ public class ServiceAccountCredentials extends GoogleCredentials String projectId, String quotaProjectId, int lifetime, - boolean useJwtAccessWithScope) { + boolean useJwtAccessWithScope, + RetryStrategy retryStrategy) { this.clientId = clientId; this.clientEmail = Preconditions.checkNotNull(clientEmail); this.privateKey = Preconditions.checkNotNull(privateKey); @@ -172,6 +176,7 @@ public class ServiceAccountCredentials extends GoogleCredentials } this.lifetime = lifetime; this.useJwtAccessWithScope = useJwtAccessWithScope; + this.retryStrategy = retryStrategy; } /** @@ -486,7 +491,8 @@ static ServiceAccountCredentials fromPkcs8( projectId, quotaProject, DEFAULT_LIFETIME_IN_SECONDS, - false); + false, + RetryStrategy.DEFAULT); } /** Helper to convert from a PKCS#8 String to an RSA private key */ @@ -580,15 +586,20 @@ public AccessToken refreshAccessToken() throws IOException { request.setNumberOfRetries(DEFAULT_NUMBER_OF_RETRIES); request.setParser(new JsonObjectParser(jsonFactory)); + ExponentialBackOff backoff = new ExponentialBackOff.Builder() + .setInitialIntervalMillis(INITIAL_RETRY_INTERVAL_MILLIS) + .setRandomizationFactor(RETRY_RANDOMIZATION_FACTOR) + .build(); + request.setUnsuccessfulResponseHandler( - new HttpBackOffUnsuccessfulResponseHandler(new ExponentialBackOff()) + new HttpBackOffUnsuccessfulResponseHandler(backoff) .setBackOffRequired( new BackOffRequired() { public boolean isRequired(HttpResponse response) { int code = response.getStatusCode(); // Server errors --- includes timeout errors, which use 500 instead of 408 - // Other 5xx codes are either not used or retries are unlickely to succeed + // Other 5xx codes are either not used or retries are unlikely to succeed return code == 500 || code == 503; } })); @@ -657,6 +668,16 @@ public IdToken idTokenWithAudience(String targetAudience, List