Skip to content

Commit be71100

Browse files
committed
feat(bigtable): attach client config UUID header to GetClientConfigurationRequest
Generate a UUID when the session client is initialized and attach it via the bigtable-client-config-uuid header on every GetClientConfigurationRequest (the initial fetch and all refresh polls) so the server can correlate requests from the same client instance.
1 parent 2413811 commit be71100

2 files changed

Lines changed: 70 additions & 1 deletion

File tree

java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/util/ClientConfigurationManager.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.google.cloud.bigtable.data.v2.internal.api.Util;
2727
import com.google.cloud.bigtable.data.v2.internal.csm.attributes.ClientInfo;
2828
import com.google.cloud.bigtable.data.v2.internal.csm.tracers.DebugTagTracer;
29+
import com.google.common.annotations.VisibleForTesting;
2930
import com.google.common.base.Preconditions;
3031
import com.google.common.collect.ImmutableMap;
3132
import com.google.protobuf.TextFormat;
@@ -52,6 +53,7 @@
5253
import java.util.Objects;
5354
import java.util.Optional;
5455
import java.util.Properties;
56+
import java.util.UUID;
5557
import java.util.concurrent.CompletableFuture;
5658
import java.util.concurrent.ScheduledExecutorService;
5759
import java.util.concurrent.ScheduledFuture;
@@ -74,6 +76,17 @@ public class ClientConfigurationManager implements AutoCloseable {
7476

7577
public static final String OVERRIDE_SYS_PROP_KEY = "bigtable.internal.client-config-override";
7678

79+
/**
80+
* Header carrying a UUID that uniquely identifies this session client instance. It is attached to
81+
* every {@link GetClientConfigurationRequest} (both the initial fetch and all subsequent refresh
82+
* polls) so the server can correlate requests originating from the same client.
83+
*/
84+
private static final String CLIENT_UUID_HEADER = "bigtable-client-config-uuid";
85+
86+
@VisibleForTesting
87+
static final Metadata.Key<String> CLIENT_UUID_KEY =
88+
Metadata.Key.of(CLIENT_UUID_HEADER, Metadata.ASCII_STRING_MARSHALLER);
89+
7790
public interface ConfigListener<T> {
7891
void onChange(T newValue);
7992
}
@@ -118,6 +131,10 @@ public void close() {
118131
private final GetClientConfigurationRequest request;
119132
private final ChannelProvider channelProvider;
120133

134+
// A UUID generated once when this session client is initialized. It is attached to the
135+
// GetClientConfigurationRequest header for both the initial and all refresh requests.
136+
private final String clientUuid;
137+
121138
@GuardedBy("this")
122139
private ManagedChannel channel;
123140

@@ -196,6 +213,13 @@ public ClientConfigurationManager(
196213
ImmutableMap.of(
197214
"instance_name", clientInfo.getInstanceName().toString(),
198215
"app_profile_id", clientInfo.getAppProfileId()));
216+
217+
// Generate a UUID that uniquely identifies this session client instance and attach it to the
218+
// request metadata. Since the same metadata is reused for the initial fetch and every refresh
219+
// poll, the header is sent on all GetClientConfigurationRequests.
220+
this.clientUuid = UUID.randomUUID().toString();
221+
this.metadata.put(CLIENT_UUID_KEY, clientUuid);
222+
199223
this.request =
200224
GetClientConfigurationRequest.newBuilder()
201225
.setInstanceName(clientInfo.getInstanceName().toString())
@@ -262,6 +286,13 @@ ClientConfiguration getDefaultConfig() {
262286
return defaultConfig;
263287
}
264288

289+
/**
290+
* Returns the UUID generated for this session client, attached to every config request header.
291+
*/
292+
String getClientUuid() {
293+
return clientUuid;
294+
}
295+
265296
public synchronized <T> ListenerHandle addListener(
266297
Function<ClientConfiguration, T> extractor, ConfigListener<T> listener) {
267298
ListenerEntry<T> entry = new ListenerEntry<>(extractor, listener);

java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/internal/util/ClientConfigurationManagerTest.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@
5353
import io.grpc.Metadata;
5454
import io.grpc.MethodDescriptor;
5555
import io.grpc.Server;
56+
import io.grpc.ServerCall;
57+
import io.grpc.ServerCallHandler;
58+
import io.grpc.ServerInterceptor;
5659
import io.grpc.Status;
5760
import io.grpc.stub.StreamObserver;
5861
import java.io.IOException;
@@ -97,14 +100,26 @@ class ClientConfigurationManagerTest {
97100
private ChannelProviders.ChannelProvider channelProvider;
98101
@Mock private ScheduledExecutorService mockExecutor;
99102
private final OutstandingRpcCounter outstandingRpcCounter = new OutstandingRpcCounter();
103+
// Captures the bigtable-client-config-uuid header seen by the server on the most recent request.
104+
private final AtomicReference<String> lastClientUuid = new AtomicReference<>();
100105
private ClientConfigurationManager manager;
101106
private final NoopMetrics.NoopDebugTracer noopDebugTracer = NoopMetrics.NoopDebugTracer.INSTANCE;
102107

103108
@BeforeEach
104109
void setUp() throws IOException {
105110
service = new FakeConfigService();
106111

107-
server = FakeServiceBuilder.create(service).start();
112+
ServerInterceptor uuidCapturingInterceptor =
113+
new ServerInterceptor() {
114+
@Override
115+
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
116+
ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
117+
lastClientUuid.set(headers.get(ClientConfigurationManager.CLIENT_UUID_KEY));
118+
return next.startCall(call, headers);
119+
}
120+
};
121+
122+
server = FakeServiceBuilder.create(service).intercept(uuidCapturingInterceptor).start();
108123

109124
channelProvider =
110125
new ForwardingChannelProvider(
@@ -145,6 +160,29 @@ void tearDown() {
145160
server.shutdown();
146161
}
147162

163+
@Test
164+
void clientUuidHeaderSentOnInitialAndRefreshRequests() throws Exception {
165+
// The header name must be prefixed with "bigtable-".
166+
assertThat(ClientConfigurationManager.CLIENT_UUID_KEY.name()).startsWith("bigtable-");
167+
168+
// Fetch the initial config and capture the header sent with it.
169+
manager.start().get();
170+
outstandingRpcCounter.waitUntilRpcsDone();
171+
172+
String initialUuid = lastClientUuid.get();
173+
// The header must be present and match the manager's generated UUID.
174+
assertThat(initialUuid).isNotNull();
175+
assertThat(initialUuid).isEqualTo(manager.getClientUuid());
176+
177+
// Trigger a refresh poll and confirm the same UUID header rides along.
178+
ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
179+
verify(mockExecutor, times(1)).schedule(runnableCaptor.capture(), anyLong(), any());
180+
runnableCaptor.getValue().run();
181+
outstandingRpcCounter.waitUntilRpcsDone();
182+
183+
assertThat(lastClientUuid.get()).isEqualTo(manager.getClientUuid());
184+
}
185+
148186
@Test
149187
void initialFetchTest() throws ExecutionException, InterruptedException {
150188
// Check the initial config is correct

0 commit comments

Comments
 (0)