Repository: googleapis/java-storage
Pull request #1629 (2022-09-19 18:41:50)

chore: update @TransportCompatibility annotation for newly implemented operations


Patch:
diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml
index c27c7269..681729ae 100644
--- a/google-cloud-storage/pom.xml
+++ b/google-cloud-storage/pom.xml
@@ -35,7 +35,6 @@
     <dependency>
       <groupId>com.google.http-client</groupId>
       <artifactId>google-http-client-gson</artifactId>
-      <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>com.google.api-client</groupId>
@@ -274,6 +273,12 @@
               <dependency>net.jqwik:jqwik-engine</dependency>
               <dependency>net.jqwik:jqwik-time</dependency>
               <dependency>net.jqwik:jqwik-web</dependency>
+              <!--
+              We're only using google-http-client-gson in our tests, however if we scope it to test that will
+              break flatten plugins resolution where google-http-client-gson is needed in order for
+              google-api-client to be able to bootstrap.
+              -->
+              <dependency>com.google.http-client:google-http-client-gson</dependency>
             </ignoredDependencies>
           </configuration>
         </plugin>
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryConversions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryConversions.java
index 37505948..85afcaee 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryConversions.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryConversions.java
@@ -42,6 +42,8 @@ import com.google.api.services.storage.model.BucketAccessControl;
 import com.google.api.services.storage.model.ObjectAccessControl;
 import com.google.api.services.storage.model.StorageObject;
 import com.google.api.services.storage.model.StorageObject.Owner;
+import com.google.cloud.Binding;
+import com.google.cloud.Policy;
 import com.google.cloud.storage.Acl.Domain;
 import com.google.cloud.storage.Acl.Entity;
 import com.google.cloud.storage.Acl.Group;
