-
Notifications
You must be signed in to change notification settings - Fork 88
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: client sends routing cookie back to server #1888
Merged
Merged
Changes from 10 commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
d274fc2
feat: client sends retry cookie back to server
mutianf 9333ba0
udpate to use trailer instead of error info
mutianf 678bc35
updating the header name
mutianf e6ab87f
address some comments
mutianf ba373a7
udpate
mutianf be27ca8
update tests and handling of retry cookie
mutianf 1fa268d
address comments
mutianf 91989cd
address comments
mutianf 1422950
add cookie to readChangeStream
mutianf 67779d5
also check headers and add a test
mutianf 92f759e
simplify code
mutianf d49ce70
clean up test
mutianf 6479464
clean up test
mutianf 3b4c178
update dependency
mutianf abad657
test
mutianf 9d9a429
move MetadataSubject to a separate file
mutianf 660085b
add the file
mutianf a0dd03d
add license
mutianf 1aaaddc
address comments
mutianf 00eb5df
close client
mutianf 6a33c0a
🦉 Updates from OwlBot post-processor
gcf-owl-bot[bot] 5927a64
Merge branch 'main' into cookie
mutianf f76fac7
🦉 Updates from OwlBot post-processor
gcf-owl-bot[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
87 changes: 87 additions & 0 deletions
87
...le-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/CookiesHolder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
/* | ||
* 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<CookiesHolder> 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<Metadata.Key<String>, String> cookies = new HashMap<>(); | ||
|
||
/** Returns CookiesHolder if presents in CallOptions, otherwise returns null. */ | ||
@Nullable | ||
static CookiesHolder fromCallOptions(CallOptions options) { | ||
mutianf marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// 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<String> key : cookies.keySet()) { | ||
headers.put(key, cookies.get(key)); | ||
} | ||
return headers; | ||
} | ||
|
||
/** | ||
* Iterate through all the keys in initial metadata, and add all the keys that match | ||
* COOKIE_KEY_PREFIX to cookies. | ||
*/ | ||
void extractCookiesFromResponseHeaders(@Nullable Metadata headers) { | ||
if (headers == null) { | ||
return; | ||
} | ||
for (String key : headers.keys()) { | ||
if (key.startsWith(COOKIE_KEY_PREFIX)) { | ||
Metadata.Key<String> metadataKey = Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER); | ||
String value = headers.get(metadataKey); | ||
cookies.put(metadataKey, value); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Iterate through all the keys in 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 extractCookiesFromResponseTrailers(@Nullable Metadata trailers) { | ||
igorbernstein2 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (trailers == null) { | ||
return; | ||
} | ||
for (String key : trailers.keys()) { | ||
if (key.startsWith(COOKIE_KEY_PREFIX)) { | ||
Metadata.Key<String> metadataKey = Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER); | ||
String value = trailers.get(metadataKey); | ||
cookies.put(metadataKey, value); | ||
} | ||
} | ||
} | ||
} |
95 changes: 95 additions & 0 deletions
95
...oud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/CookiesInterceptor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
/* | ||
* 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.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 <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall( | ||
MethodDescriptor<ReqT, RespT> methodDescriptor, CallOptions callOptions, Channel channel) { | ||
return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>( | ||
channel.newCall(methodDescriptor, callOptions)) { | ||
@Override | ||
public void start(Listener<RespT> 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<RespT> | ||
extends ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT> { | ||
|
||
private final CookiesHolder cookie; | ||
|
||
UpdateCookieListener(ClientCall.Listener<RespT> delegate, CookiesHolder cookiesHolder) { | ||
super(delegate); | ||
this.cookie = cookiesHolder; | ||
} | ||
|
||
@Override | ||
public void onHeaders(Metadata headers) { | ||
try { | ||
cookie.extractCookiesFromResponseHeaders(headers); | ||
} catch (Throwable e) { | ||
LOG.warning("Failed to extract cookie from response headers: " + e); | ||
} finally { | ||
super.onHeaders(headers); | ||
} | ||
} | ||
|
||
@Override | ||
public void onClose(Status status, Metadata trailers) { | ||
try { | ||
cookie.extractCookiesFromResponseTrailers(trailers); | ||
} catch (Throwable e) { | ||
LOG.warning("Failed to extract cookie from response trailers: " + e); | ||
mutianf marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} finally { | ||
super.onClose(status, trailers); | ||
} | ||
} | ||
} | ||
} |
48 changes: 48 additions & 0 deletions
48
.../src/main/java/com/google/cloud/bigtable/data/v2/stub/CookiesServerStreamingCallable.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<RequestT, ResponseT> | ||
extends ServerStreamingCallable<RequestT, ResponseT> { | ||
|
||
private final ServerStreamingCallable<RequestT, ResponseT> callable; | ||
|
||
CookiesServerStreamingCallable(ServerStreamingCallable<RequestT, ResponseT> innerCallable) { | ||
this.callable = innerCallable; | ||
} | ||
|
||
@Override | ||
public void call( | ||
RequestT request, ResponseObserver<ResponseT> responseObserver, ApiCallContext context) { | ||
GrpcCallContext grpcCallContext = (GrpcCallContext) context; | ||
callable.call( | ||
request, | ||
responseObserver, | ||
grpcCallContext.withCallOptions( | ||
grpcCallContext.getCallOptions().withOption(COOKIES_HOLDER_KEY, new CookiesHolder()))); | ||
} | ||
} |
44 changes: 44 additions & 0 deletions
44
...d-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/CookiesUnaryCallable.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<RequestT, ResponseT> extends UnaryCallable<RequestT, ResponseT> { | ||
private final UnaryCallable<RequestT, ResponseT> innerCallable; | ||
|
||
CookiesUnaryCallable(UnaryCallable<RequestT, ResponseT> callable) { | ||
this.innerCallable = callable; | ||
} | ||
|
||
@Override | ||
public ApiFuture<ResponseT> futureCall(RequestT request, ApiCallContext context) { | ||
GrpcCallContext grpcCallContext = (GrpcCallContext) context; | ||
return innerCallable.futureCall( | ||
request, | ||
grpcCallContext.withCallOptions( | ||
grpcCallContext.getCallOptions().withOption(COOKIES_HOLDER_KEY, new CookiesHolder()))); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would have called this a
CookieJar