|
47 | 47 | import com.google.api.client.http.HttpStatusCodes; |
48 | 48 | import com.google.api.client.http.HttpTransport; |
49 | 49 | import com.google.api.client.json.GenericJson; |
| 50 | +import com.google.api.client.json.Json; |
50 | 51 | import com.google.api.client.json.JsonFactory; |
51 | 52 | import com.google.api.client.json.JsonGenerator; |
52 | 53 | import com.google.api.client.json.JsonParser; |
53 | 54 | import com.google.api.client.json.gson.GsonFactory; |
54 | 55 | import com.google.api.client.json.webtoken.JsonWebToken.Payload; |
| 56 | +import com.google.api.client.testing.http.MockHttpTransport; |
55 | 57 | import com.google.api.client.testing.http.MockLowLevelHttpRequest; |
| 58 | +import com.google.api.client.testing.http.MockLowLevelHttpResponse; |
56 | 59 | import com.google.api.client.util.Clock; |
57 | 60 | import com.google.auth.Credentials; |
58 | 61 | import com.google.auth.ServiceAccountSigner.SigningException; |
59 | 62 | import com.google.auth.TestUtils; |
60 | 63 | import com.google.auth.http.HttpTransportFactory; |
61 | 64 | import com.google.common.collect.ImmutableList; |
62 | 65 | import com.google.common.collect.ImmutableSet; |
| 66 | +import com.google.common.util.concurrent.Uninterruptibles; |
63 | 67 | import java.io.ByteArrayOutputStream; |
64 | 68 | import java.io.IOException; |
65 | 69 | import java.io.InputStream; |
|
70 | 74 | import java.util.ArrayList; |
71 | 75 | import java.util.Arrays; |
72 | 76 | import java.util.Calendar; |
| 77 | +import java.util.Collection; |
73 | 78 | import java.util.Date; |
74 | 79 | import java.util.List; |
75 | 80 | import java.util.Map; |
| 81 | +import java.util.concurrent.CyclicBarrier; |
| 82 | +import java.util.concurrent.ExecutorService; |
| 83 | +import java.util.concurrent.Executors; |
| 84 | +import java.util.concurrent.Future; |
| 85 | +import java.util.concurrent.TimeUnit; |
| 86 | +import java.util.concurrent.atomic.AtomicInteger; |
76 | 87 | import org.junit.jupiter.api.BeforeEach; |
77 | 88 | import org.junit.jupiter.api.Test; |
78 | 89 |
|
@@ -1262,6 +1273,75 @@ void refreshAccessToken_afterSerialization_success() throws IOException, ClassNo |
1262 | 1273 | assertEquals(ACCESS_TOKEN, token.getTokenValue()); |
1263 | 1274 | } |
1264 | 1275 |
|
| 1276 | + @Test |
| 1277 | + void refreshAccessToken_concurrentColdStart_scopesAndRefreshesSourceCredentialsOnce() |
| 1278 | + throws Exception { |
| 1279 | + int numThreads = 16; |
| 1280 | + AtomicInteger createScopedCount = new AtomicInteger(0); |
| 1281 | + AtomicInteger sourceRefreshCount = new AtomicInteger(0); |
| 1282 | + |
| 1283 | + GoogleCredentials coldSourceCredentials = |
| 1284 | + new GoogleCredentials() { |
| 1285 | + @Override |
| 1286 | + public GoogleCredentials createScoped(Collection<String> scopes) { |
| 1287 | + createScopedCount.incrementAndGet(); |
| 1288 | + return new GoogleCredentials() { |
| 1289 | + @Override |
| 1290 | + public AccessToken refreshAccessToken() { |
| 1291 | + sourceRefreshCount.incrementAndGet(); |
| 1292 | + Uninterruptibles.sleepUninterruptibly(50, TimeUnit.MILLISECONDS); |
| 1293 | + return new AccessToken( |
| 1294 | + "source-token", new Date(System.currentTimeMillis() + 3600_000L)); |
| 1295 | + } |
| 1296 | + }; |
| 1297 | + } |
| 1298 | + }; |
| 1299 | + |
| 1300 | + ImpersonatedCredentials impersonatedCredentials = |
| 1301 | + (ImpersonatedCredentials) |
| 1302 | + ImpersonatedCredentials.create( |
| 1303 | + coldSourceCredentials, |
| 1304 | + IMPERSONATED_CLIENT_EMAIL, |
| 1305 | + null, |
| 1306 | + ImmutableList.of(), |
| 1307 | + VALID_LIFETIME, |
| 1308 | + () -> |
| 1309 | + new MockHttpTransport.Builder() |
| 1310 | + .setLowLevelHttpResponse( |
| 1311 | + new MockLowLevelHttpResponse() |
| 1312 | + .setContentType(Json.MEDIA_TYPE) |
| 1313 | + .setContent( |
| 1314 | + String.format( |
| 1315 | + "{\"accessToken\":\"%s\",\"expireTime\":\"%s\"}", |
| 1316 | + ACCESS_TOKEN, getDefaultExpireTime()))) |
| 1317 | + .build()) |
| 1318 | + .createScoped(IMMUTABLE_SCOPES_LIST); |
| 1319 | + |
| 1320 | + assertEquals(1, createScopedCount.get()); |
| 1321 | + |
| 1322 | + CyclicBarrier barrier = new CyclicBarrier(numThreads); |
| 1323 | + ExecutorService executor = Executors.newFixedThreadPool(numThreads); |
| 1324 | + try { |
| 1325 | + List<Future<AccessToken>> futures = new ArrayList<>(numThreads); |
| 1326 | + for (int i = 0; i < numThreads; i++) { |
| 1327 | + futures.add( |
| 1328 | + executor.submit( |
| 1329 | + () -> { |
| 1330 | + barrier.await(5, TimeUnit.SECONDS); |
| 1331 | + return impersonatedCredentials.refreshAccessToken(); |
| 1332 | + })); |
| 1333 | + } |
| 1334 | + for (Future<AccessToken> future : futures) { |
| 1335 | + assertEquals(ACCESS_TOKEN, future.get(10, TimeUnit.SECONDS).getTokenValue()); |
| 1336 | + } |
| 1337 | + } finally { |
| 1338 | + executor.shutdownNow(); |
| 1339 | + } |
| 1340 | + |
| 1341 | + assertEquals(1, createScopedCount.get()); |
| 1342 | + assertEquals(1, sourceRefreshCount.get()); |
| 1343 | + } |
| 1344 | + |
1265 | 1345 | public static String getDefaultExpireTime() { |
1266 | 1346 | return Instant.now().plusSeconds(VALID_LIFETIME).truncatedTo(ChronoUnit.SECONDS).toString(); |
1267 | 1347 | } |
|
0 commit comments