Repository: googleapis/java-datastore
Pull request #1400 (2024-04-18 14:55:40)

feat: allow opt-in for gRPC, have datastore-v1-proto-client as default
Note that HttpDatastoreRpc changes are a revert of https://github.com/googleapis/java-datastore/pull/1261, no custom changes are made besides the implementation of `close()` and `isClosed()`


Patch:
diff --git a/README.md b/README.md
index c582f2b6..dcb40a9c 100644
--- a/README.md
+++ b/README.md
@@ -50,20 +50,20 @@ If you are using Maven without the BOM, add this to your dependencies:
 If you are using Gradle 5.x or later, add this to your dependencies:
 
 ```Groovy
-implementation platform('com.google.cloud:libraries-bom:26.34.0')
+implementation platform('com.google.cloud:libraries-bom:26.37.0')
 
 implementation 'com.google.cloud:google-cloud-datastore'
 ```
 If you are using Gradle without BOM, add this to your dependencies:
 
 ```Groovy
-implementation 'com.google.cloud:google-cloud-datastore:2.18.5'
+implementation 'com.google.cloud:google-cloud-datastore:2.19.0'
 ```
 
 If you are using SBT, add this to your dependencies:
 
 ```Scala
-libraryDependencies += "com.google.cloud" % "google-cloud-datastore" % "2.18.5"
+libraryDependencies += "com.google.cloud" % "google-cloud-datastore" % "2.19.0"
 ```
 <!-- {x-version-update-end} -->
 
@@ -380,7 +380,7 @@ Java is a registered trademark of Oracle and/or its affiliates.
 [kokoro-badge-link-5]: http://storage.googleapis.com/cloud-devrel-public/java/badges/java-datastore/java11.html
 [stability-image]: https://img.shields.io/badge/stability-stable-green
 [maven-version-image]: https://img.shields.io/maven-central/v/com.google.cloud/google-cloud-datastore.svg
