Skip to content

Commit 01d5036

Browse files
authored
chore: Remove ListenableFuture from SqlAdminApiFetcher (#1307)
Changes the return type of SqlAdminApiFetcher.getInstanceData() from ListenableFuture<InstanceData> to InstanceData in an effort to simplify Future related code across this project.
1 parent 1277b5e commit 01d5036

File tree

8 files changed

+192
-131
lines changed

8 files changed

+192
-131
lines changed

core/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,12 @@
211211
<version>1.13.0</version>
212212
</dependency>
213213

214+
<dependency>
215+
<groupId>com.google.auth</groupId>
216+
<artifactId>google-auth-library-credentials</artifactId>
217+
<version>1.13.0</version>
218+
</dependency>
219+
214220
<dependency>
215221
<groupId>com.google.oauth-client</groupId>
216222
<artifactId>google-oauth-client</artifactId>

core/src/main/java/com/google/cloud/sql/core/CloudSqlInstance.java

Lines changed: 27 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import com.google.cloud.sql.AuthType;
2626
import com.google.cloud.sql.CredentialFactory;
2727
import com.google.common.base.Throwables;
28-
import com.google.common.util.concurrent.FutureCallback;
2928
import com.google.common.util.concurrent.Futures;
3029
import com.google.common.util.concurrent.ListenableFuture;
3130
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
@@ -42,7 +41,6 @@
4241
import java.util.Optional;
4342
import java.util.concurrent.ExecutionException;
4443
import java.util.concurrent.TimeUnit;
45-
import java.util.logging.Level;
4644
import java.util.logging.Logger;
4745
import javax.net.ssl.SSLSocket;
4846

@@ -89,8 +87,7 @@ class CloudSqlInstance {
8987
AuthType authType,
9088
CredentialFactory tokenSourceFactory,
9189
ListeningScheduledExecutorService executor,
92-
ListenableFuture<KeyPair> keyPair)
93-
throws IOException, InterruptedException {
90+
ListenableFuture<KeyPair> keyPair) {
9491
this.instanceName = new CloudSqlInstanceName(connectionName);
9592
this.apiFetcher = apiFetcher;
9693
this.authType = authType;
@@ -228,61 +225,38 @@ private InstanceData performRefresh() throws InterruptedException, ExecutionExce
228225
// To avoid unreasonable SQL Admin API usage, use a rate limit to throttle our usage.
229226
forcedRenewRateLimiter.acquirePermit();
230227

231-
ListenableFuture<InstanceData> refreshFuture;
232-
228+
final GoogleCredentials credentials;
233229
if (iamAuthnCredentials.isPresent()) {
234-
GoogleCredentials downscopedCredentials = getDownscopedCredentials(iamAuthnCredentials.get());
235-
refreshFuture =
236-
apiFetcher.getInstanceData(
237-
instanceName, downscopedCredentials, AuthType.IAM, executor, keyPair);
230+
credentials = getDownscopedCredentials(iamAuthnCredentials.get());
238231
} else {
239-
refreshFuture =
240-
apiFetcher.getInstanceData(instanceName, null, AuthType.PASSWORD, executor, keyPair);
232+
credentials = null;
241233
}
242234

243-
Futures.addCallback(
244-
refreshFuture,
245-
new FutureCallback<InstanceData>() {
246-
@Override
247-
public void onSuccess(InstanceData instanceData) {
248-
synchronized (instanceDataGuard) {
249-
// update currentInstanceData with the most recent results
250-
currentInstanceData = refreshFuture;
251-
// schedule a replacement before the SSLContext expires;
252-
nextInstanceData =
253-
executor.schedule(
254-
() -> performRefresh(),
255-
refreshCalculator.calculateSecondsUntilNextRefresh(
256-
Instant.now(), getInstanceData().getExpiration().toInstant()),
257-
TimeUnit.SECONDS);
258-
}
259-
}
235+
try {
236+
InstanceData data =
237+
apiFetcher.getInstanceData(
238+
this.instanceName, credentials, this.authType, executor, keyPair);
239+
240+
synchronized (instanceDataGuard) {
241+
// update currentInstanceData with the most recent results
242+
currentInstanceData = Futures.immediateFuture(data);
260243

261-
@Override
262-
public void onFailure(Throwable t) {
263-
logger.log(
264-
Level.WARNING,
265-
"An error occurred while performing refresh. Retrying immediately.",
266-
t);
267-
synchronized (instanceDataGuard) {
268-
InstanceData instanceData = null;
269-
try {
270-
instanceData = getInstanceData();
271-
} catch (Exception e) {
272-
// this means the result was invalid
273-
}
274-
if (instanceData == null
275-
|| instanceData.getExpiration().toInstant().isBefore(Instant.now())) {
276-
// replace current if it is expired or invalid
277-
currentInstanceData = refreshFuture;
278-
}
279-
nextInstanceData = executor.submit(() -> performRefresh());
280-
}
281-
}
282-
},
283-
executor);
244+
// schedule a replacement before the SSLContext expires;
245+
nextInstanceData =
246+
executor.schedule(
247+
this::performRefresh,
248+
refreshCalculator.calculateSecondsUntilNextRefresh(
249+
Instant.now(), data.getExpiration().toInstant()),
250+
TimeUnit.SECONDS);
251+
}
284252

285-
return refreshFuture.get();
253+
return data;
254+
} catch (ExecutionException | InterruptedException e) {
255+
synchronized (instanceDataGuard) {
256+
nextInstanceData = executor.submit(this::performRefresh);
257+
}
258+
throw e;
259+
}
286260
}
287261

288262
private Optional<Date> getTokenExpirationTime(Credential credentials) {

core/src/main/java/com/google/cloud/sql/core/CoreSocketFactory.java

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -355,13 +355,8 @@ Socket createSslSocket(String instanceName, List<String> ipTypes)
355355
CloudSqlInstance getCloudSqlInstance(String instanceName, AuthType authType) {
356356
return instances.computeIfAbsent(
357357
instanceName,
358-
k -> {
359-
try {
360-
return new CloudSqlInstance(
361-
k, adminApiService, authType, credentialFactory, executor, localKeyPair);
362-
} catch (IOException | InterruptedException e) {
363-
throw new RuntimeException(e);
364-
}
365-
});
358+
k ->
359+
new CloudSqlInstance(
360+
k, adminApiService, authType, credentialFactory, executor, localKeyPair));
366361
}
367362
}

