Skip to content

Commit d169a5d

Browse files
authored
interop-test: add opentelemetry tracing context propagation test (#11538)
1 parent fa18fec commit d169a5d

File tree

2 files changed

+192
-0
lines changed

2 files changed

+192
-0
lines changed

interop-testing/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ dependencies {
1313
implementation project(path: ':grpc-alts', configuration: 'shadow'),
1414
project(':grpc-auth'),
1515
project(':grpc-census'),
16+
project(':grpc-opentelemetry'),
1617
project(':grpc-gcp-csm-observability'),
1718
project(':grpc-netty'),
1819
project(':grpc-okhttp'),
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
/*
2+
* Copyright 2024 The gRPC Authors
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+
* http://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+
17+
package io.grpc.testing.integration;
18+
19+
import static org.junit.Assert.assertEquals;
20+
21+
import io.grpc.ForwardingServerCallListener;
22+
import io.grpc.InsecureServerCredentials;
23+
import io.grpc.ManagedChannelBuilder;
24+
import io.grpc.Metadata;
25+
import io.grpc.ServerBuilder;
26+
import io.grpc.ServerCall;
27+
import io.grpc.ServerCallHandler;
28+
import io.grpc.ServerInterceptor;
29+
import io.grpc.netty.InternalNettyChannelBuilder;
30+
import io.grpc.netty.NettyChannelBuilder;
31+
import io.grpc.netty.NettyServerBuilder;
32+
import io.grpc.opentelemetry.GrpcOpenTelemetry;
33+
import io.grpc.opentelemetry.GrpcTraceBinContextPropagator;
34+
import io.grpc.opentelemetry.InternalGrpcOpenTelemetry;
35+
import io.grpc.testing.integration.Messages.SimpleRequest;
36+
import io.opentelemetry.api.trace.Span;
37+
import io.opentelemetry.api.trace.Tracer;
38+
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
39+
import io.opentelemetry.context.Context;
40+
import io.opentelemetry.context.Scope;
41+
import io.opentelemetry.context.propagation.ContextPropagators;
42+
import io.opentelemetry.context.propagation.TextMapPropagator;
43+
import io.opentelemetry.sdk.OpenTelemetrySdk;
44+
import io.opentelemetry.sdk.trace.SdkTracerProvider;
45+
import java.util.Arrays;
46+
import java.util.concurrent.atomic.AtomicReference;
47+
import org.junit.Assume;
48+
import org.junit.Test;
49+
import org.junit.runner.RunWith;
50+
import org.junit.runners.Parameterized;
51+
52+
@RunWith(Parameterized.class)
53+
public class OpenTelemetryContextPropagationTest extends AbstractInteropTest {
54+
private final OpenTelemetrySdk openTelemetrySdk;
55+
private final Tracer tracer;
56+
private final GrpcOpenTelemetry grpcOpenTelemetry;
57+
private final AtomicReference<Span> applicationSpan = new AtomicReference<>();
58+
private final boolean censusClient;
59+
60+
@Parameterized.Parameters(name = "ContextPropagator={0}, CensusClient={1}")
61+
public static Iterable<Object[]> data() {
62+
return Arrays.asList(new Object[][] {
63+
{W3CTraceContextPropagator.getInstance(), false},
64+
{GrpcTraceBinContextPropagator.defaultInstance(), false},
65+
{GrpcTraceBinContextPropagator.defaultInstance(), true}
66+
});
67+
}
68+
69+
public OpenTelemetryContextPropagationTest(TextMapPropagator textMapPropagator,
70+
boolean isCensusClient) {
71+
this.openTelemetrySdk = OpenTelemetrySdk.builder()
72+
.setTracerProvider(SdkTracerProvider.builder().build())
73+
.setPropagators(ContextPropagators.create(TextMapPropagator.composite(
74+
textMapPropagator
75+
)))
76+
.build();
77+
this.tracer = openTelemetrySdk
78+
.getTracer("grpc-java-interop-test");
79+
GrpcOpenTelemetry.Builder grpcOpentelemetryBuilder = GrpcOpenTelemetry.newBuilder()
80+
.sdk(openTelemetrySdk);
81+
InternalGrpcOpenTelemetry.enableTracing(grpcOpentelemetryBuilder, true);
82+
grpcOpenTelemetry = grpcOpentelemetryBuilder.build();
83+
this.censusClient = isCensusClient;
84+
}
85+
86+
@Override
87+
protected ServerBuilder<?> getServerBuilder() {
88+
NettyServerBuilder builder = NettyServerBuilder.forPort(0, InsecureServerCredentials.create())
89+
.maxInboundMessageSize(AbstractInteropTest.MAX_MESSAGE_SIZE);
90+
builder.intercept(new ServerInterceptor() {
91+
@Override
92+
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call,
93+
Metadata headers, ServerCallHandler<ReqT, RespT> next) {
94+
ServerCall.Listener<ReqT> listener = next.startCall(call, headers);
95+
return new ForwardingServerCallListener<ReqT>() {
96+
@Override
97+
protected ServerCall.Listener<ReqT> delegate() {
98+
return listener;
99+
}
100+
101+
@Override
102+
public void onMessage(ReqT request) {
103+
applicationSpan.set(tracer.spanBuilder("InteropTest.Application.Span").startSpan());
104+
delegate().onMessage(request);
105+
}
106+
107+
@Override
108+
public void onHalfClose() {
109+
maybeCloseSpan(applicationSpan);
110+
delegate().onHalfClose();
111+
}
112+
113+
@Override
114+
public void onCancel() {
115+
maybeCloseSpan(applicationSpan);
116+
delegate().onCancel();
117+
}
118+
119+
@Override
120+
public void onComplete() {
121+
maybeCloseSpan(applicationSpan);
122+
delegate().onComplete();
123+
}
124+
};
125+
}
126+
});
127+
// To ensure proper propagation of remote spans from gRPC to your application, this interceptor
128+
// must be after any application interceptors that interact with spans. This allows the tracing
129+
// information to be correctly passed along. However, it's fine for application-level onMessage
130+
// handlers to access the span.
131+
grpcOpenTelemetry.configureServerBuilder(builder);
132+
return builder;
133+
}
134+
135+
private void maybeCloseSpan(AtomicReference<Span> applicationSpan) {
136+
Span tmp = applicationSpan.get();
137+
if (tmp != null) {
138+
tmp.end();
139+
}
140+
}
141+
142+
@Override
143+
protected boolean metricsExpected() {
144+
return false;
145+
}
146+
147+
@Override
148+
protected ManagedChannelBuilder<?> createChannelBuilder() {
149+
NettyChannelBuilder builder = NettyChannelBuilder.forAddress(getListenAddress())
150+
.maxInboundMessageSize(AbstractInteropTest.MAX_MESSAGE_SIZE)
151+
.usePlaintext();
152+
if (!censusClient) {
153+
// Disabling census-tracing is necessary to avoid trace ID mismatches.
154+
// This is because census-tracing overrides the grpc-trace-bin header with
155+
// OpenTelemetry's GrpcTraceBinPropagator.
156+
InternalNettyChannelBuilder.setTracingEnabled(builder, false);
157+
grpcOpenTelemetry.configureChannelBuilder(builder);
158+
}
159+
return builder;
160+
}
161+
162+
@Test
163+
public void otelSpanContextPropagation() {
164+
Assume.assumeFalse(censusClient);
165+
Span parentSpan = tracer.spanBuilder("Test.interopTest").startSpan();
166+
try (Scope scope = Context.current().with(parentSpan).makeCurrent()) {
167+
blockingStub.unaryCall(SimpleRequest.getDefaultInstance());
168+
}
169+
assertEquals(parentSpan.getSpanContext().getTraceId(),
170+
applicationSpan.get().getSpanContext().getTraceId());
171+
}
172+
173+
@Test
174+
@SuppressWarnings("deprecation")
175+
public void censusToOtelGrpcTraceBinPropagator() {
176+
Assume.assumeTrue(censusClient);
177+
io.opencensus.trace.Tracer censusTracer = io.opencensus.trace.Tracing.getTracer();
178+
io.opencensus.trace.Span parentSpan = censusTracer.spanBuilder("Test.interopTest")
179+
.startSpan();
180+
io.grpc.Context context = io.opencensus.trace.unsafe.ContextUtils.withValue(
181+
io.grpc.Context.current(), parentSpan);
182+
io.grpc.Context previous = context.attach();
183+
try {
184+
blockingStub.unaryCall(SimpleRequest.getDefaultInstance());
185+
assertEquals(parentSpan.getContext().getTraceId().toLowerBase16(),
186+
applicationSpan.get().getSpanContext().getTraceId());
187+
} finally {
188+
context.detach(previous);
189+
}
190+
}
191+
}

0 commit comments

Comments
 (0)