-[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-datastore/2.18.5
+[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-datastore/2.19.0
 [authentication]: https://github.com/googleapis/google-cloud-java#authentication
 [auth-scopes]: https://developers.google.com/identity/protocols/oauth2/scopes
 [predefined-iam-roles]: https://cloud.google.com/iam/docs/understanding-roles#predefined_roles
diff --git a/google-cloud-datastore/pom.xml b/google-cloud-datastore/pom.xml
index e07c6912..45ffa599 100644
--- a/google-cloud-datastore/pom.xml
+++ b/google-cloud-datastore/pom.xml
@@ -42,6 +42,10 @@
       <groupId>com.google.api.grpc</groupId>
       <artifactId>proto-google-cloud-datastore-admin-v1</artifactId>
     </dependency>
+    <dependency>
+      <groupId>com.google.cloud.datastore</groupId>
+      <artifactId>datastore-v1-proto-client</artifactId>
+    </dependency>
     <dependency>
       <groupId>io.grpc</groupId>
       <artifactId>grpc-api</artifactId>
@@ -102,6 +106,10 @@
       <groupId>com.google.oauth-client</groupId>
       <artifactId>google-oauth-client</artifactId>
     </dependency>
+    <dependency>
+      <groupId>com.google.auth</groupId>
+      <artifactId>google-auth-library-oauth2-http</artifactId>
+    </dependency>
     <dependency>
       <groupId>io.opencensus</groupId>
       <artifactId>opencensus-api</artifactId>
diff --git a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/DatastoreOptions.java b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/DatastoreOptions.java
index c852fdb7..7e5192b5 100644
--- a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/DatastoreOptions.java
+++ b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/DatastoreOptions.java
@@ -18,6 +18,7 @@ package com.google.cloud.datastore;
 
 import static com.google.cloud.datastore.Validator.validateNamespace;
 
+import com.google.api.core.BetaApi;
 import com.google.api.gax.core.CredentialsProvider;
 import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider;
 import com.google.api.gax.rpc.TransportChannelProvider;
@@ -74,15 +75,8 @@ public class DatastoreOptions extends ServiceOptions<Datastore, DatastoreOptions
       try {
         if (options.getTransportOptions() instanceof GrpcTransportOptions) {
           return new GrpcDatastoreRpc(options);
-        } else if (options.getTransportOptions() instanceof HttpTransportOptions) {
-          // todo see if we can remove this check
-          if (DatastoreUtils.isEmulator(options)) {
-            throw new IllegalArgumentException("Only GRPC channels are allowed for emulator.");
-          }
-          return new HttpDatastoreRpc(options);
         } else {
-          throw new IllegalArgumentException(
-              "unknown transport type: " + options.getTransportOptions());
+          return new HttpDatastoreRpc(options);
         }
       } catch (IOException e) {
         throw new RuntimeException(e);
@@ -96,6 +90,8 @@ public class DatastoreOptions extends ServiceOptions<Datastore, DatastoreOptions
     private String databaseId;
     private TransportChannelProvider channelProvider = null;
     private CredentialsProvider credentialsProvider = null;
+    private String host;
+    private TransportOptions transportOptions;
 
     private Builder() {}
 
@@ -109,15 +105,33 @@ public class DatastoreOptions extends ServiceOptions<Datastore, DatastoreOptions
 
     @Override
     public Builder setTransportOptions(TransportOptions transportOptions) {
+      if (!(transportOptions instanceof HttpTransportOptions)) {
+        throw new IllegalArgumentException(
+            "Only http transport is allowed for " + API_SHORT_NAME + ".");
+      }
+      this.transportOptions = transportOptions;
+      return super.setTransportOptions(transportOptions);
+    }
+
+    @BetaApi
+    public Builder setTransportOptions(GrpcTransportOptions transportOptions) {
+      this.transportOptions = transportOptions;
       return super.setTransportOptions(transportOptions);
     }
 
+    @Override
+    public Builder setHost(String host) {
+      this.host = host;
+      return super.setHost(host);
+    }
+
     /**
      * Sets the {@link TransportChannelProvider} to use with this Datastore client.
      *
      * @param channelProvider A InstantiatingGrpcChannelProvider object that defines the transport
      *     provider for this client.
      */
+    @BetaApi
     public Builder setChannelProvider(TransportChannelProvider channelProvider) {
       this.channelProvider = validateChannelProvider(channelProvider);
       return this;
@@ -129,6 +143,7 @@ public class DatastoreOptions extends ServiceOptions<Datastore, DatastoreOptions
      * @param credentialsProvider A CredentialsProvider object that defines the credential provider
      *     for this client.
      */
+    @BetaApi
     public Builder setCredentialsProvider(CredentialsProvider credentialsProvider) {
       this.credentialsProvider = credentialsProvider;
       return this;
@@ -136,6 +151,9 @@ public class DatastoreOptions extends ServiceOptions<Datastore, DatastoreOptions
 
     @Override
     public DatastoreOptions build() {
+      if (this.host == null && this.transportOptions instanceof GrpcTransportOptions) {
+        this.setHost(DatastoreSettings.getDefaultEndpoint());
+      }
       return new DatastoreOptions(this);
     }
 
@@ -153,7 +171,7 @@ public class DatastoreOptions extends ServiceOptions<Datastore, DatastoreOptions
 
   private static TransportChannelProvider validateChannelProvider(
       TransportChannelProvider channelProvider) {
-    if (!(channelProvider instanceof InstantiatingGrpcChannelProvider)) {
+    if (channelProvider != null && !(channelProvider instanceof InstantiatingGrpcChannelProvider)) {
       throw new IllegalArgumentException(
           "Only GRPC channels are allowed for " + API_SHORT_NAME + ".");
     }
@@ -165,7 +183,6 @@ public class DatastoreOptions extends ServiceOptions<Datastore, DatastoreOptions
     namespace = MoreObjects.firstNonNull(builder.namespace, defaultNamespace());
     databaseId = MoreObjects.firstNonNull(builder.databaseId, DEFAULT_DATABASE_ID);
 
-    // todo see if we can update this
     if (getTransportOptions() instanceof HttpTransportOptions
         && (builder.channelProvider != null || builder.credentialsProvider != null)) {
       throw new IllegalArgumentException(
@@ -195,7 +212,7 @@ public class DatastoreOptions extends ServiceOptions<Datastore, DatastoreOptions
   @Override
   protected String getDefaultHost() {
     String host = System.getProperty(LOCAL_HOST_ENV_VAR, System.getenv(LOCAL_HOST_ENV_VAR));
-    return host != null ? host : DatastoreSettings.getDefaultEndpoint();
+    return host != null ? host : com.google.datastore.v1.client.DatastoreFactory.DEFAULT_HOST;
   }
 
   @Override
@@ -222,8 +239,8 @@ public class DatastoreOptions extends ServiceOptions<Datastore, DatastoreOptions
       return TRANSPORT_OPTIONS;
     }
 
-    public static GrpcTransportOptions.Builder getDefaultTransportOptionsBuilder() {
-      return GrpcTransportOptions.newBuilder();
+    public static HttpTransportOptions.Builder getDefaultTransportOptionsBuilder() {
+      return HttpTransportOptions.newBuilder();
     }
   }
 
diff --git a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/spi/v1/HttpDatastoreRpc.java b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/spi/v1/HttpDatastoreRpc.java
index f5a6ebb4..0bc29af7 100644
--- a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/spi/v1/HttpDatastoreRpc.java
+++ b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/spi/v1/HttpDatastoreRpc.java
@@ -16,17 +16,15 @@
 
 package com.google.cloud.datastore.spi.v1;
 
-import static com.google.cloud.datastore.DatastoreUtils.isLocalHost;
-import static com.google.cloud.datastore.spi.v1.RpcUtils.retrySettingSetter;
-import static java.util.concurrent.TimeUnit.SECONDS;
-
+import com.google.api.client.http.HttpRequest;
+import com.google.api.client.http.HttpRequestInitializer;
+import com.google.api.client.http.HttpTransport;
 import com.google.api.core.InternalApi;
-import com.google.api.gax.core.BackgroundResource;
-import com.google.api.gax.rpc.ClientContext;
+import com.google.cloud.datastore.DatastoreException;
 import com.google.cloud.datastore.DatastoreOptions;
-import com.google.cloud.datastore.v1.DatastoreSettings;
-import com.google.cloud.datastore.v1.stub.DatastoreStubSettings;
-import com.google.cloud.datastore.v1.stub.HttpJsonDatastoreStub;
+import com.google.cloud.datastore.TraceUtil;
+import com.google.cloud.http.CensusHttpModule;
+import com.google.cloud.http.HttpTransportOptions;
 import com.google.datastore.v1.AllocateIdsRequest;
 import com.google.datastore.v1.AllocateIdsResponse;
 import com.google.datastore.v1.BeginTransactionRequest;
@@ -44,104 +42,186 @@ import com.google.datastore.v1.RunAggregationQueryResponse;
 import com.google.datastore.v1.RunQueryRequest;
 import com.google.datastore.v1.RunQueryResponse;
 import java.io.IOException;
+import java.net.InetAddress;
+import java.net.URL;
 
 @InternalApi
 public class HttpDatastoreRpc implements DatastoreRpc {
 
-  private final ClientContext clientContext;
-  private final HttpJsonDatastoreStub datastoreStub;
+  private final com.google.datastore.v1.client.Datastore client;
+
+  public HttpDatastoreRpc(DatastoreOptions options) {
+    HttpTransportOptions httpTransportOptions =
+        (HttpTransportOptions) options.getTransportOptions();
+    HttpTransport transport = httpTransportOptions.getHttpTransportFactory().create();
+    com.google.datastore.v1.client.DatastoreOptions.Builder clientBuilder =
+        new com.google.datastore.v1.client.DatastoreOptions.Builder()
+            .projectId(options.getProjectId())
+            .initializer(getHttpRequestInitializer(options, httpTransportOptions))
+            .transport(transport);
+    String normalizedHost = options.getHost() != null ? options.getHost().toLowerCase() : "";
+
+    if (isLocalHost(normalizedHost)) {
+      clientBuilder = clientBuilder.localHost(removeScheme(normalizedHost));
+    } else if (!removeScheme(com.google.datastore.v1.client.DatastoreFactory.DEFAULT_HOST)
+            .equals(removeScheme(normalizedHost))
+        && !normalizedHost.isEmpty()) {
+      String fullUrl = normalizedHost;
+      if (fullUrl.charAt(fullUrl.length() - 1) != '/') {
+        fullUrl = fullUrl + '/';
+      }
+      fullUrl =
+          fullUrl
+              + com.google.datastore.v1.client.DatastoreFactory.VERSION
+              + "/projects/"
+              + options.getProjectId();
+      clientBuilder = clientBuilder.projectId(null).projectEndpoint(fullUrl);
+    }
+    client = com.google.datastore.v1.client.DatastoreFactory.get().create(clientBuilder.build());
+  }
 
-  private boolean closed;
+  private HttpRequestInitializer getHttpRequestInitializer(
+      final DatastoreOptions options, HttpTransportOptions httpTransportOptions) {
+    // Open Census initialization
+    CensusHttpModule censusHttpModule =
+        new CensusHttpModule(TraceUtil.getInstance().getTracer(), true);
+    final HttpRequestInitializer censusHttpModuleHttpRequestInitializer =
+        censusHttpModule.getHttpRequestInitializer(
+            httpTransportOptions.getHttpRequestInitializer(options));
+
+    final String applicationName = options.getApplicationName();
+    return new HttpRequestInitializer() {
+      @Override
+      public void initialize(HttpRequest httpRequest) throws IOException {
+        censusHttpModuleHttpRequestInitializer.initialize(httpRequest);
+        httpRequest.getHeaders().setUserAgent(applicationName);
+      }
+    };
+  }
 
-  public HttpDatastoreRpc(DatastoreOptions datastoreOptions) throws IOException {
-    DatastoreSettings datastoreSettings =
-        new DatastoreSettingsBuilder(DatastoreSettings.newBuilder().build())
-            .setInternalHeaderProvider(
-                DatastoreStubSettings.defaultHttpJsonApiClientHeaderProviderBuilder().build())
-            .setTransportChannelProvider(
-                DatastoreStubSettings.defaultHttpJsonTransportProviderBuilder().build())
-            .setEndpoint(getHost(datastoreOptions))
-            .build();
+  private static boolean isLocalHost(String host) {
+    if (!host.isEmpty()) {
+      try {
+        String normalizedHost = "http://" + removeScheme(host);
+        InetAddress hostAddr = InetAddress.getByName(new URL(normalizedHost).getHost());
+        return hostAddr.isAnyLocalAddress() || hostAddr.isLoopbackAddress();
+      } catch (Exception e) {
+        // ignore
+      }
+    }
+    return false;
+  }
 
-    clientContext = ClientContext.create(datastoreSettings);
+  private static String removeScheme(String url) {
+    if (url != null) {
+      if (url.startsWith("https://")) {
+        return url.substring("https://".length());
+      } else if (url.startsWith("http://")) {
+        return url.substring("http://".length());
+      }
+    }
+    return url;
+  }
 
-    DatastoreStubSettings datastoreStubSettings =
-        DatastoreStubSettings.newBuilder(clientContext)
-            .applyToAllUnaryMethods(retrySettingSetter(datastoreOptions))
-            .build();
+  private static DatastoreException translate(
+      com.google.datastore.v1.client.DatastoreException exception) {
+    return translate(exception, true);
+  }
 
-    datastoreStub = HttpJsonDatastoreStub.create(datastoreStubSettings);
+  private static DatastoreException translate(
+      com.google.datastore.v1.client.DatastoreException exception, boolean idempotent) {
+    String reason = "";
+    if (exception.getCode() != null) {
+      reason = exception.getCode().name();
+    }
+    if (reason.isEmpty()) {
+      if (exception.getCause() instanceof IOException) {
+        return new DatastoreException((IOException) exception.getCause());
+      }
+    }
+    return new DatastoreException(
+        exception.getCode().getNumber(), exception.getMessage(), reason, idempotent, exception);
   }
 
   @Override
   public AllocateIdsResponse allocateIds(AllocateIdsRequest request) {
-    return this.datastoreStub.allocateIdsCallable().call(request);
+    try {
+      return client.allocateIds(request);
+    } catch (com.google.datastore.v1.client.DatastoreException ex) {
+      throw translate(ex);
+    }
   }
 
   @Override
   public BeginTransactionResponse beginTransaction(BeginTransactionRequest request) {
-    return this.datastoreStub.beginTransactionCallable().call(request);
+    try {
+      return client.beginTransaction(request);
+    } catch (com.google.datastore.v1.client.DatastoreException ex) {
+      throw translate(ex);
+    }
   }
 
   @Override
   public CommitResponse commit(CommitRequest request) {
-    return this.datastoreStub.commitCallable().call(request);
+    try {
+      return client.commit(request);
+    } catch (com.google.datastore.v1.client.DatastoreException ex) {
+      throw translate(ex, request.getMode() == CommitRequest.Mode.NON_TRANSACTIONAL);
+    }
   }
 
   @Override
   public LookupResponse lookup(LookupRequest request) {
-    return this.datastoreStub.lookupCallable().call(request);
+    try {
+      return client.lookup(request);
+    } catch (com.google.datastore.v1.client.DatastoreException ex) {
+      throw translate(ex);
+    }
   }
 
   @Override
   public ReserveIdsResponse reserveIds(ReserveIdsRequest request) {
-    return this.datastoreStub.reserveIdsCallable().call(request);
+    try {
+      return client.reserveIds(request);
+    } catch (com.google.datastore.v1.client.DatastoreException ex) {
+      throw translate(ex);
+    }
   }
 
   @Override
   public RollbackResponse rollback(RollbackRequest request) {
-    return this.datastoreStub.rollbackCallable().call(request);
+    try {
+      return client.rollback(request);
+    } catch (com.google.datastore.v1.client.DatastoreException ex) {
+      throw translate(ex);
+    }
   }
 
   @Override
   public RunQueryResponse runQuery(RunQueryRequest request) {
-    return this.datastoreStub.runQueryCallable().call(request);
+    try {
+      return client.runQuery(request);
+    } catch (com.google.datastore.v1.client.DatastoreException ex) {
+      throw translate(ex);
+    }
   }
 
   @Override
   public RunAggregationQueryResponse runAggregationQuery(RunAggregationQueryRequest request) {
-    return this.datastoreStub.runAggregationQueryCallable().call(request);
+    try {
+      return client.runAggregationQuery(request);
+    } catch (com.google.datastore.v1.client.DatastoreException ex) {
+      throw translate(ex);
+    }
   }
 
   @Override
   public void close() throws Exception {
-    if (!closed) {
-      datastoreStub.close();
-      for (BackgroundResource resource : clientContext.getBackgroundResources()) {
-        resource.close();
-      }
-      closed = true;
-    }
-    for (BackgroundResource resource : clientContext.getBackgroundResources()) {
-      resource.awaitTermination(1, SECONDS);
-    }
+    throw new UnsupportedOperationException("Not implemented.");
   }
 
   @Override
   public boolean isClosed() {
-    return closed && datastoreStub.isShutdown();
-  }
-
-  /**
-   * Prefixing it with http scheme when host is localhost, otherwise {@link
-   * com.google.api.gax.httpjson.HttpRequestRunnable#normalizeEndpoint(String)} will prefix it with
-   * https.
-   */
-  private String getHost(DatastoreOptions options) {
-    String host = options.getHost();
-    if (isLocalHost(host) && !host.contains("://")) {
-      return "http://" + host;
-    }
-    return host;
+    throw new UnsupportedOperationException("Not implemented.");
   }
 }


Test patch:
diff --git a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/testing/RemoteDatastoreHelper.java b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/testing/RemoteDatastoreHelper.java
index 329b20dc..1471328f 100644
--- a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/testing/RemoteDatastoreHelper.java
+++ b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/testing/RemoteDatastoreHelper.java
@@ -25,6 +25,7 @@ import com.google.cloud.datastore.Key;
 import com.google.cloud.datastore.Query;
 import com.google.cloud.datastore.QueryResults;
 import com.google.cloud.datastore.StructuredQuery;
+import com.google.cloud.grpc.GrpcTransportOptions;
 import com.google.cloud.http.HttpTransportOptions;
 import java.util.UUID;
 import org.threeten.bp.Duration;
@@ -75,11 +76,11 @@ public class RemoteDatastoreHelper {
 
   /** Creates a {@code RemoteStorageHelper} object. */
   public static RemoteDatastoreHelper create() {
-    return create("", DatastoreOptions.getDefaultGrpcTransportOptions());
+    return create("", DatastoreOptions.getDefaultHttpTransportOptions());
   }
 
   public static RemoteDatastoreHelper create(String databaseId) {
-    return create(databaseId, DatastoreOptions.getDefaultGrpcTransportOptions());
+    return create(databaseId, DatastoreOptions.getDefaultHttpTransportOptions());
   }
 
   public static RemoteDatastoreHelper create(TransportOptions transportOptions) {
@@ -88,13 +89,17 @@ public class RemoteDatastoreHelper {
 
   /** Creates a {@code RemoteStorageHelper} object. */
   public static RemoteDatastoreHelper create(String databaseId, TransportOptions transportOptions) {
-    DatastoreOptions datastoreOption =
+    DatastoreOptions.Builder builder =
         DatastoreOptions.newBuilder()
             .setDatabaseId(databaseId)
             .setNamespace(UUID.randomUUID().toString())
-            .setRetrySettings(retrySettings())
-            .setTransportOptions(transportOptions)
-            .build();
+            .setRetrySettings(retrySettings());
+    if (transportOptions instanceof GrpcTransportOptions) {
+      builder = builder.setTransportOptions((GrpcTransportOptions) transportOptions);
+    } else {
+      builder = builder.setTransportOptions(transportOptions);
+    }
+    DatastoreOptions datastoreOption = builder.build();
     return new RemoteDatastoreHelper(datastoreOption);
   }
 
diff --git a/google-cloud-datastore/src/test/java/com/google/cloud/datastore/DatastoreOptionsTest.java b/google-cloud-datastore/src/test/java/com/google/cloud/datastore/DatastoreOptionsTest.java
index 098ae342..e3f48ecc 100644
--- a/google-cloud-datastore/src/test/java/com/google/cloud/datastore/DatastoreOptionsTest.java
+++ b/google-cloud-datastore/src/test/java/com/google/cloud/datastore/DatastoreOptionsTest.java
@@ -31,6 +31,7 @@ import com.google.cloud.datastore.spi.v1.DatastoreRpc;
 import com.google.cloud.datastore.v1.DatastoreSettings;
 import com.google.cloud.grpc.GrpcTransportOptions;
 import com.google.cloud.http.HttpTransportOptions;
+import com.google.datastore.v1.client.DatastoreFactory;
 import org.easymock.EasyMock;
 import org.junit.Assert;
 import org.junit.Before;
@@ -105,6 +106,7 @@ public class DatastoreOptionsTest {
             .setServiceRpcFactory(datastoreRpcFactory)
             .setProjectId(PROJECT_ID)
             .setDatabaseId(DATABASE_ID)
+            .setTransportOptions(GrpcTransportOptions.newBuilder().build())
             .setChannelProvider(channelProvider)
             .setCredentialsProvider(noCredentialsProvider)
             .setHost("http://localhost:" + PORT)
@@ -136,26 +138,58 @@ public class DatastoreOptionsTest {
 
   @Test
   public void testTransport() {
-    // default grpc transport
-    assertThat(options.build().getTransportOptions()).isInstanceOf(GrpcTransportOptions.class);
+    // default http transport
+    assertThat(options.build().getTransportOptions()).isInstanceOf(HttpTransportOptions.class);
 
-    // custom http transport
-    DatastoreOptions httpDatastoreOptions =
+    // custom grpc transport
+    DatastoreOptions grpcTransportOptions =
         DatastoreOptions.newBuilder()
-            .setTransportOptions(HttpTransportOptions.newBuilder().build())
+            .setTransportOptions(GrpcTransportOptions.newBuilder().build())
             .setProjectId(PROJECT_ID)
+            .setCredentialsProvider(NoCredentialsProvider.create())
             .build();
-    assertThat(httpDatastoreOptions.getTransportOptions()).isInstanceOf(HttpTransportOptions.class);
-    assertThat(httpDatastoreOptions.getCredentialsProvider()).isNull();
-    assertThat(httpDatastoreOptions.getTransportChannelProvider()).isNull();
+    assertThat(grpcTransportOptions.getTransportOptions()).isInstanceOf(GrpcTransportOptions.class);
+    assertThat(grpcTransportOptions.getCredentialsProvider())
+        .isInstanceOf(NoCredentialsProvider.class);
+    assertThat(grpcTransportOptions.getTransportChannelProvider())
+        .isInstanceOf(InstantiatingGrpcChannelProvider.class);
+  }
 
-    // custom grpc transport
-    DatastoreOptions grpcDatastoreOptions =
+  @Test
+  public void testHostWithGrpcAndHttp() {
+    DatastoreOptions grpcTransportOptions =
         DatastoreOptions.newBuilder()
             .setTransportOptions(GrpcTransportOptions.newBuilder().build())
             .setProjectId(PROJECT_ID)
+            .setCredentialsProvider(NoCredentialsProvider.create())
+            .build();
+    assertThat(grpcTransportOptions.getHost()).isEqualTo(DatastoreSettings.getDefaultEndpoint());
+    assertThat(grpcTransportOptions.getHost()).isEqualTo("datastore.googleapis.com:443");
+
+    String customHost = "http://localhost:" + PORT;
+    DatastoreOptions grpcTransportOptionsCustomHost =
+        DatastoreOptions.newBuilder()
+            .setTransportOptions(GrpcTransportOptions.newBuilder().build())
+            .setHost(customHost)
+            .setProjectId(PROJECT_ID)
+            .setCredentialsProvider(NoCredentialsProvider.create())
+            .build();
+    assertThat(grpcTransportOptionsCustomHost.getHost()).isEqualTo(customHost);
+
+    DatastoreOptions httpTransportOptions =
+        DatastoreOptions.newBuilder()
+            .setProjectId(PROJECT_ID)
+            .setCredentials(NoCredentials.getInstance())
+            .build();
+    assertThat(httpTransportOptions.getHost()).isEqualTo(DatastoreFactory.DEFAULT_HOST);
+
+    DatastoreOptions httpTransportOptionsCustomHost =
+        DatastoreOptions.newBuilder()
+            .setHost(customHost)
+            .setProjectId(PROJECT_ID)
+            .setCredentials(NoCredentials.getInstance())
             .build();
-    assertThat(grpcDatastoreOptions.getTransportOptions()).isInstanceOf(GrpcTransportOptions.class);
+    assertThat(httpTransportOptionsCustomHost.getHost()).isEqualTo(customHost);
   }
 
   @Test
@@ -164,6 +198,7 @@ public class DatastoreOptionsTest {
     DatastoreOptions copy = original.toBuilder().build();
     assertEquals(original.getProjectId(), copy.getProjectId());
     assertEquals(original.getNamespace(), copy.getNamespace());
+    assertEquals(original.getDatabaseId(), copy.getDatabaseId());
     assertEquals(original.getHost(), copy.getHost());
     assertEquals(original.getRetrySettings(), copy.getRetrySettings());
     assertEquals(original.getCredentials(), copy.getCredentials());
diff --git a/google-cloud-datastore/src/test/java/com/google/cloud/datastore/DatastoreTest.java b/google-cloud-datastore/src/test/java/com/google/cloud/datastore/DatastoreTest.java
index efde25e6..18471244 100644
--- a/google-cloud-datastore/src/test/java/com/google/cloud/datastore/DatastoreTest.java
+++ b/google-cloud-datastore/src/test/java/com/google/cloud/datastore/DatastoreTest.java
@@ -16,7 +16,6 @@
 
 package com.google.cloud.datastore;
 
-import static com.google.api.gax.rpc.StatusCode.Code.ABORTED;
 import static com.google.cloud.datastore.ProtoTestData.intValue;
 import static com.google.cloud.datastore.TestUtils.matches;
 import static com.google.cloud.datastore.aggregation.Aggregation.count;
@@ -31,7 +30,6 @@ import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -252,7 +250,7 @@ public class DatastoreTest {
       transaction.commit();
       fail("Expecting a failure");
     } catch (DatastoreException expected) {
-      assertEquals(ABORTED.getHttpStatusCode(), expected.getCode());
+      assertEquals("ABORTED", expected.getReason());
     }
   }
 
@@ -281,7 +279,7 @@ public class DatastoreTest {
       transaction.commit();
       fail("Expecting a failure");
     } catch (DatastoreException expected) {
-      assertEquals(ABORTED.getHttpStatusCode(), expected.getCode());
+      assertEquals("ABORTED", expected.getReason());
     }
   }
 
@@ -1383,21 +1381,6 @@ public class DatastoreTest {
     checkKeyProperties(incompleteKey);
   }
 
-  @Test
-  public void testDatastoreClose() throws Exception {
-    Datastore datastore = options.toBuilder().build().getService();
-    Entity entity = datastore.get(KEY3);
-    assertNull(entity);
-
-    datastore.close();
-    assertTrue(datastore.isClosed());
-
-    assertThrows(
-        "io.grpc.StatusRuntimeException: UNAVAILABLE: Channel shutdown invoked",
-        DatastoreException.class,
-        () -> datastore.get(KEY3));
-  }
-
   private void checkKeyProperties(BaseKey key) {
     assertEquals(options.getDatabaseId(), key.getDatabaseId());
     assertEquals(options.getProjectId(), key.getProjectId());
diff --git a/google-cloud-datastore/src/test/java/com/google/cloud/datastore/it/AbstractITDatastoreTest.java b/google-cloud-datastore/src/test/java/com/google/cloud/datastore/it/AbstractITDatastoreTest.java
index 357471ab..7c105672 100644
--- a/google-cloud-datastore/src/test/java/com/google/cloud/datastore/it/AbstractITDatastoreTest.java
+++ b/google-cloud-datastore/src/test/java/com/google/cloud/datastore/it/AbstractITDatastoreTest.java
@@ -70,6 +70,7 @@ import com.google.cloud.datastore.StructuredQuery.PropertyFilter;
 import com.google.cloud.datastore.TimestampValue;
 import com.google.cloud.datastore.Transaction;
 import com.google.cloud.datastore.ValueType;
+import com.google.cloud.grpc.GrpcTransportOptions;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.datastore.v1.TransactionOptions;
@@ -1449,9 +1450,13 @@ public abstract class AbstractITDatastoreTest {
       datastore.runInTransaction(callable2, readOnlyOptions);
       fail("Expecting a failure");
     } catch (DatastoreException expected) {
-      assertEquals(
-          INVALID_ARGUMENT.getHttpStatusCode(),
-          ((DatastoreException) expected.getCause()).getCode());
+      if (datastore.getOptions().getTransportOptions() instanceof GrpcTransportOptions) {
+        assertEquals(
+            INVALID_ARGUMENT.getHttpStatusCode(),
+            ((DatastoreException) expected.getCause()).getCode());
+      } else {
+        assertEquals(3, ((DatastoreException) expected.getCause()).getCode());
+      }
     }
   }
 
diff --git a/google-cloud-datastore/src/test/java/com/google/cloud/datastore/it/ITDatastoreConceptsTest.java b/google-cloud-datastore/src/test/java/com/google/cloud/datastore/it/ITDatastoreConceptsTest.java
index f61db4f4..1d6b2f83 100644
--- a/google-cloud-datastore/src/test/java/com/google/cloud/datastore/it/ITDatastoreConceptsTest.java
+++ b/google-cloud-datastore/src/test/java/com/google/cloud/datastore/it/ITDatastoreConceptsTest.java
@@ -67,7 +67,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import org.junit.After;
-import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -132,11 +131,6 @@ public class ITDatastoreConceptsTest {
     datastore.delete(taskKeysToDelete);
   }
 
-  @AfterClass
-  public static void afterClass() throws Exception {
-    datastore.close();
-  }
-
   private void assertValidKey(Key taskKey) {
     datastore.put(Entity.newBuilder(taskKey, TEST_FULL_ENTITY).build());
   }
@@ -575,7 +569,7 @@ public class ITDatastoreConceptsTest {
   }
 
   @Test
-  public void testInequalityInvalid() {
+  public void testInequalityValid() {
     Query<Entity> query =
         Query.newEntityQueryBuilder()
             .setKind(TASK_CONCEPTS)
@@ -583,7 +577,7 @@ public class ITDatastoreConceptsTest {
                 CompositeFilter.and(
                     PropertyFilter.gt("created", startDate), PropertyFilter.gt("priority", 3)))
             .build();
-    assertInvalidQuery(query);
+    assertValidQuery(query);
   }
 
   @Test
diff --git a/google-cloud-datastore/src/test/java/com/google/cloud/datastore/it/ITDatastoreTestGrpc.java b/google-cloud-datastore/src/test/java/com/google/cloud/datastore/it/ITDatastoreTestGrpc.java
index d9acbd08..7bb80999 100644
--- a/google-cloud-datastore/src/test/java/com/google/cloud/datastore/it/ITDatastoreTestGrpc.java
+++ b/google-cloud-datastore/src/test/java/com/google/cloud/datastore/it/ITDatastoreTestGrpc.java
@@ -18,6 +18,8 @@ package com.google.cloud.datastore.it;
 import com.google.cloud.datastore.Datastore;
 import com.google.cloud.datastore.DatastoreOptions;
 import com.google.cloud.datastore.testing.RemoteDatastoreHelper;
+import com.google.cloud.grpc.GrpcTransportOptions;
+import com.google.common.truth.Truth;
 import java.util.Arrays;
 import org.junit.AfterClass;
 import org.junit.runner.RunWith;
@@ -26,13 +28,14 @@ import org.junit.runners.Parameterized;
 @RunWith(Parameterized.class)
 public class ITDatastoreTestGrpc extends AbstractITDatastoreTest {
   // setup for default db, grpc transport
-  protected static final RemoteDatastoreHelper HELPER_DEFAULT_GRPC = RemoteDatastoreHelper.create();
+  protected static final RemoteDatastoreHelper HELPER_DEFAULT_GRPC =
+      RemoteDatastoreHelper.create(GrpcTransportOptions.newBuilder().build());
   private static final DatastoreOptions OPTIONS_DEFAULT_GRPC = HELPER_DEFAULT_GRPC.getOptions();
   private static final Datastore DATASTORE_DEFAULT_GRPC = OPTIONS_DEFAULT_GRPC.getService();
 
   // setup for custom db, grpc transport
   private static final RemoteDatastoreHelper HELPER_CUSTOM_DB_GRPC =
-      RemoteDatastoreHelper.create(CUSTOM_DB_ID);
+      RemoteDatastoreHelper.create(CUSTOM_DB_ID, GrpcTransportOptions.newBuilder().build());
   private static final DatastoreOptions OPTIONS_CUSTOM_DB_GRPC = HELPER_CUSTOM_DB_GRPC.getOptions();
   private static final Datastore DATASTORE_CUSTOM_DB_GRPC = OPTIONS_CUSTOM_DB_GRPC.getService();
 
@@ -55,5 +58,7 @@ public class ITDatastoreTestGrpc extends AbstractITDatastoreTest {
     HELPER_CUSTOM_DB_GRPC.deleteNamespace();
     DATASTORE_DEFAULT_GRPC.close();
     DATASTORE_CUSTOM_DB_GRPC.close();
+    Truth.assertThat(DATASTORE_DEFAULT_GRPC.isClosed()).isTrue();
+    Truth.assertThat(DATASTORE_CUSTOM_DB_GRPC.isClosed()).isTrue();
   }
 }
diff --git a/google-cloud-datastore/src/test/java/com/google/cloud/datastore/it/ITDatastoreTestHttp.java b/google-cloud-datastore/src/test/java/com/google/cloud/datastore/it/ITDatastoreTestHttp.java
index 0a453bbb..3546bfca 100644
--- a/google-cloud-datastore/src/test/java/com/google/cloud/datastore/it/ITDatastoreTestHttp.java
+++ b/google-cloud-datastore/src/test/java/com/google/cloud/datastore/it/ITDatastoreTestHttp.java
@@ -18,7 +18,6 @@ package com.google.cloud.datastore.it;
 import com.google.cloud.datastore.Datastore;
 import com.google.cloud.datastore.DatastoreOptions;
 import com.google.cloud.datastore.testing.RemoteDatastoreHelper;
-import com.google.cloud.http.HttpTransportOptions;
 import java.util.Arrays;
 import org.junit.AfterClass;
 import org.junit.runner.RunWith;
@@ -27,14 +26,14 @@ import org.junit.runners.Parameterized;
 @RunWith(Parameterized.class)
 public class ITDatastoreTestHttp extends AbstractITDatastoreTest {
   // setup for default db, http transport
-  private static final RemoteDatastoreHelper HELPER_DEFAULT_HTTP =
-      RemoteDatastoreHelper.create(HttpTransportOptions.newBuilder().build());
+  private static final RemoteDatastoreHelper HELPER_DEFAULT_HTTP = RemoteDatastoreHelper.create();
+
   private static final DatastoreOptions OPTIONS_DEFAULT_HTTP = HELPER_DEFAULT_HTTP.getOptions();
   private static final Datastore DATASTORE_DEFAULT_HTTP = OPTIONS_DEFAULT_HTTP.getService();
 
   // setup for custom db, http transport
   private static final RemoteDatastoreHelper HELPER_CUSTOM_DB_HTTP =
-      RemoteDatastoreHelper.create(CUSTOM_DB_ID, HttpTransportOptions.newBuilder().build());
+      RemoteDatastoreHelper.create(CUSTOM_DB_ID);
   private static final DatastoreOptions OPTIONS_CUSTOM_DB_HTTP = HELPER_CUSTOM_DB_HTTP.getOptions();
   private static final Datastore DATASTORE_CUSTOM_DB_HTTP = OPTIONS_CUSTOM_DB_HTTP.getService();
 
@@ -55,7 +54,5 @@ public class ITDatastoreTestHttp extends AbstractITDatastoreTest {
   public static void afterClass() throws Exception {
     HELPER_DEFAULT_HTTP.deleteNamespace();
     HELPER_CUSTOM_DB_HTTP.deleteNamespace();
-    DATASTORE_DEFAULT_HTTP.close();
-    DATASTORE_CUSTOM_DB_HTTP.close();
   }
 }