core/src/main/java/com/google/cloud/sql/core/SqlAdminApiFetcher.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import java.util.Map;
5454
import java.util.Optional;
5555
import java.util.concurrent.Callable;
56+
import java.util.concurrent.ExecutionException;
5657
import java.util.logging.Logger;
5758
import javax.net.ssl.KeyManagerFactory;
5859
import javax.net.ssl.SSLContext;
@@ -93,12 +94,14 @@ private String generatePublicKeyCert(KeyPair keyPair) {
9394
+ "-----END RSA PUBLIC KEY-----\n";
9495
}
9596

96-
ListenableFuture<InstanceData> getInstanceData(
97+
InstanceData getInstanceData(
9798
CloudSqlInstanceName instanceName,
9899
OAuth2Credentials credentials,
99100
AuthType authType,
100101
ListeningScheduledExecutorService executor,
101-
ListenableFuture<KeyPair> keyPair) {
102+
ListenableFuture<KeyPair> keyPair)
103+
throws ExecutionException, InterruptedException {
104+
102105
// Fetch the metadata
103106
ListenableFuture<Metadata> metadataFuture =
104107
executor.submit(() -> fetchMetadata(instanceName, authType));
@@ -152,7 +155,7 @@ ListenableFuture<InstanceData> getInstanceData(
152155
},
153156
executor);
154157

155-
return done;
158+
return done.get();
156159
}
157160

