Skip to content

feat: Integration test for End to End tracing #3691

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

Merged
merged 21 commits into from
Mar 28, 2025
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6d54c9d
Enable End to End tracing in Integration Test
manu2 Mar 12, 2025
a0806ee
Integration test for End to End tracing
manu2 Mar 18, 2025
00a1735
Integration test for End to End tracing
manu2 Mar 18, 2025
a45ca7e
Merge branch 'googleapis:main' into main
manu2 Mar 18, 2025
680ce7f
Integration test for End to End tracing
manu2 Mar 18, 2025
11baac3
Integration test for End to End tracing
manu2 Mar 18, 2025
8f12e88
Fix errors in Integration test for End to End tracing
manu2 Mar 18, 2025
8bc559d
Fix errors in Integration test for End to End tracing
manu2 Mar 18, 2025
9f81dbb
Add TestEnvOptions to enable End to End tracing for the new Integrati…
manu2 Mar 18, 2025
19eee1c
Fix ITEndToEndTracingTest error on direct path build
manu2 Mar 18, 2025
646a6b9
Skip ITEndToEndTracingTest on permission denied error for error in di…
manu2 Mar 19, 2025
d752823
Assert server side span name
manu2 Mar 21, 2025
74b901d
Increase delay before trace fetch.
manu2 Mar 25, 2025
9c1cd08
Remove update to GlobalTelemetry. Use opentelemetrysdk created in test.
manu2 Mar 25, 2025
8ec9109
Merge branch 'googleapis:main' into main
manu2 Mar 25, 2025
eb25e67
Update retry logic for trace retrieval.
manu2 Mar 27, 2025
0611d07
Merge branch 'main' into main
surbhigarg92 Mar 28, 2025
66856a8
Remove version number from opentelemetry exporter-trace dependency
manu2 Mar 28, 2025
9f18bf7
Merge branch 'googleapis:main' into main
manu2 Mar 28, 2025
e7f3109
Revert "Remove version number from opentelemetry exporter-trace depen…
manu2 Mar 28, 2025
7e42c8c
Merge branch 'googleapis:main' into main
manu2 Mar 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions google-cloud-spanner/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,24 @@
<artifactId>opentelemetry-sdk-testing</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.cloud.opentelemetry</groupId>
<artifactId>exporter-trace</artifactId>
<version>0.33.0</version>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove the version from here. It should either fetch from bom or the latest version

<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-trace</artifactId>
<version>2.51.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.api.grpc</groupId>
<artifactId>proto-google-cloud-trace-v1</artifactId>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are we adding this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We query trace using trace client which requires this dependency.
" trace = client.getTrace(env.getTestHelper().getInstanceId().getProject(), traceId)"