@@ -118,6 +120,12 @@ final class ApiaryConversions {
   private final Codec<CustomPlacementConfig, Bucket.CustomPlacementConfig>
       customPlacementConfigCodec =
           Codec.of(this::customPlacementConfigEncode, this::customPlacementConfigDecode);
+  private final Codec<Policy, com.google.api.services.storage.model.Policy> policyCodec =
+      Codec.of(this::policyEncode, this::policyDecode);
+  private final Codec<Binding, com.google.api.services.storage.model.Policy.Bindings> bindingCodec =
+      Codec.of(this::bindingEncode, this::bindingDecode);
+  private final Codec<com.google.cloud.Condition, com.google.api.services.storage.model.Expr>
+      iamConditionCodec = Codec.of(this::conditionEncode, this::conditionDecode);
 
   private ApiaryConversions() {}
 
@@ -189,6 +197,10 @@ final class ApiaryConversions {
     return customPlacementConfigCodec;
   }
 
+  Codec<Policy, com.google.api.services.storage.model.Policy> policyCodec() {
+    return policyCodec;
+  }
+
   private StorageObject blobInfoEncode(BlobInfo from) {
     StorageObject to = blobIdEncode(from.getBlobId());
     ifNonNull(from.getAcl(), toImmutableListOf(objectAcl()::encode), to::setAcl);
@@ -790,6 +802,74 @@ final class ApiaryConversions {
     return builder.build();
   }
 
+  private com.google.api.services.storage.model.Policy policyEncode(Policy from) {
+    com.google.api.services.storage.model.Policy to =
+        new com.google.api.services.storage.model.Policy();
+    ifNonNull(from.getEtag(), to::setEtag);
+    ifNonNull(from.getVersion(), to::setVersion);
+    if (from.getBindingsList() != null) {
+      ImmutableList<com.google.api.services.storage.model.Policy.Bindings> bindings =
+          from.getBindingsList().stream()
+              .map(bindingCodec::encode)
+              .collect(ImmutableList.toImmutableList());
+      to.setBindings(bindings);
+    }
+    return to;
+  }
+
+  private Policy policyDecode(com.google.api.services.storage.model.Policy from) {
+    Policy.Builder to = Policy.newBuilder();
+    if (!from.getEtag().isEmpty()) {
+      to.setEtag(from.getEtag());
+    }
+    to.setVersion(from.getVersion());
+    if (from.getBindings() != null) {
+      ImmutableList<Binding> bindings =
+          from.getBindings().stream()
+              .map(bindingCodec::decode)
+              .collect(ImmutableList.toImmutableList());
+      to.setBindings(bindings);
+    }
+    return to.build();
+  }
+
+  private com.google.api.services.storage.model.Policy.Bindings bindingEncode(Binding from) {
+    com.google.api.services.storage.model.Policy.Bindings to =
+        new com.google.api.services.storage.model.Policy.Bindings();
+    ifNonNull(from.getRole(), to::setRole);
+    ifNonNull(from.getMembers(), to::setMembers);
+    ifNonNull(from.getCondition(), iamConditionCodec::encode, to::setCondition);
+    return to;
+  }
+
+  private Binding bindingDecode(com.google.api.services.storage.model.Policy.Bindings from) {
+    Binding.Builder to = Binding.newBuilder();
+    ifNonNull(from.getRole(), to::setRole);
+    ifNonNull(from.getMembers(), to::setMembers);
+    ifNonNull(from.getCondition(), iamConditionCodec::decode, to::setCondition);
+    return to.build();
+  }
+
+  private com.google.api.services.storage.model.Expr conditionEncode(
+      com.google.cloud.Condition from) {
+    com.google.api.services.storage.model.Expr to =
+        new com.google.api.services.storage.model.Expr();
+    ifNonNull(from.getExpression(), to::setExpression);
+    ifNonNull(from.getTitle(), to::setTitle);
+    ifNonNull(from.getDescription(), to::setDescription);
+    // apiary doesn't have a "location" field like grpc does
+    return to;
+  }
+
+  private com.google.cloud.Condition conditionDecode(
+      com.google.api.services.storage.model.Expr from) {
+    com.google.cloud.Condition.Builder to = com.google.cloud.Condition.newBuilder();
+    ifNonNull(from.getExpression(), to::setExpression);
+    ifNonNull(from.getTitle(), to::setTitle);
+    ifNonNull(from.getDescription(), to::setDescription);
+    return to.build();
+  }
+
   private Bucket.CustomPlacementConfig customPlacementConfigEncode(CustomPlacementConfig from) {
     Bucket.CustomPlacementConfig to = null;
     if (from.getDataLocations() != null) {
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java
index 161925b7..a665aef4 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java
@@ -606,7 +606,7 @@ public class Blob extends BlobInfo {
    * @see <a
    *     href="https://cloud.google.com/storage/docs/json_api/v1/objects/update">https://cloud.google.com/storage/docs/json_api/v1/objects/update</a>
    */
-  @TransportCompatibility({Transport.HTTP})
+  @TransportCompatibility({Transport.HTTP, Transport.GRPC})
   public Blob update(BlobTargetOption... options) {
     return storage.update(this, options);
   }
@@ -630,7 +630,7 @@ public class Blob extends BlobInfo {
    * @return {@code true} if blob was deleted, {@code false} if it was not found
    * @throws StorageException upon failure
    */
-  @TransportCompatibility({Transport.HTTP})
+  @TransportCompatibility({Transport.HTTP, Transport.GRPC})
   public boolean delete(BlobSourceOption... options) {
     // Don't use static imports of BlobSourceOption, it causes import resolution issues
     // with the new UnifiedOpts shim interfaces
@@ -656,7 +656,7 @@ public class Blob extends BlobInfo {
    *     blob or to complete the copy if more than one RPC request is needed
    * @throws StorageException upon failure
    */
-  @TransportCompatibility({Transport.HTTP})
+  @TransportCompatibility({Transport.HTTP, Transport.GRPC})
   public CopyWriter copyTo(BlobId targetBlob, BlobSourceOption... options) {
     CopyRequest copyRequest =
         CopyRequest.newBuilder()
@@ -687,7 +687,7 @@ public class Blob extends BlobInfo {
    *     blob or to complete the copy if more than one RPC request is needed
    * @throws StorageException upon failure
    */
-  @TransportCompatibility({Transport.HTTP})
+  @TransportCompatibility({Transport.HTTP, Transport.GRPC})
   public CopyWriter copyTo(String targetBucket, BlobSourceOption... options) {
     return copyTo(targetBucket, getName(), options);
   }
@@ -722,7 +722,7 @@ public class Blob extends BlobInfo {
    *     blob or to complete the copy if more than one RPC request is needed
    * @throws StorageException upon failure
    */
-  @TransportCompatibility({Transport.HTTP})
+  @TransportCompatibility({Transport.HTTP, Transport.GRPC})
   public CopyWriter copyTo(String targetBucket, String targetBlob, BlobSourceOption... options) {
     return copyTo(BlobId.of(targetBucket, targetBlob), options);
   }
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java
index f712e412..1b7984e1 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java
@@ -675,7 +675,7 @@ public class Bucket extends BucketInfo {
    * @return a {@code Bucket} object with updated information
    * @throws StorageException upon failure
    */
-  @TransportCompatibility({Transport.HTTP})
+  @TransportCompatibility({Transport.HTTP, Transport.GRPC})
   public Bucket update(BucketTargetOption... options) {
     return storage.update(this, options);
   }
@@ -699,7 +699,7 @@ public class Bucket extends BucketInfo {
    * @return {@code true} if bucket was deleted, {@code false} if it was not found
    * @throws StorageException upon failure
    */
-  @TransportCompatibility({Transport.HTTP})
+  @TransportCompatibility({Transport.HTTP, Transport.GRPC})
   public boolean delete(BucketSourceOption... options) {
     // Don't use static imports of BlobSourceOption, it causes import resolution issues
     // with the new UnifiedOpts shim interfaces
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcConversions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcConversions.java
index f4d6ac18..001a309c 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcConversions.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcConversions.java
@@ -24,6 +24,9 @@ import static com.google.cloud.storage.Utils.projectNameCodec;
 import static com.google.cloud.storage.Utils.toImmutableListOf;
 import static com.google.cloud.storage.Utils.todo;
 
+import com.google.cloud.Binding;
+import com.google.cloud.Condition;
+import com.google.cloud.Policy;
 import com.google.cloud.storage.Acl.Entity;
 import com.google.cloud.storage.Acl.Role;
 import com.google.cloud.storage.BlobInfo.CustomerEncryption;
@@ -50,6 +53,7 @@ import com.google.storage.v2.ObjectAccessControl;
 import com.google.storage.v2.ObjectChecksums;
 import com.google.storage.v2.Owner;
 import com.google.type.Date;
+import com.google.type.Expr;
 import java.time.Instant;
 import java.time.LocalDate;
 import java.time.OffsetDateTime;
@@ -88,6 +92,13 @@ final class GrpcConversions {
   private final Codec<BlobInfo, Object> blobInfoCodec =
       Codec.of(this::blobInfoEncode, this::blobInfoDecode);
   private final Codec<?, ?> notificationInfoCodec = Codec.of(Utils::todo, Utils::todo);
+  private final Codec<Policy, com.google.iam.v1.Policy> policyCodec =
+      Codec.of(this::policyEncode, this::policyDecode);
+  private final Codec<Binding, com.google.iam.v1.Binding> bindingCodec =
+      Codec.of(this::bindingEncode, this::bindingDecode);
+  private final Codec<Condition, Expr> iamConditionCodec =
+      Codec.of(this::conditionEncode, this::conditionDecode);
+
   private final Codec<Integer, String> crc32cCodec =
       Codec.of(this::crc32cEncode, this::crc32cDecode);
 
@@ -182,6 +193,10 @@ final class GrpcConversions {
     return todo();
   }
 
+  Codec<Policy, com.google.iam.v1.Policy> policyCodec() {
+    return policyCodec;
+  }
+
   private BucketInfo bucketInfoDecode(Bucket from) {
     BucketInfo.Builder to = new BucketInfo.BuilderImpl(bucketNameCodec.decode(from.getName()));
     to.setProject(from.getProject());
@@ -845,6 +860,61 @@ final class GrpcConversions {
     return toBuilder.build();
   }
 
+  private com.google.iam.v1.Policy policyEncode(Policy from) {
+    com.google.iam.v1.Policy.Builder to = com.google.iam.v1.Policy.newBuilder();
+    ifNonNull(from.getEtag(), ByteString::copyFromUtf8, to::setEtag);
+    ifNonNull(from.getVersion(), to::setVersion);
+    from.getBindingsList().stream().map(bindingCodec::encode).forEach(to::addBindings);
+    return to.build();
+  }
+
+  private Policy policyDecode(com.google.iam.v1.Policy from) {
+    Policy.Builder to = Policy.newBuilder();
+    if (!from.getEtag().isEmpty()) {
+      to.setEtag(from.getEtag().toStringUtf8());
+    }
+    to.setVersion(from.getVersion());
+    ImmutableList<Binding> bindings =
+        from.getBindingsList().stream()
+            .map(bindingCodec::decode)
+            .collect(ImmutableList.toImmutableList());
+    to.setBindings(bindings);
+    return to.build();
+  }
+
+  private com.google.iam.v1.Binding bindingEncode(Binding from) {
+    com.google.iam.v1.Binding.Builder to = com.google.iam.v1.Binding.newBuilder();
+    to.setRole(from.getRole());
+    to.addAllMembers(from.getMembers());
+    to.setCondition(iamConditionCodec.encode(from.getCondition()));
+    return to.build();
+  }
+
+  private Binding bindingDecode(com.google.iam.v1.Binding from) {
+    Binding.Builder to = Binding.newBuilder();
+    to.setRole(from.getRole());
+    to.setMembers(from.getMembersList());
+    to.setCondition(iamConditionCodec.decode(from.getCondition()));
+    return to.build();
+  }
+
+  private Expr conditionEncode(Condition from) {
+    Expr.Builder to = Expr.newBuilder();
+    to.setExpression(from.getExpression());
+    to.setTitle(from.getTitle());
+    to.setDescription(from.getDescription());
+    // apiary doesn't have a "location" field like grpc does
+    return to.build();
+  }
+
+  private Condition conditionDecode(Expr from) {
+    Condition.Builder to = Condition.newBuilder();
+    to.setExpression(from.getExpression());
+    to.setTitle(from.getTitle());
+    to.setDescription(from.getDescription());
+    return to.build();
+  }
+
   private int crc32cDecode(String from) {
     byte[] decodeCrc32c = BaseEncoding.base64().decode(from);
     return Ints.fromByteArray(decodeCrc32c);
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java
index 85370781..59edd305 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java
@@ -65,6 +65,9 @@ import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.io.BaseEncoding;
 import com.google.common.io.ByteStreams;
+import com.google.iam.v1.GetIamPolicyRequest;
+import com.google.iam.v1.SetIamPolicyRequest;
+import com.google.iam.v1.TestIamPermissionsRequest;
 import com.google.protobuf.ByteString;
 import com.google.protobuf.FieldMask;
 import com.google.protobuf.Message;
@@ -536,7 +539,9 @@ final class GrpcStorageImpl extends BaseService<StorageOptions> implements Stora
     GrpcCallContext grpcCallContext =
         opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
     DeleteObjectRequest.Builder builder =
-        DeleteObjectRequest.newBuilder().setBucket(blob.getBucket()).setObject(blob.getName());
+        DeleteObjectRequest.newBuilder()
+            .setBucket(bucketNameCodec.encode(blob.getBucket()))
+            .setObject(blob.getName());
     ifNonNull(blob.getGeneration(), builder::setGeneration);
     DeleteObjectRequest req = opts.deleteObjectsRequest().apply(builder).build();
     try {
@@ -1037,21 +1042,57 @@ final class GrpcStorageImpl extends BaseService<StorageOptions> implements Stora
 
   @Override
   public Policy getIamPolicy(String bucket, BucketSourceOption... options) {
-    return throwNotYetImplemented(
-        fmtMethodName("getIamPolicy", String.class, BucketSourceOption[].class));
+    Opts<BucketSourceOpt> opts = Opts.unwrap(options);
+    GrpcCallContext grpcCallContext =
+        opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
+    GetIamPolicyRequest.Builder builder =
+        GetIamPolicyRequest.newBuilder().setResource(bucketNameCodec.encode(bucket));
+    GetIamPolicyRequest req = opts.getIamPolicyRequest().apply(builder).build();
+    return Retrying.run(
+        getOptions(),
+        retryAlgorithmManager.getFor(req),
+        () -> storageClient.getIamPolicyCallable().call(req, grpcCallContext),
+        codecs.policyCodec());
   }
 
   @Override
   public Policy setIamPolicy(String bucket, Policy policy, BucketSourceOption... options) {
-    return throwNotYetImplemented(
-        fmtMethodName("setIamPolicy", String.class, Policy.class, BucketSourceOption[].class));
+    Opts<BucketSourceOpt> opts = Opts.unwrap(options);
+    GrpcCallContext grpcCallContext =
+        opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
+    SetIamPolicyRequest req =
+        SetIamPolicyRequest.newBuilder()
+            .setResource(bucketNameCodec.encode(bucket))
+            .setPolicy(codecs.policyCodec().encode(policy))
+            .build();
+    return Retrying.run(
+        getOptions(),
+        retryAlgorithmManager.getFor(req),
+        () -> storageClient.setIamPolicyCallable().call(req, grpcCallContext),
+        codecs.policyCodec());
   }
 
   @Override
   public List<Boolean> testIamPermissions(
       String bucket, List<String> permissions, BucketSourceOption... options) {
-    return throwNotYetImplemented(
-        fmtMethodName("testIamPermissions", String.class, List.class, BucketSourceOption.class));
+    Opts<BucketSourceOpt> opts = Opts.unwrap(options);
+    GrpcCallContext grpcCallContext =
+        opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
+    TestIamPermissionsRequest req =
+        TestIamPermissionsRequest.newBuilder()
+            .setResource(bucketNameCodec.encode(bucket))
+            .addAllPermissions(permissions)
+            .build();
+    return Retrying.run(
+        getOptions(),
+        retryAlgorithmManager.getFor(req),
+        () -> storageClient.testIamPermissionsCallable().call(req, grpcCallContext),
+        resp -> {
+          Set<String> heldPermissions = ImmutableSet.copyOf(resp.getPermissionsList());
+          return permissions.stream()
+              .map(heldPermissions::contains)
+              .collect(ImmutableList.toImmutableList());
+        });
   }
 
   @Override
@@ -1388,6 +1429,8 @@ final class GrpcStorageImpl extends BaseService<StorageOptions> implements Stora
         "acl",
         "default_object_acl",
         "storage_class",
-        "rpo");
+        "rpo",
+        "labels",
+        "event_based_hold");
   }
 }
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
deleted file mode 100644
index ca3e8bc3..00000000
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/PolicyHelper.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2017 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.google.cloud.storage;
-
-import com.google.api.services.storage.model.Expr;
-import com.google.api.services.storage.model.Policy.Bindings;
-import com.google.cloud.Binding;
-import com.google.cloud.Condition;
-import com.google.cloud.Policy;
-import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Helper for converting between the Policy model provided by the API and the Policy model provided
- * by this library.
- */
-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()) {
-      for (Bindings binding : bindings) {
-        Binding.Builder bindingBuilder = Binding.newBuilder();
-        bindingBuilder.setRole(binding.getRole());
-        bindingBuilder.setMembers(binding.getMembers());
-        if (binding.getCondition() != null) {
-          Condition.Builder conditionBuilder = Condition.newBuilder();
-          conditionBuilder.setTitle(binding.getCondition().getTitle());
-          conditionBuilder.setDescription(binding.getCondition().getDescription());
-          conditionBuilder.setExpression(binding.getCondition().getExpression());
-          bindingBuilder.setCondition(conditionBuilder.build());
-        }
-        coreBindings.add(bindingBuilder.build());
-      }
-    }
-    policyBuilder.setBindings(coreBindings.build());
-    return policyBuilder.setEtag(apiPolicy.getEtag()).setVersion(apiPolicy.getVersion()).build();
-  }
-
-  static com.google.api.services.storage.model.Policy convertToApiPolicy(Policy policy) {
-    List<Bindings> bindings = new ArrayList<>(policy.getBindingsList().size());
-    for (Binding binding : policy.getBindingsList()) {
-      Bindings apiBinding = new Bindings();
-      apiBinding.setRole(binding.getRole());
-      apiBinding.setMembers(new ArrayList<>(binding.getMembers()));
-      if (binding.getCondition() != null) {
-        Expr expr = new Expr();
-        expr.setTitle(binding.getCondition().getTitle());
-        expr.setDescription(binding.getCondition().getDescription());
-        expr.setExpression(binding.getCondition().getExpression());
-        apiBinding.setCondition(expr);
-      }
-      bindings.add(apiBinding);
-    }
-    return new com.google.api.services.storage.model.Policy()
-        .setBindings(bindings)
-        .setEtag(policy.getEtag())
-        .setVersion(policy.getVersion());
-  }
-
-  private PolicyHelper() {
-    // Intentionally left blank.
-  }
-}
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 14c4f618..04ece427 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
@@ -1357,6 +1357,7 @@ public interface Storage extends Service<StorageOptions>, AutoCloseable {
    * @see <a href="https://cloud.google.com/storage/docs/composite-objects#_Compose">Compose
    *     Operation</a>
    */
+  @TransportCompatibility({Transport.HTTP, Transport.GRPC})
   class ComposeRequest implements Serializable {
 
     private static final long serialVersionUID = -7385681353748590911L;
@@ -1491,6 +1492,7 @@ public interface Storage extends Service<StorageOptions>, AutoCloseable {
   }
 
   /** A class to contain all information needed for a Google Cloud Storage Copy operation. */
+  @TransportCompatibility({Transport.HTTP, Transport.GRPC})
   class CopyRequest implements Serializable {
 
     private static final long serialVersionUID = -4498650529476219937L;
@@ -2209,7 +2211,7 @@ public interface Storage extends Service<StorageOptions>, AutoCloseable {
    * @return the updated bucket
    * @throws StorageException upon failure
    */
-  @TransportCompatibility({Transport.HTTP})
+  @TransportCompatibility({Transport.HTTP, Transport.GRPC})
   Bucket update(BucketInfo bucketInfo, BucketTargetOption... options);
 
   /**
@@ -2481,7 +2483,7 @@ public interface Storage extends Service<StorageOptions>, AutoCloseable {
    * @throws StorageException upon failure
    * @see <a href="https://cloud.google.com/storage/docs/json_api/v1/objects/rewrite">Rewrite</a>
    */
-  @TransportCompatibility({Transport.HTTP})
+  @TransportCompatibility({Transport.HTTP, Transport.GRPC})
   CopyWriter copy(CopyRequest copyRequest);
 
   /**
@@ -3585,7 +3587,7 @@ public interface Storage extends Service<StorageOptions>, AutoCloseable {
    * @param options extra parameters to apply to this operation
    * @throws StorageException upon failure
    */
-  @TransportCompatibility({Transport.HTTP})
+  @TransportCompatibility({Transport.HTTP, Transport.GRPC})
   Policy getIamPolicy(String bucket, BucketSourceOption... options);
 
   /**
@@ -3613,7 +3615,7 @@ public interface Storage extends Service<StorageOptions>, AutoCloseable {
    * @param options extra parameters to apply to this operation
    * @throws StorageException upon failure
    */
-  @TransportCompatibility({Transport.HTTP})
+  @TransportCompatibility({Transport.HTTP, Transport.GRPC})
   Policy setIamPolicy(String bucket, Policy policy, BucketSourceOption... options);
 
   /**
@@ -3638,7 +3640,7 @@ public interface Storage extends Service<StorageOptions>, AutoCloseable {
    * @param options extra parameters to apply to this operation
    * @throws StorageException upon failure
    */
-  @TransportCompatibility({Transport.HTTP})
+  @TransportCompatibility({Transport.HTTP, Transport.GRPC})
   List<Boolean> testIamPermissions(
       String bucket, List<String> permissions, BucketSourceOption... options);
 
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java
index f8c73cbf..3774f605 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java
@@ -17,7 +17,6 @@
 package com.google.cloud.storage;
 
 import static com.google.cloud.RetryHelper.runWithRetries;
-import static com.google.cloud.storage.PolicyHelper.convertToApiPolicy;
 import static com.google.cloud.storage.SignedUrlEncodingHelper.Rfc3986UriEncode;
 import static com.google.common.base.MoreObjects.firstNonNull;
 import static com.google.common.base.Preconditions.checkArgument;
@@ -1417,20 +1416,21 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
     return run(
         algorithm,
         () -> storageRpc.getIamPolicy(bucket, optionsMap),
-        PolicyHelper::convertFromApiPolicy);
+        apiPolicy -> Conversions.apiary().policyCodec().decode(apiPolicy));
   }
 
   @Override
   public Policy setIamPolicy(
       final String bucket, final Policy policy, BucketSourceOption... options) {
-    com.google.api.services.storage.model.Policy pb = convertToApiPolicy(policy);
+    com.google.api.services.storage.model.Policy pb =
+        Conversions.apiary().policyCodec().encode(policy);
     ImmutableMap<StorageRpc.Option, ?> optionsMap = Opts.unwrap(options).getRpcOptions();
     ResultRetryAlgorithm<?> algorithm =
         retryAlgorithmManager.getForBucketsSetIamPolicy(bucket, pb, optionsMap);
     return run(
         algorithm,
         () -> storageRpc.setIamPolicy(bucket, pb, optionsMap),
-        PolicyHelper::convertFromApiPolicy);
+        apiPolicy -> Conversions.apiary().policyCodec().decode(apiPolicy));
   }
 
   @Override
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/UnifiedOpts.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/UnifiedOpts.java
index d8735d49..95412f18 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/UnifiedOpts.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/UnifiedOpts.java
@@ -1983,6 +1983,10 @@ final class UnifiedOpts {
       return fuseMappers(HmacKeyTargetOpt.class, HmacKeyTargetOpt::deleteHmacKey);
     }
 
+    Mapper<GetIamPolicyRequest.Builder> getIamPolicyRequest() {
+      return fuseMappers(BucketSourceOpt.class, BucketSourceOpt::getIamPolicy);
+    }
+
     Mapper<BlobInfo.Builder> blobInfoMapper() {
       return fuseMappers(ObjectTargetOpt.class, ObjectTargetOpt::blobInfo);
     }


Test patch:
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/GapicReadTimeoutTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/GapicReadTimeoutTest.java
index d18d2c54..b5f064e1 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/GapicReadTimeoutTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/GapicReadTimeoutTest.java
@@ -130,7 +130,10 @@ public final class GapicReadTimeoutTest {
         ReadObjectResponse actualResponse2 = iter.next();
         boolean hasNext = iter.hasNext();
         Stopwatch stop = started.stop();
-        assertThat(stop.elapsed(TimeUnit.MILLISECONDS)).isAtLeast(sleepDurationMillis);
+        // reduce our expectation by 1% to allow for the fact that sleep can sometimes be slightly
+        // less than the stated amount.
+        long minimumElapsedTime = sleepDurationMillis - (long) (sleepDurationMillis * 0.01);
+        assertThat(stop.elapsed(TimeUnit.MILLISECONDS)).isAtLeast(minimumElapsedTime);
         assertThat(actualResponse1).isEqualTo(resp1);
         assertThat(actualResponse2).isEqualTo(resp2);
         assertThat(hasNext).isFalse();
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 a34538c1..1dd70d38 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
@@ -57,9 +57,9 @@ public class PolicyHelperTest {
             .setEtag(ETAG)
             .setVersion(1);
 
-    Policy actualLibPolicy = PolicyHelper.convertFromApiPolicy(apiPolicy);
+    Policy actualLibPolicy = Conversions.apiary().policyCodec().decode(apiPolicy);
     com.google.api.services.storage.model.Policy actualApiPolicy =
-        PolicyHelper.convertToApiPolicy(libPolicy);
+        Conversions.apiary().policyCodec().encode(libPolicy);
 
     assertEquals(libPolicy, actualLibPolicy);
     assertTrue(new ApiPolicyMatcher(apiPolicy).matches(actualApiPolicy));
@@ -73,7 +73,7 @@ public class PolicyHelperTest {
             .setBindings(bindings)
             .setEtag(ETAG)
             .setVersion(1);
-    Policy policy = PolicyHelper.convertFromApiPolicy(apiPolicy);
+    Policy policy = Conversions.apiary().policyCodec().decode(apiPolicy);
     assertEquals(policy.getBindings().size(), 0);
   }
 }
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBucketTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBucketTest.java
index 1b6a98f7..3d6f7dac 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBucketTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBucketTest.java
@@ -23,6 +23,7 @@ import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import com.google.cloud.Policy;
 import com.google.cloud.storage.Blob;
@@ -38,115 +39,177 @@ import com.google.cloud.storage.Storage;
 import com.google.cloud.storage.Storage.BlobField;
 import com.google.cloud.storage.Storage.BucketField;
 import com.google.cloud.storage.StorageFixture;
+import com.google.cloud.storage.conformance.retry.ParallelParameterized;
 import com.google.cloud.storage.testing.RemoteStorageHelper;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized.Parameters;
 
+@RunWith(ParallelParameterized.class)
 public class ITBucketTest {
 
   @ClassRule(order = 1)
-  public static final StorageFixture storageFixture = StorageFixture.defaultHttp();
+  public static final StorageFixture storageFixtureGrpc = StorageFixture.defaultGrpc();
+
+  @ClassRule(order = 1)
+  public static final StorageFixture storageFixtureHttp = StorageFixture.defaultHttp();
 
   @ClassRule(order = 2)
-  public static final BucketFixture bucketFixture =
-      BucketFixture.newBuilder().setHandle(storageFixture::getInstance).build();
+  public static final BucketFixture bucketFixtureHttp =
+      BucketFixture.newBuilder()
+          .setBucketNameFmtString("java-storage-http-%s")
+          .setHandle(storageFixtureHttp::getInstance)
+          .build();
 
-  @ClassRule(order = 3)
-  public static final BucketFixture requesterPaysFixture =
-      BucketFixture.newBuilder().setHandle(storageFixture::getInstance).build();
+  @ClassRule(order = 2)
+  public static final BucketFixture requesterPaysFixtureHttp =
+      BucketFixture.newBuilder()
+          .setBucketNameFmtString("java-storage-http-%s")
+          .setHandle(storageFixtureHttp::getInstance)
+          .build();
 
-  private static Storage storage;
-  private static String bucketName;
-  private static String requesterPaysBucketName;
+  @ClassRule(order = 2)
+  public static final BucketFixture bucketFixtureGrpc =
+      BucketFixture.newBuilder()
+          .setBucketNameFmtString("java-storage-grpc-%s")
+          .setHandle(storageFixtureHttp::getInstance)
+          .build();
+
+  @ClassRule(order = 2)
+  public static final BucketFixture requesterPaysFixtureGrpc =
+      BucketFixture.newBuilder()
+          .setBucketNameFmtString("java-storage-grpc-%s")
+          .setHandle(storageFixtureHttp::getInstance)
+          .build();
+
+  private final StorageFixture storageFixture;
+  private final BucketFixture bucketFixture;
+  private final BucketFixture requesterPaysFixture;
+  private final String clientName;
 
   private static final byte[] BLOB_BYTE_CONTENT = {0xD, 0xE, 0xA, 0xD};
   private static final Map<String, String> BUCKET_LABELS = ImmutableMap.of("label1", "value1");
   private static final Long RETENTION_PERIOD = 5L;
 
-  @BeforeClass
-  public static void setup() {
-    storage = storageFixture.getInstance();
-    bucketName = bucketFixture.getBucketInfo().getName();
-    requesterPaysBucketName = requesterPaysFixture.getBucketInfo().getName();
+  public ITBucketTest(
+      String clientName,
+      StorageFixture storageFixture,
+      BucketFixture bucketFixture,
+      BucketFixture requesterPaysFixture) {
+    this.clientName = clientName;
+    this.storageFixture = storageFixture;
+    this.bucketFixture = bucketFixture;
+    this.requesterPaysFixture = requesterPaysFixture;
   }
 
-  @AfterClass
-  public static void cleanup() {
-    unsetRequesterPays();
+  @Parameters(name = "{0}")
+  public static Iterable<Object[]> data() {
+    return Arrays.asList(
+        new Object[] {"JSON/Prod", storageFixtureHttp, bucketFixtureHttp, requesterPaysFixtureHttp},
+        new Object[] {
+          "GRPC/TestBench", storageFixtureGrpc, bucketFixtureGrpc, requesterPaysFixtureGrpc
+        });
   }
 
   @Test(timeout = 5000)
   public void testListBuckets() throws InterruptedException {
     Iterator<Bucket> bucketIterator =
-        storage
-            .list(Storage.BucketListOption.prefix(bucketName), Storage.BucketListOption.fields())
+        storageFixture
+            .getInstance()
+            .list(
+                Storage.BucketListOption.prefix(bucketFixture.getBucketInfo().getName()),
+                Storage.BucketListOption.fields())
             .iterateAll()
             .iterator();
     while (!bucketIterator.hasNext()) {
       Thread.sleep(500);
       bucketIterator =
-          storage
-              .list(Storage.BucketListOption.prefix(bucketName), Storage.BucketListOption.fields())
+          storageFixture
+              .getInstance()
+              .list(
+                  Storage.BucketListOption.prefix(bucketFixture.getBucketInfo().getName()),
+                  Storage.BucketListOption.fields())
               .iterateAll()
               .iterator();
     }
     while (bucketIterator.hasNext()) {
       Bucket remoteBucket = bucketIterator.next();
-      assertTrue(remoteBucket.getName().startsWith(bucketName));
-      assertNull(remoteBucket.getCreateTime());
-      assertNull(remoteBucket.getSelfLink());
+      assertTrue(remoteBucket.getName().startsWith(bucketFixture.getBucketInfo().getName()));
     }
   }
 
   @Test
   public void testGetBucketSelectedFields() {
-    Bucket remoteBucket = storage.get(bucketName, Storage.BucketGetOption.fields(BucketField.ID));
-    assertEquals(bucketName, remoteBucket.getName());
+    // FieldMask not supported in GRPC get currently.
+    assumeTrue(clientName.startsWith("JSON"));
+
+    Bucket remoteBucket =
+        storageFixture
+            .getInstance()
+            .get(
+                bucketFixture.getBucketInfo().getName(),
+                Storage.BucketGetOption.fields(BucketField.ID));
+    assertEquals(bucketFixture.getBucketInfo().getName(), remoteBucket.getName());
     assertNull(remoteBucket.getCreateTime());
     assertNotNull(remoteBucket.getGeneratedId());
   }
 
   @Test
   public void testGetBucketAllSelectedFields() {
+    // FieldMask not supported in GRPC currently.
+    assumeTrue(clientName.startsWith("JSON"));
     Bucket remoteBucket =
-        storage.get(bucketName, Storage.BucketGetOption.fields(BucketField.values()));
-    assertEquals(bucketName, remoteBucket.getName());
+        storageFixture
+            .getInstance()
+            .get(
+                bucketFixture.getBucketInfo().getName(),
+                Storage.BucketGetOption.fields(BucketField.values()));
+    assertEquals(bucketFixture.getBucketInfo().getName(), remoteBucket.getName());
     assertNotNull(remoteBucket.getCreateTime());
     assertNotNull(remoteBucket.getSelfLink());
   }
 
   @Test
   public void testBucketLocationType() {
-    String bucketName = RemoteStorageHelper.generateBucketName();
-    Bucket bucket = storage.create(BucketInfo.newBuilder(bucketName).setLocation("us").build());
+    // Cannot turn on for GRPC until b/246634709 is resolved, verified locally.
+    assumeTrue(clientName.startsWith("JSON"));
+    String bucketName = bucketFixture.newBucketName();
+    Bucket bucket =
+        storageFixture
+            .getInstance()
+            .create(BucketInfo.newBuilder(bucketName).setLocation("us").build());
     assertEquals("multi-region", bucket.getLocationType());
   }
 
   @Test
   public void testBucketCustomPlacmentConfigDualRegion() {
-    String bucketName = RemoteStorageHelper.generateBucketName();
+    // Cannot turn on for GRPC until creation bug b/246634709 is resolved, verified locally.
+    assumeTrue(clientName.startsWith("JSON"));
+    String bucketName = bucketFixture.newBucketName();
     List<String> locations = new ArrayList<>();
     locations.add("US-EAST1");
     locations.add("US-WEST1");
     CustomPlacementConfig customPlacementConfig =
         CustomPlacementConfig.newBuilder().setDataLocations(locations).build();
     Bucket bucket =
-        storage.create(
-            BucketInfo.newBuilder(bucketName)
-                .setCustomPlacementConfig(customPlacementConfig)
-                .setLocation("us")
-                .build());
+        storageFixture
+            .getInstance()
+            .create(
+                BucketInfo.newBuilder(bucketName)
+                    .setCustomPlacementConfig(customPlacementConfig)
+                    .setLocation("us")
+                    .build());
     assertTrue(bucket.getCustomPlacementConfig().getDataLocations().contains("US-EAST1"));
     assertTrue(bucket.getCustomPlacementConfig().getDataLocations().contains("US-WEST1"));
     assertTrue(bucket.getLocation().equalsIgnoreCase("us"));
@@ -154,11 +217,16 @@ public class ITBucketTest {
 
   @Test
   public void testBucketLogging() throws ExecutionException, InterruptedException {
-    String logsBucket = RemoteStorageHelper.generateBucketName();
-    String loggingBucket = RemoteStorageHelper.generateBucketName();
+    // Cannot turn on until GRPC Update logic bug is fixed b/247133805
+    assumeTrue(clientName.startsWith("JSON"));
+    String logsBucket = bucketFixture.newBucketName();
+    String loggingBucket = bucketFixture.newBucketName();
     try {
-      assertNotNull(storage.create(BucketInfo.newBuilder(logsBucket).setLocation("us").build()));
-      Policy policy = storage.getIamPolicy(logsBucket);
+      assertNotNull(
+          storageFixture
+              .getInstance()
+              .create(BucketInfo.newBuilder(logsBucket).setLocation("us").build()));
+      Policy policy = storageFixture.getInstance().getIamPolicy(logsBucket);
       assertNotNull(policy);
       BucketInfo.Logging logging =
           BucketInfo.Logging.newBuilder()
@@ -166,8 +234,13 @@ public class ITBucketTest {
               .setLogObjectPrefix("test-logs")
               .build();
       Bucket bucket =
-          storage.create(
-              BucketInfo.newBuilder(loggingBucket).setLocation("us").setLogging(logging).build());
+          storageFixture
+              .getInstance()
+              .create(
+                  BucketInfo.newBuilder(loggingBucket)
+                      .setLocation("us")
+                      .setLogging(logging)
+                      .build());
       assertEquals(logsBucket, bucket.getLogging().getLogBucket());
       assertEquals("test-logs", bucket.getLogging().getLogObjectPrefix());
 
@@ -176,14 +249,18 @@ public class ITBucketTest {
       assertNull(updatedBucket.getLogging());
 
     } finally {
-      RemoteStorageHelper.forceDelete(storage, logsBucket, 5, TimeUnit.SECONDS);
-      RemoteStorageHelper.forceDelete(storage, loggingBucket, 5, TimeUnit.SECONDS);
+      RemoteStorageHelper.forceDelete(
+          storageFixture.getInstance(), logsBucket, 5, TimeUnit.SECONDS);
+      RemoteStorageHelper.forceDelete(
+          storageFixture.getInstance(), loggingBucket, 5, TimeUnit.SECONDS);
     }
   }
 
   @Test
   public void testRemoveBucketCORS() throws ExecutionException, InterruptedException {
-    String bucketName = RemoteStorageHelper.generateBucketName();
+    // GRPC Update logic bug b/247133805
+    assumeTrue(clientName.startsWith("JSON"));
+    String bucketName = bucketFixture.newBucketName();
     List<Cors.Origin> origins = ImmutableList.of(Cors.Origin.of("http://cloud.google.com"));
     List<HttpMethod> httpMethods = ImmutableList.of(HttpMethod.GET);
     List<String> responseHeaders = ImmutableList.of("Content-Type");
@@ -195,11 +272,16 @@ public class ITBucketTest {
               .setResponseHeaders(responseHeaders)
               .setMaxAgeSeconds(100)
               .build();
-      storage.create(BucketInfo.newBuilder(bucketName).setCors(ImmutableList.of(cors)).build());
+      // GRPC creation bug
+      storageFixtureHttp
+          .getInstance()
+          .create(BucketInfo.newBuilder(bucketName).setCors(ImmutableList.of(cors)).build());
 
       // case-1 : Cors are set and field selector is selected then returns not-null.
       Bucket remoteBucket =
-          storage.get(bucketName, Storage.BucketGetOption.fields(BucketField.CORS));
+          storageFixture
+              .getInstance()
+              .get(bucketName, Storage.BucketGetOption.fields(BucketField.CORS));
       assertThat(remoteBucket.getCors()).isNotNull();
       assertThat(remoteBucket.getCors().get(0).getMaxAgeSeconds()).isEqualTo(100);
       assertThat(remoteBucket.getCors().get(0).getMethods()).isEqualTo(httpMethods);
@@ -207,7 +289,7 @@ public class ITBucketTest {
       assertThat(remoteBucket.getCors().get(0).getResponseHeaders()).isEqualTo(responseHeaders);
 
       // case-2 : Cors are set but field selector isn't selected then returns not-null.
-      remoteBucket = storage.get(bucketName);
+      remoteBucket = storageFixture.getInstance().get(bucketName);
       assertThat(remoteBucket.getCors()).isNotNull();
 
       // Remove CORS configuration from the bucket.
@@ -215,45 +297,62 @@ public class ITBucketTest {
       assertThat(updatedBucket.getCors()).isNull();
 
       // case-3 : Cors are not set and field selector is selected then returns null.
-      updatedBucket = storage.get(bucketName, Storage.BucketGetOption.fields(BucketField.CORS));
+      updatedBucket =
+          storageFixture
+              .getInstance()
+              .get(bucketName, Storage.BucketGetOption.fields(BucketField.CORS));
       assertThat(updatedBucket.getCors()).isNull();
 
       // case-4 : Cors are not set and field selector isn't selected then returns null.
-      updatedBucket = storage.get(bucketName);
+      updatedBucket = storageFixture.getInstance().get(bucketName);
       assertThat(updatedBucket.getCors()).isNull();
 
     } finally {
-      RemoteStorageHelper.forceDelete(storage, bucketName, 5, TimeUnit.SECONDS);
+      RemoteStorageHelper.forceDelete(
+          storageFixtureHttp.getInstance(), bucketName, 5, TimeUnit.SECONDS);
     }
   }
 
   @Test
   public void testRpoConfig() {
-    String rpoBucket = RemoteStorageHelper.generateBucketName();
+    // Cannot turn on for GRPC until b/246634709 is resolved, verified locally.
+    assumeTrue(clientName.startsWith("JSON"));
+    String rpoBucket = bucketFixture.newBucketName();
     try {
       Bucket bucket =
-          storage.create(
-              BucketInfo.newBuilder(rpoBucket).setLocation("NAM4").setRpo(Rpo.ASYNC_TURBO).build());
+          storageFixtureHttp
+              .getInstance()
+              .create(
+                  BucketInfo.newBuilder(rpoBucket)
+                      .setLocation("NAM4")
+                      .setRpo(Rpo.ASYNC_TURBO)
+                      .build());
       assertEquals("ASYNC_TURBO", bucket.getRpo().toString());
 
       bucket.toBuilder().setRpo(Rpo.DEFAULT).build().update();
 
-      assertEquals("DEFAULT", storage.get(rpoBucket).getRpo().toString());
+      assertEquals("DEFAULT", storageFixture.getInstance().get(rpoBucket).getRpo().toString());
     } finally {
-      storage.delete(rpoBucket);
+      storageFixture.getInstance().delete(rpoBucket);
     }
   }
 
   @Test
   public void testRetentionPolicyLock() throws ExecutionException, InterruptedException {
-    retentionPolicyLockRequesterPays(true);
     retentionPolicyLockRequesterPays(false);
   }
 
+  @Test
+  public void testRetentionPolicyLockRequesterPays()
+      throws ExecutionException, InterruptedException {
+    assumeTrue(clientName.startsWith("JSON"));
+    retentionPolicyLockRequesterPays(true);
+  }
+
   private void retentionPolicyLockRequesterPays(boolean requesterPays)
       throws ExecutionException, InterruptedException {
-    String projectId = storage.getOptions().getProjectId();
-    String bucketName = RemoteStorageHelper.generateBucketName();
+    String projectId = storageFixture.getInstance().getOptions().getProjectId();
+    String bucketName = bucketFixture.newBucketName();
     BucketInfo bucketInfo;
     if (requesterPays) {
       bucketInfo =
@@ -264,21 +363,25 @@ public class ITBucketTest {
     } else {
       bucketInfo = BucketInfo.newBuilder(bucketName).setRetentionPeriod(RETENTION_PERIOD).build();
     }
-    Bucket remoteBucket = storage.create(bucketInfo);
+    Bucket remoteBucket = storageFixtureHttp.getInstance().create(bucketInfo);
     try {
       assertNull(remoteBucket.retentionPolicyIsLocked());
       assertNotNull(remoteBucket.getRetentionEffectiveTime());
       assertNotNull(remoteBucket.getMetageneration());
       if (requesterPays) {
         remoteBucket =
-            storage.lockRetentionPolicy(
-                remoteBucket,
-                Storage.BucketTargetOption.metagenerationMatch(),
-                Storage.BucketTargetOption.userProject(projectId));
+            storageFixture
+                .getInstance()
+                .lockRetentionPolicy(
+                    remoteBucket,
+                    Storage.BucketTargetOption.metagenerationMatch(),
+                    Storage.BucketTargetOption.userProject(projectId));
       } else {
         remoteBucket =
-            storage.lockRetentionPolicy(
-                remoteBucket, Storage.BucketTargetOption.metagenerationMatch());
+            storageFixture
+                .getInstance()
+                .lockRetentionPolicy(
+                    remoteBucket, Storage.BucketTargetOption.metagenerationMatch());
       }
       assertTrue(remoteBucket.retentionPolicyIsLocked());
       assertNotNull(remoteBucket.getRetentionEffectiveTime());
@@ -286,94 +389,126 @@ public class ITBucketTest {
       if (requesterPays) {
         bucketInfo = bucketInfo.toBuilder().setRequesterPays(false).build();
         Bucket updateBucket =
-            storage.update(bucketInfo, Storage.BucketTargetOption.userProject(projectId));
+            storageFixture
+                .getInstance()
+                .update(bucketInfo, Storage.BucketTargetOption.userProject(projectId));
         assertFalse(updateBucket.requesterPays());
       }
-      RemoteStorageHelper.forceDelete(storage, bucketName, 5, TimeUnit.SECONDS, projectId);
+      RemoteStorageHelper.forceDelete(
+          storageFixtureHttp.getInstance(), bucketName, 5, TimeUnit.SECONDS, projectId);
     }
   }
 
   @Test
   public void testUpdateBucketLabel() {
+    // Bug in UpdateMask
+    assumeTrue(clientName.startsWith("JSON"));
     Bucket remoteBucket =
-        storage.get(
-            bucketName, Storage.BucketGetOption.fields(BucketField.ID, BucketField.BILLING));
+        storageFixture
+            .getInstance()
+            .get(
+                bucketFixture.getBucketInfo().getName(),
+                Storage.BucketGetOption.fields(BucketField.ID, BucketField.BILLING));
     assertNull(remoteBucket.getLabels());
     remoteBucket = remoteBucket.toBuilder().setLabels(BUCKET_LABELS).build();
-    Bucket updatedBucket = storage.update(remoteBucket);
+    Bucket updatedBucket = storageFixture.getInstance().update(remoteBucket);
     assertEquals(BUCKET_LABELS, updatedBucket.getLabels());
     remoteBucket.toBuilder().setLabels(Collections.emptyMap()).build().update();
-    assertNull(storage.get(bucketName).getLabels());
+    assertNull(
+        storageFixture.getInstance().get(bucketFixture.getBucketInfo().getName()).getLabels());
   }
 
   @Test
   public void testUpdateBucketRequesterPays() {
+    // Bug in UpdateMask
+    assumeTrue(clientName.startsWith("JSON"));
     unsetRequesterPays();
     Bucket remoteBucket =
-        storage.get(
-            requesterPaysBucketName,
-            Storage.BucketGetOption.fields(BucketField.ID, BucketField.BILLING));
+        storageFixture
+            .getInstance()
+            .get(
+                requesterPaysFixture.getBucketInfo().getName(),
+                Storage.BucketGetOption.fields(BucketField.ID, BucketField.BILLING));
     assertTrue(remoteBucket.requesterPays() == null || !remoteBucket.requesterPays());
     remoteBucket = remoteBucket.toBuilder().setRequesterPays(true).build();
-    Bucket updatedBucket = storage.update(remoteBucket);
+    Bucket updatedBucket = storageFixture.getInstance().update(remoteBucket);
     assertTrue(updatedBucket.requesterPays());
 
-    String projectId = storage.getOptions().getProjectId();
+    String projectId = storageFixture.getInstance().getOptions().getProjectId();
     Bucket.BlobTargetOption option = Bucket.BlobTargetOption.userProject(projectId);
     String blobName = "test-create-empty-blob-requester-pays";
     Blob remoteBlob = updatedBucket.create(blobName, BLOB_BYTE_CONTENT, option);
     assertNotNull(remoteBlob);
     byte[] readBytes =
-        storage.readAllBytes(
-            requesterPaysBucketName, blobName, Storage.BlobSourceOption.userProject(projectId));
+        storageFixture
+            .getInstance()
+            .readAllBytes(
+                requesterPaysFixture.getBucketInfo().getName(),
+                blobName,
+                Storage.BlobSourceOption.userProject(projectId));
     assertArrayEquals(BLOB_BYTE_CONTENT, readBytes);
     remoteBucket = remoteBucket.toBuilder().setRequesterPays(false).build();
-    updatedBucket = storage.update(remoteBucket, Storage.BucketTargetOption.userProject(projectId));
+    updatedBucket =
+        storageFixture
+            .getInstance()
+            .update(remoteBucket, Storage.BucketTargetOption.userProject(projectId));
     assertFalse(updatedBucket.requesterPays());
   }
 
   @Test
   public void testEnableDisableBucketDefaultEventBasedHold()
       throws ExecutionException, InterruptedException {
-    String bucketName = RemoteStorageHelper.generateBucketName();
+    assumeTrue(clientName.startsWith("JSON"));
+    String bucketName = bucketFixture.newBucketName();
     Bucket remoteBucket =
-        storage.create(BucketInfo.newBuilder(bucketName).setDefaultEventBasedHold(true).build());
+        storageFixtureHttp
+            .getInstance()
+            .create(BucketInfo.newBuilder(bucketName).setDefaultEventBasedHold(true).build());
     try {
       assertTrue(remoteBucket.getDefaultEventBasedHold());
       remoteBucket =
-          storage.get(
-              bucketName, Storage.BucketGetOption.fields(BucketField.DEFAULT_EVENT_BASED_HOLD));
+          storageFixture
+              .getInstance()
+              .get(
+                  bucketName, Storage.BucketGetOption.fields(BucketField.DEFAULT_EVENT_BASED_HOLD));
       assertTrue(remoteBucket.getDefaultEventBasedHold());
       String blobName = "test-create-with-event-based-hold";
       BlobInfo blobInfo = BlobInfo.newBuilder(bucketName, blobName).build();
-      Blob remoteBlob = storage.create(blobInfo);
+      Blob remoteBlob = storageFixture.getInstance().create(blobInfo);
       assertTrue(remoteBlob.getEventBasedHold());
       remoteBlob =
-          storage.get(
-              blobInfo.getBlobId(), Storage.BlobGetOption.fields(BlobField.EVENT_BASED_HOLD));
+          storageFixture
+              .getInstance()
+              .get(blobInfo.getBlobId(), Storage.BlobGetOption.fields(BlobField.EVENT_BASED_HOLD));
       assertTrue(remoteBlob.getEventBasedHold());
       remoteBlob = remoteBlob.toBuilder().setEventBasedHold(false).build().update();
       assertFalse(remoteBlob.getEventBasedHold());
       remoteBucket = remoteBucket.toBuilder().setDefaultEventBasedHold(false).build().update();
       assertFalse(remoteBucket.getDefaultEventBasedHold());
     } finally {
-      RemoteStorageHelper.forceDelete(storage, bucketName, 5, TimeUnit.SECONDS);
+      RemoteStorageHelper.forceDelete(
+          storageFixtureHttp.getInstance(), bucketName, 5, TimeUnit.SECONDS);
     }
   }
 
-  private static void unsetRequesterPays() {
+  private void unsetRequesterPays() {
     Bucket remoteBucket =
-        storage.get(
-            requesterPaysBucketName,
-            Storage.BucketGetOption.fields(BucketField.ID, BucketField.BILLING),
-            Storage.BucketGetOption.userProject(storage.getOptions().getProjectId()));
+        storageFixture
+            .getInstance()
+            .get(
+                requesterPaysFixture.getBucketInfo().getName(),
+                Storage.BucketGetOption.fields(BucketField.ID, BucketField.BILLING),
+                Storage.BucketGetOption.userProject(
+                    storageFixture.getInstance().getOptions().getProjectId()));
     // Disable requester pays in case a test fails to clean up.
     if (remoteBucket.requesterPays() != null && remoteBucket.requesterPays() == true) {
       remoteBucket
           .toBuilder()
           .setRequesterPays(false)
           .build()
-          .update(Storage.BucketTargetOption.userProject(storage.getOptions().getProjectId()));
+          .update(
+              Storage.BucketTargetOption.userProject(
+                  storageFixture.getInstance().getOptions().getProjectId()));
     }
   }
 }