Skip to content

Commit

Permalink
feat: update retries and implement Retryable (#750)
Browse files Browse the repository at this point in the history
* feat: update retries and implement Retryable

* fix: retry logic fix

* feat: tests for new retries

* fix: refactor to move response handling into the specific credential implementation

* feat: additional factory method

* feat: flag to disable default retries and tests

* feat: generate retryable exceptions for UserCredentials

* fix: remove public from exception constructors, leverage builder in ServiceAccountCredentials

See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md

Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com>
  • Loading branch information
TimurSadykov and arithmetic1728 authored Feb 7, 2022
1 parent e1f8eda commit f9a9b8a
Show file tree
Hide file tree
Showing 11 changed files with 621 additions and 262 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ target/

# Intellij
*.iml
*.factorypath
.idea/

# VS Code
Expand Down
4 changes: 3 additions & 1 deletion credentials/java/com/google/auth/Credentials.java
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,9 @@ protected final void blockingGetToCallback(URI uri, RequestMetadataCallback call
*
* @param uri URI of the entry point for the request.
* @return The request metadata used for populating headers or other context.
* @throws IOException if there was an error getting up-to-date access.
* @throws IOException if there was an error getting up-to-date access. The exception should
* implement {@link Retryable} and {@code isRetryable()} will return true if the operation may
* be retried.
*/
public abstract Map<String, List<String>> getRequestMetadata(URI uri) throws IOException;

Expand Down
49 changes: 49 additions & 0 deletions credentials/java/com/google/auth/Retryable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2022 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 {
/**
* A flag indicating whether the error is retryable
*
* @return true if related error is retryable, false otherwise
*/
boolean isRetryable();

/**
* Gets a number of performed retries for related HttpRequest
*
* @return a number of performed retries
*/
int getRetryCount();
}
157 changes: 157 additions & 0 deletions oauth2_http/java/com/google/auth/oauth2/GoogleAuthException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
* Copyright 2022 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;

import com.google.api.client.http.HttpResponseException;
import com.google.auth.Retryable;
import java.io.IOException;

/**
* Base class for the standard Auth error response. It extends a default exception while keeping
* Json response format
*/
class GoogleAuthException extends IOException implements Retryable {

private final boolean isRetryable;
private final int retryCount;

/**
* Constructor with all parameters
*
* @param isRetryable A retry status for the related HTTP request
* @param retryCount A number of retries performed for the related HTTP request
* @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.)
*/
GoogleAuthException(boolean isRetryable, int retryCount, String message, Throwable cause) {
super(message, cause);
this.isRetryable = isRetryable;
this.retryCount = retryCount;
}

/**
* Constructor with message defaulted to the cause
*
* @param isRetryable A retry status for the related HTTP request
* @param retryCount A number of retries performed for the related HTTP request
* @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.) If the
* cause has retry information, it is going to be skipped in favor of the {@code retryCount}
* parameter
*/
GoogleAuthException(boolean isRetryable, int retryCount, Throwable cause) {
super(cause);
this.isRetryable = isRetryable;
this.retryCount = retryCount;
}

/**
* Constructor without explicit retry count.
*
* @param isRetryable A retry status for the related HTTP request
* @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.)
*/
GoogleAuthException(boolean isRetryable, Throwable cause) {
super(cause);
this.isRetryable = isRetryable;
this.retryCount = 0;
}

/**
* 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.)
*/
GoogleAuthException(Throwable cause) {
this(false, cause);
}

/** A default Constructor */
GoogleAuthException() {
super();
this.isRetryable = false;
this.retryCount = 0;
}

/**
* Creates an instance of the exception based on {@link HttpResponseException} and a custom error
* message.
*
* @see #createWithTokenEndpointResponseException(HttpResponseException, String)
* @param responseException an instance of {@link HttpResponseException}
* @param message The detail message (which is saved for later retrieval by the {@link
* #getMessage()} method)
* @return new instance of {@link GoogleAuthException}
*/
static GoogleAuthException createWithTokenEndpointResponseException(
HttpResponseException responseException, String message) {
int responseStatus = responseException.getStatusCode();
boolean isRetryable =
OAuth2Utils.TOKEN_ENDPOINT_RETRYABLE_STATUS_CODES.contains(responseStatus);
int retryCount = responseException.getAttemptCount() - 1;

if (message == null) {
return new GoogleAuthException(isRetryable, retryCount, responseException);
} else {
return new GoogleAuthException(isRetryable, retryCount, message, responseException);
}
}

/**
* Creates an instance of the exception based on {@link HttpResponseException} returned by Google
* token endpoint. It uses response status code information to populate the {@code #isRetryable}
* property and a number of performed attempts to populate the {@code #retryCount} property
*
* @param responseException an instance of {@link HttpResponseException}
* @return new instance of {@link GoogleAuthException}
*/
static GoogleAuthException createWithTokenEndpointResponseException(
HttpResponseException responseException) {
return GoogleAuthException.createWithTokenEndpointResponseException(responseException, null);
}

/** Returns true if the error is retryable, false otherwise */
@Override
public boolean isRetryable() {
return isRetryable;
}

/** Returns number of reties performed for the related HTTP request */
@Override
public int getRetryCount() {
return retryCount;
}
}
24 changes: 17 additions & 7 deletions oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,8 @@ public boolean createScopedRequired() {
}

/**
* If the credentials support scopes, creates a copy of the the identity with the specified
* scopes; otherwise, returns the same instance.
* 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.
* @return GoogleCredentials with requested scopes.
Expand All @@ -254,9 +254,8 @@ public GoogleCredentials createScoped(Collection<String> scopes) {
}

/**
* If the credentials support scopes, creates a copy of the the identity with the specified scopes
* and default scopes; otherwise, returns the same instance. This is mainly used by client
* libraries.
* 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.
*
* @param scopes Collection of scopes to request.
* @param defaultScopes Collection of default scopes to request.
Expand All @@ -268,8 +267,8 @@ public GoogleCredentials createScoped(
}

/**
* If the credentials support scopes, creates a copy of the the identity with the specified
* scopes; otherwise, returns the same instance.
* 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.
* @return GoogleCredentials with requested scopes.
Expand All @@ -278,6 +277,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 defaultRetriesEnabled a flag enabling or disabling default retries
* @return GoogleCredentials with the new default retries configuration.
*/
public GoogleCredentials createWithCustomRetryStrategy(boolean defaultRetriesEnabled) {
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.
Expand Down
8 changes: 8 additions & 0 deletions oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,11 @@
import java.math.BigDecimal;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/** Internal utilities for the com.google.auth.oauth2 namespace. */
class OAuth2Utils {
Expand All @@ -74,6 +77,11 @@ class OAuth2Utils {

static final String BEARER_PREFIX = AuthHttpConstants.BEARER + " ";

// Includes expected server errors from Google token endpoint
// Other 5xx codes are either not used or retries are unlikely to succeed
public static final Set<Integer> TOKEN_ENDPOINT_RETRYABLE_STATUS_CODES =
new HashSet<>(Arrays.asList(500, 503, 408, 429));

static class DefaultHttpTransportFactory implements HttpTransportFactory {

public HttpTransport create() {
Expand Down
3 changes: 1 addition & 2 deletions oauth2_http/java/com/google/auth/oauth2/OAuthException.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,13 @@

import static com.google.common.base.Preconditions.checkNotNull;

import java.io.IOException;
import javax.annotation.Nullable;

/**
* Encapsulates the standard OAuth error response. See
* https://tools.ietf.org/html/rfc6749#section-5.2.
*/
class OAuthException extends IOException {
class OAuthException extends GoogleAuthException {

private final String errorCode;
@Nullable private final String errorDescription;
Expand Down
Loading

0 comments on commit f9a9b8a

Please sign in to comment.