Skip to content

Commit 3607955

Browse files
fix: misc fixes for bigtable-proxy (#9736)
* fix: misc fixes for bigtable-proxy * fix dependecy conflict that shared-configuration created for the transitive dep google-cloud-core * some docs * typo in Verify & extract constants * hard fail if call options credentials receives are missing a tracer * rename the label apiclient -> api_client for readability * remove stray imports * format * presence typo * fix ping and warm * update readme for verify * add multiple response detection to ping and warm * fix comment * add a TODO for a debug metric
1 parent 9ec2f11 commit 3607955

File tree

11 files changed

+135
-44
lines changed

11 files changed

+135
-44
lines changed

bigtable/bigtable-proxy/README.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,27 @@ in a project your choosing. The metrics will be published under the namespace
6464
# Build the binary
6565
mvn package
6666

67-
# use the binary
67+
# unpack the binary on the proxy host
6868
unzip target/bigtable-proxy-0.0.1-SNAPSHOT-bin.zip
6969
cd bigtable-proxy-0.0.1-SNAPSHOT
70+
71+
# Verify that the proxy has require permissions using an existing table. Please note that the table
72+
# data will not be modified, however a test metric will be written.
73+
./bigtable-verify.sh \
74+
--bigtable-project-id=$BIGTABLE_PROJECT_ID \
75+
--bigtable-instance-id=$BIGTABLE_INSTANCE_ID \
76+
--bigtable-table-id=$BIGTABLE_TABLE_ID \
77+
--metrics-project-id=$METRICS_PROJECT_ID
78+
79+
# Then start the proxy on the specified port. The proxy can forward requests for multiple
80+
# Bigtable projects/instances/tables. However it will export health metrics to a single project
81+
# specified by `metrics-project-id`.
7082
./bigtable-proxy.sh \
7183
--listen-port=1234 \
7284
--metrics-project-id=SOME_GCP_PROJECT
7385

74-
export BIGTABLE_EMULATOR_HOST=1234
86+
# Start your application, and redirect the bigtable client to connect to the local proxy.
87+
export BIGTABLE_EMULATOR_HOST="localhost:1234"
7588
path/to/application/with/bigtable/client
7689
```
7790

bigtable/bigtable-proxy/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,11 @@
130130
<artifactId>exporter-metrics</artifactId>
131131
<version>${exporter-metrics.version}</version>
132132
</dependency>
133+
<!-- Workaround shared-configuration incorrectly marking google-cloud-core as a test dep -->
134+
<dependency>
135+
<groupId>com.google.cloud</groupId>
136+
<artifactId>google-cloud-core</artifactId>
137+
</dependency>
133138
<dependency>
134139
<groupId>io.opentelemetry.contrib</groupId>
135140
<artifactId>opentelemetry-gcp-resources</artifactId>

bigtable/bigtable-proxy/src/main/java/com/google/cloud/bigtable/examples/proxy/Main.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818

1919
import com.google.cloud.bigtable.examples.proxy.commands.Serve;
2020
import com.google.cloud.bigtable.examples.proxy.commands.Verify;
21-
import org.slf4j.Logger;
22-
import org.slf4j.LoggerFactory;
2321
import org.slf4j.bridge.SLF4JBridgeHandler;
2422
import picocli.CommandLine;
2523
import picocli.CommandLine.Command;

bigtable/bigtable-proxy/src/main/java/com/google/cloud/bigtable/examples/proxy/channelpool/DataChannel.java

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,28 @@
1717
package com.google.cloud.bigtable.examples.proxy.channelpool;
1818

1919
import com.google.bigtable.v2.BigtableGrpc;
20-
import com.google.bigtable.v2.BigtableGrpc.BigtableFutureStub;
2120
import com.google.bigtable.v2.PingAndWarmRequest;
2221
import com.google.bigtable.v2.PingAndWarmResponse;
22+
import com.google.cloud.bigtable.examples.proxy.metrics.CallLabels;
2323
import com.google.cloud.bigtable.examples.proxy.metrics.Metrics;
2424
import com.google.cloud.bigtable.examples.proxy.metrics.Tracer;
2525
import com.google.common.util.concurrent.ListenableFuture;
26+
import com.google.common.util.concurrent.SettableFuture;
2627
import io.grpc.CallCredentials;
2728
import io.grpc.CallOptions;
2829
import io.grpc.ClientCall;
30+
import io.grpc.ClientCall.Listener;
2931
import io.grpc.ConnectivityState;
3032
import io.grpc.Deadline;
3133
import io.grpc.ExperimentalApi;
3234
import io.grpc.ManagedChannel;
3335
import io.grpc.ManagedChannelBuilder;
36+
import io.grpc.Metadata;
3437
import io.grpc.MethodDescriptor;
38+
import io.grpc.Status;
3539
import io.grpc.StatusRuntimeException;
40+
import java.net.URLEncoder;
41+
import java.nio.charset.StandardCharsets;
3642
import java.util.List;
3743
import java.util.Optional;
3844
import java.util.concurrent.ExecutionException;
@@ -50,7 +56,7 @@ public class DataChannel extends ManagedChannel {
5056
private final ManagedChannel inner;
5157
private final Metrics metrics;
5258
private final ResourceCollector resourceCollector;
53-
private final BigtableFutureStub warmingStub;
59+
private final CallCredentials callCredentials;
5460
private final ScheduledFuture<?> antiIdleTask;
5561

5662
private final AtomicBoolean closed = new AtomicBoolean();
@@ -65,6 +71,7 @@ public DataChannel(
6571
Metrics metrics) {
6672
this.resourceCollector = resourceCollector;
6773

74+
this.callCredentials = callCredentials;
6875
inner =
6976
ManagedChannelBuilder.forAddress(endpoint, port)
7077
.userAgent(userAgent)
@@ -76,8 +83,6 @@ public DataChannel(
7683
this.metrics = metrics;
7784

7885
try {
79-
warmingStub = BigtableGrpc.newFutureStub(inner).withCallCredentials(callCredentials);
80-
8186
warm();
8287
} catch (RuntimeException e) {
8388
try {
@@ -107,10 +112,8 @@ private void warm() {
107112
return;
108113
}
109114

110-
BigtableFutureStub timedStub = warmingStub.withDeadline(Deadline.after(1, TimeUnit.MINUTES));
111-
112115
List<ListenableFuture<PingAndWarmResponse>> futures =
113-
requests.stream().map(timedStub::pingAndWarm).collect(Collectors.toList());
116+
requests.stream().map(this::sendPingAndWarm).collect(Collectors.toList());
114117

115118
int successCount = 0;
116119
int failures = 0;
@@ -148,6 +151,61 @@ private void warm() {
148151
}
149152
}
150153

154+
private ListenableFuture<PingAndWarmResponse> sendPingAndWarm(PingAndWarmRequest request) {
155+
CallLabels callLabels =
156+
CallLabels.create(
157+
BigtableGrpc.getPingAndWarmMethod(),
158+
Optional.of("bigtableproxy"),
159+
Optional.of(request.getName()),
160+
Optional.of(request.getAppProfileId()));
161+
Tracer tracer = new Tracer(metrics, callLabels);
162+
163+
CallOptions callOptions =
164+
CallOptions.DEFAULT
165+
.withCallCredentials(callCredentials)
166+
.withDeadline(Deadline.after(1, TimeUnit.MINUTES));
167+
callOptions = tracer.injectIntoCallOptions(callOptions);
168+
169+
ClientCall<PingAndWarmRequest, PingAndWarmResponse> call =
170+
inner.newCall(BigtableGrpc.getPingAndWarmMethod(), callOptions);
171+
172+
Metadata metadata = new Metadata();
173+
metadata.put(
174+
CallLabels.REQUEST_PARAMS,
175+
String.format(
176+
"name=projects/%s/instances/%s",
177+
URLEncoder.encode(request.getName(), StandardCharsets.UTF_8),
178+
URLEncoder.encode(request.getAppProfileId(), StandardCharsets.UTF_8)));
179+
180+
SettableFuture<PingAndWarmResponse> f = SettableFuture.create();
181+
call.start(
182+
new Listener<>() {
183+
@Override
184+
public void onMessage(PingAndWarmResponse response) {
185+
if (!f.set(response)) {
186+
// TODO: set a metric
187+
LOGGER.warn("PingAndWarm returned multiple responses");
188+
}
189+
}
190+
191+
@Override
192+
public void onClose(Status status, Metadata trailers) {
193+
tracer.onCallFinished(status);
194+
195+
if (status.isOk()) {
196+
f.setException(new IllegalStateException("PingAndWarm was missing a response"));
197+
} else {
198+
f.setException(status.asRuntimeException());
199+
}
200+
}
201+
},
202+
metadata);
203+
call.sendMessage(request);
204+
call.request(Integer.MAX_VALUE);
205+
206+
return f;
207+
}
208+
151209
@Override
152210
public ManagedChannel shutdown() {
153211
if (closed.compareAndSet(false, true)) {
@@ -208,9 +266,13 @@ public void enterIdle() {
208266
@Override
209267
public <RequestT, ResponseT> ClientCall<RequestT, ResponseT> newCall(
210268
MethodDescriptor<RequestT, ResponseT> methodDescriptor, CallOptions callOptions) {
211-
Optional.ofNullable(Tracer.extractTracerFromCallOptions(callOptions))
212-
.map(Tracer::getCallLabels)
213-
.ifPresent(resourceCollector::collect);
269+
Tracer tracer =
270+
Optional.ofNullable(Tracer.extractTracerFromCallOptions(callOptions))
271+
.orElseThrow(
272+
() ->
273+
new IllegalStateException(
274+
"DataChannel failed to extract Tracer from CallOptions"));
275+
resourceCollector.collect(tracer.getCallLabels());
214276

215277
return inner.newCall(methodDescriptor, callOptions);
216278
}

bigtable/bigtable-proxy/src/main/java/com/google/cloud/bigtable/examples/proxy/commands/Serve.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ void start() throws IOException {
106106
new InstrumentedCallCredentials(MoreCallCredentials.from(credentials));
107107

108108
if (metrics == null) {
109+
// InstrumentedCallCredentials expect to only be called when a Tracer is available in the
110+
// CallOptions. This is only true for DataChannel pingAndWarm and things invoked by
111+
// ProxyHandler. MetricsImpl does not do this, so it must get undecorated credentials.
109112
metrics = new MetricsImpl(credentials, metricsProjectId);
110113
}
111114

bigtable/bigtable-proxy/src/main/java/com/google/cloud/bigtable/examples/proxy/commands/Verify.java

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,9 @@
2929
import com.google.bigtable.v2.RowFilter;
3030
import com.google.bigtable.v2.RowFilter.Chain;
3131
import com.google.bigtable.v2.RowSet;
32+
import com.google.cloud.bigtable.examples.proxy.metrics.MetricsImpl;
3233
import com.google.cloud.opentelemetry.metric.GoogleCloudMetricExporter;
3334
import com.google.cloud.opentelemetry.metric.MetricConfiguration;
34-
import com.google.cloud.opentelemetry.resource.GcpResource;
35-
import com.google.cloud.opentelemetry.resource.ResourceTranslator;
3635
import com.google.common.collect.ImmutableList;
3736
import com.google.common.net.PercentEscaper;
3837
import com.google.protobuf.ByteString;
@@ -53,7 +52,6 @@
5352
import io.opentelemetry.api.common.Attributes;
5453
import io.opentelemetry.contrib.gcp.resource.GCPResourceProvider;
5554
import io.opentelemetry.sdk.common.CompletableResultCode;
56-
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
5755
import io.opentelemetry.sdk.metrics.data.MetricData;
5856
import io.opentelemetry.sdk.metrics.export.MetricExporter;
5957
import io.opentelemetry.sdk.metrics.internal.data.ImmutableGaugeData;
@@ -102,7 +100,6 @@ public class Verify implements Callable<Void> {
102100
showDefaultValue = Visibility.ALWAYS)
103101
Endpoint dataEndpoint = Endpoint.create("bigtable.googleapis.com", 443);
104102

105-
106103
Credentials credentials = null;
107104

108105
@Override
@@ -141,7 +138,7 @@ private void checkBigtable(CallCredentials callCredentials, String tableName) {
141138
.setTableName(
142139
String.format(
143140
"projects/%s/instances/%s/tables/%s",
144-
bigtableProjectId, bigtableTableId, bigtableTableId))
141+
bigtableProjectId, bigtableInstanceId, bigtableTableId))
145142
.setRowsLimit(1)
146143
.setRows(
147144
RowSet.newBuilder().addRowKeys(ByteString.copyFromUtf8("some-nonexistent-row")))
@@ -194,7 +191,6 @@ void checkMetrics(Credentials creds) throws IOException {
194191

195192
GCPResourceProvider resourceProvider = new GCPResourceProvider();
196193
Resource resource = Resource.create(resourceProvider.getAttributes());
197-
GcpResource gcpResource = ResourceTranslator.mapResource(resource);
198194

199195
MetricExporter exporter =
200196
GoogleCloudMetricExporter.createWithConfiguration(
@@ -208,10 +204,10 @@ void checkMetrics(Credentials creds) throws IOException {
208204
ImmutableList.of(
209205
ImmutableMetricData.createLongGauge(
210206
resource,
211-
InstrumentationScopeInfo.create("bigtable-proxy"),
212-
"bigtableproxy.presence",
213-
"Number of proxy processes",
214-
"{process}",
207+
MetricsImpl.INSTRUMENTATION_SCOPE_INFO,
208+
MetricsImpl.METRIC_PRESENCE_NAME,
209+
MetricsImpl.METRIC_PRESENCE_DESC,
210+
MetricsImpl.METRIC_PRESENCE_UNIT,
215211
ImmutableGaugeData.create(
216212
ImmutableList.of(
217213
ImmutableLongPointData.create(
@@ -222,6 +218,7 @@ void checkMetrics(Credentials creds) throws IOException {
222218
CompletableResultCode result = exporter.export(metricData);
223219
result.join(1, TimeUnit.MINUTES);
224220

221+
System.out.println("Metrics resource: " + resource);
225222
if (result.isSuccess()) {
226223
System.out.println("Metrics write: OK");
227224
} else {

bigtable/bigtable-proxy/src/main/java/com/google/cloud/bigtable/examples/proxy/metrics/CallLabels.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,16 @@
2929
* A value class to encapsulate call identity.
3030
*
3131
* <p>This call extracts relevant information from request headers and makes it accessible to
32-
* metrics & the upstream client. The primary headers consulted are:</p>
32+
* metrics & the upstream client. The primary headers consulted are:
3333
*
3434
* <ul>
35-
* <li>{@code x-goog-request-params} - contains the resource and app profile id</li>
36-
* <li>{@code x-goog-api-client} - contains the client info of the downstream client</li>
35+
* <li>{@code x-goog-request-params} - contains the resource and app profile id
36+
* <li>{@code x-goog-api-client} - contains the client info of the downstream client
3737
* </ul>
3838
*/
3939
@AutoValue
4040
public abstract class CallLabels {
41-
private static final Key<String> REQUEST_PARAMS =
41+
public static final Key<String> REQUEST_PARAMS =
4242
Key.of("x-goog-request-params", Metadata.ASCII_STRING_MARSHALLER);
4343
private static final Key<String> API_CLIENT =
4444
Key.of("x-goog-api-client", Metadata.ASCII_STRING_MARSHALLER);

bigtable/bigtable-proxy/src/main/java/com/google/cloud/bigtable/examples/proxy/metrics/InstrumentedCallCredentials.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.time.Duration;
2525
import java.util.concurrent.Executor;
2626
import java.util.concurrent.TimeUnit;
27+
import javax.annotation.Nullable;
2728

2829
public class InstrumentedCallCredentials extends CallCredentials
2930
implements InternalMayRequireSpecificExecutor {
@@ -40,7 +41,13 @@ public InstrumentedCallCredentials(CallCredentials inner) {
4041
@Override
4142
public void applyRequestMetadata(
4243
RequestInfo requestInfo, Executor appExecutor, MetadataApplier applier) {
43-
Tracer tracer = Tracer.extractTracerFromCallOptions(requestInfo.getCallOptions());
44+
@Nullable Tracer tracer = Tracer.extractTracerFromCallOptions(requestInfo.getCallOptions());
45+
if (tracer == null) {
46+
applier.fail(
47+
Status.INTERNAL.withDescription(
48+
"InstrumentedCallCredentials failed to extract tracer from CallOptions"));
49+
return;
50+
}
4451
final Stopwatch stopwatch = Stopwatch.createStarted();
4552

4653
inner.applyRequestMetadata(

bigtable/bigtable-proxy/src/main/java/com/google/cloud/bigtable/examples/proxy/metrics/MetricsImpl.java

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import io.opentelemetry.api.metrics.LongUpDownCounter;
2929
import io.opentelemetry.api.metrics.Meter;
3030
import io.opentelemetry.contrib.gcp.resource.GCPResourceProvider;
31+
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
3132
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
3233
import io.opentelemetry.sdk.metrics.export.MetricExporter;
3334
import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;
@@ -39,14 +40,21 @@
3940
import java.util.function.Supplier;
4041

4142
public class MetricsImpl implements Closeable, Metrics {
43+
public static final InstrumentationScopeInfo INSTRUMENTATION_SCOPE_INFO =
44+
InstrumentationScopeInfo.create("bigtable-proxy");
45+
4246
public static final String METRIC_PREFIX = "bigtableproxy.";
4347

44-
static final AttributeKey<String> API_CLIENT_KEY = AttributeKey.stringKey("apiclient");
48+
static final AttributeKey<String> API_CLIENT_KEY = AttributeKey.stringKey("api_client");
4549
static final AttributeKey<String> RESOURCE_KEY = AttributeKey.stringKey("resource");
4650
static final AttributeKey<String> APP_PROFILE_KEY = AttributeKey.stringKey("app_profile");
4751
static final AttributeKey<String> METHOD_KEY = AttributeKey.stringKey("method");
4852
static final AttributeKey<String> STATUS_KEY = AttributeKey.stringKey("status");
4953

54+
public static final String METRIC_PRESENCE_NAME = METRIC_PREFIX + "presence";
55+
public static final String METRIC_PRESENCE_DESC = "Number of proxy processes";
56+
public static final String METRIC_PRESENCE_UNIT = "{process}";
57+
5058
private final SdkMeterProvider meterProvider;
5159

5260
private final DoubleHistogram gfeLatency;
@@ -67,7 +75,11 @@ public class MetricsImpl implements Closeable, Metrics {
6775

6876
public MetricsImpl(Credentials credentials, String projectId) throws IOException {
6977
meterProvider = createMeterProvider(credentials, projectId);
70-
Meter meter = meterProvider.meterBuilder("bigtableproxy").build();
78+
Meter meter =
79+
meterProvider
80+
.meterBuilder(INSTRUMENTATION_SCOPE_INFO.getName())
81+
.setInstrumentationVersion(INSTRUMENTATION_SCOPE_INFO.getVersion())
82+
.build();
7183

7284
serverCallsStarted =
7385
meter
@@ -151,9 +163,9 @@ public MetricsImpl(Credentials credentials, String projectId) throws IOException
151163
.buildWithCallback(o -> o.record(maxSeen.getAndSet(0)));
152164

153165
meter
154-
.gaugeBuilder(METRIC_PREFIX + ".presence")
155-
.setDescription("Number of proxy processes")
156-
.setUnit("{process}")
166+
.gaugeBuilder(METRIC_PRESENCE_NAME)
167+
.setDescription(METRIC_PRESENCE_DESC)
168+
.setUnit(METRIC_PRESENCE_UNIT)
157169
.ofLongs()
158170
.buildWithCallback(o -> o.record(1));
159171
}

bigtable/bigtable-proxy/src/test/java/com/google/cloud/bigtable/examples/proxy/commands/ServeMetricsTest.java

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -98,19 +98,13 @@ public void setUp() throws Exception {
9898

9999
fakeServiceChannel =
100100
grpcCleanup.register(
101-
ManagedChannelBuilder.forAddress("localhost", server.getPort())
102-
.usePlaintext()
103-
.build()
104-
);
101+
ManagedChannelBuilder.forAddress("localhost", server.getPort()).usePlaintext().build());
105102

106103
serve = createAndStartCommand(fakeServiceChannel, fakeCredentials, mockMetrics);
107104

108105
proxyChannel =
109106
grpcCleanup.register(
110-
ManagedChannelBuilder.forAddress("localhost", serve.listenPort)
111-
.usePlaintext()
112-
.build()
113-
);
107+
ManagedChannelBuilder.forAddress("localhost", serve.listenPort).usePlaintext().build());
114108
}
115109

116110
@After

0 commit comments

Comments
 (0)