Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow setting channel and credential providers in DatastoreOptions #1299

Merged
merged 10 commits into from
Feb 8, 2024
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.29.0')
implementation platform('com.google.cloud:libraries-bom:26.31.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.0'
implementation 'com.google.cloud:google-cloud-datastore:2.18.3'
```

If you are using SBT, add this to your dependencies:

```Scala
libraryDependencies += "com.google.cloud" % "google-cloud-datastore" % "2.18.0"
libraryDependencies += "com.google.cloud" % "google-cloud-datastore" % "2.18.3"
```
<!-- {x-version-update-end} -->

Expand Down Expand Up @@ -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.0
[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-datastore/2.18.3
[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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@

import static com.google.cloud.datastore.Validator.validateNamespace;

import com.google.api.gax.core.CredentialsProvider;
import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider;
import com.google.api.gax.rpc.TransportChannelProvider;
import com.google.cloud.ServiceDefaults;
import com.google.cloud.ServiceOptions;
import com.google.cloud.ServiceRpc;
Expand Down Expand Up @@ -46,6 +49,9 @@ public class DatastoreOptions extends ServiceOptions<Datastore, DatastoreOptions
public static final String PROJECT_ID_ENV_VAR = "DATASTORE_PROJECT_ID";
public static final String LOCAL_HOST_ENV_VAR = "DATASTORE_EMULATOR_HOST";

private transient TransportChannelProvider channelProvider = null;
private transient CredentialsProvider credentialsProvider = null;

private final String namespace;
private final String databaseId;

Expand All @@ -69,6 +75,10 @@ public ServiceRpc create(DatastoreOptions options) {
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.");
meltsufin marked this conversation as resolved.
Show resolved Hide resolved
}
return new HttpDatastoreRpc(options);
} else {
throw new IllegalArgumentException(
Expand All @@ -84,20 +94,46 @@ public static class Builder extends ServiceOptions.Builder<Datastore, DatastoreO

private String namespace;
private String databaseId;
private TransportChannelProvider channelProvider = null;
private CredentialsProvider credentialsProvider = null;

private Builder() {}

private Builder(DatastoreOptions options) {
super(options);
namespace = options.namespace;
databaseId = options.databaseId;
this.namespace = options.namespace;
this.databaseId = options.databaseId;
this.channelProvider = validateChannelProvider(options.channelProvider);
this.credentialsProvider = options.credentialsProvider;
}

@Override
public Builder setTransportOptions(TransportOptions transportOptions) {
return super.setTransportOptions(transportOptions);
}

/**
* Sets the {@link TransportChannelProvider} to use with this Datastore client.
*
* @param channelProvider A InstantiatingGrpcChannelProvider object that defines the transport
* provider for this client.
*/
public Builder setChannelProvider(TransportChannelProvider channelProvider) {
this.channelProvider = validateChannelProvider(channelProvider);
return this;
}

/**
* Sets the {@link CredentialsProvider} to use with this Datastore client.
*
* @param credentialsProvider A CredentialsProvider object that defines the credential provider
* for this client.
*/
public Builder setCredentialsProvider(CredentialsProvider credentialsProvider) {
this.credentialsProvider = credentialsProvider;
return this;
}

@Override
public DatastoreOptions build() {
return new DatastoreOptions(this);
Expand All @@ -115,10 +151,45 @@ public Builder setDatabaseId(String databaseId) {
}
}

private static TransportChannelProvider validateChannelProvider(
TransportChannelProvider channelProvider) {
if (!(channelProvider instanceof InstantiatingGrpcChannelProvider)) {
throw new IllegalArgumentException(
"Only GRPC channels are allowed for " + API_SHORT_NAME + ".");
}
return channelProvider;
}

private DatastoreOptions(Builder builder) {
super(DatastoreFactory.class, DatastoreRpcFactory.class, builder, new DatastoreDefaults());
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(
"Only gRPC transport allows setting of channel provider or credentials provider");
} else if (getTransportOptions() instanceof GrpcTransportOptions) {
this.channelProvider =
kolea2 marked this conversation as resolved.
Show resolved Hide resolved
builder.channelProvider != null
? builder.channelProvider
: GrpcTransportOptions.setUpChannelProvider(
DatastoreSettings.defaultGrpcTransportProviderBuilder(), this);

this.credentialsProvider =
builder.credentialsProvider != null
? builder.credentialsProvider
: GrpcTransportOptions.setUpCredentialsProvider(this);
}
}

public CredentialsProvider getCredentialsProvider() {
return credentialsProvider;
}

public TransportChannelProvider getTransportChannelProvider() {
return channelProvider;
}

@Override
Expand All @@ -134,6 +205,7 @@ protected String getDefaultProject() {
}

private static class DatastoreDefaults implements ServiceDefaults<Datastore, DatastoreOptions> {
private final TransportOptions TRANSPORT_OPTIONS = getDefaultTransportOptionsBuilder().build();

@Override
public DatastoreFactory getDefaultServiceFactory() {
Expand All @@ -147,7 +219,11 @@ public DatastoreRpcFactory getDefaultRpcFactory() {

@Override
public TransportOptions getDefaultTransportOptions() {
return getDefaultGrpcTransportOptions();
return TRANSPORT_OPTIONS;
}

public static GrpcTransportOptions.Builder getDefaultTransportOptionsBuilder() {
return GrpcTransportOptions.newBuilder();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,19 @@
package com.google.cloud.datastore;

import com.google.api.core.InternalApi;
import com.google.cloud.NoCredentials;
import com.google.common.base.Strings;
import java.net.InetAddress;
import java.net.URL;

@InternalApi
public class DatastoreUtils {

public static boolean isEmulator(DatastoreOptions datastoreOptions) {
return isLocalHost(datastoreOptions.getHost())
|| NoCredentials.getInstance().equals(datastoreOptions.getCredentials());
}

public static boolean isLocalHost(String host) {
if (Strings.isNullOrEmpty(host)) {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package com.google.cloud.datastore.spi.v1;

import static com.google.cloud.datastore.DatastoreUtils.isLocalHost;
import static com.google.cloud.datastore.DatastoreUtils.isEmulator;
import static com.google.cloud.datastore.DatastoreUtils.removeScheme;
import static com.google.cloud.datastore.spi.v1.RpcUtils.retrySettingSetter;
import static java.util.concurrent.TimeUnit.SECONDS;
Expand All @@ -30,14 +30,12 @@
import com.google.api.gax.rpc.HeaderProvider;
import com.google.api.gax.rpc.NoHeaderProvider;
import com.google.api.gax.rpc.TransportChannel;
import com.google.cloud.NoCredentials;
import com.google.cloud.ServiceOptions;
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.GrpcDatastoreStub;
import com.google.cloud.grpc.GrpcTransportOptions;
import com.google.common.base.Strings;
import com.google.datastore.v1.AllocateIdsRequest;
import com.google.datastore.v1.AllocateIdsResponse;
Expand Down Expand Up @@ -69,7 +67,6 @@ public class GrpcDatastoreRpc implements DatastoreRpc {
private boolean closed;

public GrpcDatastoreRpc(DatastoreOptions datastoreOptions) throws IOException {

try {
clientContext =
isEmulator(datastoreOptions)
Expand Down Expand Up @@ -146,11 +143,6 @@ public boolean isClosed() {
return closed && datastoreStub.isShutdown();
}

private boolean isEmulator(DatastoreOptions datastoreOptions) {
return isLocalHost(datastoreOptions.getHost())
|| NoCredentials.getInstance().equals(datastoreOptions.getCredentials());
}

private ClientContext getClientContextForEmulator(DatastoreOptions datastoreOptions)
throws IOException {
ManagedChannel managedChannel =
Expand All @@ -177,11 +169,8 @@ private ClientContext getClientContext(DatastoreOptions datastoreOptions) throws

DatastoreSettingsBuilder settingsBuilder =
new DatastoreSettingsBuilder(DatastoreSettings.newBuilder().build());
settingsBuilder.setCredentialsProvider(
GrpcTransportOptions.setUpCredentialsProvider(datastoreOptions));
settingsBuilder.setTransportChannelProvider(
GrpcTransportOptions.setUpChannelProvider(
DatastoreSettings.defaultGrpcTransportProviderBuilder(), datastoreOptions));
settingsBuilder.setCredentialsProvider(datastoreOptions.getCredentialsProvider());
settingsBuilder.setTransportChannelProvider(datastoreOptions.getTransportChannelProvider());
settingsBuilder.setInternalHeaderProvider(internalHeaderProvider);
settingsBuilder.setHeaderProvider(
datastoreOptions.getMergedHeaderProvider(new NoHeaderProvider()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,17 @@
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;

import com.google.api.gax.core.NoCredentialsProvider;
import com.google.api.gax.grpc.ChannelPoolSettings;
import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider;
import com.google.cloud.NoCredentials;
import com.google.cloud.datastore.spi.DatastoreRpcFactory;
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 org.easymock.EasyMock;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

Expand All @@ -48,7 +54,9 @@ public void setUp() {
.setServiceRpcFactory(datastoreRpcFactory)
.setProjectId(PROJECT_ID)
.setDatabaseId(DATABASE_ID)
.setCredentials(NoCredentials.getInstance())
.setHost("http://localhost:" + PORT);

EasyMock.expect(datastoreRpcFactory.create(EasyMock.anyObject(DatastoreOptions.class)))
.andReturn(datastoreRpc)
.anyTimes();
Expand Down Expand Up @@ -81,6 +89,51 @@ public void testDatastore() {
assertSame(datastoreRpc, options.build().getRpc());
}

@Test
public void testCustomChannelAndCredentials() {
NoCredentialsProvider noCredentialsProvider = NoCredentialsProvider.create();
InstantiatingGrpcChannelProvider channelProvider =
DatastoreSettings.defaultGrpcTransportProviderBuilder()
.setChannelPoolSettings(
ChannelPoolSettings.builder()
.setInitialChannelCount(10)
.setMaxChannelCount(20)
.build())
.build();
DatastoreOptions datastoreOptions =
DatastoreOptions.newBuilder()
.setServiceRpcFactory(datastoreRpcFactory)
.setProjectId(PROJECT_ID)
.setDatabaseId(DATABASE_ID)
.setChannelProvider(channelProvider)
.setCredentialsProvider(noCredentialsProvider)
.setHost("http://localhost:" + PORT)
.build();
assertEquals(datastoreOptions.getTransportChannelProvider(), channelProvider);
assertEquals(datastoreOptions.getCredentialsProvider(), noCredentialsProvider);
}

@Test
public void testInvalidConfigForHttp() {
DatastoreOptions.Builder options =
DatastoreOptions.newBuilder()
.setServiceRpcFactory(datastoreRpcFactory)
.setProjectId(PROJECT_ID)
.setDatabaseId(DATABASE_ID)
.setTransportOptions(HttpTransportOptions.newBuilder().build())
.setChannelProvider(
DatastoreSettings.defaultGrpcTransportProviderBuilder()
.setChannelPoolSettings(
ChannelPoolSettings.builder()
.setInitialChannelCount(10)
.setMaxChannelCount(20)
.build())
.build())
.setCredentialsProvider(NoCredentialsProvider.create())
.setHost("http://localhost:" + PORT);
Assert.assertThrows(IllegalArgumentException.class, options::build);
}

@Test
public void testTransport() {
// default grpc transport
Expand All @@ -93,6 +146,8 @@ public void testTransport() {
.setProjectId(PROJECT_ID)
.build();
assertThat(httpDatastoreOptions.getTransportOptions()).isInstanceOf(HttpTransportOptions.class);
assertThat(httpDatastoreOptions.getCredentialsProvider()).isNull();
assertThat(httpDatastoreOptions.getTransportChannelProvider()).isNull();

// custom grpc transport
DatastoreOptions grpcDatastoreOptions =
Expand Down
Loading