<version>2.51.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<profile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,27 @@
import com.google.api.client.util.ExponentialBackOff;
import com.google.api.gax.longrunning.OperationFuture;
import com.google.cloud.Timestamp;
import com.google.cloud.opentelemetry.trace.TraceConfiguration;
import com.google.cloud.opentelemetry.trace.TraceExporter;
import com.google.cloud.spanner.DatabaseInfo.DatabaseField;
import com.google.cloud.spanner.testing.EmulatorSpannerHelper;
import com.google.cloud.spanner.testing.RemoteSpannerHelper;
import com.google.common.collect.Iterators;
import com.google.spanner.admin.instance.v1.CreateInstanceMetadata;
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand Down Expand Up @@ -67,10 +80,22 @@ public class IntegrationTestEnv extends ExternalResource {
private final boolean alwaysCreateNewInstance;
private RemoteSpannerHelper testHelper;

private Collection<TestEnvOptions> testEnvOptions = Collections.emptyList();

public enum TestEnvOptions {
USE_END_TO_END_TRACING;
// TODO : Move alwaysCreateNewInstance to TestEnvOptions
}

public IntegrationTestEnv() {
this(false);
}

public IntegrationTestEnv(Collection<TestEnvOptions> testEnvOptions) {
this(false);
this.testEnvOptions = testEnvOptions;
}

public IntegrationTestEnv(final boolean alwaysCreateNewInstance) {
this.alwaysCreateNewInstance = alwaysCreateNewInstance;
}
Expand Down Expand Up @@ -107,8 +132,15 @@ protected void before() throws Throwable {
assumeFalse(alwaysCreateNewInstance && isCloudDevel());

this.config.setUp();

SpannerOptions options = config.spannerOptions();
if (testEnvOptions.stream()
.anyMatch(testEnvOption -> TestEnvOptions.USE_END_TO_END_TRACING.equals(testEnvOption))) {
// OpenTelemetry set up for enabling End to End tracing for all integration test env.
// The gRPC stub and connections are created during test env set up using SpannerOptions and
// are
// reused for executing statements.
options = spannerOptionsWithEndToEndTracing(options);
}
String instanceProperty = System.getProperty(TEST_INSTANCE_PROPERTY, "");
InstanceId instanceId;
if (!instanceProperty.isEmpty() && !alwaysCreateNewInstance) {
Expand All @@ -133,6 +165,38 @@ protected void before() throws Throwable {
}
}

public SpannerOptions spannerOptionsWithEndToEndTracing(SpannerOptions options) {
assumeFalse("This test requires credentials", EmulatorSpannerHelper.isUsingEmulator());

TraceConfiguration.Builder traceConfigurationBuilder = TraceConfiguration.builder();
if (options.getCredentials() != null) {
traceConfigurationBuilder.setCredentials(options.getCredentials());
}
SpanExporter traceExporter =
TraceExporter.createWithConfiguration(
traceConfigurationBuilder.setProjectId(options.getProjectId()).build());

String serviceName = "java-spanner-integration-tests-" + ThreadLocalRandom.current().nextInt();
SdkTracerProvider sdkTracerProvider =
SdkTracerProvider.builder()
// Always sample in this test to ensure we know what we get.
.setSampler(Sampler.alwaysOn())
.setResource(Resource.builder().put("service.name", serviceName).build())
.addSpanProcessor(BatchSpanProcessor.builder(traceExporter).build())
.build();
OpenTelemetrySdk openTelemetry =
OpenTelemetrySdk.builder()
.setTracerProvider(sdkTracerProvider)
.setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance()))
.build();
SpannerOptions.enableOpenTelemetryTraces();
return options
.toBuilder()
.setOpenTelemetry(openTelemetry)
.setEnableEndToEndTracing(true)
.build();
}

