diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml index 208af343821b..d304652e1c44 100644 --- a/.github/.OwlBot.lock.yaml +++ b/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-java:latest - digest: sha256:fb7584f6adb3847ac480ed49a4bfe1463965026b2919a1be270e3174f3ce1191 -# created: 2023-10-26T23:22:21.357007231Z + digest: sha256:6f431774e11cc46619cf093fd1481193c4024031073697fa18f0099b943aab88 +# created: 2023-12-01T19:50:20.444857406Z diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e27b2c57563c..ae66b1973b17 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -25,7 +25,7 @@ jobs: strategy: fail-fast: false matrix: - java: [11, 17] + java: [11, 17, 21] steps: - uses: actions/checkout@v3 - uses: actions/setup-java@v3 diff --git a/CHANGELOG.md b/CHANGELOG.md index c3bef1b670eb..e03e2c932131 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## [2.30.0](https://github.com/googleapis/java-bigtable/compare/v2.29.1...v2.30.0) (2023-12-05) + + +### Features + +* Client sends routing cookie back to server ([#1888](https://github.com/googleapis/java-bigtable/issues/1888)) ([4c73abd](https://github.com/googleapis/java-bigtable/commit/4c73abd2f4a07808b591dd9178e87715d2f3008d)) + + +### Dependencies + +* Update dependency org.junit.vintage:junit-vintage-engine to v5.10.1 ([#1990](https://github.com/googleapis/java-bigtable/issues/1990)) ([7ad70e3](https://github.com/googleapis/java-bigtable/commit/7ad70e3abc1af7dfab715386978bf14f02b34e5d)) +* Update shared dependencies ([#2016](https://github.com/googleapis/java-bigtable/issues/2016)) ([4e49dff](https://github.com/googleapis/java-bigtable/commit/4e49dffa72db8dd04e75ca86178d875fab6f566b)) + ## [2.29.1](https://github.com/googleapis/java-bigtable/compare/v2.29.0...v2.29.1) (2023-11-07) diff --git a/README.md b/README.md index 984083333090..5caf555e9485 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ If you are using Maven without the BOM, add this to your dependencies: com.google.cloud google-cloud-bigtable - 2.29.0 + 2.29.1 ``` @@ -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.26.0') +implementation platform('com.google.cloud:libraries-bom:26.27.0') implementation 'com.google.cloud:google-cloud-bigtable' ``` If you are using Gradle without BOM, add this to your dependencies: ```Groovy -implementation 'com.google.cloud:google-cloud-bigtable:2.29.0' +implementation 'com.google.cloud:google-cloud-bigtable:2.29.1' ``` If you are using SBT, add this to your dependencies: ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-bigtable" % "2.29.0" +libraryDependencies += "com.google.cloud" % "google-cloud-bigtable" % "2.29.1" ``` @@ -609,7 +609,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-bigtable/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-bigtable.svg -[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-bigtable/2.29.0 +[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-bigtable/2.29.1 [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-bigtable-bom/pom.xml b/google-cloud-bigtable-bom/pom.xml index c8684b1d3a2c..7512e99da650 100644 --- a/google-cloud-bigtable-bom/pom.xml +++ b/google-cloud-bigtable-bom/pom.xml @@ -3,12 +3,12 @@ 4.0.0 com.google.cloud google-cloud-bigtable-bom - 2.29.1 + 2.30.0 pom com.google.cloud google-cloud-shared-config - 1.6.0 + 1.6.1 @@ -63,42 +63,42 @@ com.google.cloud google-cloud-bigtable - 2.29.1 + 2.30.0 com.google.cloud google-cloud-bigtable-emulator - 0.166.1 + 0.167.0 com.google.cloud google-cloud-bigtable-emulator-core - 0.166.1 + 0.167.0 com.google.api.grpc grpc-google-cloud-bigtable-admin-v2 - 2.29.1 + 2.30.0 com.google.api.grpc grpc-google-cloud-bigtable-v2 - 2.29.1 + 2.30.0 com.google.api.grpc proto-google-cloud-bigtable-admin-v2 - 2.29.1 + 2.30.0 com.google.api.grpc proto-google-cloud-bigtable-v2 - 2.29.1 + 2.30.0 com.google.cloud google-cloud-bigtable-stats - 2.29.1 + 2.30.0 diff --git a/google-cloud-bigtable-deps-bom/pom.xml b/google-cloud-bigtable-deps-bom/pom.xml index baa7485fe4a4..b0733f362d30 100644 --- a/google-cloud-bigtable-deps-bom/pom.xml +++ b/google-cloud-bigtable-deps-bom/pom.xml @@ -7,13 +7,13 @@ com.google.cloud google-cloud-shared-config - 1.6.0 + 1.6.1 com.google.cloud google-cloud-bigtable-deps-bom - 2.29.1 + 2.30.0 pom @@ -66,14 +66,14 @@ com.google.cloud gapic-libraries-bom - 1.24.0 + 1.25.0 pom import com.google.cloud google-cloud-shared-dependencies - 3.19.0 + 3.20.0 pom import diff --git a/google-cloud-bigtable-emulator-core/pom.xml b/google-cloud-bigtable-emulator-core/pom.xml index 7cebe63c5437..b00a569bc0d4 100644 --- a/google-cloud-bigtable-emulator-core/pom.xml +++ b/google-cloud-bigtable-emulator-core/pom.xml @@ -7,11 +7,11 @@ google-cloud-bigtable-parent com.google.cloud - 2.29.1 + 2.30.0 google-cloud-bigtable-emulator-core - 0.166.1 + 0.167.0 A Java wrapper for the Cloud Bigtable emulator. diff --git a/google-cloud-bigtable-emulator/pom.xml b/google-cloud-bigtable-emulator/pom.xml index 7ea51c92b886..63241aa35d0e 100644 --- a/google-cloud-bigtable-emulator/pom.xml +++ b/google-cloud-bigtable-emulator/pom.xml @@ -5,7 +5,7 @@ 4.0.0 google-cloud-bigtable-emulator - 0.166.1 + 0.167.0 Google Cloud Java - Bigtable Emulator https://github.com/googleapis/java-bigtable @@ -14,7 +14,7 @@ com.google.cloud google-cloud-bigtable-parent - 2.29.1 + 2.30.0 scm:git:git@github.com:googleapis/java-bigtable.git @@ -81,14 +81,14 @@ com.google.cloud google-cloud-bigtable-deps-bom - 2.29.1 + 2.30.0 pom import com.google.cloud google-cloud-bigtable-bom - 2.29.1 + 2.30.0 pom import @@ -99,7 +99,7 @@ com.google.cloud google-cloud-bigtable-emulator-core - 0.166.1 + 0.167.0 diff --git a/google-cloud-bigtable-stats/pom.xml b/google-cloud-bigtable-stats/pom.xml index 83c7ad36e56a..b5e46be9d3fd 100644 --- a/google-cloud-bigtable-stats/pom.xml +++ b/google-cloud-bigtable-stats/pom.xml @@ -5,7 +5,7 @@ com.google.cloud google-cloud-bigtable-parent - 2.29.1 + 2.30.0 4.0.0 @@ -13,7 +13,7 @@ through Stackdriver. Built-in metrics will be implemented with shaded OpenCensus so it won't interfere with customer's application metrics. --> google-cloud-bigtable-stats - 2.29.1 + 2.30.0 Experimental project to shade OpenCensus dependencies. @@ -21,7 +21,7 @@ com.google.cloud google-cloud-bigtable-deps-bom - 2.29.1 + 2.30.0 pom import diff --git a/google-cloud-bigtable/pom.xml b/google-cloud-bigtable/pom.xml index 8fa180fb5799..57c976d877a1 100644 --- a/google-cloud-bigtable/pom.xml +++ b/google-cloud-bigtable/pom.xml @@ -2,7 +2,7 @@ 4.0.0 google-cloud-bigtable - 2.29.1 + 2.30.0 jar Google Cloud Bigtable https://github.com/googleapis/java-bigtable @@ -12,11 +12,11 @@ com.google.cloud google-cloud-bigtable-parent - 2.29.1 + 2.30.0 - 2.29.1 + 2.30.0 google-cloud-bigtable @@ -38,8 +38,8 @@ batch-bigtable.googleapis.com:443 - 1.59.0 - 3.24.4 + 1.59.1 + 3.25.1 @@ -47,14 +47,14 @@ com.google.cloud google-cloud-bigtable-deps-bom - 2.29.1 + 2.30.0 pom import com.google.cloud google-cloud-bigtable-bom - 2.29.1 + 2.30.0 pom import @@ -630,7 +630,7 @@ org.codehaus.mojo build-helper-maven-plugin - 3.4.0 + 3.5.0 add-source diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/Version.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/Version.java index 79c93d0148a8..60fb95c75cef 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/Version.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/Version.java @@ -20,6 +20,6 @@ @InternalApi("For internal use only") public final class Version { // {x-version-update-start:google-cloud-bigtable:current} - public static String VERSION = "2.29.1"; + public static String VERSION = "2.30.0"; // {x-version-update-end} } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/CookiesHolder.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/CookiesHolder.java new file mode 100644 index 000000000000..7d7ca6a0291c --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/CookiesHolder.java @@ -0,0 +1,70 @@ +/* + * Copyright 2023 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.bigtable.data.v2.stub; + +import io.grpc.CallOptions; +import io.grpc.Metadata; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nullable; + +/** A cookie that holds information for retry or routing */ +class CookiesHolder { + + static final CallOptions.Key COOKIES_HOLDER_KEY = + CallOptions.Key.create("bigtable-cookies"); + + /** Routing cookie key prefix. */ + static final String COOKIE_KEY_PREFIX = "x-goog-cbt-cookie"; + + /** A map that stores all the routing cookies. */ + private final Map, String> cookies = new HashMap<>(); + + /** Returns CookiesHolder if presents in CallOptions, otherwise returns null. */ + @Nullable + static CookiesHolder fromCallOptions(CallOptions options) { + // CookiesHolder should be added by CookiesServerStreamingCallable and + // CookiesUnaryCallable for most methods. However, methods like PingAndWarm + // doesn't support routing cookie, in which case this will return null. + return options.getOption(COOKIES_HOLDER_KEY); + } + + /** Add all the routing cookies to headers if any. */ + Metadata injectCookiesInRequestHeaders(Metadata headers) { + for (Metadata.Key key : cookies.keySet()) { + headers.put(key, cookies.get(key)); + } + return headers; + } + + /** + * Iterate through all the keys in initial or trailing metadata, and add all the keys that match + * COOKIE_KEY_PREFIX to cookies. Values in trailers will override the value set in initial + * metadata for the same keys. + */ + void extractCookiesFromMetadata(@Nullable Metadata trailers) { + if (trailers == null) { + return; + } + for (String key : trailers.keys()) { + if (key.startsWith(COOKIE_KEY_PREFIX)) { + Metadata.Key metadataKey = Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER); + String value = trailers.get(metadataKey); + cookies.put(metadataKey, value); + } + } + } +} diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/CookiesInterceptor.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/CookiesInterceptor.java new file mode 100644 index 000000000000..77387851fabd --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/CookiesInterceptor.java @@ -0,0 +1,96 @@ +/* + * Copyright 2023 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.bigtable.data.v2.stub; + +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.ForwardingClientCall; +import io.grpc.ForwardingClientCallListener; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.Status; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A cookie interceptor that checks the cookie value from returned trailer, updates the cookie + * holder, and inject it in the header of the next request. + */ +class CookiesInterceptor implements ClientInterceptor { + + private static final Logger LOG = Logger.getLogger(CookiesInterceptor.class.getName()); + + @Override + public ClientCall interceptCall( + MethodDescriptor methodDescriptor, CallOptions callOptions, Channel channel) { + return new ForwardingClientCall.SimpleForwardingClientCall( + channel.newCall(methodDescriptor, callOptions)) { + @Override + public void start(Listener responseListener, Metadata headers) { + // Gets the CookiesHolder added from CookiesServerStreamingCallable and + // CookiesUnaryCallable. + // Add CookiesHolder content to request headers if there's any. + try { + CookiesHolder cookie = CookiesHolder.fromCallOptions(callOptions); + if (cookie != null) { + headers = cookie.injectCookiesInRequestHeaders(headers); + responseListener = new UpdateCookieListener<>(responseListener, cookie); + } + } catch (Throwable e) { + LOG.warning("Failed to inject cookie to request headers: " + e); + } finally { + super.start(responseListener, headers); + } + } + }; + } + + /** Add headers and trailers to CookiesHolder if there's any. * */ + static class UpdateCookieListener + extends ForwardingClientCallListener.SimpleForwardingClientCallListener { + + private final CookiesHolder cookie; + + UpdateCookieListener(ClientCall.Listener delegate, CookiesHolder cookiesHolder) { + super(delegate); + this.cookie = cookiesHolder; + } + + @Override + public void onHeaders(Metadata headers) { + try { + cookie.extractCookiesFromMetadata(headers); + } catch (Throwable e) { + LOG.log(Level.WARNING, "Failed to extract cookie from response headers.", e); + } finally { + super.onHeaders(headers); + } + } + + @Override + public void onClose(Status status, Metadata trailers) { + try { + cookie.extractCookiesFromMetadata(trailers); + } catch (Throwable e) { + LOG.log(Level.WARNING, "Failed to extract cookie from response trailers.", e); + } finally { + super.onClose(status, trailers); + } + } + } +} diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/CookiesServerStreamingCallable.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/CookiesServerStreamingCallable.java new file mode 100644 index 000000000000..0d012b8ea061 --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/CookiesServerStreamingCallable.java @@ -0,0 +1,48 @@ +/* + * Copyright 2023 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.bigtable.data.v2.stub; + +import static com.google.cloud.bigtable.data.v2.stub.CookiesHolder.COOKIES_HOLDER_KEY; + +import com.google.api.gax.grpc.GrpcCallContext; +import com.google.api.gax.rpc.ApiCallContext; +import com.google.api.gax.rpc.ResponseObserver; +import com.google.api.gax.rpc.ServerStreamingCallable; + +/** + * The cookie holder will act as operation scoped storage for all retry attempts. Each attempt's + * cookies will be merged into the value holder and will be sent out with the next retry attempt. + */ +class CookiesServerStreamingCallable + extends ServerStreamingCallable { + + private final ServerStreamingCallable callable; + + CookiesServerStreamingCallable(ServerStreamingCallable innerCallable) { + this.callable = innerCallable; + } + + @Override + public void call( + RequestT request, ResponseObserver responseObserver, ApiCallContext context) { + GrpcCallContext grpcCallContext = (GrpcCallContext) context; + callable.call( + request, + responseObserver, + grpcCallContext.withCallOptions( + grpcCallContext.getCallOptions().withOption(COOKIES_HOLDER_KEY, new CookiesHolder()))); + } +} diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/CookiesUnaryCallable.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/CookiesUnaryCallable.java new file mode 100644 index 000000000000..b0d42d59552a --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/CookiesUnaryCallable.java @@ -0,0 +1,44 @@ +/* + * Copyright 2023 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.bigtable.data.v2.stub; + +import static com.google.cloud.bigtable.data.v2.stub.CookiesHolder.COOKIES_HOLDER_KEY; + +import com.google.api.core.ApiFuture; +import com.google.api.gax.grpc.GrpcCallContext; +import com.google.api.gax.rpc.ApiCallContext; +import com.google.api.gax.rpc.UnaryCallable; + +/** + * The cookie holder will act as operation scoped storage for all retry attempts. Each attempt's + * cookies will be merged into the value holder and will be sent out with the next retry attempt. + */ +class CookiesUnaryCallable extends UnaryCallable { + private final UnaryCallable innerCallable; + + CookiesUnaryCallable(UnaryCallable callable) { + this.innerCallable = callable; + } + + @Override + public ApiFuture futureCall(RequestT request, ApiCallContext context) { + GrpcCallContext grpcCallContext = (GrpcCallContext) context; + return innerCallable.futureCall( + request, + grpcCallContext.withCallOptions( + grpcCallContext.getCallOptions().withOption(COOKIES_HOLDER_KEY, new CookiesHolder()))); + } +} diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java index 0420e47dcf4b..705b3027ed62 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java @@ -185,6 +185,14 @@ public static EnhancedBigtableStubSettings finalizeSettings( // workaround JWT audience issues patchCredentials(builder); + // patch cookies interceptor + InstantiatingGrpcChannelProvider.Builder transportProvider = null; + if (builder.getTransportChannelProvider() instanceof InstantiatingGrpcChannelProvider) { + transportProvider = + ((InstantiatingGrpcChannelProvider) builder.getTransportChannelProvider()).toBuilder(); + transportProvider.setInterceptorProvider(() -> ImmutableList.of(new CookiesInterceptor())); + } + // Inject channel priming if (settings.isRefreshingChannel()) { // Fix the credentials so that they can be shared @@ -194,20 +202,18 @@ public static EnhancedBigtableStubSettings finalizeSettings( } builder.setCredentialsProvider(FixedCredentialsProvider.create(credentials)); - // Inject the primer - InstantiatingGrpcChannelProvider transportProvider = - (InstantiatingGrpcChannelProvider) settings.getTransportChannelProvider(); - - builder.setTransportChannelProvider( - transportProvider - .toBuilder() - .setChannelPrimer( - BigtableChannelPrimer.create( - credentials, - settings.getProjectId(), - settings.getInstanceId(), - settings.getAppProfileId())) - .build()); + if (transportProvider != null) { + transportProvider.setChannelPrimer( + BigtableChannelPrimer.create( + credentials, + settings.getProjectId(), + settings.getInstanceId(), + settings.getAppProfileId())); + } + } + + if (transportProvider != null) { + builder.setTransportChannelProvider(transportProvider.build()); } ImmutableMap attributes = @@ -365,7 +371,11 @@ public ServerStreamingCallable createReadRowsCallable( new TracedServerStreamingCallable<>( readRowsUserCallable, clientContext.getTracerFactory(), span); - return traced.withDefaultCallContext(clientContext.getDefaultCallContext()); + // CookieHolder needs to be injected to the CallOptions outside of retries, otherwise retry + // attempts won't see a CookieHolder. + ServerStreamingCallable withCookie = new CookiesServerStreamingCallable<>(traced); + + return withCookie.withDefaultCallContext(clientContext.getDefaultCallContext()); } /** @@ -401,7 +411,9 @@ public UnaryCallable createReadRowCallable(RowAdapter new TracedUnaryCallable<>( firstRow, clientContext.getTracerFactory(), getSpanName("ReadRow")); - return traced.withDefaultCallContext(clientContext.getDefaultCallContext()); + UnaryCallable withCookie = new CookiesUnaryCallable<>(traced); + + return withCookie.withDefaultCallContext(clientContext.getDefaultCallContext()); } /** @@ -642,7 +654,9 @@ private UnaryCallable createBulkMutateRowsCallable() { new TracedUnaryCallable<>( tracedBatcherUnaryCallable, clientContext.getTracerFactory(), spanName); - return traced.withDefaultCallContext(clientContext.getDefaultCallContext()); + UnaryCallable withCookie = new CookiesUnaryCallable<>(traced); + + return withCookie.withDefaultCallContext(clientContext.getDefaultCallContext()); } /** @@ -924,7 +938,10 @@ public Map extract( ServerStreamingCallable traced = new TracedServerStreamingCallable<>(retrying, clientContext.getTracerFactory(), span); - return traced.withDefaultCallContext(clientContext.getDefaultCallContext()); + ServerStreamingCallable withCookie = + new CookiesServerStreamingCallable<>(traced); + + return withCookie.withDefaultCallContext(clientContext.getDefaultCallContext()); } /** @@ -1004,7 +1021,10 @@ public Map extract( new TracedServerStreamingCallable<>( readChangeStreamUserCallable, clientContext.getTracerFactory(), span); - return traced.withDefaultCallContext(clientContext.getDefaultCallContext()); + ServerStreamingCallable withCookie = + new CookiesServerStreamingCallable<>(traced); + + return withCookie.withDefaultCallContext(clientContext.getDefaultCallContext()); } /** @@ -1017,7 +1037,11 @@ private UnaryCallable createUserFacin UnaryCallable traced = new TracedUnaryCallable<>(inner, clientContext.getTracerFactory(), getSpanName(methodName)); - return traced.withDefaultCallContext(clientContext.getDefaultCallContext()); + // CookieHolder needs to be injected to the CallOptions outside of retries, otherwise retry + // attempts won't see a CookieHolder. + UnaryCallable withCookie = new CookiesUnaryCallable<>(traced); + + return withCookie.withDefaultCallContext(clientContext.getDefaultCallContext()); } private UnaryCallable createPingAndWarmCallable() { diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/MetadataSubject.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/MetadataSubject.java new file mode 100644 index 000000000000..5e76ce38cd30 --- /dev/null +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/MetadataSubject.java @@ -0,0 +1,60 @@ +/* + * Copyright 2023 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.bigtable.data.v2; + +import static com.google.common.truth.Truth.assertAbout; + +import com.google.common.truth.FailureMetadata; +import com.google.common.truth.Subject; +import io.grpc.Metadata; +import javax.annotation.Nullable; + +/** Utility class to test key-value pairs in {@link io.grpc.Metadata}. */ +public final class MetadataSubject extends Subject { + + @Nullable private final Metadata actual; + + public static Factory metadata() { + return MetadataSubject::new; + } + + private MetadataSubject(FailureMetadata metadata, @Nullable Metadata actual) { + super(metadata, actual); + this.actual = actual; + } + + public static MetadataSubject assertThat(@Nullable Metadata actual) { + return assertAbout(metadata()).that(actual); + } + + public void containsAtLeast(String... keyValuePairs) { + assert actual != null; + for (int i = 0; i < keyValuePairs.length; i += 2) { + check("containsAtLeast()") + .that(actual.get(Metadata.Key.of(keyValuePairs[i], Metadata.ASCII_STRING_MARSHALLER))) + .isEqualTo(keyValuePairs[i + 1]); + } + } + + public void doesNotContainKeys(String... keys) { + assert actual != null; + for (String key : keys) { + check("doesNotContainKeys()") + .that(actual.containsKey(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER))) + .isFalse(); + } + } +} diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/CookiesHolderTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/CookiesHolderTest.java new file mode 100644 index 000000000000..5dac05352374 --- /dev/null +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/CookiesHolderTest.java @@ -0,0 +1,561 @@ +/* + * Copyright 2023 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.bigtable.data.v2.stub; + +import static com.google.cloud.bigtable.data.v2.MetadataSubject.assertThat; +import static com.google.common.truth.Truth.assertThat; + +import com.google.api.gax.retrying.RetrySettings; +import com.google.api.gax.rpc.StatusCode; +import com.google.bigtable.v2.BigtableGrpc; +import com.google.bigtable.v2.CheckAndMutateRowRequest; +import com.google.bigtable.v2.CheckAndMutateRowResponse; +import com.google.bigtable.v2.GenerateInitialChangeStreamPartitionsRequest; +import com.google.bigtable.v2.GenerateInitialChangeStreamPartitionsResponse; +import com.google.bigtable.v2.MutateRowRequest; +import com.google.bigtable.v2.MutateRowResponse; +import com.google.bigtable.v2.MutateRowsRequest; +import com.google.bigtable.v2.MutateRowsResponse; +import com.google.bigtable.v2.ReadChangeStreamRequest; +import com.google.bigtable.v2.ReadChangeStreamResponse; +import com.google.bigtable.v2.ReadModifyWriteRowRequest; +import com.google.bigtable.v2.ReadModifyWriteRowResponse; +import com.google.bigtable.v2.ReadRowsRequest; +import com.google.bigtable.v2.ReadRowsResponse; +import com.google.bigtable.v2.SampleRowKeysRequest; +import com.google.bigtable.v2.SampleRowKeysResponse; +import com.google.cloud.bigtable.data.v2.BigtableDataClient; +import com.google.cloud.bigtable.data.v2.BigtableDataSettings; +import com.google.cloud.bigtable.data.v2.FakeServiceBuilder; +import com.google.cloud.bigtable.data.v2.models.BulkMutation; +import com.google.cloud.bigtable.data.v2.models.ConditionalRowMutation; +import com.google.cloud.bigtable.data.v2.models.Mutation; +import com.google.cloud.bigtable.data.v2.models.Query; +import com.google.cloud.bigtable.data.v2.models.ReadChangeStreamQuery; +import com.google.cloud.bigtable.data.v2.models.ReadModifyWriteRow; +import com.google.cloud.bigtable.data.v2.models.RowMutation; +import com.google.cloud.bigtable.data.v2.models.RowMutationEntry; +import io.grpc.ForwardingServerCall; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.Server; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import io.grpc.stub.StreamObserver; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.threeten.bp.Duration; + +@RunWith(JUnit4.class) +public class CookiesHolderTest { + private static final Metadata.Key ROUTING_COOKIE_1 = + Metadata.Key.of("x-goog-cbt-cookie-routing", Metadata.ASCII_STRING_MARSHALLER); + private static final Metadata.Key ROUTING_COOKIE_2 = + Metadata.Key.of("x-goog-cbt-cookie-random", Metadata.ASCII_STRING_MARSHALLER); + private static final Metadata.Key BAD_KEY = + Metadata.Key.of("x-goog-cbt-not-cookie", Metadata.ASCII_STRING_MARSHALLER); + private static final String testCookie = "test-routing-cookie"; + + private Server server; + private final FakeService fakeService = new FakeService(); + private BigtableDataClient client; + private final List serverMetadata = new ArrayList<>(); + + private final Set methods = new HashSet<>(); + + @Before + public void setup() throws Exception { + ServerInterceptor serverInterceptor = + new ServerInterceptor() { + @Override + public ServerCall.Listener interceptCall( + ServerCall serverCall, + Metadata metadata, + ServerCallHandler serverCallHandler) { + serverMetadata.add(metadata); + if (metadata.containsKey(ROUTING_COOKIE_1)) { + methods.add(serverCall.getMethodDescriptor().getBareMethodName()); + } + return serverCallHandler.startCall(serverCall, metadata); + } + }; + + server = FakeServiceBuilder.create(fakeService).intercept(serverInterceptor).start(); + + BigtableDataSettings.Builder settings = + BigtableDataSettings.newBuilderForEmulator(server.getPort()) + .setProjectId("fake-project") + .setInstanceId("fake-instance"); + + // Override CheckAndMutate and ReadModifyWrite retry settings here. These operations + // are currently not retryable but this could change in the future after we + // have routing cookie sends back meaningful information and changes how retry works. + // Routing cookie still needs to be respected and handled by the callables. + settings + .stubSettings() + .checkAndMutateRowSettings() + .setRetrySettings( + RetrySettings.newBuilder() + .setInitialRetryDelay(Duration.ofMillis(10)) + .setMaxRetryDelay(Duration.ofMinutes(1)) + .setMaxAttempts(2) + .build()) + .setRetryableCodes(StatusCode.Code.UNAVAILABLE); + + settings + .stubSettings() + .readModifyWriteRowSettings() + .setRetrySettings( + RetrySettings.newBuilder() + .setInitialRetryDelay(Duration.ofMillis(10)) + .setMaxRetryDelay(Duration.ofMinutes(1)) + .setMaxAttempts(2) + .build()) + .setRetryableCodes(StatusCode.Code.UNAVAILABLE); + + client = BigtableDataClient.create(settings.build()); + } + + @After + public void tearDown() throws Exception { + if (client != null) { + client.close(); + } + if (server != null) { + server.shutdown(); + } + } + + @Test + public void testReadRows() { + client.readRows(Query.create("fake-table")).iterator().hasNext(); + + assertThat(fakeService.count.get()).isGreaterThan(1); + assertThat(serverMetadata).hasSize(fakeService.count.get()); + + Metadata lastMetadata = serverMetadata.get(fakeService.count.get() - 1); + + assertThat(lastMetadata) + .containsAtLeast(ROUTING_COOKIE_1.name(), "readRows", ROUTING_COOKIE_2.name(), testCookie); + assertThat(lastMetadata).doesNotContainKeys(BAD_KEY.name()); + + serverMetadata.clear(); + } + + @Test + public void testReadRow() { + client.readRow("fake-table", "key"); + + assertThat(fakeService.count.get()).isGreaterThan(1); + assertThat(serverMetadata).hasSize(fakeService.count.get()); + + Metadata lastMetadata = serverMetadata.get(fakeService.count.get() - 1); + + assertThat(lastMetadata) + .containsAtLeast(ROUTING_COOKIE_1.name(), "readRows", ROUTING_COOKIE_2.name(), testCookie); + assertThat(lastMetadata).doesNotContainKeys(BAD_KEY.name()); + + serverMetadata.clear(); + } + + @Test + public void testMutateRows() { + client.bulkMutateRows( + BulkMutation.create("fake-table") + .add(RowMutationEntry.create("key").setCell("cf", "q", "v"))); + + assertThat(fakeService.count.get()).isGreaterThan(1); + assertThat(serverMetadata).hasSize(fakeService.count.get()); + + Metadata lastMetadata = serverMetadata.get(fakeService.count.get() - 1); + + assertThat(lastMetadata) + .containsAtLeast( + ROUTING_COOKIE_1.name(), "mutateRows", ROUTING_COOKIE_2.name(), testCookie); + assertThat(lastMetadata).doesNotContainKeys(BAD_KEY.name()); + + serverMetadata.clear(); + } + + @Test + public void testMutateRow() { + client.mutateRow(RowMutation.create("table", "key").setCell("cf", "q", "v")); + + assertThat(fakeService.count.get()).isGreaterThan(1); + assertThat(serverMetadata).hasSize(fakeService.count.get()); + + Metadata lastMetadata = serverMetadata.get(fakeService.count.get() - 1); + + assertThat(lastMetadata) + .containsAtLeast(ROUTING_COOKIE_1.name(), "mutateRow", ROUTING_COOKIE_2.name(), testCookie); + assertThat(lastMetadata).doesNotContainKeys(BAD_KEY.name()); + + serverMetadata.clear(); + } + + @Test + public void testSampleRowKeys() { + + client.sampleRowKeys("fake-table"); + + assertThat(fakeService.count.get()).isGreaterThan(1); + assertThat(serverMetadata).hasSize(fakeService.count.get()); + + Metadata lastMetadata = serverMetadata.get(fakeService.count.get() - 1); + + assertThat(lastMetadata) + .containsAtLeast( + ROUTING_COOKIE_1.name(), "sampleRowKeys", ROUTING_COOKIE_2.name(), testCookie); + assertThat(lastMetadata).doesNotContainKeys(BAD_KEY.name()); + + serverMetadata.clear(); + } + + @Test + public void testNoCookieSucceedReadRows() { + fakeService.returnCookie = false; + + client.readRows(Query.create("fake-table")).iterator().hasNext(); + + assertThat(fakeService.count.get()).isGreaterThan(1); + assertThat(serverMetadata).hasSize(fakeService.count.get()); + + Metadata lastMetadata = serverMetadata.get(fakeService.count.get() - 1); + + assertThat(lastMetadata).doesNotContainKeys(ROUTING_COOKIE_1.name(), ROUTING_COOKIE_2.name()); + + serverMetadata.clear(); + } + + @Test + public void testNoCookieSucceedReadRow() { + fakeService.returnCookie = false; + + client.readRow("fake-table", "key"); + + assertThat(fakeService.count.get()).isGreaterThan(1); + assertThat(serverMetadata).hasSize(fakeService.count.get()); + + Metadata lastMetadata = serverMetadata.get(fakeService.count.get() - 1); + + assertThat(lastMetadata) + .doesNotContainKeys(ROUTING_COOKIE_1.name(), ROUTING_COOKIE_2.name(), BAD_KEY.name()); + + serverMetadata.clear(); + } + + @Test + public void testNoCookieSucceedMutateRows() { + fakeService.returnCookie = false; + + client.bulkMutateRows( + BulkMutation.create("fake-table") + .add(RowMutationEntry.create("key").setCell("cf", "q", "v"))); + + assertThat(fakeService.count.get()).isGreaterThan(1); + assertThat(serverMetadata).hasSize(fakeService.count.get()); + + Metadata lastMetadata = serverMetadata.get(fakeService.count.get() - 1); + + assertThat(lastMetadata) + .doesNotContainKeys(ROUTING_COOKIE_1.name(), ROUTING_COOKIE_2.name(), BAD_KEY.name()); + + serverMetadata.clear(); + } + + @Test + public void testNoCookieSucceedMutateRow() { + fakeService.returnCookie = false; + + client.mutateRow(RowMutation.create("fake-table", "key").setCell("cf", "q", "v")); + + assertThat(fakeService.count.get()).isGreaterThan(1); + assertThat(serverMetadata).hasSize(fakeService.count.get()); + + Metadata lastMetadata = serverMetadata.get(fakeService.count.get() - 1); + + assertThat(lastMetadata) + .doesNotContainKeys(ROUTING_COOKIE_1.name(), ROUTING_COOKIE_2.name(), BAD_KEY.name()); + + serverMetadata.clear(); + } + + @Test + public void testNoCookieSucceedSampleRowKeys() { + fakeService.returnCookie = false; + + client.sampleRowKeys("fake-table"); + + assertThat(fakeService.count.get()).isGreaterThan(1); + assertThat(serverMetadata).hasSize(fakeService.count.get()); + + Metadata lastMetadata = serverMetadata.get(fakeService.count.get() - 1); + + assertThat(lastMetadata) + .doesNotContainKeys(ROUTING_COOKIE_1.name(), ROUTING_COOKIE_2.name(), BAD_KEY.name()); + + serverMetadata.clear(); + } + + @Test + public void testCookiesInHeaders() throws Exception { + // Send 2 cookies in the headers, with routingCookieKey and ROUTING_COOKIE_2. ROUTING_COOKIE_2 + // is also sent in the trailers so the value should be overridden. + final Metadata.Key routingCookieKey = + Metadata.Key.of("x-goog-cbt-cookie-no-override", Metadata.ASCII_STRING_MARSHALLER); + final String routingCookieValue = "no-override"; + ServerInterceptor serverInterceptor = + new ServerInterceptor() { + @Override + public ServerCall.Listener interceptCall( + ServerCall serverCall, + Metadata metadata, + ServerCallHandler serverCallHandler) { + serverMetadata.add(metadata); + + metadata.put(routingCookieKey, routingCookieValue); + return serverCallHandler.startCall( + new ForwardingServerCall.SimpleForwardingServerCall(serverCall) { + @Override + public void sendHeaders(Metadata headers) { + headers.put(routingCookieKey, routingCookieValue); + headers.put(ROUTING_COOKIE_2, "will-be-overridden"); + super.sendHeaders(headers); + } + }, + metadata); + } + }; + + Server newServer = null; + try { + newServer = FakeServiceBuilder.create(fakeService).intercept(serverInterceptor).start(); + + BigtableDataSettings.Builder settings = + BigtableDataSettings.newBuilderForEmulator(newServer.getPort()) + .setProjectId("fake-project") + .setInstanceId("fake-instance"); + + try (BigtableDataClient client = BigtableDataClient.create(settings.build())) { + + client.readRows(Query.create("table")).iterator().hasNext(); + + Metadata lastMetadata = serverMetadata.get(fakeService.count.get() - 1); + + assertThat(lastMetadata) + .containsAtLeast( + ROUTING_COOKIE_2.name(), testCookie, routingCookieKey.name(), routingCookieValue); + } + } finally { + if (newServer != null) { + newServer.shutdown(); + } + } + } + + @Test + public void testAllMethodsAreCalled() throws InterruptedException { + // This test ensures that all methods respect the retry cookie except for the ones that are + // explicitly added to the methods list. It requires that any newly method is exercised in this + // test. This is enforced by introspecting grpc method descriptors. + client.readRows(Query.create("fake-table")).iterator().hasNext(); + + fakeService.count.set(0); + client.mutateRow(RowMutation.create("fake-table", "key").setCell("cf", "q", "v")); + + fakeService.count.set(0); + client.bulkMutateRows( + BulkMutation.create("fake-table") + .add(RowMutationEntry.create("key").setCell("cf", "q", "v"))); + + fakeService.count.set(0); + client.sampleRowKeys("fake-table"); + + fakeService.count.set(0); + client.checkAndMutateRow( + ConditionalRowMutation.create("fake-table", "key") + .then(Mutation.create().setCell("cf", "q", "v"))); + + fakeService.count.set(0); + client.readModifyWriteRow( + ReadModifyWriteRow.create("fake-table", "key").append("cf", "q", "v")); + + fakeService.count.set(0); + client.generateInitialChangeStreamPartitions("fake-table").iterator().hasNext(); + + fakeService.count.set(0); + client.readChangeStream(ReadChangeStreamQuery.create("fake-table")).iterator().hasNext(); + + Set expected = + BigtableGrpc.getServiceDescriptor().getMethods().stream() + .map(MethodDescriptor::getBareMethodName) + .collect(Collectors.toSet()); + + // Exclude methods that are not supported by routing cookie + methods.add("PingAndWarm"); + + assertThat(methods).containsExactlyElementsIn(expected); + } + + static class FakeService extends BigtableGrpc.BigtableImplBase { + + private boolean returnCookie = true; + private final AtomicInteger count = new AtomicInteger(); + + @Override + public void readRows( + ReadRowsRequest request, StreamObserver responseObserver) { + if (count.getAndIncrement() < 1) { + Metadata trailers = new Metadata(); + maybePopulateCookie(trailers, "readRows"); + responseObserver.onNext(ReadRowsResponse.getDefaultInstance()); + StatusRuntimeException exception = new StatusRuntimeException(Status.UNAVAILABLE, trailers); + responseObserver.onError(exception); + return; + } + responseObserver.onNext(ReadRowsResponse.getDefaultInstance()); + responseObserver.onCompleted(); + } + + @Override + public void mutateRow( + MutateRowRequest request, StreamObserver responseObserver) { + if (count.getAndIncrement() < 1) { + Metadata trailers = new Metadata(); + maybePopulateCookie(trailers, "mutateRow"); + StatusRuntimeException exception = new StatusRuntimeException(Status.UNAVAILABLE, trailers); + responseObserver.onError(exception); + return; + } + responseObserver.onNext(MutateRowResponse.getDefaultInstance()); + responseObserver.onCompleted(); + } + + @Override + public void mutateRows( + MutateRowsRequest request, StreamObserver responseObserver) { + if (count.getAndIncrement() < 1) { + Metadata trailers = new Metadata(); + maybePopulateCookie(trailers, "mutateRows"); + StatusRuntimeException exception = new StatusRuntimeException(Status.UNAVAILABLE, trailers); + responseObserver.onError(exception); + return; + } + responseObserver.onNext( + MutateRowsResponse.newBuilder() + .addEntries(MutateRowsResponse.Entry.getDefaultInstance()) + .build()); + responseObserver.onCompleted(); + } + + @Override + public void sampleRowKeys( + SampleRowKeysRequest request, StreamObserver responseObserver) { + if (count.getAndIncrement() < 1) { + Metadata trailers = new Metadata(); + maybePopulateCookie(trailers, "sampleRowKeys"); + StatusRuntimeException exception = new StatusRuntimeException(Status.UNAVAILABLE, trailers); + responseObserver.onError(exception); + return; + } + responseObserver.onNext(SampleRowKeysResponse.getDefaultInstance()); + responseObserver.onCompleted(); + } + + @Override + public void checkAndMutateRow( + CheckAndMutateRowRequest request, + StreamObserver responseObserver) { + if (count.getAndIncrement() < 1) { + Metadata trailers = new Metadata(); + maybePopulateCookie(trailers, "checkAndMutate"); + StatusRuntimeException exception = new StatusRuntimeException(Status.UNAVAILABLE, trailers); + responseObserver.onError(exception); + return; + } + responseObserver.onNext(CheckAndMutateRowResponse.getDefaultInstance()); + responseObserver.onCompleted(); + } + + @Override + public void readModifyWriteRow( + ReadModifyWriteRowRequest request, + StreamObserver responseObserver) { + if (count.getAndIncrement() < 1) { + Metadata trailers = new Metadata(); + maybePopulateCookie(trailers, "readModifyWrite"); + StatusRuntimeException exception = new StatusRuntimeException(Status.UNAVAILABLE, trailers); + responseObserver.onError(exception); + return; + } + responseObserver.onNext(ReadModifyWriteRowResponse.getDefaultInstance()); + responseObserver.onCompleted(); + } + + @Override + public void readChangeStream( + ReadChangeStreamRequest request, + StreamObserver responseObserver) { + if (count.getAndIncrement() < 1) { + Metadata trailers = new Metadata(); + maybePopulateCookie(trailers, "readChangeStream"); + StatusRuntimeException exception = new StatusRuntimeException(Status.UNAVAILABLE, trailers); + responseObserver.onError(exception); + return; + } + responseObserver.onNext( + ReadChangeStreamResponse.newBuilder() + .setCloseStream(ReadChangeStreamResponse.CloseStream.getDefaultInstance()) + .build()); + responseObserver.onCompleted(); + } + + @Override + public void generateInitialChangeStreamPartitions( + GenerateInitialChangeStreamPartitionsRequest request, + StreamObserver responseObserver) { + if (count.getAndIncrement() < 1) { + Metadata trailers = new Metadata(); + maybePopulateCookie(trailers, "generateInitialChangeStreamPartitions"); + StatusRuntimeException exception = new StatusRuntimeException(Status.UNAVAILABLE, trailers); + responseObserver.onError(exception); + return; + } + responseObserver.onNext(GenerateInitialChangeStreamPartitionsResponse.getDefaultInstance()); + responseObserver.onCompleted(); + } + + private void maybePopulateCookie(Metadata trailers, String label) { + if (returnCookie) { + trailers.put(ROUTING_COOKIE_1, label); + trailers.put(ROUTING_COOKIE_2, testCookie); + trailers.put(BAD_KEY, "bad-key"); + } + } + } +} diff --git a/grpc-google-cloud-bigtable-admin-v2/pom.xml b/grpc-google-cloud-bigtable-admin-v2/pom.xml index 2a5084eada4b..2c9af872ff9c 100644 --- a/grpc-google-cloud-bigtable-admin-v2/pom.xml +++ b/grpc-google-cloud-bigtable-admin-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-bigtable-admin-v2 - 2.29.1 + 2.30.0 grpc-google-cloud-bigtable-admin-v2 GRPC library for grpc-google-cloud-bigtable-admin-v2 com.google.cloud google-cloud-bigtable-parent - 2.29.1 + 2.30.0 @@ -18,14 +18,14 @@ com.google.cloud google-cloud-bigtable-deps-bom - 2.29.1 + 2.30.0 pom import com.google.cloud google-cloud-bigtable-bom - 2.29.1 + 2.30.0 pom import diff --git a/grpc-google-cloud-bigtable-v2/pom.xml b/grpc-google-cloud-bigtable-v2/pom.xml index e7590cc180ca..5f0ad3cb46d9 100644 --- a/grpc-google-cloud-bigtable-v2/pom.xml +++ b/grpc-google-cloud-bigtable-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-bigtable-v2 - 2.29.1 + 2.30.0 grpc-google-cloud-bigtable-v2 GRPC library for grpc-google-cloud-bigtable-v2 com.google.cloud google-cloud-bigtable-parent - 2.29.1 + 2.30.0 @@ -18,14 +18,14 @@ com.google.cloud google-cloud-bigtable-deps-bom - 2.29.1 + 2.30.0 pom import com.google.cloud google-cloud-bigtable-bom - 2.29.1 + 2.30.0 pom import diff --git a/pom.xml b/pom.xml index 3011d9fca657..d1ceadc6425f 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ google-cloud-bigtable-parent pom - 2.29.1 + 2.30.0 Google Cloud Bigtable Parent https://github.com/googleapis/java-bigtable @@ -14,7 +14,7 @@ com.google.cloud google-cloud-shared-config - 1.6.0 + 1.6.1 @@ -153,27 +153,27 @@ com.google.api.grpc proto-google-cloud-bigtable-v2 - 2.29.1 + 2.30.0 com.google.api.grpc proto-google-cloud-bigtable-admin-v2 - 2.29.1 + 2.30.0 com.google.api.grpc grpc-google-cloud-bigtable-v2 - 2.29.1 + 2.30.0 com.google.api.grpc grpc-google-cloud-bigtable-admin-v2 - 2.29.1 + 2.30.0 com.google.cloud google-cloud-bigtable - 2.29.1 + 2.30.0 diff --git a/proto-google-cloud-bigtable-admin-v2/pom.xml b/proto-google-cloud-bigtable-admin-v2/pom.xml index 672a0c4c04d9..60044ab20a7f 100644 --- a/proto-google-cloud-bigtable-admin-v2/pom.xml +++ b/proto-google-cloud-bigtable-admin-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-bigtable-admin-v2 - 2.29.1 + 2.30.0 proto-google-cloud-bigtable-admin-v2 PROTO library for proto-google-cloud-bigtable-admin-v2 com.google.cloud google-cloud-bigtable-parent - 2.29.1 + 2.30.0 @@ -18,14 +18,14 @@ com.google.cloud google-cloud-bigtable-deps-bom - 2.29.1 + 2.30.0 pom import com.google.cloud google-cloud-bigtable-bom - 2.29.1 + 2.30.0 pom import diff --git a/proto-google-cloud-bigtable-v2/pom.xml b/proto-google-cloud-bigtable-v2/pom.xml index cdadfae0580e..b59ed7b8353e 100644 --- a/proto-google-cloud-bigtable-v2/pom.xml +++ b/proto-google-cloud-bigtable-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-bigtable-v2 - 2.29.1 + 2.30.0 proto-google-cloud-bigtable-v2 PROTO library for proto-google-cloud-bigtable-v2 com.google.cloud google-cloud-bigtable-parent - 2.29.1 + 2.30.0 @@ -18,14 +18,14 @@ com.google.cloud google-cloud-bigtable-deps-bom - 2.29.1 + 2.30.0 pom import com.google.cloud google-cloud-bigtable-bom - 2.29.1 + 2.30.0 pom import diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index b0fa13a80a45..e7462e744d3e 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -29,7 +29,7 @@ com.google.cloud google-cloud-bigtable - 2.29.0 + 2.29.1 @@ -53,7 +53,7 @@ org.codehaus.mojo build-helper-maven-plugin - 3.4.0 + 3.5.0 add-snippets-source diff --git a/samples/native-image-sample/pom.xml b/samples/native-image-sample/pom.xml index b6c62862874c..377134cef1e2 100644 --- a/samples/native-image-sample/pom.xml +++ b/samples/native-image-sample/pom.xml @@ -83,7 +83,7 @@ org.junit.vintage junit-vintage-engine - 5.10.0 + 5.10.1 test diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index 8ac838d3fef3..7bd6ab705d38 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -28,7 +28,7 @@ com.google.cloud google-cloud-bigtable - 2.29.1 + 2.30.0 @@ -52,7 +52,7 @@ org.codehaus.mojo build-helper-maven-plugin - 3.4.0 + 3.5.0 add-snippets-source diff --git a/test-proxy/pom.xml b/test-proxy/pom.xml index 1f941596cbf7..c3c154927773 100644 --- a/test-proxy/pom.xml +++ b/test-proxy/pom.xml @@ -12,11 +12,11 @@ google-cloud-bigtable-parent com.google.cloud - 2.29.1 + 2.30.0 - 2.29.1 + 2.30.0 diff --git a/versions.txt b/versions.txt index dff167209295..d46263f5514a 100644 --- a/versions.txt +++ b/versions.txt @@ -1,10 +1,10 @@ # Format: # module:released-version:current-version -google-cloud-bigtable:2.29.1:2.29.1 -grpc-google-cloud-bigtable-admin-v2:2.29.1:2.29.1 -grpc-google-cloud-bigtable-v2:2.29.1:2.29.1 -proto-google-cloud-bigtable-admin-v2:2.29.1:2.29.1 -proto-google-cloud-bigtable-v2:2.29.1:2.29.1 -google-cloud-bigtable-emulator:0.166.1:0.166.1 -google-cloud-bigtable-emulator-core:0.166.1:0.166.1 +google-cloud-bigtable:2.30.0:2.30.0 +grpc-google-cloud-bigtable-admin-v2:2.30.0:2.30.0 +grpc-google-cloud-bigtable-v2:2.30.0:2.30.0 +proto-google-cloud-bigtable-admin-v2:2.30.0:2.30.0 +proto-google-cloud-bigtable-v2:2.30.0:2.30.0 +google-cloud-bigtable-emulator:0.167.0:0.167.0 +google-cloud-bigtable-emulator-core:0.167.0:0.167.0