From 6b58a5a3040a6c2f5b84e340645d59f1d7b42c5c Mon Sep 17 00:00:00 2001 From: "Zhenli(Jenny) Jiang" Date: Tue, 23 Mar 2021 18:55:32 -0700 Subject: [PATCH 01/16] add grpc-gcp extensions --- .../gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.jar | Bin 0 -> 67032 bytes .../gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.jar.md5 | 1 + .../grpc.gcp/0.0.1/grpc.gcp-0.0.1.jar.sha1 | 1 + .../gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom | 8 ++ .../gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom.md5 | 1 + .../grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom.sha1 | 1 + .../grpc/gcp/grpc.gcp/maven-metadata.xml | 12 +++ .../grpc/gcp/grpc.gcp/maven-metadata.xml.md5 | 1 + .../grpc/gcp/grpc.gcp/maven-metadata.xml.sha1 | 1 + google-cloud-spanner/pom.xml | 13 +++ .../google/cloud/spanner/SpannerOptions.java | 13 +++ .../cloud/spanner/spi/v1/GapicSpannerRpc.java | 23 ++++ .../main/resources/grpc-gcp-apiconfig.json | 99 ++++++++++++++++++ 13 files changed, 174 insertions(+) create mode 100644 google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.jar create mode 100644 google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.jar.md5 create mode 100644 google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.jar.sha1 create mode 100644 google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom create mode 100644 google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom.md5 create mode 100644 google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom.sha1 create mode 100644 google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/maven-metadata.xml create mode 100644 google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/maven-metadata.xml.md5 create mode 100644 google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/maven-metadata.xml.sha1 create mode 100644 google-cloud-spanner/src/main/resources/grpc-gcp-apiconfig.json diff --git a/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.jar b/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.jar new file mode 100644 index 0000000000000000000000000000000000000000..d35a79203922dee0266f49ac3618968bd219b5a9 GIT binary patch literal 67032 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 literal 0 HcmV?d00001 diff --git a/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.jar.md5 b/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.jar.md5 new file mode 100644 index 00000000000..b29738ff327 --- /dev/null +++ b/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.jar.md5 @@ -0,0 +1 @@ +7da91178703f5a131f1b6714e3c91a06 \ No newline at end of file diff --git a/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.jar.sha1 b/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.jar.sha1 new file mode 100644 index 00000000000..544f78e30b0 --- /dev/null +++ b/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.jar.sha1 @@ -0,0 +1 @@ +55f3b339ab174107236080f5cc015dd78e0868f4 \ No newline at end of file diff --git a/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom b/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom new file mode 100644 index 00000000000..4be68693e04 --- /dev/null +++ b/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom @@ -0,0 +1,8 @@ + + + 4.0.0 + grpc.gcp + grpc.gcp + 0.0.1 + diff --git a/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom.md5 b/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom.md5 new file mode 100644 index 00000000000..90b2fa2b49f --- /dev/null +++ b/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom.md5 @@ -0,0 +1 @@ +26cc8e28112a7b0ad74cc996cc8e679b \ No newline at end of file diff --git a/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom.sha1 b/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom.sha1 new file mode 100644 index 00000000000..4d818a93b02 --- /dev/null +++ b/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom.sha1 @@ -0,0 +1 @@ +1b2bbfba61904344fcde27931dc64f0d463bc349 \ No newline at end of file diff --git a/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/maven-metadata.xml b/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/maven-metadata.xml new file mode 100644 index 00000000000..81949266520 --- /dev/null +++ b/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/maven-metadata.xml @@ -0,0 +1,12 @@ + + + grpc.gcp + grpc.gcp + + 0.0.1 + + 0.0.1 + + 20210324014307 + + diff --git a/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/maven-metadata.xml.md5 b/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/maven-metadata.xml.md5 new file mode 100644 index 00000000000..10179e808d2 --- /dev/null +++ b/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/maven-metadata.xml.md5 @@ -0,0 +1 @@ +b045ebca85082a017cf7f66674415c85 \ No newline at end of file diff --git a/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/maven-metadata.xml.sha1 b/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/maven-metadata.xml.sha1 new file mode 100644 index 00000000000..c546591c340 --- /dev/null +++ b/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/maven-metadata.xml.sha1 @@ -0,0 +1 @@ +6cbc0cc2c4c48c405fadb0ffdc291aaab8908cf3 \ No newline at end of file diff --git a/google-cloud-spanner/pom.xml b/google-cloud-spanner/pom.xml index 787d0932957..fec6e7571a3 100644 --- a/google-cloud-spanner/pom.xml +++ b/google-cloud-spanner/pom.xml @@ -118,7 +118,20 @@ + + + local-maven-repo + file:///${project.basedir}/local-maven-repo + + + + + + grpc.gcp + grpc.gcp + 0.0.1 + io.grpc grpc-api diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java index d5789870090..e8a8ee5e1a2 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java @@ -103,6 +103,7 @@ public class SpannerOptions extends ServiceOptions { private final InstanceAdminStubSettings instanceAdminStubSettings; private final DatabaseAdminStubSettings databaseAdminStubSettings; private final Duration partitionedDmlTimeout; + private final boolean useGrpcGcpExtension; private final boolean autoThrottleAdministrativeRequests; private final boolean trackTransactionStarter; /** @@ -557,6 +558,7 @@ private SpannerOptions(Builder builder) { throw SpannerExceptionFactory.newSpannerException(e); } partitionedDmlTimeout = builder.partitionedDmlTimeout; + this.useGrpcGcpExtension = builder.useGrpcGcpExtension; autoThrottleAdministrativeRequests = builder.autoThrottleAdministrativeRequests; trackTransactionStarter = builder.trackTransactionStarter; defaultQueryOptions = builder.defaultQueryOptions; @@ -636,6 +638,7 @@ public static class Builder private DatabaseAdminStubSettings.Builder databaseAdminStubSettingsBuilder = DatabaseAdminStubSettings.newBuilder(); private Duration partitionedDmlTimeout = Duration.ofHours(2L); + private boolean useGrpcGcpExtension = true; private boolean autoThrottleAdministrativeRequests = false; private boolean trackTransactionStarter = false; private Map defaultQueryOptions = new HashMap<>(); @@ -683,6 +686,7 @@ private Builder() { this.instanceAdminStubSettingsBuilder = options.instanceAdminStubSettings.toBuilder(); this.databaseAdminStubSettingsBuilder = options.databaseAdminStubSettings.toBuilder(); this.partitionedDmlTimeout = options.partitionedDmlTimeout; + this.useGrpcGcpExtension = useGrpcGcpExtension; this.autoThrottleAdministrativeRequests = options.autoThrottleAdministrativeRequests; this.trackTransactionStarter = options.trackTransactionStarter; this.defaultQueryOptions = options.defaultQueryOptions; @@ -999,6 +1003,11 @@ public Builder setHost(String host) { return this; } + public Builder setUseGrpcGcpExtension(boolean useGrpcGcpExtension) { + this.useGrpcGcpExtension = useGrpcGcpExtension; + return this; + } + /** * Sets the host of an emulator to use. By default the value is read from an environment * variable. If the environment variable is not set, this will be null. @@ -1099,6 +1108,10 @@ public Duration getPartitionedDmlTimeout() { return partitionedDmlTimeout; } + public boolean isUseGrpcGcpExtension() { + return useGrpcGcpExtension; + } + public boolean isAutoThrottleAdministrativeRequests() { return autoThrottleAdministrativeRequests; } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java index 94a7a121d67..ebb0ee6919d 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java @@ -18,6 +18,7 @@ import static com.google.cloud.spanner.SpannerExceptionFactory.newSpannerException; +import com.google.api.core.ApiFunction; import com.google.api.core.ApiFuture; import com.google.api.core.InternalApi; import com.google.api.core.NanoClock; @@ -82,6 +83,7 @@ import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.RateLimiter; import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.google.grpc.gcp.GcpManagedChannelBuilder; import com.google.iam.v1.GetIamPolicyRequest; import com.google.iam.v1.Policy; import com.google.iam.v1.SetIamPolicyRequest; @@ -156,7 +158,9 @@ import com.google.spanner.v1.Transaction; import io.grpc.CallCredentials; import io.grpc.Context; +import io.grpc.ManagedChannelBuilder; import io.grpc.MethodDescriptor; +import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; @@ -248,6 +252,7 @@ private void awaitTermination() throws InterruptedException { private static final String CLIENT_LIBRARY_LANGUAGE = "spanner-java"; public static final String DEFAULT_USER_AGENT = CLIENT_LIBRARY_LANGUAGE + "/" + GaxProperties.getLibraryVersion(GapicSpannerRpc.class); + private static final String API_FILE = "grpc-gcp-apiconfig.json"; private final ManagedInstantiatingExecutorProvider executorProvider; private boolean rpcIsClosed; @@ -359,6 +364,24 @@ public GapicSpannerRpc(final SpannerOptions options) { // Attempts direct access to spanner service over gRPC to improve throughput, // whether the attempt is allowed is totally controlled by service owner. .setAttemptDirectPath(true); + if (options.isUseGrpcGcpExtension() && options.getChannelConfigurator() == null) { + // Disables the gax channel pool and uses the channel pool provided by the GCP extension + // instead. + // TODO: may need to update GcpManagedChannelBuilder so that Spanner could specify the channel + // pool size using options.getNumChannels(). + final File configFile = + new File(GapicSpannerRpc.class.getClassLoader().getResource(API_FILE).getFile()); + ApiFunction apiFunction = + new ApiFunction() { + @Override + public ManagedChannelBuilder apply(ManagedChannelBuilder channelBuilder) { + return GcpManagedChannelBuilder.forDelegateBuilder(channelBuilder) + .withApiConfigJsonFile(configFile); + } + }; + defaultChannelProviderBuilder = + defaultChannelProviderBuilder.setPoolSize(1).setChannelConfigurator(apiFunction); + } TransportChannelProvider channelProvider = MoreObjects.firstNonNull( diff --git a/google-cloud-spanner/src/main/resources/grpc-gcp-apiconfig.json b/google-cloud-spanner/src/main/resources/grpc-gcp-apiconfig.json new file mode 100644 index 00000000000..5ba11458768 --- /dev/null +++ b/google-cloud-spanner/src/main/resources/grpc-gcp-apiconfig.json @@ -0,0 +1,99 @@ +{ + "channelPool": { + "maxSize": 3, + "maxConcurrentStreamsLowWatermark": 2 + }, + "method": [ + { + "name": ["google.spanner.v1.Spanner/CreateSession"], + "affinity" : { + "command": "BIND", + "affinityKey": "name" + } + }, + { + "name": ["google.spanner.v1.Spanner/BatchCreateSessions"], + "affinity" : { + "command": "BIND", + "affinityKey": "session.name" + } + }, + { + "name": ["google.spanner.v1.Spanner/GetSession"], + "affinity": { + "command": "BOUND", + "affinityKey": "name" + } + }, + { + "name": ["google.spanner.v1.Spanner/DeleteSession"], + "affinity": { + "command": "UNBIND", + "affinityKey": "name" + } + }, + { + "name": ["google.spanner.v1.Spanner/ExecuteSql"], + "affinity": { + "command": "BOUND", + "affinityKey": "session" + } + }, + { + "name": ["google.spanner.v1.Spanner/ExecuteStreamingSql"], + "affinity": { + "command": "BOUND", + "affinityKey": "session" + } + }, + { + "name": ["google.spanner.v1.Spanner/Read"], + "affinity": { + "command": "BOUND", + "affinityKey": "session" + } + }, + { + "name": ["google.spanner.v1.Spanner/StreamingRead"], + "affinity": { + "command": "BOUND", + "affinityKey": "session" + } + }, + { + "name": ["google.spanner.v1.Spanner/BeginTransaction"], + "affinity": { + "command": "BOUND", + "affinityKey": "session" + } + }, + { + "name": ["google.spanner.v1.Spanner/Commit"], + "affinity": { + "command": "BOUND", + "affinityKey": "session" + } + }, + { + "name": ["google.spanner.v1.Spanner/PartitionRead"], + "affinity": { + "command": "BOUND", + "affinityKey": "session" + } + }, + { + "name": ["google.spanner.v1.Spanner/PartitionQuery"], + "affinity": { + "command": "BOUND", + "affinityKey": "session" + } + }, + { + "name": ["google.spanner.v1.Spanner/Rollback"], + "affinity": { + "command": "BOUND", + "affinityKey": "session" + } + } + ] +} From 9e59de185c8178f5367730df27910dd42d920c0b Mon Sep 17 00:00:00 2001 From: "Zhenli(Jenny) Jiang" Date: Wed, 24 Mar 2021 15:30:32 -0700 Subject: [PATCH 02/16] remove local files --- .../grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.jar | Bin 67032 -> 0 bytes .../gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.jar.md5 | 1 - .../gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.jar.sha1 | 1 - .../grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom | 8 -------- .../gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom.md5 | 1 - .../gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom.sha1 | 1 - .../grpc/gcp/grpc.gcp/maven-metadata.xml | 12 ------------ .../grpc/gcp/grpc.gcp/maven-metadata.xml.md5 | 1 - .../grpc/gcp/grpc.gcp/maven-metadata.xml.sha1 | 1 - google-cloud-spanner/pom.xml | 11 ++--------- 10 files changed, 2 insertions(+), 35 deletions(-) delete mode 100644 google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.jar delete mode 100644 google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.jar.md5 delete mode 100644 google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.jar.sha1 delete mode 100644 google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom delete mode 100644 google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom.md5 delete mode 100644 google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom.sha1 delete mode 100644 google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/maven-metadata.xml delete mode 100644 google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/maven-metadata.xml.md5 delete mode 100644 google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/maven-metadata.xml.sha1 diff --git a/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.jar b/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.jar deleted file mode 100644 index d35a79203922dee0266f49ac3618968bd219b5a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 67032 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 diff --git a/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.jar.md5 b/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.jar.md5 deleted file mode 100644 index b29738ff327..00000000000 --- a/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.jar.md5 +++ /dev/null @@ -1 +0,0 @@ -7da91178703f5a131f1b6714e3c91a06 \ No newline at end of file diff --git a/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.jar.sha1 b/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.jar.sha1 deleted file mode 100644 index 544f78e30b0..00000000000 --- a/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -55f3b339ab174107236080f5cc015dd78e0868f4 \ No newline at end of file diff --git a/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom b/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom deleted file mode 100644 index 4be68693e04..00000000000 --- a/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom +++ /dev/null @@ -1,8 +0,0 @@ - - - 4.0.0 - grpc.gcp - grpc.gcp - 0.0.1 - diff --git a/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom.md5 b/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom.md5 deleted file mode 100644 index 90b2fa2b49f..00000000000 --- a/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom.md5 +++ /dev/null @@ -1 +0,0 @@ -26cc8e28112a7b0ad74cc996cc8e679b \ No newline at end of file diff --git a/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom.sha1 b/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom.sha1 deleted file mode 100644 index 4d818a93b02..00000000000 --- a/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/0.0.1/grpc.gcp-0.0.1.pom.sha1 +++ /dev/null @@ -1 +0,0 @@ -1b2bbfba61904344fcde27931dc64f0d463bc349 \ No newline at end of file diff --git a/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/maven-metadata.xml b/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/maven-metadata.xml deleted file mode 100644 index 81949266520..00000000000 --- a/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/maven-metadata.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - grpc.gcp - grpc.gcp - - 0.0.1 - - 0.0.1 - - 20210324014307 - - diff --git a/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/maven-metadata.xml.md5 b/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/maven-metadata.xml.md5 deleted file mode 100644 index 10179e808d2..00000000000 --- a/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/maven-metadata.xml.md5 +++ /dev/null @@ -1 +0,0 @@ -b045ebca85082a017cf7f66674415c85 \ No newline at end of file diff --git a/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/maven-metadata.xml.sha1 b/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/maven-metadata.xml.sha1 deleted file mode 100644 index c546591c340..00000000000 --- a/google-cloud-spanner/local-maven-repo/grpc/gcp/grpc.gcp/maven-metadata.xml.sha1 +++ /dev/null @@ -1 +0,0 @@ -6cbc0cc2c4c48c405fadb0ffdc291aaab8908cf3 \ No newline at end of file diff --git a/google-cloud-spanner/pom.xml b/google-cloud-spanner/pom.xml index fec6e7571a3..7632845ed99 100644 --- a/google-cloud-spanner/pom.xml +++ b/google-cloud-spanner/pom.xml @@ -118,18 +118,11 @@ - - - local-maven-repo - file:///${project.basedir}/local-maven-repo - - - - grpc.gcp - grpc.gcp + grpc.gcp.testing + grpc.gcp.testing 0.0.1 From 2349c47eaebd435b46298d5a817c4ee4e451eb15 Mon Sep 17 00:00:00 2001 From: "Zhenli(Jenny) Jiang" Date: Thu, 25 Mar 2021 01:08:21 -0700 Subject: [PATCH 03/16] update read apiconfig --- google-cloud-spanner/pom.xml | 4 ++-- .../cloud/spanner/spi/v1/GapicSpannerRpc.java | 20 ++++++++++++++----- .../spanner/spi/v1}/grpc-gcp-apiconfig.json | 0 3 files changed, 17 insertions(+), 7 deletions(-) rename google-cloud-spanner/src/main/resources/{ => com/google/cloud/spanner/spi/v1}/grpc-gcp-apiconfig.json (100%) diff --git a/google-cloud-spanner/pom.xml b/google-cloud-spanner/pom.xml index 7632845ed99..16f26be18dc 100644 --- a/google-cloud-spanner/pom.xml +++ b/google-cloud-spanner/pom.xml @@ -121,8 +121,8 @@ - grpc.gcp.testing - grpc.gcp.testing + grpc.gcp.extension + grpc.gcp.extension 0.0.1 diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java index ebb0ee6919d..80e87f0fed3 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java @@ -160,8 +160,8 @@ import io.grpc.Context; import io.grpc.ManagedChannelBuilder; import io.grpc.MethodDescriptor; -import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; @@ -369,14 +369,24 @@ public GapicSpannerRpc(final SpannerOptions options) { // instead. // TODO: may need to update GcpManagedChannelBuilder so that Spanner could specify the channel // pool size using options.getNumChannels(). - final File configFile = - new File(GapicSpannerRpc.class.getClassLoader().getResource(API_FILE).getFile()); + // InputStreamReader reader = new + // InputStreamReader(GapicSpannerRpc.class.getResourceAsStream(API_FILE)); + InputStream inputStream = GapicSpannerRpc.class.getResourceAsStream(API_FILE); + StringBuilder sb = new StringBuilder(); + try { + for (int ch; (ch = inputStream.read()) != -1; ) { + sb.append((char) ch); + } + } catch (IOException e) { + throw newSpannerException(e); + } + final String jsonApiConfig = sb.toString(); ApiFunction apiFunction = new ApiFunction() { @Override public ManagedChannelBuilder apply(ManagedChannelBuilder channelBuilder) { - return GcpManagedChannelBuilder.forDelegateBuilder(channelBuilder) - .withApiConfigJsonFile(configFile); + return GcpManagedChannelBuilder.forDelegateBuilder(channelBuilder); + // .withApiConfigJsonString(jsonApiConfig); } }; defaultChannelProviderBuilder = diff --git a/google-cloud-spanner/src/main/resources/grpc-gcp-apiconfig.json b/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/spi/v1/grpc-gcp-apiconfig.json similarity index 100% rename from google-cloud-spanner/src/main/resources/grpc-gcp-apiconfig.json rename to google-cloud-spanner/src/main/resources/com/google/cloud/spanner/spi/v1/grpc-gcp-apiconfig.json From e1c55d8a3d5a12923e99fc2c4712433c0031ccea Mon Sep 17 00:00:00 2001 From: "Zhenli(Jenny) Jiang" Date: Thu, 25 Mar 2021 01:11:41 -0700 Subject: [PATCH 04/16] update read apiconfig --- .../com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java index 80e87f0fed3..850edbb14c8 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java @@ -369,8 +369,6 @@ public GapicSpannerRpc(final SpannerOptions options) { // instead. // TODO: may need to update GcpManagedChannelBuilder so that Spanner could specify the channel // pool size using options.getNumChannels(). - // InputStreamReader reader = new - // InputStreamReader(GapicSpannerRpc.class.getResourceAsStream(API_FILE)); InputStream inputStream = GapicSpannerRpc.class.getResourceAsStream(API_FILE); StringBuilder sb = new StringBuilder(); try { @@ -385,8 +383,8 @@ public GapicSpannerRpc(final SpannerOptions options) { new ApiFunction() { @Override public ManagedChannelBuilder apply(ManagedChannelBuilder channelBuilder) { - return GcpManagedChannelBuilder.forDelegateBuilder(channelBuilder); - // .withApiConfigJsonString(jsonApiConfig); + return GcpManagedChannelBuilder.forDelegateBuilder(channelBuilder) + .withApiConfigJsonString(jsonApiConfig); } }; defaultChannelProviderBuilder = From 418da2b3925ac2db31e1fd84e850cd81dc415b07 Mon Sep 17 00:00:00 2001 From: "Zhenli(Jenny) Jiang" Date: Wed, 31 Mar 2021 17:39:07 -0700 Subject: [PATCH 05/16] Add custom pool size for GCP extension --- google-cloud-spanner/pom.xml | 2 +- .../main/java/com/google/cloud/spanner/SpannerOptions.java | 2 +- .../com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java | 7 +++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/google-cloud-spanner/pom.xml b/google-cloud-spanner/pom.xml index 16f26be18dc..fe801232759 100644 --- a/google-cloud-spanner/pom.xml +++ b/google-cloud-spanner/pom.xml @@ -123,7 +123,7 @@ grpc.gcp.extension grpc.gcp.extension - 0.0.1 + 1.0.0 io.grpc diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java index e8a8ee5e1a2..dc19f88e812 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java @@ -558,7 +558,7 @@ private SpannerOptions(Builder builder) { throw SpannerExceptionFactory.newSpannerException(e); } partitionedDmlTimeout = builder.partitionedDmlTimeout; - this.useGrpcGcpExtension = builder.useGrpcGcpExtension; + useGrpcGcpExtension = builder.useGrpcGcpExtension; autoThrottleAdministrativeRequests = builder.autoThrottleAdministrativeRequests; trackTransactionStarter = builder.trackTransactionStarter; defaultQueryOptions = builder.defaultQueryOptions; diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java index 850edbb14c8..5b83247ebd8 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java @@ -365,10 +365,8 @@ public GapicSpannerRpc(final SpannerOptions options) { // whether the attempt is allowed is totally controlled by service owner. .setAttemptDirectPath(true); if (options.isUseGrpcGcpExtension() && options.getChannelConfigurator() == null) { - // Disables the gax channel pool and uses the channel pool provided by the GCP extension + // Disables the gax channel pool and uses the channel pool provided by the gRPC-GCP extension // instead. - // TODO: may need to update GcpManagedChannelBuilder so that Spanner could specify the channel - // pool size using options.getNumChannels(). InputStream inputStream = GapicSpannerRpc.class.getResourceAsStream(API_FILE); StringBuilder sb = new StringBuilder(); try { @@ -384,7 +382,8 @@ public GapicSpannerRpc(final SpannerOptions options) { @Override public ManagedChannelBuilder apply(ManagedChannelBuilder channelBuilder) { return GcpManagedChannelBuilder.forDelegateBuilder(channelBuilder) - .withApiConfigJsonString(jsonApiConfig); + .withApiConfigJsonString(jsonApiConfig) + .setPoolSize(options.getNumChannels()); } }; defaultChannelProviderBuilder = From 422302b5292e489eb13e2278d992f6e24d722c0a Mon Sep 17 00:00:00 2001 From: "Zhenli(Jenny) Jiang" Date: Sun, 4 Apr 2021 23:28:20 -0700 Subject: [PATCH 06/16] add one more entry in aip config file --- .../google/cloud/spanner/spi/v1/grpc-gcp-apiconfig.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/spi/v1/grpc-gcp-apiconfig.json b/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/spi/v1/grpc-gcp-apiconfig.json index 5ba11458768..2cbbabb9378 100644 --- a/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/spi/v1/grpc-gcp-apiconfig.json +++ b/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/spi/v1/grpc-gcp-apiconfig.json @@ -39,6 +39,13 @@ "affinityKey": "session" } }, + { + "name": ["google.spanner.v1.Spanner/ExecuteBatchDml"], + "affinity": { + "command": "BOUND", + "affinityKey": "session" + } + }, { "name": ["google.spanner.v1.Spanner/ExecuteStreamingSql"], "affinity": { From 75bc079dca3a527dc9f545588992a62db5a06df2 Mon Sep 17 00:00:00 2001 From: "Zhenli(Jenny) Jiang" Date: Thu, 8 Apr 2021 15:01:47 -0700 Subject: [PATCH 07/16] change gcp package name --- google-cloud-spanner/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-spanner/pom.xml b/google-cloud-spanner/pom.xml index fcabe07f7e6..bbb1eda83f3 100644 --- a/google-cloud-spanner/pom.xml +++ b/google-cloud-spanner/pom.xml @@ -122,7 +122,7 @@ grpc.gcp.extension - grpc.gcp.extension + grpc-gcp-extension 1.0.0 From ea76c3288bcc2a002527b56bb5a74162a2c28853 Mon Sep 17 00:00:00 2001 From: Yuri Golobokov Date: Mon, 31 May 2021 16:38:23 -0700 Subject: [PATCH 08/16] Adjust grpc-gcp package name and group --- google-cloud-spanner/pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/google-cloud-spanner/pom.xml b/google-cloud-spanner/pom.xml index bbb1eda83f3..adce562637c 100644 --- a/google-cloud-spanner/pom.xml +++ b/google-cloud-spanner/pom.xml @@ -121,9 +121,9 @@ - grpc.gcp.extension - grpc-gcp-extension - 1.0.0 + com.google.cloud + grpc-gcp + 1.0.0-SNAPSHOT io.grpc From 5d2afc83c463fd61e085e5e20df9309ae51a3d3b Mon Sep 17 00:00:00 2001 From: Yuri Golobokov Date: Tue, 1 Jun 2021 12:01:06 -0700 Subject: [PATCH 09/16] Set grpc-gcp low streams watermark to 1 This allows sessions to be spread across channels when the Spanner client starts up. --- .../com/google/cloud/spanner/spi/v1/grpc-gcp-apiconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/spi/v1/grpc-gcp-apiconfig.json b/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/spi/v1/grpc-gcp-apiconfig.json index 2cbbabb9378..cae46996a47 100644 --- a/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/spi/v1/grpc-gcp-apiconfig.json +++ b/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/spi/v1/grpc-gcp-apiconfig.json @@ -1,7 +1,7 @@ { "channelPool": { "maxSize": 3, - "maxConcurrentStreamsLowWatermark": 2 + "maxConcurrentStreamsLowWatermark": 1 }, "method": [ { From 76332de4a45901626c80b3326c850052f6e94267 Mon Sep 17 00:00:00 2001 From: Yuri Golobokov Date: Tue, 1 Jun 2021 12:05:13 -0700 Subject: [PATCH 10/16] Add gRPC-GCP extension options to SpannerOptions --- .../google/cloud/spanner/SpannerOptions.java | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java index dc19f88e812..3773371143b 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java @@ -48,6 +48,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.google.grpc.gcp.GcpManagedChannelOptions; import com.google.spanner.v1.ExecuteSqlRequest; import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions; import com.google.spanner.v1.SpannerGrpc; @@ -104,6 +105,7 @@ public class SpannerOptions extends ServiceOptions { private final DatabaseAdminStubSettings databaseAdminStubSettings; private final Duration partitionedDmlTimeout; private final boolean useGrpcGcpExtension; + private final GcpManagedChannelOptions grpcGcpOptions; private final boolean autoThrottleAdministrativeRequests; private final boolean trackTransactionStarter; /** @@ -559,6 +561,7 @@ private SpannerOptions(Builder builder) { } partitionedDmlTimeout = builder.partitionedDmlTimeout; useGrpcGcpExtension = builder.useGrpcGcpExtension; + grpcGcpOptions = builder.grpcGcpOptions; autoThrottleAdministrativeRequests = builder.autoThrottleAdministrativeRequests; trackTransactionStarter = builder.trackTransactionStarter; defaultQueryOptions = builder.defaultQueryOptions; @@ -638,7 +641,8 @@ public static class Builder private DatabaseAdminStubSettings.Builder databaseAdminStubSettingsBuilder = DatabaseAdminStubSettings.newBuilder(); private Duration partitionedDmlTimeout = Duration.ofHours(2L); - private boolean useGrpcGcpExtension = true; + private boolean useGrpcGcpExtension = false; + private GcpManagedChannelOptions grpcGcpOptions; private boolean autoThrottleAdministrativeRequests = false; private boolean trackTransactionStarter = false; private Map defaultQueryOptions = new HashMap<>(); @@ -686,7 +690,8 @@ private Builder() { this.instanceAdminStubSettingsBuilder = options.instanceAdminStubSettings.toBuilder(); this.databaseAdminStubSettingsBuilder = options.databaseAdminStubSettings.toBuilder(); this.partitionedDmlTimeout = options.partitionedDmlTimeout; - this.useGrpcGcpExtension = useGrpcGcpExtension; + this.useGrpcGcpExtension = options.useGrpcGcpExtension; + this.grpcGcpOptions = options.grpcGcpOptions; this.autoThrottleAdministrativeRequests = options.autoThrottleAdministrativeRequests; this.trackTransactionStarter = options.trackTransactionStarter; this.defaultQueryOptions = options.defaultQueryOptions; @@ -1003,11 +1008,26 @@ public Builder setHost(String host) { return this; } + /** + * Sets the preference for gRPC-GCP extension (default: false). When enabled the gRPC-GCP + * channel pool will be used. + */ public Builder setUseGrpcGcpExtension(boolean useGrpcGcpExtension) { this.useGrpcGcpExtension = useGrpcGcpExtension; return this; } + /** + * Sets the options for gRPC-GCP extension. The metric registry and default Spanner metric + * labels will be added automatically. + * + *