158161
private Optional<Date> getTokenExpirationTime(OAuth2Credentials credentials) {

core/src/test/java/com/google/cloud/sql/core/BadConnectionFactory.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ public void addHeader(String name, String value) throws IOException {
3838

3939
@Override
4040
public LowLevelHttpResponse execute() throws IOException {
41+
try {
42+
Thread.sleep(100);
43+
} catch (InterruptedException e) {
44+
// Ignore the interruption
45+
}
4146
throw new SocketTimeoutException("Fake connect timeout");
4247
}
4348
}

core/src/test/java/com/google/cloud/sql/core/SqlAdminApiFetcherTest.java

Lines changed: 68 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import com.google.auth.oauth2.OAuth2CredentialsWithRefresh;
2424
import com.google.cloud.sql.AuthType;
2525
import com.google.common.util.concurrent.Futures;
26-
import com.google.common.util.concurrent.ListenableFuture;
2726
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
2827
import com.google.common.util.concurrent.MoreExecutors;
2928
import java.security.GeneralSecurityException;
@@ -55,15 +54,13 @@ public void testFetchInstanceData_returnsIpAddresses()
5554
new StubApiFetcherFactory(mockAdminApi.getHttpTransport())
5655
.create(new StubCredentialFactory().create());
5756

58-
ListenableFuture<InstanceData> instanceDataFuture =
57+
InstanceData instanceData =
5958
fetcher.getInstanceData(
6059
new CloudSqlInstanceName(INSTANCE_CONNECTION_NAME),
6160
null,
6261
AuthType.PASSWORD,
6362
newTestExecutor(),
6463
Futures.immediateFuture(mockAdminApi.getClientKeyPair()));
65-
66-
InstanceData instanceData = instanceDataFuture.get();
6764
assertThat(instanceData.getSslContext()).isInstanceOf(SSLContext.class);
6865

6966
Map<String, String> ipAddrs = instanceData.getIpAddrs();
@@ -89,20 +86,22 @@ public void testFetchInstanceData_throwsException_whenIamAuthnIsNotSupported()
8986
new StubApiFetcherFactory(mockAdminApi.getHttpTransport())
9087
.create(new StubCredentialFactory().create());
9188

92-
ListenableFuture<InstanceData> instanceData =
93-
fetcher.getInstanceData(
94-
new CloudSqlInstanceName(INSTANCE_CONNECTION_NAME),
95-
OAuth2CredentialsWithRefresh.newBuilder()
96-
.setRefreshHandler(
97-
mockAdminApi.getRefreshHandler(
98-
"refresh-token", Date.from(Instant.now().plus(1, ChronoUnit.HOURS))))
99-
.setAccessToken(new AccessToken("my-token", Date.from(Instant.now())))
100-
.build(),
101-
AuthType.IAM,
102-
newTestExecutor(),
103-
Futures.immediateFuture(mockAdminApi.getClientKeyPair()));
104-
105-
ExecutionException ex = assertThrows(ExecutionException.class, instanceData::get);
89+
ExecutionException ex =
90+
assertThrows(
91+
ExecutionException.class,
92+
() -> {
93+
fetcher.getInstanceData(
94+
new CloudSqlInstanceName(INSTANCE_CONNECTION_NAME),
95+
OAuth2CredentialsWithRefresh.newBuilder()
96+
.setRefreshHandler(
97+
mockAdminApi.getRefreshHandler(
98+
"refresh-token", Date.from(Instant.now().plus(1, ChronoUnit.HOURS))))
99+
.setAccessToken(new AccessToken("my-token", Date.from(Instant.now())))
100+
.build(),
101+
AuthType.IAM,
102+
newTestExecutor(),
103+
Futures.immediateFuture(mockAdminApi.getClientKeyPair()));
104+
});
106105
assertThat(ex)
107106
.hasMessageThat()
108107
.contains("[p:r:i] IAM Authentication is not supported for SQL Server instances");
@@ -116,20 +115,22 @@ public void testFetchInstanceData_throwsException_whenTokenIsEmpty()
116115
new StubApiFetcherFactory(mockAdminApi.getHttpTransport())
117116
.create(new StubCredentialFactory().create());
118117

119-
ListenableFuture<InstanceData> instanceData =
120-
fetcher.getInstanceData(
121-
new CloudSqlInstanceName(INSTANCE_CONNECTION_NAME),
122-
OAuth2CredentialsWithRefresh.newBuilder()
123-
.setRefreshHandler(
124-
mockAdminApi.getRefreshHandler(
125-
"", Date.from(Instant.now().plus(1, ChronoUnit.HOURS)) /* empty */))
126-
.setAccessToken(new AccessToken("" /* ignored */, Date.from(Instant.now())))
127-
.build(),
128-
AuthType.IAM,
129-
newTestExecutor(),
130-
Futures.immediateFuture(mockAdminApi.getClientKeyPair()));
131-
132-
ExecutionException ex = assertThrows(ExecutionException.class, instanceData::get);
118+
ExecutionException ex =
119+
assertThrows(
120+
ExecutionException.class,
121+
() -> {
122+
fetcher.getInstanceData(
123+
new CloudSqlInstanceName(INSTANCE_CONNECTION_NAME),
124+
OAuth2CredentialsWithRefresh.newBuilder()
125+
.setRefreshHandler(
126+
mockAdminApi.getRefreshHandler(
127+
"", Date.from(Instant.now().plus(1, ChronoUnit.HOURS)) /* empty */))
128+
.setAccessToken(new AccessToken("" /* ignored */, Date.from(Instant.now())))
129+
.build(),
130+
AuthType.IAM,
131+
newTestExecutor(),
132+
Futures.immediateFuture(mockAdminApi.getClientKeyPair()));
133+
});
133134

134135
assertThat(ex).hasMessageThat().contains("Access Token has length of zero");
135136
}
@@ -142,21 +143,23 @@ public void testFetchInstanceData_throwsException_whenTokenIsExpired()
142143
new StubApiFetcherFactory(mockAdminApi.getHttpTransport())
143144
.create(new StubCredentialFactory().create());
144145

