Skip to content

Commit f9a9b8a

Browse files
feat: update retries and implement Retryable (googleapis#750)
* 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 <[email protected]>
1 parent e1f8eda commit f9a9b8a

File tree

11 files changed

+621
-262
lines changed

11 files changed

+621
-262
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ target/
99

1010
# Intellij
1111
*.iml
12+
*.factorypath
1213
.idea/
1314

1415
# VS Code

credentials/java/com/google/auth/Credentials.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,9 @@ protected final void blockingGetToCallback(URI uri, RequestMetadataCallback call
129129
*
130130
* @param uri URI of the entry point for the request.
131131
* @return The request metadata used for populating headers or other context.
132-
* @throws IOException if there was an error getting up-to-date access.
132+
* @throws IOException if there was an error getting up-to-date access. The exception should
133+
* implement {@link Retryable} and {@code isRetryable()} will return true if the operation may
134+
* be retried.
133135
*/
134136
public abstract Map<String, List<String>> getRequestMetadata(URI uri) throws IOException;
135137

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2022 Google LLC
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are
6+
* met:
7+
*
8+
* * Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* * Redistributions in binary form must reproduce the above
11+
* copyright notice, this list of conditions and the following disclaimer
12+
* in the documentation and/or other materials provided with the
13+
* distribution.
14+
*
15+
* * Neither the name of Google LLC nor the names of its
16+
* contributors may be used to endorse or promote products derived from
17+
* this software without specific prior written permission.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
*/
31+
32+
package com.google.auth;
33+
34+
// an interface to identify retryable errors
35+
public interface Retryable {
36+
/**
37+
* A flag indicating whether the error is retryable
38+
*
39+
* @return true if related error is retryable, false otherwise
40+
*/
41+
boolean isRetryable();
42+
43+
/**
44+
* Gets a number of performed retries for related HttpRequest
45+
*
46+
* @return a number of performed retries
47+
*/
48+
int getRetryCount();
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
/*
2+
* Copyright 2022 Google LLC
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are
6+
* met:
7+
*
8+
* * Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* * Redistributions in binary form must reproduce the above
11+
* copyright notice, this list of conditions and the following disclaimer
12+
* in the documentation and/or other materials provided with the
13+
* distribution.
14+
*
15+
* * Neither the name of Google LLC nor the names of its
16+
* contributors may be used to endorse or promote products derived from
17+
* this software without specific prior written permission.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
*/
31+
32+
package com.google.auth.oauth2;
33+
34+
import com.google.api.client.http.HttpResponseException;
35+
import com.google.auth.Retryable;
36+
import java.io.IOException;
37+
38+
/**
39+
* Base class for the standard Auth error response. It extends a default exception while keeping
40+
* Json response format
41+
*/
42+
class GoogleAuthException extends IOException implements Retryable {
43+
44+
private final boolean isRetryable;
45+
private final int retryCount;
46+
47+
/**
48+
* Constructor with all parameters
49+
*
50+
* @param isRetryable A retry status for the related HTTP request
51+
* @param retryCount A number of retries performed for the related HTTP request
52+
* @param message The detail message (which is saved for later retrieval by the {@link
53+
* #getMessage()} method)
54+
* @param cause The cause (which is saved for later retrieval by the {@link #getCause()} method).
55+
* (A null value is permitted, and indicates that the cause is nonexistent or unknown.)
56+
*/
57+
GoogleAuthException(boolean isRetryable, int retryCount, String message, Throwable cause) {
58+
super(message, cause);
59+
this.isRetryable = isRetryable;
60+
this.retryCount = retryCount;
61+
}
62+
63+
/**
64+
* Constructor with message defaulted to the cause
65+
*
66+
* @param isRetryable A retry status for the related HTTP request
67+
* @param retryCount A number of retries performed for the related HTTP request
68+
* @param cause The cause (which is saved for later retrieval by the {@link #getCause()} method).
69+
* (A null value is permitted, and indicates that the cause is nonexistent or unknown.) If the
70+
* cause has retry information, it is going to be skipped in favor of the {@code retryCount}
71+
* parameter
72+
*/
73+
GoogleAuthException(boolean isRetryable, int retryCount, Throwable cause) {
74+
super(cause);
75+
this.isRetryable = isRetryable;
76+
this.retryCount = retryCount;
77+
}
78+
79+
/**
80+
* Constructor without explicit retry count.
81+
*
82+
* @param isRetryable A retry status for the related HTTP request
83+
* @param cause The cause (which is saved for later retrieval by the {@link #getCause()} method).
84+
* (A null value is permitted, and indicates that the cause is nonexistent or unknown.)
85+
*/
86+
GoogleAuthException(boolean isRetryable, Throwable cause) {
87+
super(cause);
88+
this.isRetryable = isRetryable;
89+
this.retryCount = 0;
90+
}
91+
92+
/**
93+
* Constructor without retry info
94+
*
95+
* @param cause The cause (which is saved for later retrieval by the {@link #getCause()} method).
96+
* (A null value is permitted, and indicates that the cause is nonexistent or unknown.)
97+
*/
98+
GoogleAuthException(Throwable cause) {
99+
this(false, cause);
100+
}
101+
102+
/** A default Constructor */
103+
GoogleAuthException() {
104+
super();
105+
this.isRetryable = false;
106+
this.retryCount = 0;
107+
}
108+
109+
/**
110+
* Creates an instance of the exception based on {@link HttpResponseException} and a custom error
111+
* message.
112+
*
113+
* @see #createWithTokenEndpointResponseException(HttpResponseException, String)
114+
* @param responseException an instance of {@link HttpResponseException}
115+
* @param message The detail message (which is saved for later retrieval by the {@link
116+
* #getMessage()} method)
117+
* @return new instance of {@link GoogleAuthException}
118+
*/
119+
static GoogleAuthException createWithTokenEndpointResponseException(
120+
HttpResponseException responseException, String message) {
121+
int responseStatus = responseException.getStatusCode();
122+
boolean isRetryable =
123+
OAuth2Utils.TOKEN_ENDPOINT_RETRYABLE_STATUS_CODES.contains(responseStatus);
124+
int retryCount = responseException.getAttemptCount() - 1;
125+
126+
if (message == null) {
127+
return new GoogleAuthException(isRetryable, retryCount, responseException);
128+
} else {
129+
return new GoogleAuthException(isRetryable, retryCount, message, responseException);
130+
}
131+
}
132+
133+
/**
134+
* Creates an instance of the exception based on {@link HttpResponseException} returned by Google
135+
* token endpoint. It uses response status code information to populate the {@code #isRetryable}
136+
* property and a number of performed attempts to populate the {@code #retryCount} property
137+
*
138+
* @param responseException an instance of {@link HttpResponseException}
139+
* @return new instance of {@link GoogleAuthException}
140+
*/
141+
static GoogleAuthException createWithTokenEndpointResponseException(
142+
HttpResponseException responseException) {
143+
return GoogleAuthException.createWithTokenEndpointResponseException(responseException, null);
144+
}
145+
146+
/** Returns true if the error is retryable, false otherwise */
147+
@Override
148+
public boolean isRetryable() {
149+
return isRetryable;
150+
}
151+
152+
/** Returns number of reties performed for the related HTTP request */
153+
@Override
154+
public int getRetryCount() {
155+
return retryCount;
156+
}
157+
}

oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java

+17-7
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,8 @@ public boolean createScopedRequired() {
243243
}
244244

245245
/**
246-
* If the credentials support scopes, creates a copy of the the identity with the specified
247-
* scopes; otherwise, returns the same instance.
246+
* If the credentials support scopes, creates a copy of the identity with the specified scopes;
247+
* otherwise, returns the same instance.
248248
*
249249
* @param scopes Collection of scopes to request.
250250
* @return GoogleCredentials with requested scopes.
@@ -254,9 +254,8 @@ public GoogleCredentials createScoped(Collection<String> scopes) {
254254
}
255255

256256
/**
257-
* If the credentials support scopes, creates a copy of the the identity with the specified scopes
258-
* and default scopes; otherwise, returns the same instance. This is mainly used by client
259-
* libraries.
257+
* If the credentials support scopes, creates a copy of the identity with the specified scopes and
258+
* default scopes; otherwise, returns the same instance. This is mainly used by client libraries.
260259
*
261260
* @param scopes Collection of scopes to request.
262261
* @param defaultScopes Collection of default scopes to request.
@@ -268,8 +267,8 @@ public GoogleCredentials createScoped(
268267
}
269268

270269
/**
271-
* If the credentials support scopes, creates a copy of the the identity with the specified
272-
* scopes; otherwise, returns the same instance.
270+
* If the credentials support scopes, creates a copy of the identity with the specified scopes;
271+
* otherwise, returns the same instance.
273272
*
274273
* @param scopes Collection of scopes to request.
275274
* @return GoogleCredentials with requested scopes.
@@ -278,6 +277,17 @@ public GoogleCredentials createScoped(String... scopes) {
278277
return createScoped(ImmutableList.copyOf(scopes));
279278
}
280279

280+
/**
281+
* If the credentials support automatic retries, creates a copy of the identity with the provided
282+
* retry strategy
283+
*
284+
* @param defaultRetriesEnabled a flag enabling or disabling default retries
285+
* @return GoogleCredentials with the new default retries configuration.
286+
*/
287+
public GoogleCredentials createWithCustomRetryStrategy(boolean defaultRetriesEnabled) {
288+
return this;
289+
}
290+
281291
/**
282292
* If the credentials support domain-wide delegation, creates a copy of the identity so that it
283293
* impersonates the specified user; otherwise, returns the same instance.

oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java

+8
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,11 @@
5050
import java.math.BigDecimal;
5151
import java.net.URI;
5252
import java.nio.charset.StandardCharsets;
53+
import java.util.Arrays;
5354
import java.util.Collection;
55+
import java.util.HashSet;
5456
import java.util.Map;
57+
import java.util.Set;
5558

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

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

80+
// Includes expected server errors from Google token endpoint
81+
// Other 5xx codes are either not used or retries are unlikely to succeed
82+
public static final Set<Integer> TOKEN_ENDPOINT_RETRYABLE_STATUS_CODES =
83+
new HashSet<>(Arrays.asList(500, 503, 408, 429));
84+
7785
static class DefaultHttpTransportFactory implements HttpTransportFactory {
7886

7987
public HttpTransport create() {

oauth2_http/java/com/google/auth/oauth2/OAuthException.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,13 @@
3333

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

36-
import java.io.IOException;
3736
import javax.annotation.Nullable;
3837

3938
/**
4039
* Encapsulates the standard OAuth error response. See
4140
* https://tools.ietf.org/html/rfc6749#section-5.2.
4241
*/
43-
class OAuthException extends IOException {
42+
class OAuthException extends GoogleAuthException {
4443

4544
private final String errorCode;
4645
@Nullable private final String errorDescription;

0 commit comments

Comments
 (0)