From d4d9a38e392c89ce132b1c7f4012d20ea57f7481 Mon Sep 17 00:00:00 2001 From: Sneha Sahu Date: Mon, 2 Nov 2026 13:12:32 +0000 Subject: [PATCH 1/2] feat(storage): add Rapid Cache samples Adds create, get, list, update and disable Rapid Cache samples for Storage Control, with an integration test. Bumps libraries-bom to 26.90.0 (storage-control 2.75.0 with RapidCache). b/482946642 [Generated-by: AI] --- .../samples/install-without-bom/pom.xml | 6 +- java-storage/samples/snippets/pom.xml | 4 +- .../storage/control/v2/RapidCacheCreate.java | 64 +++++++ .../storage/control/v2/RapidCacheDisable.java | 59 ++++++ .../storage/control/v2/RapidCacheGet.java | 52 ++++++ .../storage/control/v2/RapidCacheList.java | 52 ++++++ .../storage/control/v2/RapidCacheUpdate.java | 73 ++++++++ .../storage/control/v2/ITRapidCacheTest.java | 170 ++++++++++++++++++ 8 files changed, 475 insertions(+), 5 deletions(-) create mode 100644 java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheCreate.java create mode 100644 java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheDisable.java create mode 100644 java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheGet.java create mode 100644 java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheList.java create mode 100644 java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheUpdate.java create mode 100644 java-storage/samples/snippets/src/test/java/com/example/storage/control/v2/ITRapidCacheTest.java diff --git a/java-storage/samples/install-without-bom/pom.xml b/java-storage/samples/install-without-bom/pom.xml index acea9f11b9e5..013d0a397326 100644 --- a/java-storage/samples/install-without-bom/pom.xml +++ b/java-storage/samples/install-without-bom/pom.xml @@ -30,12 +30,12 @@ com.google.cloud google-cloud-storage - 2.64.0 + 2.75.0 com.google.cloud google-cloud-storage-control - 2.64.0 + 2.75.0 @@ -78,7 +78,7 @@ com.google.cloud google-cloud-storage - 2.64.0 + 2.75.0 tests test diff --git a/java-storage/samples/snippets/pom.xml b/java-storage/samples/snippets/pom.xml index b261b5110150..6394382cf2ad 100644 --- a/java-storage/samples/snippets/pom.xml +++ b/java-storage/samples/snippets/pom.xml @@ -31,7 +31,7 @@ com.google.cloud libraries-bom - 26.78.0 + 26.90.0 pom import @@ -99,7 +99,7 @@ com.google.cloud google-cloud-storage - 2.64.0 + 2.75.0 tests test diff --git a/java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheCreate.java b/java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheCreate.java new file mode 100644 index 000000000000..1940438681d9 --- /dev/null +++ b/java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheCreate.java @@ -0,0 +1,64 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.storage.control.v2; + +// [START storage_control_create_rapid_cache] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.storage.control.v2.BucketName; +import com.google.storage.control.v2.CreateRapidCacheMetadata; +import com.google.storage.control.v2.CreateRapidCacheRequest; +import com.google.storage.control.v2.RapidCache; +import com.google.storage.control.v2.StorageControlClient; +import java.io.IOException; +import java.util.concurrent.ExecutionException; + +public final class RapidCacheCreate { + + public static void rapidCacheCreate(String bucketName, String zoneName) + throws InterruptedException, ExecutionException, IOException { + // The ID of your GCS bucket + // String bucketName = "your-unique-bucket-name"; + + // The zone in which to create the Rapid Cache. The zone must be in the same region as the + // bucket. + // String zoneName = "us-central1-a"; + + try (StorageControlClient storageControl = StorageControlClient.create()) { + + // Rapid Cache Ultra requires a regional bucket with hierarchical namespace enabled. + RapidCache rapidCache = + RapidCache.newBuilder().setZone(zoneName).setCacheType("rapid-cache-ultra").build(); + + CreateRapidCacheRequest request = + CreateRapidCacheRequest.newBuilder() + // Set project to "_" to signify globally scoped bucket + .setParent(BucketName.format("_", bucketName)) + .setRapidCache(rapidCache) + .build(); + + // Start a long-running operation (LRO). + OperationFuture operation = + storageControl.createRapidCacheAsync(request); + + // Await the LRO's completion. + RapidCache createdRapidCache = operation.get(); + System.out.printf("Created Rapid Cache: %s%n", createdRapidCache.getName()); + } + } +} +// [END storage_control_create_rapid_cache] diff --git a/java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheDisable.java b/java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheDisable.java new file mode 100644 index 000000000000..a7b3349fb81a --- /dev/null +++ b/java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheDisable.java @@ -0,0 +1,59 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.storage.control.v2; + +// [START storage_control_disable_rapid_cache] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.storage.control.v2.DisableRapidCacheMetadata; +import com.google.storage.control.v2.DisableRapidCacheRequest; +import com.google.storage.control.v2.RapidCache; +import com.google.storage.control.v2.RapidCacheName; +import com.google.storage.control.v2.StorageControlClient; +import java.io.IOException; +import java.util.concurrent.ExecutionException; + +public final class RapidCacheDisable { + + public static void rapidCacheDisable(String bucketName, String cacheId) + throws InterruptedException, ExecutionException, IOException { + // The ID of your GCS bucket + // String bucketName = "your-unique-bucket-name"; + + // The ID of the Rapid Cache + // String cacheId = "your-rapid-cache-id"; + + try (StorageControlClient storageControl = StorageControlClient.create()) { + + // Set project to "_" to signify globally scoped bucket + String rapidCacheName = RapidCacheName.format("_", bucketName, cacheId); + DisableRapidCacheRequest request = + DisableRapidCacheRequest.newBuilder().setName(rapidCacheName).build(); + + // Disabling a Rapid Cache starts a long-running operation (LRO). The cache moves to the + // "disabled" state within seconds, but the operation only completes once the cache has been + // fully removed, which can take more than 30 minutes. This sample does not wait for the + // operation to complete; use RapidCacheGet to check the cache state. + OperationFuture operation = + storageControl.disableRapidCacheAsync(request); + + System.out.printf("Disabling Rapid Cache: %s%n", rapidCacheName); + System.out.printf("Operation: %s%n", operation.getName()); + } + } +} +// [END storage_control_disable_rapid_cache] diff --git a/java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheGet.java b/java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheGet.java new file mode 100644 index 000000000000..1d7f3e215728 --- /dev/null +++ b/java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheGet.java @@ -0,0 +1,52 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.storage.control.v2; + +// [START storage_control_get_rapid_cache] + +import com.google.storage.control.v2.GetRapidCacheRequest; +import com.google.storage.control.v2.RapidCache; +import com.google.storage.control.v2.RapidCacheName; +import com.google.storage.control.v2.StorageControlClient; +import java.io.IOException; + +public final class RapidCacheGet { + + public static void rapidCacheGet(String bucketName, String cacheId) throws IOException { + // The ID of your GCS bucket + // String bucketName = "your-unique-bucket-name"; + + // The ID of the Rapid Cache + // String cacheId = "your-rapid-cache-id"; + + try (StorageControlClient storageControl = StorageControlClient.create()) { + + GetRapidCacheRequest request = + GetRapidCacheRequest.newBuilder() + // Set project to "_" to signify globally scoped bucket + .setName(RapidCacheName.format("_", bucketName, cacheId)) + .build(); + + RapidCache rapidCache = storageControl.getRapidCache(request); + + System.out.printf("Got Rapid Cache: %s%n", rapidCache.getName()); + System.out.printf("Zone: %s%n", rapidCache.getZone()); + System.out.printf("State: %s%n", rapidCache.getState()); + } + } +} +// [END storage_control_get_rapid_cache] diff --git a/java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheList.java b/java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheList.java new file mode 100644 index 000000000000..a55b7a9202ad --- /dev/null +++ b/java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheList.java @@ -0,0 +1,52 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.storage.control.v2; + +// [START storage_control_list_rapid_caches] + +import com.google.storage.control.v2.BucketName; +import com.google.storage.control.v2.ListRapidCachesRequest; +import com.google.storage.control.v2.RapidCache; +import com.google.storage.control.v2.StorageControlClient; +import com.google.storage.control.v2.StorageControlClient.ListRapidCachesPagedResponse; +import java.io.IOException; + +public final class RapidCacheList { + + public static void rapidCacheList(String bucketName) throws IOException { + // The ID of your GCS bucket + // String bucketName = "your-unique-bucket-name"; + + try (StorageControlClient storageControl = StorageControlClient.create()) { + + ListRapidCachesRequest request = + ListRapidCachesRequest.newBuilder() + // Set project to "_" to signify globally scoped bucket + .setParent(BucketName.format("_", bucketName)) + .build(); + + // The paged response handles pagination automatically. + ListRapidCachesPagedResponse response = storageControl.listRapidCaches(request); + for (RapidCache rapidCache : response.iterateAll()) { + System.out.println(rapidCache.getName()); + } + + System.out.printf("Listed Rapid Caches in bucket %s%n", bucketName); + } + } +} +// [END storage_control_list_rapid_caches] diff --git a/java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheUpdate.java b/java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheUpdate.java new file mode 100644 index 000000000000..22af896b191b --- /dev/null +++ b/java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheUpdate.java @@ -0,0 +1,73 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.storage.control.v2; + +// [START storage_control_update_rapid_cache] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.protobuf.Duration; +import com.google.protobuf.FieldMask; +import com.google.storage.control.v2.RapidCache; +import com.google.storage.control.v2.RapidCacheName; +import com.google.storage.control.v2.StorageControlClient; +import com.google.storage.control.v2.UpdateRapidCacheMetadata; +import com.google.storage.control.v2.UpdateRapidCacheRequest; +import java.io.IOException; +import java.util.concurrent.ExecutionException; + +public final class RapidCacheUpdate { + + public static void rapidCacheUpdate(String bucketName, String cacheId, long ttlSeconds) + throws InterruptedException, ExecutionException, IOException { + // The ID of your GCS bucket + // String bucketName = "your-unique-bucket-name"; + + // The ID of the Rapid Cache + // String cacheId = "your-rapid-cache-id"; + + // The new time to live (TTL) for cache entries, in seconds. The TTL must be between 1 hour and + // 7 days. + // long ttlSeconds = 172800; // 2 days + + try (StorageControlClient storageControl = StorageControlClient.create()) { + + RapidCache pendingUpdate = + RapidCache.newBuilder() + // Set project to "_" to signify globally scoped bucket + .setName(RapidCacheName.format("_", bucketName, cacheId)) + .setTtl(Duration.newBuilder().setSeconds(ttlSeconds).build()) + .build(); + + UpdateRapidCacheRequest request = + UpdateRapidCacheRequest.newBuilder() + .setRapidCache(pendingUpdate) + // Only the fields listed in the update mask are changed. + .setUpdateMask(FieldMask.newBuilder().addPaths("ttl").build()) + .build(); + + // Start a long-running operation (LRO). + OperationFuture operation = + storageControl.updateRapidCacheAsync(request); + + // Await the LRO's completion. + RapidCache updatedRapidCache = operation.get(); + System.out.printf("Updated Rapid Cache: %s%n", updatedRapidCache.getName()); + System.out.printf("TTL: %d seconds%n", updatedRapidCache.getTtl().getSeconds()); + } + } +} +// [END storage_control_update_rapid_cache] diff --git a/java-storage/samples/snippets/src/test/java/com/example/storage/control/v2/ITRapidCacheTest.java b/java-storage/samples/snippets/src/test/java/com/example/storage/control/v2/ITRapidCacheTest.java new file mode 100644 index 000000000000..8953deb3528b --- /dev/null +++ b/java-storage/samples/snippets/src/test/java/com/example/storage/control/v2/ITRapidCacheTest.java @@ -0,0 +1,170 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.storage.control.v2; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assume.assumeTrue; + +import com.example.storage.TestBase; +import com.google.cloud.storage.BucketInfo; +import com.google.cloud.storage.BucketInfo.HierarchicalNamespace; +import com.google.cloud.storage.BucketInfo.IamConfiguration; +import com.google.cloud.storage.StorageException; +import com.google.cloud.storage.it.runner.registry.Zone; +import com.google.cloud.storage.testing.RemoteStorageHelper; +import com.google.common.base.MoreObjects; +import com.google.common.base.Strings; +import com.google.storage.control.v2.RapidCacheName; +import java.util.concurrent.TimeUnit; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public final class ITRapidCacheTest extends TestBase { + + private static final String CREATED_PREFIX = "Created Rapid Cache: "; + // Poll for up to 2 minutes for the cache to reach the "disabled" state. + private static final int DISABLE_POLL_ATTEMPTS = 12; + private static final long DISABLE_POLL_INTERVAL_SECONDS = 10; + // Bucket deletion is rejected while the Rapid Cache is still being removed. + private static final long BUCKET_DELETE_TIMEOUT_SECONDS = 120; + private static final long BUCKET_DELETE_INTERVAL_SECONDS = 15; + + // The zone in which to create the Rapid Cache. Override with the RAPID_CACHE_ZONE environment + // variable. + private static final Zone ZONE = + Zone.parse( + MoreObjects.firstNonNull( + Strings.emptyToNull(System.getenv("RAPID_CACHE_ZONE")), "us-central1-a")); + + private String bucketName; + private int outputOffset; + + @Before + public void setUp() { + assumeTrue( + "RapidCache sample tests skipped in CI due to very long operation times.", + Strings.isNullOrEmpty(System.getenv("JOB_TYPE"))); + // Rapid Cache Ultra requires a regional bucket with hierarchical namespace enabled, located in + // the same region as the cache zone. + String name = RemoteStorageHelper.generateBucketName(); + storage.create( + BucketInfo.newBuilder(name) + .setLocation(ZONE.getRegion()) + .setIamConfiguration( + // Hierarchical namespace buckets must use uniform bucket-level access. + IamConfiguration.newBuilder().setIsUniformBucketLevelAccessEnabled(true).build()) + .setHierarchicalNamespace(HierarchicalNamespace.newBuilder().setEnabled(true).build()) + .build()); + bucketName = name; + } + + @After + public void tearDown() throws InterruptedException { + if (bucketName == null) { + return; + } + // Best-effort cleanup. A bucket with a Rapid Cache cannot be deleted until the cache has been + // fully removed, which can take a long time after it is disabled. Any leftover bucket is + // removed by the stale bucket cleanup. + long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(BUCKET_DELETE_TIMEOUT_SECONDS); + while (true) { + try { + storage.delete(bucketName); + return; + } catch (StorageException e) { + if (e.getCode() != 409 || System.nanoTime() > deadline) { + System.err.printf("Leaving bucket %s for cleanup: %s%n", bucketName, e.getMessage()); + return; + } + TimeUnit.SECONDS.sleep(BUCKET_DELETE_INTERVAL_SECONDS); + } + } + } + + @Test + public void createGetListUpdateDisable() throws Exception { + // Test create Rapid Cache + RapidCacheCreate.rapidCacheCreate(bucketName, ZONE.getZone()); + String out = newOutput(); + assertThat(out).contains(CREATED_PREFIX); + String cacheName = + out.substring(out.indexOf(CREATED_PREFIX) + CREATED_PREFIX.length()).trim().split("\\s")[0]; + String cacheId = RapidCacheName.parse(cacheName).getRapidCache(); + assertThat(cacheId).isNotEmpty(); + + Throwable failure = null; + try { + // Test get Rapid Cache + RapidCacheGet.rapidCacheGet(bucketName, cacheId); + out = newOutput(); + assertThat(out).contains(cacheName); + assertThat(out).contains("Zone: " + ZONE.getZone()); + assertThat(out).contains("State: "); + + // Test list Rapid Caches + RapidCacheList.rapidCacheList(bucketName); + assertThat(newOutput()).contains(cacheName); + + // Test update Rapid Cache + RapidCacheUpdate.rapidCacheUpdate(bucketName, cacheId, 172800); + out = newOutput(); + assertThat(out).contains("Updated Rapid Cache: " + cacheName); + assertThat(out).contains("TTL: 172800 seconds"); + } catch (Throwable t) { + failure = t; + throw t; + } finally { + // Disable the Rapid Cache. This also cleans up the cache so that the bucket can be deleted + // once the cache has been fully removed. + newOutput(); + try { + RapidCacheDisable.rapidCacheDisable(bucketName, cacheId); + } catch (Exception e) { + if (failure == null) { + throw e; + } + // Keep the original test failure as the primary error. + failure.addSuppressed(e); + } + } + + // Test disable Rapid Cache + String disableOut = newOutput(); + assertThat(disableOut).contains("Disabling Rapid Cache: " + cacheName); + assertThat(disableOut).contains("Operation: "); + + // The cache moves to the "disabled" state shortly after the request. + for (int i = 0; i < DISABLE_POLL_ATTEMPTS; i++) { + RapidCacheGet.rapidCacheGet(bucketName, cacheId); + out = newOutput(); + if (out.contains("State: disabled")) { + break; + } + TimeUnit.SECONDS.sleep(DISABLE_POLL_INTERVAL_SECONDS); + } + assertThat(out).contains("State: disabled"); + } + + /** Returns the output captured since the previous call. */ + private String newOutput() { + String all = stdOut.getCapturedOutputAsUtf8String(); + String out = all.substring(outputOffset); + outputOffset = all.length(); + return out; + } +} From 255524037ac4412dcd4da9f310ef00689546a3aa Mon Sep 17 00:00:00 2001 From: Sneha Sahu Date: Mon, 2 Nov 2026 18:29:42 +0000 Subject: [PATCH 2/2] docs(storage): align rapid cache samples with reference integration test Set the explicit cache name (ID = zone), cache_type rapid-cache-ultra and ttl 24h on create; wait for the disable LRO; tests assert running state, cache type, list membership and the updated ttl, and bound the disable wait. Aligns with googleapis/google-cloud-java#14529. b/482946642 [Generated-by: AI] --- .../storage/control/v2/RapidCacheCreate.java | 13 +- .../storage/control/v2/RapidCacheDisable.java | 14 +- .../storage/control/v2/RapidCacheGet.java | 1 + .../storage/control/v2/ITRapidCacheTest.java | 138 +++++++++++------- 4 files changed, 105 insertions(+), 61 deletions(-) diff --git a/java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheCreate.java b/java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheCreate.java index 1940438681d9..f9d265996a51 100644 --- a/java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheCreate.java +++ b/java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheCreate.java @@ -19,10 +19,12 @@ // [START storage_control_create_rapid_cache] import com.google.api.gax.longrunning.OperationFuture; +import com.google.protobuf.Duration; import com.google.storage.control.v2.BucketName; import com.google.storage.control.v2.CreateRapidCacheMetadata; import com.google.storage.control.v2.CreateRapidCacheRequest; import com.google.storage.control.v2.RapidCache; +import com.google.storage.control.v2.RapidCacheName; import com.google.storage.control.v2.StorageControlClient; import java.io.IOException; import java.util.concurrent.ExecutionException; @@ -42,7 +44,14 @@ public static void rapidCacheCreate(String bucketName, String zoneName) // Rapid Cache Ultra requires a regional bucket with hierarchical namespace enabled. RapidCache rapidCache = - RapidCache.newBuilder().setZone(zoneName).setCacheType("rapid-cache-ultra").build(); + RapidCache.newBuilder() + // The Rapid Cache ID must be the same as the zone. + .setName(RapidCacheName.format("_", bucketName, zoneName)) + .setZone(zoneName) + .setCacheType("rapid-cache-ultra") + // How long a cache entry can live (between 1 hour and 7 days). 86400s = 24 hours. + .setTtl(Duration.newBuilder().setSeconds(86400).build()) + .build(); CreateRapidCacheRequest request = CreateRapidCacheRequest.newBuilder() @@ -58,6 +67,8 @@ public static void rapidCacheCreate(String bucketName, String zoneName) // Await the LRO's completion. RapidCache createdRapidCache = operation.get(); System.out.printf("Created Rapid Cache: %s%n", createdRapidCache.getName()); + System.out.printf("Cache type: %s%n", createdRapidCache.getCacheType()); + System.out.printf("State: %s%n", createdRapidCache.getState()); } } } diff --git a/java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheDisable.java b/java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheDisable.java index a7b3349fb81a..0442a04249f3 100644 --- a/java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheDisable.java +++ b/java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheDisable.java @@ -44,15 +44,17 @@ public static void rapidCacheDisable(String bucketName, String cacheId) DisableRapidCacheRequest request = DisableRapidCacheRequest.newBuilder().setName(rapidCacheName).build(); - // Disabling a Rapid Cache starts a long-running operation (LRO). The cache moves to the - // "disabled" state within seconds, but the operation only completes once the cache has been - // fully removed, which can take more than 30 minutes. This sample does not wait for the - // operation to complete; use RapidCacheGet to check the cache state. + // Start a long-running operation (LRO). OperationFuture operation = storageControl.disableRapidCacheAsync(request); - System.out.printf("Disabling Rapid Cache: %s%n", rapidCacheName); - System.out.printf("Operation: %s%n", operation.getName()); + // Await the LRO's completion. The cache moves to the "disabled" state within seconds, but + // the operation only completes once the cache has been fully removed, which can take longer + // than the client's default polling timeout. If the wait times out, the cache is already + // disabled; use RapidCacheGet to check its state. + RapidCache disabledRapidCache = operation.get(); + System.out.printf("Disabled Rapid Cache: %s%n", disabledRapidCache.getName()); + System.out.printf("State: %s%n", disabledRapidCache.getState()); } } } diff --git a/java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheGet.java b/java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheGet.java index 1d7f3e215728..2e6a57557f25 100644 --- a/java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheGet.java +++ b/java-storage/samples/snippets/src/main/java/com/example/storage/control/v2/RapidCacheGet.java @@ -45,6 +45,7 @@ public static void rapidCacheGet(String bucketName, String cacheId) throws IOExc System.out.printf("Got Rapid Cache: %s%n", rapidCache.getName()); System.out.printf("Zone: %s%n", rapidCache.getZone()); + System.out.printf("Cache type: %s%n", rapidCache.getCacheType()); System.out.printf("State: %s%n", rapidCache.getState()); } } diff --git a/java-storage/samples/snippets/src/test/java/com/example/storage/control/v2/ITRapidCacheTest.java b/java-storage/samples/snippets/src/test/java/com/example/storage/control/v2/ITRapidCacheTest.java index 8953deb3528b..b6d733f4d34d 100644 --- a/java-storage/samples/snippets/src/test/java/com/example/storage/control/v2/ITRapidCacheTest.java +++ b/java-storage/samples/snippets/src/test/java/com/example/storage/control/v2/ITRapidCacheTest.java @@ -29,17 +29,27 @@ import com.google.common.base.MoreObjects; import com.google.common.base.Strings; import com.google.storage.control.v2.RapidCacheName; +import com.google.storage.control.v2.StorageControlClient; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import org.junit.After; import org.junit.Before; import org.junit.Test; public final class ITRapidCacheTest extends TestBase { - private static final String CREATED_PREFIX = "Created Rapid Cache: "; - // Poll for up to 2 minutes for the cache to reach the "disabled" state. - private static final int DISABLE_POLL_ATTEMPTS = 12; - private static final long DISABLE_POLL_INTERVAL_SECONDS = 10; + private static final String CACHE_TYPE = "rapid-cache-ultra"; + // Wait for the bucket metadata to propagate before creating the Rapid Cache. + private static final long BUCKET_PROPAGATION_SECONDS = 20; + // The disable operation only completes once the cache has been fully removed, which can take + // more than an hour. The cache moves to the "disabled" state within seconds, so the test bounds + // the wait and then checks the state instead. + private static final long DISABLE_TIMEOUT_MINUTES = 10; // Bucket deletion is rejected while the Rapid Cache is still being removed. private static final long BUCKET_DELETE_TIMEOUT_SECONDS = 120; private static final long BUCKET_DELETE_INTERVAL_SECONDS = 15; @@ -52,10 +62,13 @@ public final class ITRapidCacheTest extends TestBase { Strings.emptyToNull(System.getenv("RAPID_CACHE_ZONE")), "us-central1-a")); private String bucketName; + // The Rapid Cache ID must be the same as the zone. + private final String cacheId = ZONE.getZone(); + private boolean cacheDisabled; private int outputOffset; @Before - public void setUp() { + public void setUp() throws InterruptedException { assumeTrue( "RapidCache sample tests skipped in CI due to very long operation times.", Strings.isNullOrEmpty(System.getenv("JOB_TYPE"))); @@ -71,6 +84,7 @@ public void setUp() { .setHierarchicalNamespace(HierarchicalNamespace.newBuilder().setEnabled(true).build()) .build()); bucketName = name; + TimeUnit.SECONDS.sleep(BUCKET_PROPAGATION_SECONDS); } @After @@ -78,6 +92,9 @@ public void tearDown() throws InterruptedException { if (bucketName == null) { return; } + if (!cacheDisabled) { + disableQuietly(); + } // Best-effort cleanup. A bucket with a Rapid Cache cannot be deleted until the cache has been // fully removed, which can take a long time after it is disabled. Any leftover bucket is // removed by the stale bucket cleanup. @@ -98,66 +115,79 @@ public void tearDown() throws InterruptedException { @Test public void createGetListUpdateDisable() throws Exception { + String cacheName = RapidCacheName.format("_", bucketName, cacheId); + // Test create Rapid Cache RapidCacheCreate.rapidCacheCreate(bucketName, ZONE.getZone()); String out = newOutput(); - assertThat(out).contains(CREATED_PREFIX); - String cacheName = - out.substring(out.indexOf(CREATED_PREFIX) + CREATED_PREFIX.length()).trim().split("\\s")[0]; - String cacheId = RapidCacheName.parse(cacheName).getRapidCache(); - assertThat(cacheId).isNotEmpty(); - - Throwable failure = null; + assertThat(out).contains("Created Rapid Cache: " + cacheName); + assertThat(out).contains("Cache type: " + CACHE_TYPE); + + // Test get Rapid Cache + RapidCacheGet.rapidCacheGet(bucketName, cacheId); + out = newOutput(); + assertThat(out).contains("Got Rapid Cache: " + cacheName); + assertThat(out).contains("Zone: " + ZONE.getZone()); + assertThat(out).contains("Cache type: " + CACHE_TYPE); + assertThat(out).contains("State: running"); + + // Test list Rapid Caches + RapidCacheList.rapidCacheList(bucketName); + assertThat(newOutput()).contains(cacheName); + + // Test update Rapid Cache + RapidCacheUpdate.rapidCacheUpdate(bucketName, cacheId, 172800); + out = newOutput(); + assertThat(out).contains("Updated Rapid Cache: " + cacheName); + assertThat(out).contains("TTL: 172800 seconds"); + + // Test disable Rapid Cache. The sample waits for the operation to complete, so run it with a + // bounded wait. + ExecutorService executor = Executors.newSingleThreadExecutor(); try { - // Test get Rapid Cache - RapidCacheGet.rapidCacheGet(bucketName, cacheId); - out = newOutput(); - assertThat(out).contains(cacheName); - assertThat(out).contains("Zone: " + ZONE.getZone()); - assertThat(out).contains("State: "); - - // Test list Rapid Caches - RapidCacheList.rapidCacheList(bucketName); - assertThat(newOutput()).contains(cacheName); - - // Test update Rapid Cache - RapidCacheUpdate.rapidCacheUpdate(bucketName, cacheId, 172800); - out = newOutput(); - assertThat(out).contains("Updated Rapid Cache: " + cacheName); - assertThat(out).contains("TTL: 172800 seconds"); - } catch (Throwable t) { - failure = t; - throw t; - } finally { - // Disable the Rapid Cache. This also cleans up the cache so that the bucket can be deleted - // once the cache has been fully removed. - newOutput(); + Future disable = + executor.submit( + () -> { + RapidCacheDisable.rapidCacheDisable(bucketName, cacheId); + return null; + }); try { - RapidCacheDisable.rapidCacheDisable(bucketName, cacheId); - } catch (Exception e) { - if (failure == null) { + disable.get(DISABLE_TIMEOUT_MINUTES, TimeUnit.MINUTES); + cacheDisabled = true; + assertThat(newOutput()).contains("Disabled Rapid Cache: " + cacheName); + } catch (TimeoutException | ExecutionException e) { + // A TimeoutException means our wait expired. A CancellationException means the client + // stopped polling the operation (the default LRO polling timeout is 5 minutes). In both + // cases the operation is still running on the server, and the cache must already be in + // the "disabled" state. Any other error fails the test. + if (e instanceof ExecutionException && !(e.getCause() instanceof CancellationException)) { throw e; } - // Keep the original test failure as the primary error. - failure.addSuppressed(e); + // Cancelling the local wait does not cancel the operation on the server. + disable.cancel(true); + cacheDisabled = true; + newOutput(); + RapidCacheGet.rapidCacheGet(bucketName, cacheId); + assertThat(newOutput()).contains("State: disabled"); } + } finally { + executor.shutdownNow(); } + } - // Test disable Rapid Cache - String disableOut = newOutput(); - assertThat(disableOut).contains("Disabling Rapid Cache: " + cacheName); - assertThat(disableOut).contains("Operation: "); - - // The cache moves to the "disabled" state shortly after the request. - for (int i = 0; i < DISABLE_POLL_ATTEMPTS; i++) { - RapidCacheGet.rapidCacheGet(bucketName, cacheId); - out = newOutput(); - if (out.contains("State: disabled")) { - break; - } - TimeUnit.SECONDS.sleep(DISABLE_POLL_INTERVAL_SECONDS); + /** + * Used in teardown: starts disabling the Rapid Cache without waiting for the operation, ignoring + * errors (for example, if the cache was never created). + */ + private void disableQuietly() { + try (StorageControlClient storageControl = StorageControlClient.create()) { + storageControl + .disableRapidCacheAsync(RapidCacheName.format("_", bucketName, cacheId)) + .getInitialFuture() + .get(1, TimeUnit.MINUTES); + } catch (Exception e) { + System.err.printf("Failed to disable Rapid Cache in bucket %s: %s%n", bucketName, e); } - assertThat(out).contains("State: disabled"); } /** Returns the output captured since the previous call. */