Skip to content

Commit a0806ee

Browse files
committed
Integration test for End to End tracing
1 parent 6d54c9d commit a0806ee

File tree

3 files changed

+197
-125
lines changed

3 files changed

+197
-125
lines changed

google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestEnv.java

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,26 @@
2222
import com.google.api.client.util.ExponentialBackOff;
2323
import com.google.api.gax.longrunning.OperationFuture;
2424
import com.google.cloud.Timestamp;
25+
import com.google.cloud.opentelemetry.trace.TraceConfiguration;
26+
import com.google.cloud.opentelemetry.trace.TraceExporter;
2527
import com.google.cloud.spanner.DatabaseInfo.DatabaseField;
2628
import com.google.cloud.spanner.testing.EmulatorSpannerHelper;
2729
import com.google.cloud.spanner.testing.RemoteSpannerHelper;
2830
import com.google.common.collect.Iterators;
2931
import com.google.spanner.admin.instance.v1.CreateInstanceMetadata;
32+
import io.opentelemetry.api.GlobalOpenTelemetry;
33+
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
34+
import io.opentelemetry.context.propagation.ContextPropagators;
35+
import io.opentelemetry.sdk.OpenTelemetrySdk;
36+
import io.opentelemetry.sdk.resources.Resource;
37+
import io.opentelemetry.sdk.trace.SdkTracerProvider;
38+
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
39+
import io.opentelemetry.sdk.trace.export.SpanExporter;
40+
import io.opentelemetry.sdk.trace.samplers.Sampler;
3041
import java.util.Objects;
3142
import java.util.Random;
3243
import java.util.concurrent.ExecutionException;
44+
import java.util.concurrent.ThreadLocalRandom;
3345
import java.util.concurrent.TimeUnit;
3446
import java.util.logging.Level;
3547
import java.util.logging.Logger;
@@ -45,7 +57,9 @@
4557
*/
4658
public class IntegrationTestEnv extends ExternalResource {
4759

48-
/** Names a property that provides the class name of the {@link TestEnvConfig} to use. */
60+
/**
61+
* Names a property that provides the class name of the {@link TestEnvConfig} to use.
62+
*/
4963
public static final String TEST_ENV_CONFIG_CLASS_NAME = "spanner.testenv.config.class";
5064

5165
public static final String CONFIG_CLASS = System.getProperty(TEST_ENV_CONFIG_CLASS_NAME, null);
@@ -107,11 +121,10 @@ protected void before() throws Throwable {
107121
assumeFalse(alwaysCreateNewInstance && isCloudDevel());
108122

109123
this.config.setUp();
110-
111-
SpannerOptions options = config.spannerOptions();
124+
// OpenTelemetry with End to End tracing enabled for all integration test env. The grpc stub and connections are created during env set up using SpannerOptions and are reused for executing statements.
125+
SpannerOptions options = spannerOptionsWithEndToEndTracing();
112126
String instanceProperty = System.getProperty(TEST_INSTANCE_PROPERTY, "");
113127
instanceProperty = "projects/span-cloud-testing/instances/manu-demo1-mr-nam6";
114-
System.out.println("Instance Property Name " + instanceProperty);
115128
InstanceId instanceId;
116129
if (!instanceProperty.isEmpty() && !alwaysCreateNewInstance) {
117130
instanceId = InstanceId.of(instanceProperty);
@@ -135,6 +148,37 @@ protected void before() throws Throwable {
135148
}
136149
}
137150

151+
public SpannerOptions spannerOptionsWithEndToEndTracing() {
152+
GlobalOpenTelemetry.resetForTest(); // reset global context for test
153+
assumeFalse("This test requires credentials", EmulatorSpannerHelper.isUsingEmulator());
154+
155+
SpannerOptions options = config.spannerOptions();
156+
TraceConfiguration.Builder traceConfigurationBuilder = TraceConfiguration.builder();
157+
if (options.getCredentials() != null) {
158+
traceConfigurationBuilder.setCredentials(options.getCredentials());
159+
}
160+
SpanExporter traceExporter = TraceExporter.createWithConfiguration(
161+
traceConfigurationBuilder.setProjectId(options.getProjectId()).build());
162+
163+
String serviceName =
164+
"java-spanner-jdbc-integration-tests-" + ThreadLocalRandom.current().nextInt();
165+
SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder()
166+
// Always sample in this test to ensure we know what we get.
167+
.setSampler(Sampler.alwaysOn())
168+
.setResource(Resource.builder().put("service.name", serviceName).build())
169+
.addSpanProcessor(BatchSpanProcessor.builder(traceExporter).build())
170+
.build();
171+
OpenTelemetrySdk openTelemetry =
172+
OpenTelemetrySdk.builder()
173+
.setTracerProvider(sdkTracerProvider)
174+
.setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance()))
175+
.buildAndRegisterGlobal();
176+
SpannerOptions.enableOpenTelemetryTraces();
177+
return options.toBuilder().setOpenTelemetry(openTelemetry).setEnableEndToEndTracing(true)
178+
.build();
179+
}
180+
181+
138182
RemoteSpannerHelper createTestHelper(SpannerOptions options, InstanceId instanceId)
139183
throws Throwable {
140184
return RemoteSpannerHelper.create(options, instanceId);
@@ -220,9 +264,9 @@ static boolean isRetryableResourceExhaustedException(SpannerException exception)
220264
return false;
221265
}
222266
return exception
223-
.getMessage()
224-
.contains(
225-
"Quota exceeded for quota metric 'Instance create requests' and limit 'Instance create requests per minute'")
267+
.getMessage()
268+
.contains(
269+
"Quota exceeded for quota metric 'Instance create requests' and limit 'Instance create requests per minute'")
226270
|| exception.getMessage().matches(".*cannot add \\d+ nodes in region.*");
227271
}
228272

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package com.google.cloud.spanner.it;
2+
3+
import static com.google.common.truth.Truth.assertThat;
4+
import static org.junit.Assert.assertTrue;
5+
6+
import com.google.api.gax.core.FixedCredentialsProvider;
7+
import com.google.api.gax.rpc.ApiException;
8+
import com.google.api.gax.rpc.ResourceExhaustedException;
9+
import com.google.api.gax.rpc.StatusCode;
10+
import com.google.cloud.spanner.Database;
11+
import com.google.cloud.spanner.DatabaseClient;
12+
import com.google.cloud.spanner.IntegrationTestEnv;
13+
import com.google.cloud.spanner.ParallelIntegrationTest;
14+
import com.google.cloud.spanner.ResultSet;
15+
import com.google.cloud.spanner.SpannerOptions;
16+
import com.google.cloud.spanner.SpannerOptionsHelper;
17+
import com.google.cloud.spanner.Statement;
18+
import com.google.cloud.spanner.Struct;
19+
import com.google.cloud.spanner.Type;
20+
import com.google.cloud.spanner.Type.StructField;
21+
import com.google.cloud.spanner.connection.ConnectionOptions;
22+
import com.google.cloud.trace.v1.TraceServiceClient;
23+
import com.google.cloud.trace.v1.TraceServiceSettings;
24+
import com.google.devtools.cloudtrace.v1.Trace;
25+
import io.opentelemetry.api.GlobalOpenTelemetry;
26+
import io.opentelemetry.api.trace.Span;
27+
import io.opentelemetry.api.trace.Tracer;
28+
import io.opentelemetry.context.Scope;
29+
import java.io.IOException;
30+
import org.junit.AfterClass;
31+
import org.junit.Before;
32+
import org.junit.BeforeClass;
33+
import org.junit.ClassRule;
34+
import org.junit.Test;
35+
import org.junit.experimental.categories.Category;
36+
import org.junit.runner.RunWith;
37+
import org.junit.runners.JUnit4;
38+
39+
/**
40+
* Integration tests for End to End Tracing.
41+
*/
42+
@Category(ParallelIntegrationTest.class)
43+
@RunWith(JUnit4.class)
44+
public class ITEndToEndTracingTest {
45+
46+
@ClassRule
47+
public static IntegrationTestEnv env = new IntegrationTestEnv();
48+
private static DatabaseClient googleStandardSQLClient;
49+
private String selectValueQuery;
50+
51+
static {
52+
SpannerOptionsHelper.resetActiveTracingFramework();
53+
SpannerOptions.enableOpenTelemetryMetrics();
54+
SpannerOptions.enableOpenTelemetryTraces();
55+
}
56+
57+
@BeforeClass
58+
public static void setUp() {
59+
setUpDatabase();
60+
}
61+
62+
public static void setUpDatabase() {
63+
// Empty database.
64+
Database googleStandardSQLDatabase = env.getTestHelper().createTestDatabase();
65+
googleStandardSQLClient = env.getTestHelper().getDatabaseClient(googleStandardSQLDatabase);
66+
}
67+
68+
@Before
69+
public void initSelectValueQuery() {
70+
selectValueQuery = "SELECT @p1 + @p1 ";
71+
}
72+
73+
@AfterClass
74+
public static void teardown() {
75+
ConnectionOptions.closeSpanner();
76+
}
77+
78+
private void assertTrace(String spanName, String traceid)
79+
throws IOException, InterruptedException {
80+
TraceServiceSettings settings =
81+
env.getTestHelper().getOptions().getCredentials() == null
82+
? TraceServiceSettings.newBuilder().build()
83+
: TraceServiceSettings.newBuilder()
84+
.setCredentialsProvider(
85+
FixedCredentialsProvider.create(
86+
env.getTestHelper().getOptions().getCredentials()))
87+
.build();
88+
try (TraceServiceClient client = TraceServiceClient.create(settings)) {
89+
// It can take a few seconds before the trace is visible.
90+
Thread.sleep(5000L);
91+
boolean foundTrace = false;
92+
for (int attempts = 0; attempts < 2; attempts++) {
93+
try {
94+
Trace clientTrace = client.getTrace(env.getTestHelper().getInstanceId().getProject(),
95+
traceid);
96+
// Assert Spanner Frontend Trace is present
97+
assertTrue(clientTrace.getSpansList().stream().anyMatch(
98+
span -> "CloudSpannerOperation.ExecuteStreamingQuery".equals(span.getName())));
99+
foundTrace = true;
100+
break;
101+
} catch (ApiException apiException) {
102+
assertThat(apiException.getStatusCode().getCode()).isEqualTo(StatusCode.Code.NOT_FOUND);
103+
Thread.sleep(5000L);
104+
}
105+
}
106+
assertTrue(foundTrace);
107+
} catch (ResourceExhaustedException resourceExhaustedException) {
108+
if (resourceExhaustedException
109+
.getMessage()
110+
.contains("Quota exceeded for quota metric 'Read requests (free)'")) {
111+
// Ignore and allow the test to succeed.
112+
System.out.println("RESOURCE_EXHAUSTED error ignored");
113+
} else {
114+
throw resourceExhaustedException;
115+
}
116+
}
117+
}
118+
119+
private Struct executeWithRowResultType(Statement statement, Type expectedRowType) {
120+
ResultSet resultSet =
121+
statement.executeQuery(googleStandardSQLClient.singleUse());
122+
assertThat(resultSet.next()).isTrue();
123+
assertThat(resultSet.getType()).isEqualTo(expectedRowType);
124+
Struct row = resultSet.getCurrentRowAsStruct();
125+
assertThat(resultSet.next()).isFalse();
126+
return row;
127+
}
128+
129+
@Test
130+
public void simpleSelect() throws IOException, InterruptedException {
131+
Tracer tracer = GlobalOpenTelemetry.getTracer(ITEndToEndTracingTest.class.getName());
132+
String spanName = "simpleSelect";
133+
Span span = tracer.spanBuilder(spanName).startSpan();
134+
Scope scope = span.makeCurrent();
135+
Type rowType = Type.struct(StructField.of("", Type.int64()));
136+
Struct row = executeWithRowResultType(
137+
Statement.newBuilder(selectValueQuery).bind("p1").to(1234).build(), rowType);
138+
assertThat(row.isNull(0)).isFalse();
139+
assertThat(row.getLong(0)).isEqualTo(2468);
140+
scope.close();
141+
span.end();
142+
assertTrace(spanName, span.getSpanContext().getTraceId());
143+
}
144+
}

0 commit comments

Comments
 (0)