Note that gRPC-GCP extension must be enabled first with {@code setUseGrpcGcpExtension}. + */ + public Builder setGrpcGcpOptions(GcpManagedChannelOptions options) { + this.grpcGcpOptions = options; + return this; + } + /** * Sets the host of an emulator to use. By default the value is read from an environment * variable. If the environment variable is not set, this will be null. @@ -1112,6 +1132,10 @@ public boolean isUseGrpcGcpExtension() { return useGrpcGcpExtension; } + public GcpManagedChannelOptions getGrpcGcpOptions() { + return grpcGcpOptions; + } + public boolean isAutoThrottleAdministrativeRequests() { return autoThrottleAdministrativeRequests; } From f8006c41181e58c16ceb1269b8854b6301c10715 Mon Sep 17 00:00:00 2001 From: Yuri Golobokov Date: Tue, 1 Jun 2021 12:22:41 -0700 Subject: [PATCH 11/16] feat: add gRPC-GCP channel pool as an option This enables a user to opt-in for using the gRPC-GCP extension channel pool and configure its options. --- .../cloud/spanner/spi/v1/GapicSpannerRpc.java | 100 +++++++++++++----- 1 file changed, 75 insertions(+), 25 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java index 5b83247ebd8..b3a1ce8e9f6 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java @@ -84,6 +84,8 @@ import com.google.common.util.concurrent.RateLimiter; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.grpc.gcp.GcpManagedChannelBuilder; +import com.google.grpc.gcp.GcpManagedChannelOptions; +import com.google.grpc.gcp.GcpManagedChannelOptions.GcpMetricsOptions; import com.google.iam.v1.GetIamPolicyRequest; import com.google.iam.v1.Policy; import com.google.iam.v1.SetIamPolicyRequest; @@ -160,6 +162,9 @@ import io.grpc.Context; import io.grpc.ManagedChannelBuilder; import io.grpc.MethodDescriptor; +import io.opencensus.metrics.LabelKey; +import io.opencensus.metrics.LabelValue; +import io.opencensus.metrics.Metrics; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; @@ -364,31 +369,9 @@ public GapicSpannerRpc(final SpannerOptions options) { // Attempts direct access to spanner service over gRPC to improve throughput, // whether the attempt is allowed is totally controlled by service owner. .setAttemptDirectPath(true); - if (options.isUseGrpcGcpExtension() && options.getChannelConfigurator() == null) { - // Disables the gax channel pool and uses the channel pool provided by the gRPC-GCP extension - // instead. - InputStream inputStream = GapicSpannerRpc.class.getResourceAsStream(API_FILE); - StringBuilder sb = new StringBuilder(); - try { - for (int ch; (ch = inputStream.read()) != -1; ) { - sb.append((char) ch); - } - } catch (IOException e) { - throw newSpannerException(e); - } - final String jsonApiConfig = sb.toString(); - ApiFunction apiFunction = - new ApiFunction() { - @Override - public ManagedChannelBuilder apply(ManagedChannelBuilder channelBuilder) { - return GcpManagedChannelBuilder.forDelegateBuilder(channelBuilder) - .withApiConfigJsonString(jsonApiConfig) - .setPoolSize(options.getNumChannels()); - } - }; - defaultChannelProviderBuilder = - defaultChannelProviderBuilder.setPoolSize(1).setChannelConfigurator(apiFunction); - } + + // If it is enabled in options uses the channel pool provided by the gRPC-GCP extension. + maybeEnableGrpcGcpExtension(defaultChannelProviderBuilder, options); TransportChannelProvider channelProvider = MoreObjects.firstNonNull( @@ -521,6 +504,73 @@ public UnaryCallable createUnaryCalla } } + private static String parseGrpcGcpApiConfig() { + InputStream inputStream = GapicSpannerRpc.class.getResourceAsStream(API_FILE); + StringBuilder sb = new StringBuilder(); + try { + for (int ch; (ch = inputStream.read()) != -1; ) { + sb.append((char) ch); + } + } catch (IOException e) { + throw newSpannerException(e); + } + return sb.toString(); + } + + // Enhance metric options for gRPC-GCP extension. Adds metric registry if not specified. Adds + // more labels on top of provided. + private static GcpManagedChannelOptions grpcGcpOptionsWithMetrics(SpannerOptions options) { + GcpManagedChannelOptions grpcGcpOptions = + MoreObjects.firstNonNull(options.getGrpcGcpOptions(), new GcpManagedChannelOptions()); + GcpMetricsOptions metricsOptions = MoreObjects.firstNonNull( + grpcGcpOptions.getMetricsOptions(), GcpMetricsOptions.newBuilder().build()); + GcpMetricsOptions.Builder metricsOptionsBuilder = GcpMetricsOptions.newBuilder(metricsOptions); + if (metricsOptions.getMetricRegistry() == null) { + metricsOptionsBuilder.withMetricRegistry(Metrics.getMetricRegistry()); + } + List labelKeys = metricsOptions.getLabelKeys(); + List labelValues = metricsOptions.getLabelValues(); + // TODO: Add default labels with values: client_id, database, instance_id. + labelKeys.add(LabelKey.create("grpc_gcp_version", "gRPC-GCP library version")); + String gRpcGcpVersion = GcpManagedChannelBuilder.class.getPackage().getImplementationVersion(); + if (gRpcGcpVersion == null) { + gRpcGcpVersion = ""; + } + labelValues.add(LabelValue.create(gRpcGcpVersion)); + metricsOptionsBuilder.withLabels(labelKeys, labelValues); + if (metricsOptions.getNamePrefix().equals("")) { + metricsOptionsBuilder.withNamePrefix("cloud.google.com/java/spanner/gcp-channel-pool/"); + } + return GcpManagedChannelOptions.newBuilder(grpcGcpOptions) + .withMetricsOptions(metricsOptionsBuilder.build()) + .build(); + } + + @SuppressWarnings("rawtypes") + private static void maybeEnableGrpcGcpExtension(InstantiatingGrpcChannelProvider.Builder defaultChannelProviderBuilder, final SpannerOptions options) { + if (!options.isUseGrpcGcpExtension()) { + return; + } + + final String jsonApiConfig = parseGrpcGcpApiConfig(); + final GcpManagedChannelOptions grpcGcpOptions = grpcGcpOptionsWithMetrics(options); + + ApiFunction apiFunction = + channelBuilder -> { + if (options.getChannelConfigurator() != null) { + channelBuilder = options.getChannelConfigurator().apply(channelBuilder); + } + return GcpManagedChannelBuilder.forDelegateBuilder(channelBuilder) + .withApiConfigJsonString(jsonApiConfig) + .withOptions(grpcGcpOptions) + .setPoolSize(options.getNumChannels()); + }; + + // Disable the GAX channel pooling functionality by setting the GAX channel pool size to 1. + // Enable gRPC-GCP channel pool via the channel configurator. + defaultChannelProviderBuilder.setPoolSize(1).setChannelConfigurator(apiFunction); + } + private static HeaderProvider headerProviderWithUserAgentFrom(HeaderProvider headerProvider) { final Map headersWithUserAgent = new HashMap<>(headerProvider.getHeaders()); final String userAgent = headersWithUserAgent.get(USER_AGENT_KEY); From b6ccf7af61a8fd616ccdef9c88425b0d0ad88b21 Mon Sep 17 00:00:00 2001 From: Yuri Golobokov Date: Sun, 13 Jun 2021 12:04:51 -0700 Subject: [PATCH 12/16] Addressed comments. --- google-cloud-spanner/pom.xml | 2 +- .../google/cloud/spanner/SpannerOptions.java | 11 +++++------ .../cloud/spanner/spi/v1/GapicSpannerRpc.java | 18 ++++-------------- .../spanner/spi/v1/grpc-gcp-apiconfig.json | 2 +- 4 files changed, 11 insertions(+), 22 deletions(-) diff --git a/google-cloud-spanner/pom.xml b/google-cloud-spanner/pom.xml index adce562637c..774b4effa6a 100644 --- a/google-cloud-spanner/pom.xml +++ b/google-cloud-spanner/pom.xml @@ -123,7 +123,7 @@ com.google.cloud grpc-gcp - 1.0.0-SNAPSHOT + 1.0.0 io.grpc diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java index 3773371143b..33a264bd5ea 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java @@ -48,7 +48,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.ThreadFactoryBuilder; -import com.google.grpc.gcp.GcpManagedChannelOptions; +import com.google.cloud.grpc.GcpManagedChannelOptions; import com.google.spanner.v1.ExecuteSqlRequest; import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions; import com.google.spanner.v1.SpannerGrpc; @@ -1018,12 +1018,11 @@ public Builder setUseGrpcGcpExtension(boolean useGrpcGcpExtension) { } /** - * Sets the options for gRPC-GCP extension. The metric registry and default Spanner metric - * labels will be added automatically. - * - *

Note that gRPC-GCP extension must be enabled first with {@code setUseGrpcGcpExtension}. + * Enables gRPC-GCP extension and uses provided options for configuration. The metric registry + * and default Spanner metric labels will be added automatically. */ - public Builder setGrpcGcpOptions(GcpManagedChannelOptions options) { + public Builder withGrpcGcpOptions(GcpManagedChannelOptions options) { + this.useGrpcGcpExtension = true; this.grpcGcpOptions = options; return this; } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java index b3a1ce8e9f6..133f1008956 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java @@ -83,9 +83,9 @@ import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.RateLimiter; import com.google.common.util.concurrent.ThreadFactoryBuilder; -import com.google.grpc.gcp.GcpManagedChannelBuilder; -import com.google.grpc.gcp.GcpManagedChannelOptions; -import com.google.grpc.gcp.GcpManagedChannelOptions.GcpMetricsOptions; +import com.google.cloud.grpc.GcpManagedChannelBuilder; +import com.google.cloud.grpc.GcpManagedChannelOptions; +import com.google.cloud.grpc.GcpManagedChannelOptions.GcpMetricsOptions; import com.google.iam.v1.GetIamPolicyRequest; import com.google.iam.v1.Policy; import com.google.iam.v1.SetIamPolicyRequest; @@ -517,8 +517,7 @@ private static String parseGrpcGcpApiConfig() { return sb.toString(); } - // Enhance metric options for gRPC-GCP extension. Adds metric registry if not specified. Adds - // more labels on top of provided. + // Enhance metric options for gRPC-GCP extension. Adds metric registry if not specified. private static GcpManagedChannelOptions grpcGcpOptionsWithMetrics(SpannerOptions options) { GcpManagedChannelOptions grpcGcpOptions = MoreObjects.firstNonNull(options.getGrpcGcpOptions(), new GcpManagedChannelOptions()); @@ -528,16 +527,7 @@ private static GcpManagedChannelOptions grpcGcpOptionsWithMetrics(SpannerOptions if (metricsOptions.getMetricRegistry() == null) { metricsOptionsBuilder.withMetricRegistry(Metrics.getMetricRegistry()); } - List labelKeys = metricsOptions.getLabelKeys(); - List labelValues = metricsOptions.getLabelValues(); // TODO: Add default labels with values: client_id, database, instance_id. - labelKeys.add(LabelKey.create("grpc_gcp_version", "gRPC-GCP library version")); - String gRpcGcpVersion = GcpManagedChannelBuilder.class.getPackage().getImplementationVersion(); - if (gRpcGcpVersion == null) { - gRpcGcpVersion = ""; - } - labelValues.add(LabelValue.create(gRpcGcpVersion)); - metricsOptionsBuilder.withLabels(labelKeys, labelValues); if (metricsOptions.getNamePrefix().equals("")) { metricsOptionsBuilder.withNamePrefix("cloud.google.com/java/spanner/gcp-channel-pool/"); } diff --git a/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/spi/v1/grpc-gcp-apiconfig.json b/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/spi/v1/grpc-gcp-apiconfig.json index cae46996a47..1761bd2d382 100644 --- a/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/spi/v1/grpc-gcp-apiconfig.json +++ b/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/spi/v1/grpc-gcp-apiconfig.json @@ -1,7 +1,7 @@ { "channelPool": { "maxSize": 3, - "maxConcurrentStreamsLowWatermark": 1 + "maxConcurrentStreamsLowWatermark": 0 }, "method": [ { From 6b94299e33cd60cd46722bc7144b8e0ac5736c79 Mon Sep 17 00:00:00 2001 From: Yuri Golobokov Date: Mon, 14 Jun 2021 11:23:26 -0700 Subject: [PATCH 13/16] Fixed linting issues. --- .../google/cloud/spanner/SpannerOptions.java | 2 +- .../cloud/spanner/spi/v1/GapicSpannerRpc.java | 26 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java index 318dc47ca9d..2237b8bbf9c 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java @@ -29,6 +29,7 @@ import com.google.cloud.ServiceOptions; import com.google.cloud.ServiceRpc; import com.google.cloud.TransportOptions; +import com.google.cloud.grpc.GcpManagedChannelOptions; import com.google.cloud.grpc.GrpcTransportOptions; import com.google.cloud.spanner.Options.QueryOption; import com.google.cloud.spanner.SpannerOptions.CallContextConfigurator; @@ -48,7 +49,6 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.ThreadFactoryBuilder; -import com.google.cloud.grpc.GcpManagedChannelOptions; import com.google.spanner.v1.ExecuteSqlRequest; import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions; import com.google.spanner.v1.SpannerGrpc; diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java index 13d772d08ef..2d6b564d925 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java @@ -55,6 +55,9 @@ import com.google.api.pathtemplate.PathTemplate; import com.google.cloud.RetryHelper; import com.google.cloud.RetryHelper.RetryHelperException; +import com.google.cloud.grpc.GcpManagedChannelBuilder; +import com.google.cloud.grpc.GcpManagedChannelOptions; +import com.google.cloud.grpc.GcpManagedChannelOptions.GcpMetricsOptions; import com.google.cloud.grpc.GrpcTransportOptions; import com.google.cloud.spanner.AdminRequestsPerMinuteExceededException; import com.google.cloud.spanner.ErrorCode; @@ -81,11 +84,9 @@ import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.google.common.io.Resources; import com.google.common.util.concurrent.RateLimiter; import com.google.common.util.concurrent.ThreadFactoryBuilder; -import com.google.cloud.grpc.GcpManagedChannelBuilder; -import com.google.cloud.grpc.GcpManagedChannelOptions; -import com.google.cloud.grpc.GcpManagedChannelOptions.GcpMetricsOptions; import com.google.iam.v1.GetIamPolicyRequest; import com.google.iam.v1.Policy; import com.google.iam.v1.SetIamPolicyRequest; @@ -164,9 +165,9 @@ import io.grpc.MethodDescriptor; import io.opencensus.metrics.Metrics; import java.io.IOException; -import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Comparator; import java.util.HashMap; @@ -521,24 +522,21 @@ public UnaryCallable createUnaryCalla } private static String parseGrpcGcpApiConfig() { - InputStream inputStream = GapicSpannerRpc.class.getResourceAsStream(API_FILE); - StringBuilder sb = new StringBuilder(); try { - for (int ch; (ch = inputStream.read()) != -1; ) { - sb.append((char) ch); - } + return Resources.toString( + GapicSpannerRpc.class.getResource(API_FILE), Charset.forName("UTF8")); } catch (IOException e) { throw newSpannerException(e); } - return sb.toString(); } // Enhance metric options for gRPC-GCP extension. Adds metric registry if not specified. private static GcpManagedChannelOptions grpcGcpOptionsWithMetrics(SpannerOptions options) { GcpManagedChannelOptions grpcGcpOptions = MoreObjects.firstNonNull(options.getGrpcGcpOptions(), new GcpManagedChannelOptions()); - GcpMetricsOptions metricsOptions = MoreObjects.firstNonNull( - grpcGcpOptions.getMetricsOptions(), GcpMetricsOptions.newBuilder().build()); + GcpMetricsOptions metricsOptions = + MoreObjects.firstNonNull( + grpcGcpOptions.getMetricsOptions(), GcpMetricsOptions.newBuilder().build()); GcpMetricsOptions.Builder metricsOptionsBuilder = GcpMetricsOptions.newBuilder(metricsOptions); if (metricsOptions.getMetricRegistry() == null) { metricsOptionsBuilder.withMetricRegistry(Metrics.getMetricRegistry()); @@ -553,7 +551,9 @@ private static GcpManagedChannelOptions grpcGcpOptionsWithMetrics(SpannerOptions } @SuppressWarnings("rawtypes") - private static void maybeEnableGrpcGcpExtension(InstantiatingGrpcChannelProvider.Builder defaultChannelProviderBuilder, final SpannerOptions options) { + private static void maybeEnableGrpcGcpExtension( + InstantiatingGrpcChannelProvider.Builder defaultChannelProviderBuilder, + final SpannerOptions options) { if (!options.isUseGrpcGcpExtension()) { return; } From ea82c123bc43056c71ad1b0156ffec6453240b94 Mon Sep 17 00:00:00 2001 From: Yuri Golobokov Date: Tue, 15 Jun 2021 15:48:17 -0700 Subject: [PATCH 14/16] Add integration test with enabled gRPC-GCP extension --- .../cloud/spanner/it/ITWithGrpcGcpTest.java | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWithGrpcGcpTest.java diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWithGrpcGcpTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWithGrpcGcpTest.java new file mode 100644 index 00000000000..105c3fb484d --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWithGrpcGcpTest.java @@ -0,0 +1,115 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.it; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.spanner.Database; +import com.google.cloud.spanner.DatabaseClient; +import com.google.cloud.spanner.IntegrationTestEnv; +import com.google.cloud.spanner.Key; +import com.google.cloud.spanner.Mutation; +import com.google.cloud.spanner.ParallelIntegrationTest; +import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.Statement; +import com.google.cloud.spanner.Struct; +import com.google.cloud.spanner.TimestampBound; +import com.google.cloud.spanner.testing.RemoteSpannerHelper; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Integration tests for using gRPC-GCP extension. */ +@Category(ParallelIntegrationTest.class) +@RunWith(JUnit4.class) +public class ITWithGrpcGcpTest { + + @ClassRule public static IntegrationTestEnv env = new IntegrationTestEnv(); + private static final String TABLE_NAME = "TestTable"; + private static final List ALL_COLUMNS = Arrays.asList("Key", "StringValue"); + + private static RemoteSpannerHelper testHelper; + private static Database db; + private static DatabaseClient client; + + @BeforeClass + public static void setUpDatabase() { + // Get default spanner options for an integration test. + SpannerOptions.Builder builder = env.getTestHelper().getOptions().toBuilder(); + builder.setUseGrpcGcpExtension(true); + + // Create a new testHelper with the gRPC-GCP extension enabled. + testHelper = RemoteSpannerHelper.create(builder.build(), env.getTestHelper().getInstanceId()); + + db = + env.getTestHelper() + .createTestDatabase( + "CREATE TABLE " + + TABLE_NAME + + " (" + + " Key STRING(MAX) NOT NULL," + + " StringValue STRING(MAX)," + + ") PRIMARY KEY (Key)"); + client = testHelper.getDatabaseClient(db); + + List mutations = new ArrayList<>(); + for (int i = 0; i < 3; ++i) { + mutations.add( + Mutation.newInsertOrUpdateBuilder(TABLE_NAME) + .set("Key") + .to("k" + i) + .set("StringValue") + .to("v" + i) + .build()); + } + client.write(mutations); + } + + @Test + public void singleRead() { + Struct row = + client.singleUse(TimestampBound.strong()).readRow(TABLE_NAME, Key.of("k1"), ALL_COLUMNS); + assertThat(row).isNotNull(); + assertThat(row.getString(0)).isEqualTo("k1"); + assertThat(row.getString(1)).isEqualTo("v1"); + } + + @Test + public void usingTransaction() { + final Long updatedCount = + client + .readWriteTransaction() + .run( + transaction -> + transaction.executeUpdate( + Statement.of( + "UPDATE " + TABLE_NAME + " SET StringValue='v2upd' WHERE Key='k2'"))); + assertThat(updatedCount).isEqualTo(1L); + + Struct row = + client.singleUse(TimestampBound.strong()).readRow(TABLE_NAME, Key.of("k2"), ALL_COLUMNS); + assertThat(row).isNotNull(); + assertThat(row.getString(0)).isEqualTo("k2"); + assertThat(row.getString(1)).isEqualTo("v2upd"); + } +} From ed3d4ad647e0195d5cfd67246a000734ef83d9a6 Mon Sep 17 00:00:00 2001 From: Yuri Golobokov Date: Tue, 15 Jun 2021 16:10:50 -0700 Subject: [PATCH 15/16] Remake gRPC-GCP extension related SpannerOptions --- .../google/cloud/spanner/SpannerOptions.java | 33 ++++++++++--------- .../cloud/spanner/spi/v1/GapicSpannerRpc.java | 2 +- .../cloud/spanner/it/ITWithGrpcGcpTest.java | 2 +- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java index 2237b8bbf9c..a58d93720ae 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java @@ -32,8 +32,6 @@ import com.google.cloud.grpc.GcpManagedChannelOptions; import com.google.cloud.grpc.GrpcTransportOptions; import com.google.cloud.spanner.Options.QueryOption; -import com.google.cloud.spanner.SpannerOptions.CallContextConfigurator; -import com.google.cloud.spanner.SpannerOptions.SpannerCallContextTimeoutConfigurator; import com.google.cloud.spanner.admin.database.v1.DatabaseAdminSettings; import com.google.cloud.spanner.admin.database.v1.stub.DatabaseAdminStubSettings; import com.google.cloud.spanner.admin.instance.v1.InstanceAdminSettings; @@ -104,7 +102,7 @@ public class SpannerOptions extends ServiceOptions { private final InstanceAdminStubSettings instanceAdminStubSettings; private final DatabaseAdminStubSettings databaseAdminStubSettings; private final Duration partitionedDmlTimeout; - private final boolean useGrpcGcpExtension; + private final boolean grpcGcpExtensionEnabled; private final GcpManagedChannelOptions grpcGcpOptions; private final boolean autoThrottleAdministrativeRequests; private final RetrySettings retryAdministrativeRequestsSettings; @@ -557,7 +555,7 @@ private SpannerOptions(Builder builder) { throw SpannerExceptionFactory.newSpannerException(e); } partitionedDmlTimeout = builder.partitionedDmlTimeout; - useGrpcGcpExtension = builder.useGrpcGcpExtension; + grpcGcpExtensionEnabled = builder.grpcGcpExtensionEnabled; grpcGcpOptions = builder.grpcGcpOptions; autoThrottleAdministrativeRequests = builder.autoThrottleAdministrativeRequests; retryAdministrativeRequestsSettings = builder.retryAdministrativeRequestsSettings; @@ -663,7 +661,7 @@ public static class Builder private DatabaseAdminStubSettings.Builder databaseAdminStubSettingsBuilder = DatabaseAdminStubSettings.newBuilder(); private Duration partitionedDmlTimeout = Duration.ofHours(2L); - private boolean useGrpcGcpExtension = false; + private boolean grpcGcpExtensionEnabled = false; private GcpManagedChannelOptions grpcGcpOptions; private RetrySettings retryAdministrativeRequestsSettings = DEFAULT_ADMIN_REQUESTS_LIMIT_EXCEEDED_RETRY_SETTINGS; @@ -714,7 +712,7 @@ private Builder() { this.instanceAdminStubSettingsBuilder = options.instanceAdminStubSettings.toBuilder(); this.databaseAdminStubSettingsBuilder = options.databaseAdminStubSettings.toBuilder(); this.partitionedDmlTimeout = options.partitionedDmlTimeout; - this.useGrpcGcpExtension = options.useGrpcGcpExtension; + this.grpcGcpExtensionEnabled = options.grpcGcpExtensionEnabled; this.grpcGcpOptions = options.grpcGcpOptions; this.autoThrottleAdministrativeRequests = options.autoThrottleAdministrativeRequests; this.retryAdministrativeRequestsSettings = options.retryAdministrativeRequestsSettings; @@ -1044,12 +1042,9 @@ public Builder setHost(String host) { return this; } - /** - * Sets the preference for gRPC-GCP extension (default: false). When enabled the gRPC-GCP - * channel pool will be used. - */ - public Builder setUseGrpcGcpExtension(boolean useGrpcGcpExtension) { - this.useGrpcGcpExtension = useGrpcGcpExtension; + /** Enables gRPC-GCP extension with the default settings. */ + public Builder enableGrpcGcpExtension() { + this.grpcGcpExtensionEnabled = true; return this; } @@ -1057,12 +1052,18 @@ public Builder setUseGrpcGcpExtension(boolean useGrpcGcpExtension) { * Enables gRPC-GCP extension and uses provided options for configuration. The metric registry * and default Spanner metric labels will be added automatically. */ - public Builder withGrpcGcpOptions(GcpManagedChannelOptions options) { - this.useGrpcGcpExtension = true; + public Builder enableGrpcGcpExtension(GcpManagedChannelOptions options) { + this.grpcGcpExtensionEnabled = true; this.grpcGcpOptions = options; return this; } + /** Disables gRPC-GCP extension. */ + public Builder disableGrpcGcpExtension() { + this.grpcGcpExtensionEnabled = false; + return this; + } + /** * Sets the host of an emulator to use. By default the value is read from an environment * variable. If the environment variable is not set, this will be null. @@ -1156,8 +1157,8 @@ public Duration getPartitionedDmlTimeout() { return partitionedDmlTimeout; } - public boolean isUseGrpcGcpExtension() { - return useGrpcGcpExtension; + public boolean isGrpcGcpExtensionEnabled() { + return grpcGcpExtensionEnabled; } public GcpManagedChannelOptions getGrpcGcpOptions() { diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java index 2d6b564d925..0f97898a40a 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java @@ -554,7 +554,7 @@ private static GcpManagedChannelOptions grpcGcpOptionsWithMetrics(SpannerOptions private static void maybeEnableGrpcGcpExtension( InstantiatingGrpcChannelProvider.Builder defaultChannelProviderBuilder, final SpannerOptions options) { - if (!options.isUseGrpcGcpExtension()) { + if (!options.isGrpcGcpExtensionEnabled()) { return; } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWithGrpcGcpTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWithGrpcGcpTest.java index 105c3fb484d..8fdf687e444 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWithGrpcGcpTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWithGrpcGcpTest.java @@ -56,7 +56,7 @@ public class ITWithGrpcGcpTest { public static void setUpDatabase() { // Get default spanner options for an integration test. SpannerOptions.Builder builder = env.getTestHelper().getOptions().toBuilder(); - builder.setUseGrpcGcpExtension(true); + builder.enableGrpcGcpExtension(); // Create a new testHelper with the gRPC-GCP extension enabled. testHelper = RemoteSpannerHelper.create(builder.build(), env.getTestHelper().getInstanceId()); From 204479805e2b3de137e93f565d46c3b1ccc43fbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Wed, 23 Jun 2021 12:02:41 -0700 Subject: [PATCH 16/16] Add ChannelUsageTest --- .../cloud/spanner/ChannelUsageTest.java | 235 ++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 google-cloud-spanner/src/test/java/com/google/cloud/spanner/ChannelUsageTest.java diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ChannelUsageTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ChannelUsageTest.java new file mode 100644 index 00000000000..e390e581206 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ChannelUsageTest.java @@ -0,0 +1,235 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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.spanner; + +import static org.junit.Assert.assertEquals; + +import com.google.cloud.NoCredentials; +import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.protobuf.ListValue; +import com.google.spanner.v1.ResultSetMetadata; +import com.google.spanner.v1.SpannerGrpc; +import com.google.spanner.v1.StructType; +import com.google.spanner.v1.StructType.Field; +import com.google.spanner.v1.TypeCode; +import io.grpc.Attributes; +import io.grpc.Context; +import io.grpc.Contexts; +import io.grpc.Metadata; +import io.grpc.Server; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; +import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +/** + * Tests that the Spanner client opens multiple channels, and that each session is associated with + * one specific channel. + */ +@RunWith(Parameterized.class) +public class ChannelUsageTest { + + @Parameter(0) + public int numChannels; + + @Parameter(1) + public boolean enableGcpPool; + + @Parameters(name = "num channels = {0}, enable GCP pool = {1}") + public static Collection data() { + return Arrays.asList( + new Object[][] {{1, true}, {1, false}, {2, true}, {2, false}, {4, true}, {4, false}}); + } + + private static final Statement SELECT1 = Statement.of("SELECT 1 AS COL1"); + private static final ResultSetMetadata SELECT1_METADATA = + ResultSetMetadata.newBuilder() + .setRowType( + StructType.newBuilder() + .addFields( + Field.newBuilder() + .setName("COL1") + .setType( + com.google.spanner.v1.Type.newBuilder() + .setCode(TypeCode.INT64) + .build()) + .build()) + .build()) + .build(); + private static final com.google.spanner.v1.ResultSet SELECT1_RESULTSET = + com.google.spanner.v1.ResultSet.newBuilder() + .addRows( + ListValue.newBuilder() + .addValues(com.google.protobuf.Value.newBuilder().setStringValue("1").build()) + .build()) + .setMetadata(SELECT1_METADATA) + .build(); + + private static MockSpannerServiceImpl mockSpanner; + private static Server server; + private static InetSocketAddress address; + private static final Set batchCreateSessionLocalIps = + ConcurrentHashMap.newKeySet(); + private static final Set executeSqlLocalIps = ConcurrentHashMap.newKeySet(); + + @BeforeClass + public static void startServer() throws IOException { + mockSpanner = new MockSpannerServiceImpl(); + mockSpanner.setAbortProbability(0.0D); // We don't want any unpredictable aborted transactions. + mockSpanner.putStatementResult(StatementResult.query(SELECT1, SELECT1_RESULTSET)); + + address = new InetSocketAddress("localhost", 0); + server = + NettyServerBuilder.forAddress(address) + .addService(mockSpanner) + // Add a server interceptor to register the remote addresses that we are seeing. This + // indicates how many channels are used client side to communicate with the server. + .intercept( + new ServerInterceptor() { + @Override + public ServerCall.Listener interceptCall( + ServerCall call, + Metadata headers, + ServerCallHandler next) { + Attributes attributes = call.getAttributes(); + @SuppressWarnings({"unchecked", "deprecation"}) + Attributes.Key key = + (Attributes.Key) + attributes.keys().stream() + .filter(k -> k.toString().equals("remote-addr")) + .findFirst() + .orElse(null); + if (key != null) { + if (call.getMethodDescriptor() + .equals(SpannerGrpc.getBatchCreateSessionsMethod())) { + batchCreateSessionLocalIps.add(attributes.get(key)); + } + if (call.getMethodDescriptor() + .equals(SpannerGrpc.getExecuteStreamingSqlMethod())) { + executeSqlLocalIps.add(attributes.get(key)); + } + } + return Contexts.interceptCall(Context.current(), call, headers, next); + } + }) + .build() + .start(); + } + + @AfterClass + public static void stopServer() throws InterruptedException { + server.shutdown(); + server.awaitTermination(); + } + + @After + public void reset() { + mockSpanner.reset(); + batchCreateSessionLocalIps.clear(); + executeSqlLocalIps.clear(); + } + + private SpannerOptions createSpannerOptions() { + String endpoint = address.getHostString() + ":" + server.getPort(); + SpannerOptions.Builder builder = + SpannerOptions.newBuilder() + .setProjectId("[PROJECT]") + .setChannelConfigurator( + input -> { + input.usePlaintext(); + return input; + }) + .setNumChannels(numChannels) + .setSessionPoolOption( + SessionPoolOptions.newBuilder() + .setMinSessions(numChannels * 2) + .setMaxSessions(numChannels * 2) + .build()) + .setHost("http://" + endpoint) + .setCredentials(NoCredentials.getInstance()); + if (enableGcpPool) { + builder.enableGrpcGcpExtension(); + } + + return builder.build(); + } + + @Test + public void testCreatesNumChannels() { + try (Spanner spanner = createSpannerOptions().getService()) { + DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + try (ResultSet resultSet = client.singleUse().executeQuery(SELECT1)) { + while (resultSet.next()) {} + } + } + assertEquals(numChannels, batchCreateSessionLocalIps.size()); + } + + @Test + public void testUsesAllChannels() throws InterruptedException, ExecutionException { + try (Spanner spanner = createSpannerOptions().getService()) { + DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + ListeningExecutorService executor = + MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(numChannels * 2)); + CountDownLatch latch = new CountDownLatch(numChannels * 2); + List> futures = new ArrayList<>(numChannels * 2); + for (int run = 0; run < numChannels * 2; run++) { + futures.add( + executor.submit( + () -> { + try (ReadOnlyTransaction transaction = client.readOnlyTransaction()) { + try (ResultSet resultSet = transaction.executeQuery(SELECT1)) { + while (resultSet.next()) {} + latch.countDown(); + try { + return latch.await(10L, TimeUnit.SECONDS); + } catch (InterruptedException e) { + throw SpannerExceptionFactory.asSpannerException(e); + } + } + } + })); + } + assertEquals(numChannels * 2, Futures.allAsList(futures).get().size()); + } + assertEquals(numChannels, executeSqlLocalIps.size()); + } +}