Repository: googleapis/java-storage
Pull request #1152 (2021-11-17 20:32:01)

docs: add new sample storage_configure_retries
Outstanding items:
* [x] Libraries bom updated to a version which includes google-cloud-storage >= v2.2.0


snippet-bot[bot]: <!-- probot comment [11299897]-->
Here is the summary of changes.
<details>
  <summary>You are about to add 1 region tag.</summary>

  - [samples/snippets/src/main/java/com/example/storage/ConfigureRetries.java:18](https://github.com/BenWhitehead/java-storage/blob/5fa29a85925e2d97ca78d8179b40c915a812d531/samples/snippets/src/main/java/com/example/storage/ConfigureRetries.java#L18), tag `storage_configure_retries`

</details>

---
This comment is generated by [snippet-bot](https://github.com/apps/snippet-bot).
If you find problems with this result, please file an issue at:
https://github.com/googleapis/repo-automation-bots/issues.
To update this comment, add `snippet-bot:force-run` label or use the checkbox below:
- [ ] Refresh this comment


Patch:
diff --git a/.repo-metadata.json b/.repo-metadata.json
index 6b3a63ae..b258be10 100644
--- a/.repo-metadata.json
+++ b/.repo-metadata.json
@@ -13,6 +13,7 @@
   "codeowner_team": "@googleapis/cloud-storage-dpe",
   "api_id": "storage.googleapis.com",
   "requires_billing": true,
+  "library_type": "GAPIC_COMBO",
   "extra_versioned_modules": "gapic-google-cloud-storage-v2",
   "excluded_poms": "google-cloud-storage-bom,google-cloud-storage"
 }
diff --git a/README.md b/README.md
index 1ffef84c..c9fcb38d 100644
--- a/README.md
+++ b/README.md
@@ -227,6 +227,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-storage/tree/
 
 | Sample                      | Source Code                       | Try it |
 | --------------------------- | --------------------------------- | ------ |
+| Configure Retries | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/ConfigureRetries.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/ConfigureRetries.java) |
 | Quickstart Sample | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/QuickstartSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/QuickstartSample.java) |
 
 
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/PolicyHelper.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/PolicyHelper.java
index e198d853..ca3e8bc3 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/PolicyHelper.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/PolicyHelper.java
@@ -34,8 +34,8 @@ class PolicyHelper {
   static Policy convertFromApiPolicy(com.google.api.services.storage.model.Policy apiPolicy) {
     Policy.Builder policyBuilder = Policy.newBuilder();
     List<Bindings> bindings = apiPolicy.getBindings();
+    ImmutableList.Builder<Binding> coreBindings = ImmutableList.builder();
     if (null != bindings && !bindings.isEmpty()) {
-      ImmutableList.Builder<Binding> coreBindings = ImmutableList.builder();
       for (Bindings binding : bindings) {
         Binding.Builder bindingBuilder = Binding.newBuilder();
         bindingBuilder.setRole(binding.getRole());
@@ -49,10 +49,8 @@ class PolicyHelper {
         }
         coreBindings.add(bindingBuilder.build());
       }
-      policyBuilder.setBindings(coreBindings.build());
-    } else {
-      throw new IllegalStateException("Missing required bindings.");
     }
+    policyBuilder.setBindings(coreBindings.build());
     return policyBuilder.setEtag(apiPolicy.getEtag()).setVersion(apiPolicy.getVersion()).build();
   }
 
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java
index 02bdb8dd..21185994 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java
@@ -3540,6 +3540,11 @@ public interface Storage extends Service<StorageOptions> {
   /**
    * Gets the IAM policy for the provided bucket.
    *
+   * <p>It's possible for bindings to be empty and instead have permissions inherited through
+   * Project or Organization IAM Policies. To prevent corrupting policies when you update an IAM
+   * policy with {@code Storage.setIamPolicy}, the ETAG value is used to perform optimistic
+   * concurrency.
+   *
    * <p>Example of getting the IAM policy for a bucket.
    *
    * <pre>{@code
@@ -3556,6 +3561,9 @@ public interface Storage extends Service<StorageOptions> {
   /**
    * Updates the IAM policy on the specified bucket.
    *
+   * <p>To prevent corrupting policies when you update an IAM policy with {@code
+   * Storage.setIamPolicy}, the ETAG value is used to perform optimistic concurrency.
+   *
    * <p>Example of updating the IAM policy on a bucket.
    *
    * <pre>{@code
diff --git a/samples/snippets/src/main/java/com/example/storage/ConfigureRetries.java b/samples/snippets/src/main/java/com/example/storage/ConfigureRetries.java
new file mode 100644
index 00000000..6c031e54
--- /dev/null
+++ b/samples/snippets/src/main/java/com/example/storage/ConfigureRetries.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2021 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;
+// [START storage_configure_retries]
+
+import com.google.api.gax.retrying.RetrySettings;
+import com.google.cloud.storage.BlobId;
+import com.google.cloud.storage.Storage;
+import com.google.cloud.storage.StorageOptions;
+import com.google.cloud.storage.StorageRetryStrategy;
+import org.threeten.bp.Duration;
+
+public final class ConfigureRetries {
+  public static void main(String[] args) {
+    String bucketName = "my-bucket";
+    String blobName = "blob/to/delete";
+    deleteBlob(bucketName, blobName);
+  }
+
+  static void deleteBlob(String bucketName, String blobName) {
+    // Update the retry settings
+    RetrySettings retrySettings =
+        StorageOptions.getDefaultRetrySettings()
+            .toBuilder()
+            // to set the max number of attempts to 10
+            .setMaxAttempts(10)
+            // to set the backoff multiplier to 3.0
+            .setRetryDelayMultiplier(3.0)
+            // to set the max duration of all attempts to 5 minutes
+            .setTotalTimeout(Duration.ofMinutes(5))
+            .build();
+
+    StorageOptions alwaysRetryStorageOptions =
+        StorageOptions.newBuilder()
+            // Configure our options so all requests will be retried even if they are
+            // non-idempotent.
+            .setStorageRetryStrategy(StorageRetryStrategy.getUniformStorageRetryStrategy())
+            // provide the previously configured retrySettings
+            .setRetrySettings(retrySettings)
+            .build();
+
+    // Instantiate a client with our options
+    Storage storage = alwaysRetryStorageOptions.getService();
+
+    // Delete the blob
+    BlobId blobId = BlobId.of(bucketName, blobName);
+    boolean success = storage.delete(blobId);
+
+    System.out.printf(
+        "Deletion of Blob %s completed %s.%n", blobId, success ? "successfully" : "unsuccessfully");
+  }
+}
+// [END storage_configure_retries]


Test patch:
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/PolicyHelperTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/PolicyHelperTest.java
index dd5c0f78..a34538c1 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/PolicyHelperTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/PolicyHelperTest.java
@@ -65,11 +65,15 @@ public class PolicyHelperTest {
     assertTrue(new ApiPolicyMatcher(apiPolicy).matches(actualApiPolicy));
   }
 
-  @Test(expected = IllegalStateException.class)
+  @Test
   public void testApiPolicyWithoutBinding() {
     List<Bindings> bindings = null;
     com.google.api.services.storage.model.Policy apiPolicy =
-        new com.google.api.services.storage.model.Policy().setBindings(bindings).setEtag(ETAG);
-    PolicyHelper.convertFromApiPolicy(apiPolicy);
+        new com.google.api.services.storage.model.Policy()
+            .setBindings(bindings)
+            .setEtag(ETAG)
+            .setVersion(1);
+    Policy policy = PolicyHelper.convertFromApiPolicy(apiPolicy);
+    assertEquals(policy.getBindings().size(), 0);
   }
 }
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/TestBench.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/TestBench.java
index 4dd14f40..e38076a3 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/TestBench.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/TestBench.java
@@ -239,6 +239,30 @@ public final class TestBench implements TestRule {
               e.getCause());
         } finally {
           process.destroy();
+          // wait for the server to shutdown
+          runWithRetries(
+              () -> {
+                try {
+                  listRetryTests();
+                } catch (SocketException e) {
+                  // desired result
+                  return null;
+                }
+                throw new NotShutdownException();
+              },
+              RetrySettings.newBuilder()
+                  .setTotalTimeout(Duration.ofSeconds(30))
+                  .setInitialRetryDelay(Duration.ofMillis(500))
+                  .setRetryDelayMultiplier(1.5)
+                  .setMaxRetryDelay(Duration.ofSeconds(5))
+                  .build(),
+              new BasicResultRetryAlgorithm<List<?>>() {
+                @Override
+                public boolean shouldRetry(Throwable previousThrowable, List<?> previousResponse) {
+                  return previousThrowable instanceof NotShutdownException;
+                }
+              },
+              NanoClock.getDefaultClock());
           if (cleanupStrategy == CleanupStrategy.ALWAYS
               || (success && cleanupStrategy == CleanupStrategy.ONLY_ON_SUCCESS)) {
             Files.delete(errPath);
@@ -388,4 +412,6 @@ public final class TestBench implements TestRule {
           containerName);
     }
   }
+
+  private static final class NotShutdownException extends RuntimeException {}
 }
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBlobWriteChannelTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBlobWriteChannelTest.java
index 38f2e61e..59fc07a4 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBlobWriteChannelTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBlobWriteChannelTest.java
@@ -73,13 +73,7 @@ public final class ITBlobWriteChannelTest {
 
   @ClassRule
   public static final TestBench testBench =
-      TestBench.newBuilder()
-          .setContainerName("blob-write-channel-test")
-          // set a different base uri to prevent collision with conformance tests.
-          // It's possible for ITRetryConformanceTest to finish running and start shutting down
-          // when this test starts so the liveness check passes against the shutting down server.
-          .setBaseUri("http://localhost:9100")
-          .build();
+      TestBench.newBuilder().setContainerName("blob-write-channel-test").build();
 
   @Rule public final TestName testName = new TestName();
 
diff --git a/samples/snippets/src/test/java/com/example/storage/ConfigureRetriesTest.java b/samples/snippets/src/test/java/com/example/storage/ConfigureRetriesTest.java
new file mode 100644
index 00000000..76ae1e38
--- /dev/null
+++ b/samples/snippets/src/test/java/com/example/storage/ConfigureRetriesTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2021 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.cloud.storage.Blob;
+import com.google.cloud.storage.BlobInfo;
+import com.google.cloud.storage.BucketInfo;
+import com.google.cloud.storage.Storage;
+import com.google.cloud.storage.StorageOptions;
+import com.google.cloud.storage.testing.RemoteStorageHelper;
+import com.google.cloud.testing.junit4.StdOutCaptureRule;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public final class ConfigureRetriesTest {
+  @Rule public StdOutCaptureRule stdOut = new StdOutCaptureRule();
+
+  private String bucketName;
+  private Storage storage;
+  private String blobName;
+
+  private Blob blob;
+
+  @Before
+  public void setUp() {
+    blobName = "blob";
+    bucketName = RemoteStorageHelper.generateBucketName();
+    storage = StorageOptions.getDefaultInstance().getService();
+    storage.create(BucketInfo.of(bucketName));
+    blob = storage.create(BlobInfo.newBuilder(bucketName, blobName).build());
+  }
+
+  @After
+  public void tearDown() {
+    if (blob != null && blob.exists()) {
+      blob.delete();
+    }
+    storage.delete(bucketName);
+  }
+
+  @Test
+  public void testConfigureRetries() {
+    ConfigureRetries.deleteBlob(bucketName, blobName);
+    assertThat(stdOut.getCapturedOutputAsUtf8String()).contains("Deletion");
+    assertThat(stdOut.getCapturedOutputAsUtf8String()).contains("successfully");
+    assertThat(stdOut.getCapturedOutputAsUtf8String()).doesNotContain("unsuccessfully");
+  }
+}