145-
ListenableFuture<InstanceData> instanceData =
146-
fetcher.getInstanceData(
147-
new CloudSqlInstanceName(INSTANCE_CONNECTION_NAME),
148-
OAuth2CredentialsWithRefresh.newBuilder()
149-
.setRefreshHandler(
150-
mockAdminApi.getRefreshHandler(
151-
"refresh-token",
152-
Date.from(Instant.now().minus(1, ChronoUnit.HOURS)) /* 1 hour ago */))
153-
.setAccessToken(new AccessToken("original-token", Date.from(Instant.now())))
154-
.build(),
155-
AuthType.IAM,
156-
newTestExecutor(),
157-
Futures.immediateFuture(mockAdminApi.getClientKeyPair()));
158-
159-
ExecutionException ex = assertThrows(ExecutionException.class, instanceData::get);
146+
ExecutionException ex =
147+
assertThrows(
148+
ExecutionException.class,
149+
() -> {
150+
fetcher.getInstanceData(
151+
new CloudSqlInstanceName(INSTANCE_CONNECTION_NAME),
152+
OAuth2CredentialsWithRefresh.newBuilder()
153+
.setRefreshHandler(
154+
mockAdminApi.getRefreshHandler(
155+
"refresh-token",
156+
Date.from(Instant.now().minus(1, ChronoUnit.HOURS)) /* 1 hour ago */))
157+
.setAccessToken(new AccessToken("original-token", Date.from(Instant.now())))
158+
.build(),
159+
AuthType.IAM,
160+
newTestExecutor(),
161+
Futures.immediateFuture(mockAdminApi.getClientKeyPair()));
162+
});
160163

161164
assertThat(ex).hasMessageThat().contains("Access Token expiration time is in the past");
162165
}
@@ -169,21 +172,24 @@ public void testFetchInstanceData_throwsException_whenRequestsTimeout()
169172
new StubApiFetcherFactory(new BadConnectionFactory())
170173
.create(new StubCredentialFactory().create());
171174

172-
ListenableFuture<InstanceData> instanceData =
173-
fetcher.getInstanceData(
174-
new CloudSqlInstanceName(INSTANCE_CONNECTION_NAME),
175-
OAuth2CredentialsWithRefresh.newBuilder()
176-
.setRefreshHandler(
177-
mockAdminApi.getRefreshHandler(
178-
"refresh-token",
179-
Date.from(Instant.now().plus(1, ChronoUnit.HOURS)) /* 1 hour from now */))
180-
.setAccessToken(new AccessToken("original-token", Date.from(Instant.now())))
181-
.build(),
182-
AuthType.IAM,
183-
newTestExecutor(),
184-
Futures.immediateFuture(mockAdminApi.getClientKeyPair()));
185-
186-
ExecutionException ex = assertThrows(ExecutionException.class, instanceData::get);
175+
ExecutionException ex =
176+
assertThrows(
177+
ExecutionException.class,
178+
() -> {
179+
fetcher.getInstanceData(
180+
new CloudSqlInstanceName(INSTANCE_CONNECTION_NAME),
181+
OAuth2CredentialsWithRefresh.newBuilder()
182+
.setRefreshHandler(
183+
mockAdminApi.getRefreshHandler(
184+
"refresh-token",
185+
Date.from(
186+
Instant.now().plus(1, ChronoUnit.HOURS)) /* 1 hour from now */))
187+
.setAccessToken(new AccessToken("original-token", Date.from(Instant.now())))
188+
.build(),
189+
AuthType.IAM,
190+
newTestExecutor(),
191+
Futures.immediateFuture(mockAdminApi.getClientKeyPair()));
192+
});
187193

188194
assertThat(ex.getCause().getCause()).hasMessageThat().contains("Fake connect timeout");
189195
}

0 commit comments

Comments
 (0)