RemoteSpannerHelper createTestHelper(SpannerOptions options, InstanceId instanceId)
throws Throwable {
return RemoteSpannerHelper.create(options, instanceId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* Copyright 2025 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
*
* http://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.it;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;

import com.google.api.gax.core.FixedCredentialsProvider;
import com.google.api.gax.rpc.ApiException;
import com.google.api.gax.rpc.ResourceExhaustedException;
import com.google.api.gax.rpc.StatusCode;
import com.google.cloud.spanner.Database;
import com.google.cloud.spanner.DatabaseClient;
import com.google.cloud.spanner.IntegrationTestEnv;
import com.google.cloud.spanner.IntegrationTestEnv.TestEnvOptions;
import com.google.cloud.spanner.ParallelIntegrationTest;
import com.google.cloud.spanner.ResultSet;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.SpannerOptionsHelper;
import com.google.cloud.spanner.Statement;
import com.google.cloud.spanner.Struct;
import com.google.cloud.spanner.Type;
import com.google.cloud.spanner.Type.StructField;
import com.google.cloud.spanner.connection.ConnectionOptions;
import com.google.cloud.trace.v1.TraceServiceClient;
import com.google.cloud.trace.v1.TraceServiceSettings;
import com.google.common.base.Stopwatch;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/** Integration tests for End to End Tracing. */
@Category(ParallelIntegrationTest.class)
@RunWith(JUnit4.class)
public class ITEndToEndTracingTest {
public static Collection<TestEnvOptions> testEnvOptions =
Arrays.asList(TestEnvOptions.USE_END_TO_END_TRACING);
@ClassRule public static IntegrationTestEnv env = new IntegrationTestEnv(testEnvOptions);
private static DatabaseClient googleStandardSQLClient;

static {
SpannerOptionsHelper.resetActiveTracingFramework();
SpannerOptions.enableOpenTelemetryTraces();
}

private static String selectValueQuery = "SELECT @p1 + @p1";

@BeforeClass
public static void setUp() {
setUpDatabase();
}

public static void setUpDatabase() {
// Empty database.
Database googleStandardSQLDatabase = env.getTestHelper().createTestDatabase();
googleStandardSQLClient = env.getTestHelper().getDatabaseClient(googleStandardSQLDatabase);
}

@AfterClass
public static void teardown() {
ConnectionOptions.closeSpanner();
}

private void assertTrace(String traceId) throws IOException, InterruptedException {
TraceServiceSettings settings =
env.getTestHelper().getOptions().getCredentials() == null
? TraceServiceSettings.newBuilder().build()
: TraceServiceSettings.newBuilder()
.setCredentialsProvider(
FixedCredentialsProvider.create(
env.getTestHelper().getOptions().getCredentials()))
.build();
try (TraceServiceClient client = TraceServiceClient.create(settings)) {
boolean foundTrace = false;
Stopwatch metricsPollingStopwatch = Stopwatch.createStarted();
while (!foundTrace && metricsPollingStopwatch.elapsed(TimeUnit.SECONDS) < 30) {
// Try every 5 seconds
Thread.sleep(5000);
try {
foundTrace =
client.getTrace(env.getTestHelper().getInstanceId().getProject(), traceId)
.getSpansList().stream()
.anyMatch(span -> "Spanner.ExecuteStreamingSql".equals(span.getName()));
} catch (ApiException apiException) {
assumeTrue(
apiException.getStatusCode() != null
&& StatusCode.Code.NOT_FOUND.equals(apiException.getStatusCode().getCode()));
System.out.println("Trace NOT_FOUND error ignored");
}
}
assertTrue(foundTrace);
} catch (ResourceExhaustedException resourceExhaustedException) {
if (resourceExhaustedException
.getMessage()
.contains("Quota exceeded for quota metric 'Read requests (free)'")) {
// Ignore and allow the test to succeed.
System.out.println("RESOURCE_EXHAUSTED error ignored");
} else {
throw resourceExhaustedException;
}
}
}

private Struct executeWithRowResultType(Statement statement, Type expectedRowType) {
ResultSet resultSet = statement.executeQuery(googleStandardSQLClient.singleUse());
assertThat(resultSet.next()).isTrue();
assertThat(resultSet.getType()).isEqualTo(expectedRowType);
Struct row = resultSet.getCurrentRowAsStruct();
assertThat(resultSet.next()).isFalse();
return row;
}

@Test
public void simpleSelect() throws IOException, InterruptedException {
Tracer tracer =
env.getTestHelper()
.getOptions()
.getOpenTelemetry()
.getTracer(ITEndToEndTracingTest.class.getName());
Span span = tracer.spanBuilder("simpleSelect").startSpan();
Scope scope = span.makeCurrent();
Type rowType = Type.struct(StructField.of("", Type.int64()));
Struct row =
executeWithRowResultType(
Statement.newBuilder(selectValueQuery).bind("p1").to(1234).build(), rowType);
assertThat(row.isNull(0)).isFalse();
assertThat(row.getLong(0)).isEqualTo(2468);
scope.close();
span.end();
assertTrace(span.getSpanContext().getTraceId());
}
}
Loading