From aaa13aabe9539a9ffd0a4fa313c5747d5945b8a7 Mon Sep 17 00:00:00 2001 From: yifeizhuang Date: Tue, 17 Sep 2024 18:07:55 -0700 Subject: [PATCH 1/3] add interop test --- interop-testing/build.gradle | 1 + .../OpenTelemetryContextFilterTest.java | 131 ++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 interop-testing/src/test/java/io/grpc/testing/integration/OpenTelemetryContextFilterTest.java diff --git a/interop-testing/build.gradle b/interop-testing/build.gradle index a19efb00155..a85aec97adf 100644 --- a/interop-testing/build.gradle +++ b/interop-testing/build.gradle @@ -13,6 +13,7 @@ dependencies { implementation project(path: ':grpc-alts', configuration: 'shadow'), project(':grpc-auth'), project(':grpc-census'), + project(':grpc-opentelemetry'), project(':grpc-gcp-csm-observability'), project(':grpc-netty'), project(':grpc-okhttp'), diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/OpenTelemetryContextFilterTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/OpenTelemetryContextFilterTest.java new file mode 100644 index 00000000000..6ff8966e602 --- /dev/null +++ b/interop-testing/src/test/java/io/grpc/testing/integration/OpenTelemetryContextFilterTest.java @@ -0,0 +1,131 @@ +/* + * Copyright 2024 The gRPC Authors + * + * 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 io.grpc.testing.integration; + +import static org.junit.Assert.assertEquals; + +import io.grpc.ForwardingServerCallListener; +import io.grpc.InsecureServerCredentials; +import io.grpc.ManagedChannelBuilder; +import io.grpc.Metadata; +import io.grpc.ServerBuilder; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; +import io.grpc.netty.NettyChannelBuilder; +import io.grpc.netty.NettyServerBuilder; +import io.grpc.opentelemetry.GrpcOpenTelemetry; +import io.grpc.testing.integration.Messages.SimpleRequest; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class OpenTelemetryContextFilterTest extends AbstractInteropTest { + private final OpenTelemetrySdk openTelemetrySdk = OpenTelemetrySdk.builder() + .setTracerProvider(SdkTracerProvider.builder().build()) + .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) + .build(); + private final Tracer tracer = openTelemetrySdk.getTracer("grpc-java"); + private final GrpcOpenTelemetry grpcOpenTelemetry = GrpcOpenTelemetry.newBuilder() + .sdk(openTelemetrySdk).build(); + private final AtomicReference applicationSpan = new AtomicReference<>(); + + @Override + protected ServerBuilder getServerBuilder() { + NettyServerBuilder builder = NettyServerBuilder.forPort(0, InsecureServerCredentials.create()) + .maxInboundMessageSize(AbstractInteropTest.MAX_MESSAGE_SIZE); + builder.intercept(new ServerInterceptor() { + @Override + public ServerCall.Listener interceptCall(ServerCall call, + Metadata headers, ServerCallHandler next) { + ServerCall.Listener listener = next.startCall(call, headers); + return new ForwardingServerCallListener() { + @Override + protected ServerCall.Listener delegate() { + return listener; + } + + @Override + public void onMessage(ReqT request) { + applicationSpan.set(tracer.spanBuilder("InteropTest.Application.Span").startSpan()); + delegate().onMessage(request); + } + + @Override + public void onHalfClose() { + if (applicationSpan.get() != null) { + applicationSpan.get().end(); + } + delegate().onHalfClose(); + } + + @Override + public void onCancel() { + if (applicationSpan.get() != null) { + applicationSpan.get().end(); + } + delegate().onCancel(); + } + + @Override + public void onComplete() { + if (applicationSpan.get() != null) { + applicationSpan.get().end(); + } + delegate().onComplete(); + } + }; + } + }); + grpcOpenTelemetry.configureServerBuilder(builder); + return builder; + } + + @Override + protected boolean metricsExpected() { + return false; + } + + @Override + protected ManagedChannelBuilder createChannelBuilder() { + NettyChannelBuilder builder = NettyChannelBuilder.forAddress(getListenAddress()) + .maxInboundMessageSize(AbstractInteropTest.MAX_MESSAGE_SIZE) + .usePlaintext(); + grpcOpenTelemetry.configureChannelBuilder(builder); + return builder; + } + + @Test + public void otelSpanContextPropagation() { + Span parentSpan = tracer.spanBuilder("Test.interopTest").startSpan(); + try (Scope scope = Context.current().with(parentSpan).makeCurrent()) { + blockingStub.unaryCall(SimpleRequest.getDefaultInstance()); + } + assertEquals(parentSpan.getSpanContext().getTraceId(), + applicationSpan.get().getSpanContext().getTraceId()); + } +} From 096441ed31459d7f6958ba7aa52ace5fc22a1d5c Mon Sep 17 00:00:00 2001 From: yifeizhuang Date: Wed, 18 Sep 2024 13:48:46 -0700 Subject: [PATCH 2/3] refactor test to parameterized --- ... OpenTelemetryContextPropagationTest.java} | 102 ++++++++++++++---- 1 file changed, 81 insertions(+), 21 deletions(-) rename interop-testing/src/test/java/io/grpc/testing/integration/{OpenTelemetryContextFilterTest.java => OpenTelemetryContextPropagationTest.java} (51%) diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/OpenTelemetryContextFilterTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/OpenTelemetryContextPropagationTest.java similarity index 51% rename from interop-testing/src/test/java/io/grpc/testing/integration/OpenTelemetryContextFilterTest.java rename to interop-testing/src/test/java/io/grpc/testing/integration/OpenTelemetryContextPropagationTest.java index 6ff8966e602..52f572ae2d9 100644 --- a/interop-testing/src/test/java/io/grpc/testing/integration/OpenTelemetryContextFilterTest.java +++ b/interop-testing/src/test/java/io/grpc/testing/integration/OpenTelemetryContextPropagationTest.java @@ -26,9 +26,12 @@ import io.grpc.ServerCall; import io.grpc.ServerCallHandler; import io.grpc.ServerInterceptor; +import io.grpc.netty.InternalNettyChannelBuilder; import io.grpc.netty.NettyChannelBuilder; import io.grpc.netty.NettyServerBuilder; import io.grpc.opentelemetry.GrpcOpenTelemetry; +import io.grpc.opentelemetry.GrpcTraceBinContextPropagator; +import io.grpc.opentelemetry.InternalGrpcOpenTelemetry; import io.grpc.testing.integration.Messages.SimpleRequest; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.Tracer; @@ -36,23 +39,49 @@ import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.trace.SdkTracerProvider; +import java.util.Arrays; import java.util.concurrent.atomic.AtomicReference; +import org.junit.Assume; import org.junit.Test; import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class OpenTelemetryContextFilterTest extends AbstractInteropTest { - private final OpenTelemetrySdk openTelemetrySdk = OpenTelemetrySdk.builder() - .setTracerProvider(SdkTracerProvider.builder().build()) - .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) - .build(); - private final Tracer tracer = openTelemetrySdk.getTracer("grpc-java"); - private final GrpcOpenTelemetry grpcOpenTelemetry = GrpcOpenTelemetry.newBuilder() - .sdk(openTelemetrySdk).build(); +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class OpenTelemetryContextPropagationTest extends AbstractInteropTest { + private final OpenTelemetrySdk openTelemetrySdk; + private final Tracer tracer; + private final GrpcOpenTelemetry grpcOpenTelemetry; private final AtomicReference applicationSpan = new AtomicReference<>(); + private final boolean censusClient; + + @Parameterized.Parameters(name = "ContextPropagator={0}, CensusClient={1}") + public static Iterable data() { + return Arrays.asList(new Object[][] { + {W3CTraceContextPropagator.getInstance(), false}, + {GrpcTraceBinContextPropagator.defaultInstance(), false}, + {GrpcTraceBinContextPropagator.defaultInstance(), true} + }); + } + + public OpenTelemetryContextPropagationTest(TextMapPropagator textMapPropagator, + boolean isCensusClient) { + this.openTelemetrySdk = OpenTelemetrySdk.builder() + .setTracerProvider(SdkTracerProvider.builder().build()) + .setPropagators(ContextPropagators.create(TextMapPropagator.composite( + textMapPropagator + ))) + .build(); + this.tracer = openTelemetrySdk + .getTracer("grpc-java-interop-test"); + GrpcOpenTelemetry.Builder grpcOpentelemetryBuilder = GrpcOpenTelemetry.newBuilder() + .sdk(openTelemetrySdk); + InternalGrpcOpenTelemetry.enableTracing(grpcOpentelemetryBuilder, true); + grpcOpenTelemetry = grpcOpentelemetryBuilder.build(); + this.censusClient = isCensusClient; + } @Override protected ServerBuilder getServerBuilder() { @@ -77,34 +106,39 @@ public void onMessage(ReqT request) { @Override public void onHalfClose() { - if (applicationSpan.get() != null) { - applicationSpan.get().end(); - } + maybeCloseSpan(applicationSpan); delegate().onHalfClose(); } @Override public void onCancel() { - if (applicationSpan.get() != null) { - applicationSpan.get().end(); - } + maybeCloseSpan(applicationSpan); delegate().onCancel(); } @Override public void onComplete() { - if (applicationSpan.get() != null) { - applicationSpan.get().end(); - } + maybeCloseSpan(applicationSpan); delegate().onComplete(); } }; } }); + // To ensure proper propagation of remote spans from gRPC to your application, this interceptor + // must be after any application interceptors that interact with spans. This allows the tracing + // information to be correctly passed along. However, it's fine for application-level onMessage + // handlers to access the span. grpcOpenTelemetry.configureServerBuilder(builder); return builder; } + private void maybeCloseSpan(AtomicReference applicationSpan) { + Span tmp = applicationSpan.get(); + if (tmp != null) { + tmp.end(); + } + } + @Override protected boolean metricsExpected() { return false; @@ -115,12 +149,19 @@ protected ManagedChannelBuilder createChannelBuilder() { NettyChannelBuilder builder = NettyChannelBuilder.forAddress(getListenAddress()) .maxInboundMessageSize(AbstractInteropTest.MAX_MESSAGE_SIZE) .usePlaintext(); - grpcOpenTelemetry.configureChannelBuilder(builder); + if (!censusClient) { + // Disabling census-tracing is necessary to avoid trace ID mismatches. + // This is because census-tracing overrides the grpc-trace-bin header with + // OpenTelemetry's GrpcTraceBinPropagator. + InternalNettyChannelBuilder.setTracingEnabled(builder, false); + grpcOpenTelemetry.configureChannelBuilder(builder); + } return builder; } @Test public void otelSpanContextPropagation() { + Assume.assumeFalse(censusClient); Span parentSpan = tracer.spanBuilder("Test.interopTest").startSpan(); try (Scope scope = Context.current().with(parentSpan).makeCurrent()) { blockingStub.unaryCall(SimpleRequest.getDefaultInstance()); @@ -128,4 +169,23 @@ public void otelSpanContextPropagation() { assertEquals(parentSpan.getSpanContext().getTraceId(), applicationSpan.get().getSpanContext().getTraceId()); } + + @Test + @SuppressWarnings("deprecation") + public void censusToOtelGrpcTraceBinPropagator() { + Assume.assumeTrue(censusClient); + io.opencensus.trace.Tracer censusTracer = io.opencensus.trace.Tracing.getTracer(); + io.opencensus.trace.Span parentSpan = censusTracer.spanBuilder("Test.interopTest") + .startSpan(); + io.grpc.Context context = io.opencensus.trace.unsafe.ContextUtils.withValue( + io.grpc.Context.current(), parentSpan); + io.grpc.Context previous = context.attach(); + try { + blockingStub.unaryCall(SimpleRequest.getDefaultInstance()); + assertEquals(parentSpan.getContext().getTraceId().toLowerBase16(), + applicationSpan.get().getSpanContext().getTraceId()); + } finally { + context.detach(previous); + } + } } From 69a2a8d1bd220ab060717d92a454e3a2ce898bd9 Mon Sep 17 00:00:00 2001 From: yifeizhuang Date: Wed, 18 Sep 2024 15:32:31 -0700 Subject: [PATCH 3/3] fix format --- .../integration/OpenTelemetryContextPropagationTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/OpenTelemetryContextPropagationTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/OpenTelemetryContextPropagationTest.java index 52f572ae2d9..3884d977a6e 100644 --- a/interop-testing/src/test/java/io/grpc/testing/integration/OpenTelemetryContextPropagationTest.java +++ b/interop-testing/src/test/java/io/grpc/testing/integration/OpenTelemetryContextPropagationTest.java @@ -153,8 +153,8 @@ protected ManagedChannelBuilder createChannelBuilder() { // Disabling census-tracing is necessary to avoid trace ID mismatches. // This is because census-tracing overrides the grpc-trace-bin header with // OpenTelemetry's GrpcTraceBinPropagator. - InternalNettyChannelBuilder.setTracingEnabled(builder, false); - grpcOpenTelemetry.configureChannelBuilder(builder); + InternalNettyChannelBuilder.setTracingEnabled(builder, false); + grpcOpenTelemetry.configureChannelBuilder(builder); } return builder; }