diff --git a/google-cloud-spanner/clirr-ignored-differences.xml b/google-cloud-spanner/clirr-ignored-differences.xml index db1526835e2..8e7a392302a 100644 --- a/google-cloud-spanner/clirr-ignored-differences.xml +++ b/google-cloud-spanner/clirr-ignored-differences.xml @@ -695,6 +695,13 @@ boolean isEnableApiTracing() + + + 7012 + com/google/cloud/spanner/SpannerOptions$SpannerEnvironment + boolean isEnableEndToEndTracing() + + 7012 diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java index 3bfa3ee4069..5756ff64b89 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java @@ -163,6 +163,7 @@ public class SpannerOptions extends ServiceOptions { private final boolean enableApiTracing; private final boolean enableBuiltInMetrics; private final boolean enableExtendedTracing; + private final boolean enableEndToEndTracing; enum TracingFramework { OPEN_CENSUS, @@ -670,6 +671,7 @@ protected SpannerOptions(Builder builder) { enableApiTracing = builder.enableApiTracing; enableExtendedTracing = builder.enableExtendedTracing; enableBuiltInMetrics = builder.enableBuiltInMetrics; + enableEndToEndTracing = builder.enableEndToEndTracing; } /** @@ -706,6 +708,10 @@ default boolean isEnableApiTracing() { default boolean isEnableBuiltInMetrics() { return false; } + + default boolean isEnableEndToEndTracing() { + return false; + } } /** @@ -720,6 +726,8 @@ private static class SpannerEnvironmentImpl implements SpannerEnvironment { private static final String SPANNER_ENABLE_EXTENDED_TRACING = "SPANNER_ENABLE_EXTENDED_TRACING"; private static final String SPANNER_ENABLE_API_TRACING = "SPANNER_ENABLE_API_TRACING"; private static final String SPANNER_ENABLE_BUILTIN_METRICS = "SPANNER_ENABLE_BUILTIN_METRICS"; + private static final String SPANNER_ENABLE_END_TO_END_TRACING = + "SPANNER_ENABLE_END_TO_END_TRACING"; private SpannerEnvironmentImpl() {} @@ -752,6 +760,11 @@ public boolean isEnableBuiltInMetrics() { // removed in the future. return Boolean.parseBoolean(System.getenv(SPANNER_ENABLE_BUILTIN_METRICS)); } + + @Override + public boolean isEnableEndToEndTracing() { + return Boolean.parseBoolean(System.getenv(SPANNER_ENABLE_END_TO_END_TRACING)); + } } /** Builder for {@link SpannerOptions} instances. */ @@ -816,6 +829,7 @@ public static class Builder private boolean enableApiTracing = SpannerOptions.environment.isEnableApiTracing(); private boolean enableExtendedTracing = SpannerOptions.environment.isEnableExtendedTracing(); private boolean enableBuiltInMetrics = SpannerOptions.environment.isEnableBuiltInMetrics(); + private boolean enableEndToEndTracing = SpannerOptions.environment.isEnableEndToEndTracing(); private static String createCustomClientLibToken(String token) { return token + " " + ServiceOptions.getGoogApiClientLibName(); @@ -882,6 +896,7 @@ protected Builder() { this.enableApiTracing = options.enableApiTracing; this.enableExtendedTracing = options.enableExtendedTracing; this.enableBuiltInMetrics = options.enableBuiltInMetrics; + this.enableEndToEndTracing = options.enableEndToEndTracing; } @Override @@ -1415,6 +1430,17 @@ public Builder setEnableExtendedTracing(boolean enableExtendedTracing) { return this; } + /** + * Sets whether to enable end to end tracing. Enabling this option will create the trace spans + * at the Spanner layer. By default, end to end tracing is disabled. Enabling end to end tracing + * requires OpenTelemetry to be set up. Simply enabling this option won't generate traces at + * Spanner layer. + */ + public Builder setEnableEndToEndTracing(boolean enableEndToEndTracing) { + this.enableEndToEndTracing = enableEndToEndTracing; + return this; + } + @SuppressWarnings("rawtypes") @Override public SpannerOptions build() { @@ -1504,6 +1530,7 @@ public static void enableOpenCensusTraces() { */ @ObsoleteApi( "The OpenCensus project is deprecated. Use enableOpenTelemetryTraces to switch to OpenTelemetry traces") + @VisibleForTesting static void resetActiveTracingFramework() { activeTracingFramework = null; } @@ -1745,6 +1772,14 @@ public boolean isEnableExtendedTracing() { return enableExtendedTracing; } + /** + * Returns whether end to end tracing is enabled. If this option is enabled then trace spans will + * be created at the Spanner layer. + */ + public boolean isEndToEndTracingEnabled() { + return enableEndToEndTracing; + } + /** Returns the default query options to use for the specific database. */ public QueryOptions getDefaultQueryOptions(DatabaseId databaseId) { // Use the specific query options for the database if any have been specified. These have diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java index b389ea6a31a..2360b5d5173 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java @@ -272,6 +272,7 @@ public class GapicSpannerRpc implements SpannerRpc { private static final ConcurrentMap ADMINISTRATIVE_REQUESTS_RATE_LIMITERS = new ConcurrentHashMap<>(); private final boolean leaderAwareRoutingEnabled; + private final boolean endToEndTracingEnabled; private final int numChannels; private final boolean isGrpcGcpExtensionEnabled; @@ -325,6 +326,7 @@ public GapicSpannerRpc(final SpannerOptions options) { this.callCredentialsProvider = options.getCallCredentialsProvider(); this.compressorName = options.getCompressorName(); this.leaderAwareRoutingEnabled = options.isLeaderAwareRoutingEnabled(); + this.endToEndTracingEnabled = options.isEndToEndTracingEnabled(); this.numChannels = options.getNumChannels(); this.isGrpcGcpExtensionEnabled = options.isGrpcGcpExtensionEnabled(); @@ -350,6 +352,8 @@ public GapicSpannerRpc(final SpannerOptions options) { MoreObjects.firstNonNull( options.getInterceptorProvider(), SpannerInterceptorProvider.createDefault(options.getOpenTelemetry()))) + // This sets the trace context headers. + .withTraceContext(endToEndTracingEnabled, options.getOpenTelemetry()) // This sets the response compressor (Server -> Client). .withEncoding(compressorName)) .setHeaderProvider(headerProviderWithUserAgent) @@ -2007,6 +2011,9 @@ GrpcCallContext newCallContext( if (routeToLeader && leaderAwareRoutingEnabled) { context = context.withExtraHeaders(metadataProvider.newRouteToLeaderHeader()); } + if (endToEndTracingEnabled) { + context = context.withExtraHeaders(metadataProvider.newEndToEndTracingHeader()); + } if (callCredentialsProvider != null) { CallCredentials callCredentials = callCredentialsProvider.getCallCredentials(); if (callCredentials != null) { diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerInterceptorProvider.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerInterceptorProvider.java index 9b1a2fd3c1f..b4d28ef0789 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerInterceptorProvider.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerInterceptorProvider.java @@ -74,6 +74,14 @@ SpannerInterceptorProvider withEncoding(String encoding) { return this; } + SpannerInterceptorProvider withTraceContext( + boolean endToEndTracingEnabled, OpenTelemetry openTelemetry) { + if (endToEndTracingEnabled) { + return with(new TraceContextInterceptor(openTelemetry)); + } + return this; + } + @Override public List getInterceptors() { return clientInterceptors; diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerMetadataProvider.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerMetadataProvider.java index 0b8d76d52df..2ebc4925788 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerMetadataProvider.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerMetadataProvider.java @@ -37,6 +37,7 @@ class SpannerMetadataProvider { private final Map, String> headers; private final String resourceHeaderKey; private static final String ROUTE_TO_LEADER_HEADER_KEY = "x-goog-spanner-route-to-leader"; + private static final String END_TO_END_TRACING_HEADER_KEY = "x-goog-spanner-end-to-end-tracing"; private static final Pattern[] RESOURCE_TOKEN_PATTERNS = { Pattern.compile("^(?projects/[^/]*/instances/[^/]*/databases/[^/]*)(.*)?"), Pattern.compile("^(?projects/[^/]*/instances/[^/]*)(.*)?") @@ -44,6 +45,8 @@ class SpannerMetadataProvider { private static final Map> ROUTE_TO_LEADER_HEADER_MAP = ImmutableMap.of(ROUTE_TO_LEADER_HEADER_KEY, Collections.singletonList("true")); + private static final Map> END_TO_END_TRACING_HEADER_MAP = + ImmutableMap.of(END_TO_END_TRACING_HEADER_KEY, Collections.singletonList("true")); private SpannerMetadataProvider(Map headers, String resourceHeaderKey) { this.resourceHeaderKey = resourceHeaderKey; @@ -89,6 +92,10 @@ Map> newRouteToLeaderHeader() { return ROUTE_TO_LEADER_HEADER_MAP; } + Map> newEndToEndTracingHeader() { + return END_TO_END_TRACING_HEADER_MAP; + } + private Map, String> constructHeadersAsMetadata( Map headers) { ImmutableMap.Builder, String> headersAsMetadataBuilder = diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/TraceContextInterceptor.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/TraceContextInterceptor.java new file mode 100644 index 00000000000..3b46ba4f880 --- /dev/null +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/TraceContextInterceptor.java @@ -0,0 +1,73 @@ +/* + * Copyright 2024 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.spanner.spi.v1; + +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.ForwardingClientCall.SimpleForwardingClientCall; +import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.context.propagation.TextMapSetter; + +/** + * Intercepts all gRPC calls and injects trace context related headers to propagate trace context to + * Spanner. This class takes reference from OpenTelemetry's JAVA instrumentation library for gRPC. + * https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/9ecf7965aa455d41ea8cc0761b6c6b6eeb106324/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/TracingClientInterceptor.java#L27 + */ +class TraceContextInterceptor implements ClientInterceptor { + + private final TextMapPropagator textMapPropagator; + + TraceContextInterceptor(OpenTelemetry openTelemetry) { + this.textMapPropagator = openTelemetry.getPropagators().getTextMapPropagator(); + } + + enum MetadataSetter implements TextMapSetter { + INSTANCE; + + @SuppressWarnings("null") + @Override + public void set(Metadata carrier, String key, String value) { + carrier.put(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER), value); + } + } + + private static final class NoopSimpleForwardingClientCallListener + extends SimpleForwardingClientCallListener { + public NoopSimpleForwardingClientCallListener(ClientCall.Listener responseListener) { + super(responseListener); + } + } + + @Override + public ClientCall interceptCall( + MethodDescriptor method, CallOptions callOptions, Channel next) { + return new SimpleForwardingClientCall(next.newCall(method, callOptions)) { + @Override + public void start(Listener responseListener, Metadata headers) { + Context parentContext = Context.current(); + textMapPropagator.inject(parentContext, headers, MetadataSetter.INSTANCE); + super.start(new NoopSimpleForwardingClientCallListener(responseListener), headers); + } + }; + } +} diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsHelper.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsHelper.java new file mode 100644 index 00000000000..db02c625099 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsHelper.java @@ -0,0 +1,29 @@ +/* + * Copyright 2024 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.spanner; + +/** Helper to configure SpannerOptions for tests. */ +public class SpannerOptionsHelper { + + /** + * Resets the activeTracingFramework. This variable is used for internal testing, and is not a + * valid production scenario. + */ + public static void resetActiveTracingFramework() { + SpannerOptions.resetActiveTracingFramework(); + } +} diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java index 73455f06688..e8421cd235c 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java @@ -736,6 +736,24 @@ public void testLeaderAwareRoutingEnablement() { .isLeaderAwareRoutingEnabled()); } + @Test + public void testEndToEndTracingEnablement() { + // Test that end to end tracing is disabled by default. + assertFalse(SpannerOptions.newBuilder().setProjectId("p").build().isEndToEndTracingEnabled()); + assertTrue( + SpannerOptions.newBuilder() + .setProjectId("p") + .setEnableEndToEndTracing(true) + .build() + .isEndToEndTracingEnabled()); + assertFalse( + SpannerOptions.newBuilder() + .setProjectId("p") + .setEnableEndToEndTracing(false) + .build() + .isEndToEndTracingEnabled()); + } + @Test public void testSetDirectedReadOptions() { final DirectedReadOptions directedReadOptions = diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpcTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpcTest.java index 42a07ed9ea6..b3ff3b8f1c2 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpcTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpcTest.java @@ -47,6 +47,7 @@ import com.google.cloud.spanner.SpannerExceptionFactory; import com.google.cloud.spanner.SpannerOptions; import com.google.cloud.spanner.SpannerOptions.CallContextConfigurator; +import com.google.cloud.spanner.SpannerOptionsHelper; import com.google.cloud.spanner.Statement; import com.google.cloud.spanner.TransactionRunner; import com.google.cloud.spanner.spi.v1.GapicSpannerRpc.AdminRequestsLimitExceededRetryAlgorithm; @@ -76,6 +77,12 @@ import io.grpc.auth.MoreCallCredentials; import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder; import io.grpc.protobuf.lite.ProtoLiteUtils; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.samplers.Sampler; import java.io.IOException; import java.net.InetSocketAddress; import java.util.HashMap; @@ -148,6 +155,8 @@ public class GapicSpannerRpcTest { private static String defaultUserAgent; private static Spanner spanner; private static boolean isRouteToLeader; + private static boolean isEndToEndTracing; + private static boolean isTraceContextPresent; @Parameter public Dialect dialect; @@ -158,6 +167,10 @@ public static Object[] data() { @Before public void startServer() throws IOException { + // Enable OpenTelemetry tracing. + SpannerOptionsHelper.resetActiveTracingFramework(); + SpannerOptions.enableOpenTelemetryTraces(); + assumeTrue( "Skip tests when emulator is enabled as this test interferes with the check whether the emulator is running", System.getenv("SPANNER_EMULATOR_HOST") == null); @@ -194,13 +207,23 @@ public ServerCall.Listener interceptCall( if (call.getMethodDescriptor() .equals(SpannerGrpc.getExecuteStreamingSqlMethod()) || call.getMethodDescriptor().equals(SpannerGrpc.getExecuteSqlMethod())) { + String traceParentHeader = + headers.get(Key.of("traceparent", Metadata.ASCII_STRING_MARSHALLER)); + isTraceContextPresent = (traceParentHeader != null); String routeToLeaderHeader = headers.get( Key.of( "x-goog-spanner-route-to-leader", Metadata.ASCII_STRING_MARSHALLER)); + String endToEndTracingHeader = + headers.get( + Key.of( + "x-goog-spanner-end-to-end-tracing", + Metadata.ASCII_STRING_MARSHALLER)); isRouteToLeader = (routeToLeaderHeader != null && routeToLeaderHeader.equals("true")); + isEndToEndTracing = + (endToEndTracingHeader != null && endToEndTracingHeader.equals("true")); } return Contexts.interceptCall(Context.current(), call, headers, next); } @@ -224,6 +247,8 @@ public void reset() throws InterruptedException { server.awaitTermination(); } isRouteToLeader = false; + isEndToEndTracing = false; + isTraceContextPresent = false; } @Test @@ -464,6 +489,83 @@ public void testNewCallContextWithRouteToLeaderHeaderAndLarDisabled() { rpc.shutdown(); } + @Test + public void testNewCallContextWithEndToEndTracingHeader() { + SpannerOptions options = + SpannerOptions.newBuilder() + .setProjectId("some-project") + .setEnableEndToEndTracing(true) + .build(); + GapicSpannerRpc rpc = new GapicSpannerRpc(options, false); + GrpcCallContext callContext = + rpc.newCallContext( + optionsMap, + "/some/resource", + ExecuteSqlRequest.getDefaultInstance(), + SpannerGrpc.getExecuteSqlMethod()); + assertNotNull(callContext); + assertEquals( + ImmutableList.of("true"), + callContext.getExtraHeaders().get("x-goog-spanner-end-to-end-tracing")); + assertEquals( + ImmutableList.of("projects/some-project"), + callContext.getExtraHeaders().get(ApiClientHeaderProvider.getDefaultResourceHeaderKey())); + rpc.shutdown(); + } + + @Test + public void testNewCallContextWithoutEndToEndTracingHeader() { + SpannerOptions options = + SpannerOptions.newBuilder() + .setProjectId("some-project") + .setEnableEndToEndTracing(false) + .build(); + GapicSpannerRpc rpc = new GapicSpannerRpc(options, false); + GrpcCallContext callContext = + rpc.newCallContext( + optionsMap, + "/some/resource", + ExecuteSqlRequest.getDefaultInstance(), + SpannerGrpc.getExecuteSqlMethod()); + assertNotNull(callContext); + assertNull(callContext.getExtraHeaders().get("x-goog-spanner-end-to-end-tracing")); + rpc.shutdown(); + } + + @Test + public void testEndToEndTracingHeaderWithEnabledTracing() { + final SpannerOptions options = + createSpannerOptions().toBuilder().setEnableEndToEndTracing(true).build(); + try (Spanner spanner = options.getService()) { + final DatabaseClient databaseClient = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + TransactionRunner runner = databaseClient.readWriteTransaction(); + runner.run( + transaction -> { + transaction.executeUpdate(UPDATE_FOO_STATEMENT); + return null; + }); + } + assertTrue(isEndToEndTracing); + } + + @Test + public void testEndToEndTracingHeaderWithDisabledTracing() { + final SpannerOptions options = + createSpannerOptions().toBuilder().setEnableEndToEndTracing(false).build(); + try (Spanner spanner = options.getService()) { + final DatabaseClient databaseClient = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + TransactionRunner runner = databaseClient.readWriteTransaction(); + runner.run( + transaction -> { + transaction.executeUpdate(UPDATE_FOO_STATEMENT); + return null; + }); + } + assertFalse(isEndToEndTracing); + } + @Test public void testAdminRequestsLimitExceededRetryAlgorithm() { AdminRequestsLimitExceededRetryAlgorithm alg = @@ -535,6 +637,73 @@ public void testCustomUserAgent() { } } + @Test + public void testTraceContextHeaderWithOpenTelemetryAndEndToEndTracingEnabled() { + OpenTelemetry openTelemetry = + OpenTelemetrySdk.builder() + .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) + .setTracerProvider(SdkTracerProvider.builder().setSampler(Sampler.alwaysOn()).build()) + .build(); + + final SpannerOptions options = + createSpannerOptions() + .toBuilder() + .setOpenTelemetry(openTelemetry) + .setEnableEndToEndTracing(true) + .build(); + try (Spanner spanner = options.getService()) { + final DatabaseClient databaseClient = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + + try (final ResultSet rs = databaseClient.singleUse().executeQuery(SELECT1AND2)) { + rs.next(); + } + + assertTrue(isTraceContextPresent); + } + } + + @Test + public void testTraceContextHeaderWithOpenTelemetryAndEndToEndTracingDisabled() { + OpenTelemetry openTelemetry = + OpenTelemetrySdk.builder() + .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) + .setTracerProvider(SdkTracerProvider.builder().setSampler(Sampler.alwaysOn()).build()) + .build(); + + final SpannerOptions options = + createSpannerOptions() + .toBuilder() + .setOpenTelemetry(openTelemetry) + .setEnableEndToEndTracing(false) + .build(); + try (Spanner spanner = options.getService()) { + final DatabaseClient databaseClient = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + + try (final ResultSet rs = databaseClient.singleUse().executeQuery(SELECT1AND2)) { + rs.next(); + } + + assertFalse(isTraceContextPresent); + } + } + + @Test + public void testTraceContextHeaderWithoutOpenTelemetry() { + final SpannerOptions options = createSpannerOptions(); + try (Spanner spanner = options.getService()) { + final DatabaseClient databaseClient = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + + try (final ResultSet rs = databaseClient.singleUse().executeQuery(SELECT1AND2)) { + rs.next(); + } + + assertFalse(isTraceContextPresent); + } + } + @Test public void testRouteToLeaderHeaderForReadOnly() { final SpannerOptions options = diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/SpannerMetadataProviderTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/SpannerMetadataProviderTest.java index cc43e2dc334..c4fdd6200af 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/SpannerMetadataProviderTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/SpannerMetadataProviderTest.java @@ -94,6 +94,17 @@ public void testNewRouteToLeaderHeader() { assertTrue(Maps.difference(extraHeaders, expectedHeaders).areEqual()); } + @Test + public void testNewEndToEndTracingHeader() { + SpannerMetadataProvider metadataProvider = + SpannerMetadataProvider.create(ImmutableMap.of(), "header1"); + Map> extraHeaders = metadataProvider.newEndToEndTracingHeader(); + Map> expectedHeaders = + ImmutableMap.>of( + "x-goog-spanner-end-to-end-tracing", ImmutableList.of("true")); + assertTrue(Maps.difference(extraHeaders, expectedHeaders).areEqual()); + } + private String getResourceHeaderValue( SpannerMetadataProvider headerProvider, String resourceTokenTemplate) { Metadata metadata = headerProvider.newMetadata(resourceTokenTemplate, "projects/p");