Skip to content

Commit bfa156d

Browse files
authored
chore: refactor to wrap client context in BigtableClientContext (#2433)
Refactor ClientContext creation. We need to create OpenTelemetry before client context is created so we can inject the PerConnectionErrorTracker interceptor on the ManagedChannel. We need to access the open telemetry instance later when we create the TracerFactory. This PR creates a new BigtableCleintContext class that wraps gax ClientContext and OpenTelemetry so we can access both later to avoid creating a global open telemetry instance. Also moved client context creation logic from EnhancedBigtableStub to BigtableClientContext.
1 parent 3156889 commit bfa156d

File tree

6 files changed

+283
-239
lines changed

6 files changed

+283
-239
lines changed

google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataClientFactory.java

Lines changed: 16 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,10 @@
1616
package com.google.cloud.bigtable.data.v2;
1717

1818
import com.google.api.core.BetaApi;
19-
import com.google.api.gax.core.BackgroundResource;
2019
import com.google.api.gax.rpc.ClientContext;
20+
import com.google.cloud.bigtable.data.v2.stub.BigtableClientContext;
2121
import com.google.cloud.bigtable.data.v2.stub.EnhancedBigtableStub;
22-
import io.opentelemetry.api.OpenTelemetry;
2322
import java.io.IOException;
24-
import java.util.logging.Level;
25-
import java.util.logging.Logger;
2623
import javax.annotation.Nonnull;
2724

2825
/**
@@ -66,11 +63,8 @@
6663
@BetaApi("This feature is currently experimental and can change in the future")
6764
public final class BigtableDataClientFactory implements AutoCloseable {
6865

69-
private static final Logger logger = Logger.getLogger(BigtableDataClientFactory.class.getName());
70-
7166
private final BigtableDataSettings defaultSettings;
72-
private final ClientContext sharedClientContext;
73-
private final OpenTelemetry openTelemetry;
67+
private final BigtableClientContext sharedClientContext;
7468

7569
/**
7670
* Create a instance of this factory.
@@ -80,31 +74,16 @@ public final class BigtableDataClientFactory implements AutoCloseable {
8074
*/
8175
public static BigtableDataClientFactory create(BigtableDataSettings defaultSettings)
8276
throws IOException {
83-
ClientContext sharedClientContext =
84-
EnhancedBigtableStub.createClientContext(defaultSettings.getStubSettings());
85-
OpenTelemetry openTelemetry = null;
86-
try {
87-
// We don't want client side metrics to crash the client, so catch any exception when getting
88-
// the OTEL instance and log the exception instead.
89-
openTelemetry =
90-
EnhancedBigtableStub.getOpenTelemetry(
91-
defaultSettings.getProjectId(),
92-
defaultSettings.getMetricsProvider(),
93-
sharedClientContext.getCredentials(),
94-
defaultSettings.getStubSettings().getMetricsEndpoint());
95-
} catch (Throwable t) {
96-
logger.log(Level.WARNING, "Failed to get OTEL, will skip exporting client side metrics", t);
97-
}
98-
return new BigtableDataClientFactory(sharedClientContext, defaultSettings, openTelemetry);
77+
BigtableClientContext sharedClientContext =
78+
EnhancedBigtableStub.createBigtableClientContext(defaultSettings.getStubSettings());
79+
80+
return new BigtableDataClientFactory(sharedClientContext, defaultSettings);
9981
}
10082

10183
private BigtableDataClientFactory(
102-
ClientContext sharedClientContext,
103-
BigtableDataSettings defaultSettings,
104-
OpenTelemetry openTelemetry) {
84+
BigtableClientContext sharedClientContext, BigtableDataSettings defaultSettings) {
10585
this.sharedClientContext = sharedClientContext;
10686
this.defaultSettings = defaultSettings;
107-
this.openTelemetry = openTelemetry;
10887
}
10988

11089
/**
@@ -114,9 +93,7 @@ private BigtableDataClientFactory(
11493
*/
11594
@Override
11695
public void close() throws Exception {
117-
for (BackgroundResource resource : sharedClientContext.getBackgroundResources()) {
118-
resource.close();
119-
}
96+
sharedClientContext.close();
12097
}
12198

12299
/**
@@ -132,10 +109,11 @@ public BigtableDataClient createDefault() {
132109
try {
133110
ClientContext clientContext =
134111
sharedClientContext
112+
.getClientContext()
135113
.toBuilder()
136114
.setTracerFactory(
137115
EnhancedBigtableStub.createBigtableTracerFactory(
138-
defaultSettings.getStubSettings(), openTelemetry))
116+
defaultSettings.getStubSettings(), sharedClientContext.getOpenTelemetry()))
139117
.build();
140118

141119
return BigtableDataClient.createWithClientContext(defaultSettings, clientContext);
@@ -161,10 +139,11 @@ public BigtableDataClient createForAppProfile(@Nonnull String appProfileId) thro
161139

162140
ClientContext clientContext =
163141
sharedClientContext
142+
.getClientContext()
164143
.toBuilder()
165144
.setTracerFactory(
166145
EnhancedBigtableStub.createBigtableTracerFactory(
167-
settings.getStubSettings(), openTelemetry))
146+
settings.getStubSettings(), sharedClientContext.getOpenTelemetry()))
168147
.build();
169148
return BigtableDataClient.createWithClientContext(settings, clientContext);
170149
}
@@ -190,10 +169,11 @@ public BigtableDataClient createForInstance(@Nonnull String projectId, @Nonnull
190169

191170
ClientContext clientContext =
192171
sharedClientContext
172+
.getClientContext()
193173
.toBuilder()
194174
.setTracerFactory(
195175
EnhancedBigtableStub.createBigtableTracerFactory(
196-
settings.getStubSettings(), openTelemetry))
176+
settings.getStubSettings(), sharedClientContext.getOpenTelemetry()))
197177
.build();
198178

199179
return BigtableDataClient.createWithClientContext(settings, clientContext);
@@ -220,10 +200,11 @@ public BigtableDataClient createForInstance(
220200
.build();
221201
ClientContext clientContext =
222202
sharedClientContext
203+
.getClientContext()
223204
.toBuilder()
224205
.setTracerFactory(
225206
EnhancedBigtableStub.createBigtableTracerFactory(
226-
settings.getStubSettings(), openTelemetry))
207+
settings.getStubSettings(), sharedClientContext.getOpenTelemetry()))
227208
.build();
228209
return BigtableDataClient.createWithClientContext(settings, clientContext);
229210
}
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.google.cloud.bigtable.data.v2.stub;
17+
18+
import com.google.api.core.ApiFunction;
19+
import com.google.api.core.InternalApi;
20+
import com.google.api.gax.core.BackgroundResource;
21+
import com.google.api.gax.core.CredentialsProvider;
22+
import com.google.api.gax.core.FixedCredentialsProvider;
23+
import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider;
24+
import com.google.api.gax.rpc.ClientContext;
25+
import com.google.auth.Credentials;
26+
import com.google.auth.oauth2.ServiceAccountJwtAccessCredentials;
27+
import com.google.cloud.bigtable.data.v2.BigtableDataSettings;
28+
import com.google.cloud.bigtable.data.v2.internal.JwtCredentialsWithAudience;
29+
import com.google.cloud.bigtable.data.v2.stub.metrics.CustomOpenTelemetryMetricsProvider;
30+
import com.google.cloud.bigtable.data.v2.stub.metrics.DefaultMetricsProvider;
31+
import com.google.cloud.bigtable.data.v2.stub.metrics.ErrorCountPerConnectionMetricTracker;
32+
import com.google.cloud.bigtable.data.v2.stub.metrics.MetricsProvider;
33+
import com.google.cloud.bigtable.data.v2.stub.metrics.NoopMetricsProvider;
34+
import io.grpc.ManagedChannelBuilder;
35+
import io.opentelemetry.api.OpenTelemetry;
36+
import java.io.IOException;
37+
import java.net.URI;
38+
import java.net.URISyntaxException;
39+
import java.util.logging.Level;
40+
import java.util.logging.Logger;
41+
import javax.annotation.Nullable;
42+
43+
/**
44+
* This class wraps all state needed during the lifetime of the Bigtable client. This includes gax's
45+
* {@link ClientContext} plus any additional state that Bigtable Client needs.
46+
*/
47+
@InternalApi
48+
public class BigtableClientContext {
49+
50+
private static final Logger logger = Logger.getLogger(BigtableClientContext.class.getName());
51+
52+
@Nullable private final OpenTelemetry openTelemetry;
53+
private final ClientContext clientContext;
54+
55+
public static BigtableClientContext create(EnhancedBigtableStubSettings settings)
56+
throws IOException {
57+
EnhancedBigtableStubSettings.Builder builder = settings.toBuilder();
58+
59+
// Set up credentials
60+
patchCredentials(builder);
61+
62+
// Fix the credentials so that they can be shared
63+
Credentials credentials = null;
64+
if (builder.getCredentialsProvider() != null) {
65+
credentials = builder.getCredentialsProvider().getCredentials();
66+
}
67+
builder.setCredentialsProvider(FixedCredentialsProvider.create(credentials));
68+
69+
// Set up OpenTelemetry
70+
OpenTelemetry openTelemetry = null;
71+
try {
72+
// We don't want client side metrics to crash the client, so catch any exception when getting
73+
// the OTEL instance and log the exception instead.
74+
// TODO openTelemetry doesn't need to be tied to a project id. This is incorrect and will be
75+
// fixed in the following PR.
76+
openTelemetry =
77+
getOpenTelemetryFromMetricsProvider(
78+
settings.getProjectId(),
79+
settings.getMetricsProvider(),
80+
credentials,
81+
settings.getMetricsEndpoint());
82+
} catch (Throwable t) {
83+
logger.log(Level.WARNING, "Failed to get OTEL, will skip exporting client side metrics", t);
84+
}
85+
86+
// Set up channel
87+
InstantiatingGrpcChannelProvider.Builder transportProvider =
88+
builder.getTransportChannelProvider() instanceof InstantiatingGrpcChannelProvider
89+
? ((InstantiatingGrpcChannelProvider) builder.getTransportChannelProvider()).toBuilder()
90+
: null;
91+
92+
ErrorCountPerConnectionMetricTracker errorCountPerConnectionMetricTracker = null;
93+
94+
if (transportProvider != null) {
95+
// Set up cookie holder if routing cookie is enabled
96+
if (builder.getEnableRoutingCookie()) {
97+
setupCookieHolder(transportProvider);
98+
}
99+
// Set up per connection error count tracker if OpenTelemetry is not null
100+
if (openTelemetry != null) {
101+
errorCountPerConnectionMetricTracker =
102+
setupPerConnectionErrorTracer(builder, transportProvider, openTelemetry);
103+
}
104+
// Inject channel priming if enabled
105+
if (builder.isRefreshingChannel()) {
106+
transportProvider.setChannelPrimer(
107+
BigtableChannelPrimer.create(
108+
credentials,
109+
settings.getProjectId(),
110+
settings.getInstanceId(),
111+
settings.getAppProfileId()));
112+
}
113+
114+
builder.setTransportChannelProvider(transportProvider.build());
115+
}
116+
117+
ClientContext clientContext = ClientContext.create(builder.build());
118+
119+
if (errorCountPerConnectionMetricTracker != null) {
120+
errorCountPerConnectionMetricTracker.startConnectionErrorCountTracker(
121+
clientContext.getExecutor());
122+
}
123+
124+
return new BigtableClientContext(clientContext, openTelemetry);
125+
}
126+
127+
private BigtableClientContext(ClientContext clientContext, OpenTelemetry openTelemetry) {
128+
this.clientContext = clientContext;
129+
this.openTelemetry = openTelemetry;
130+
}
131+
132+
public OpenTelemetry getOpenTelemetry() {
133+
return this.openTelemetry;
134+
}
135+
136+
public ClientContext getClientContext() {
137+
return this.clientContext;
138+
}
139+
140+
public void close() throws Exception {
141+
for (BackgroundResource resource : clientContext.getBackgroundResources()) {
142+
resource.close();
143+
}
144+
}
145+
146+
private static OpenTelemetry getOpenTelemetryFromMetricsProvider(
147+
String projectId,
148+
MetricsProvider metricsProvider,
149+
@Nullable Credentials defaultCredentials,
150+
@Nullable String metricsEndpoint)
151+
throws IOException {
152+
if (metricsProvider instanceof CustomOpenTelemetryMetricsProvider) {
153+
CustomOpenTelemetryMetricsProvider customMetricsProvider =
154+
(CustomOpenTelemetryMetricsProvider) metricsProvider;
155+
return customMetricsProvider.getOpenTelemetry();
156+
} else if (metricsProvider instanceof DefaultMetricsProvider) {
157+
Credentials credentials =
158+
BigtableDataSettings.getMetricsCredentials() != null
159+
? BigtableDataSettings.getMetricsCredentials()
160+
: defaultCredentials;
161+
DefaultMetricsProvider defaultMetricsProvider = (DefaultMetricsProvider) metricsProvider;
162+
return defaultMetricsProvider.getOpenTelemetry(projectId, metricsEndpoint, credentials);
163+
} else if (metricsProvider instanceof NoopMetricsProvider) {
164+
return null;
165+
}
166+
throw new IOException("Invalid MetricsProvider type " + metricsProvider);
167+
}
168+
169+
private static void patchCredentials(EnhancedBigtableStubSettings.Builder settings)
170+
throws IOException {
171+
int i = settings.getEndpoint().lastIndexOf(":");
172+
String host = settings.getEndpoint().substring(0, i);
173+
String audience = settings.getJwtAudienceMapping().get(host);
174+
175+
if (audience == null) {
176+
return;
177+
}
178+
URI audienceUri = null;
179+
try {
180+
audienceUri = new URI(audience);
181+
} catch (URISyntaxException e) {
182+
throw new IllegalStateException("invalid JWT audience override", e);
183+
}
184+
185+
CredentialsProvider credentialsProvider = settings.getCredentialsProvider();
186+
if (credentialsProvider == null) {
187+
return;
188+
}
189+
190+
Credentials credentials = credentialsProvider.getCredentials();
191+
if (credentials == null) {
192+
return;
193+
}
194+
195+
if (!(credentials instanceof ServiceAccountJwtAccessCredentials)) {
196+
return;
197+
}
198+
199+
ServiceAccountJwtAccessCredentials jwtCreds = (ServiceAccountJwtAccessCredentials) credentials;
200+
JwtCredentialsWithAudience patchedCreds = new JwtCredentialsWithAudience(jwtCreds, audienceUri);
201+
settings.setCredentialsProvider(FixedCredentialsProvider.create(patchedCreds));
202+
}
203+
204+
private static ErrorCountPerConnectionMetricTracker setupPerConnectionErrorTracer(
205+
EnhancedBigtableStubSettings.Builder builder,
206+
InstantiatingGrpcChannelProvider.Builder transportProvider,
207+
OpenTelemetry openTelemetry) {
208+
ErrorCountPerConnectionMetricTracker errorCountPerConnectionMetricTracker =
209+
new ErrorCountPerConnectionMetricTracker(
210+
openTelemetry, EnhancedBigtableStub.createBuiltinAttributes(builder.build()));
211+
ApiFunction<ManagedChannelBuilder, ManagedChannelBuilder> oldChannelConfigurator =
212+
transportProvider.getChannelConfigurator();
213+
transportProvider.setChannelConfigurator(
214+
managedChannelBuilder -> {
215+
managedChannelBuilder.intercept(errorCountPerConnectionMetricTracker.getInterceptor());
216+
217+
if (oldChannelConfigurator != null) {
218+
managedChannelBuilder = oldChannelConfigurator.apply(managedChannelBuilder);
219+
}
220+
return managedChannelBuilder;
221+
});
222+
return errorCountPerConnectionMetricTracker;
223+
}
224+
225+
private static void setupCookieHolder(
226+
InstantiatingGrpcChannelProvider.Builder transportProvider) {
227+
ApiFunction<ManagedChannelBuilder, ManagedChannelBuilder> oldChannelConfigurator =
228+
transportProvider.getChannelConfigurator();
229+
transportProvider.setChannelConfigurator(
230+
managedChannelBuilder -> {
231+
managedChannelBuilder.intercept(new CookiesInterceptor());
232+
233+
if (oldChannelConfigurator != null) {
234+
managedChannelBuilder = oldChannelConfigurator.apply(managedChannelBuilder);
235+
}
236+
return managedChannelBuilder;
237+
});
238+
}
239+
}

0 commit comments

Comments
 (0)