diff --git a/README.md b/README.md index 753dbd6a..31a8ed03 100644 --- a/README.md +++ b/README.md @@ -413,5 +413,14 @@ by starting command have the -d parameter. Upload full logs to the JIRA not just where the issue occurred if possible +### Tracing + +The SDK is set up to trace all gRPC communications: +* All outgoing messages bear trace metadata, allowing correlation with peers. +* Each request/response is computed as a span. +* If you use OpenTelemetry in your program, the gRPC span will correlate using the thread local context to the parent context. + +The SDK accepts all environment variables as described in the [OpenTelemetry specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md). + Creative Commons License
This work is licensed under a Creative Commons Attribution 4.0 International License. diff --git a/dependencies-suppressions.xml b/dependencies-suppressions.xml new file mode 100644 index 00000000..8ca8ddbc --- /dev/null +++ b/dependencies-suppressions.xml @@ -0,0 +1,10 @@ + + + + + ^pkg:maven/io\.opentelemetry\.instrumentation/opentelemetry\-grpc\-1\.6@.*$ + CVE-2020-7768 + + diff --git a/pom.xml b/pom.xml index 43c642a4..a35d58a6 100644 --- a/pom.xml +++ b/pom.xml @@ -68,6 +68,18 @@ + + + + io.opentelemetry + opentelemetry-bom + 1.5.0 + pom + import + + + + org.mockito @@ -220,6 +232,36 @@ 1.3.2 + + io.opentelemetry + opentelemetry-api + + + io.opentelemetry + opentelemetry-sdk + + + io.opentelemetry + opentelemetry-sdk-trace + + + io.opentelemetry + opentelemetry-exporter-otlp + + + io.opentelemetry + opentelemetry-extension-trace-propagators + + + io.opentelemetry.instrumentation + opentelemetry-grpc-1.6 + 1.5.1-alpha + + + io.opentelemetry + opentelemetry-sdk-extension-autoconfigure + 1.5.0-alpha + @@ -576,8 +618,27 @@ - - + + org.owasp + dependency-check-maven + 6.2.2 + + true + true + true + 7 + + dependencies-suppressions.xml + + + + + + check + + + + diff --git a/src/main/java/org/hyperledger/fabric/sdk/Endpoint.java b/src/main/java/org/hyperledger/fabric/sdk/Endpoint.java index 3e52df5f..fb290976 100644 --- a/src/main/java/org/hyperledger/fabric/sdk/Endpoint.java +++ b/src/main/java/org/hyperledger/fabric/sdk/Endpoint.java @@ -36,6 +36,7 @@ import javax.net.ssl.SSLException; import com.google.common.collect.ImmutableMap; +import io.grpc.ClientInterceptor; import io.grpc.ManagedChannelBuilder; import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts; import io.grpc.netty.shaded.io.grpc.netty.NegotiationType; @@ -43,6 +44,8 @@ import io.grpc.netty.shaded.io.netty.handler.ssl.SslContext; import io.grpc.netty.shaded.io.netty.handler.ssl.SslContextBuilder; import io.grpc.netty.shaded.io.netty.handler.ssl.SslProvider; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.grpc.v1_6.GrpcTracing; import org.apache.commons.codec.binary.Hex; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -67,6 +70,8 @@ class Endpoint { private static final String SSLPROVIDER = Config.getConfig().getDefaultSSLProvider(); private static final String SSLNEGOTIATION = Config.getConfig().getDefaultSSLNegotiationType(); + private static final OpenTelemetry openTelemetry = Config.getConfig().getOpenTelemetry(); + private static final GrpcTracing grpcTracing = GrpcTracing.create(openTelemetry); private final String addr; private final int port; @@ -236,13 +241,14 @@ class Endpoint { } try { + ClientInterceptor clientInterceptor = grpcTracing.newClientInterceptor(); if (protocol.equalsIgnoreCase("grpc")) { - this.channelBuilder = NettyChannelBuilder.forAddress(addr, port).negotiationType(NegotiationType.PLAINTEXT); + this.channelBuilder = NettyChannelBuilder.forAddress(addr, port).negotiationType(NegotiationType.PLAINTEXT).intercept(clientInterceptor); addNettyBuilderProps(channelBuilder, properties); } else if (protocol.equalsIgnoreCase("grpcs")) { if (pemBytes == null) { // use root certificate - this.channelBuilder = NettyChannelBuilder.forAddress(addr, port); + this.channelBuilder = NettyChannelBuilder.forAddress(addr, port).intercept(clientInterceptor); addNettyBuilderProps(channelBuilder, properties); } else { try { diff --git a/src/main/java/org/hyperledger/fabric/sdk/helper/Config.java b/src/main/java/org/hyperledger/fabric/sdk/helper/Config.java index 5683443c..169bfd46 100644 --- a/src/main/java/org/hyperledger/fabric/sdk/helper/Config.java +++ b/src/main/java/org/hyperledger/fabric/sdk/helper/Config.java @@ -23,6 +23,11 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.OpenTelemetrySdkBuilder; +import io.opentelemetry.sdk.autoconfigure.OpenTelemetrySdkAutoConfiguration; +import io.opentelemetry.sdk.trace.SdkTracerProvider; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.log4j.Level; @@ -111,6 +116,7 @@ public class Config { private static Config config; private static final Properties sdkProperties = new Properties(); private static final AtomicLong count = new AtomicLong(0); + private final OpenTelemetrySdk openTelemetry; //Provides a unique id for logging to identify a specific instance. public String getNextID() { @@ -241,6 +247,7 @@ private Config() { } + openTelemetry = OpenTelemetrySdkAutoConfiguration.initialize(false); } } @@ -348,6 +355,10 @@ public String getDefaultSSLNegotiationType() { } + public OpenTelemetry getOpenTelemetry() { + return openTelemetry; + } + private Map curveMapping = null; /** diff --git a/src/test/java/org/hyperledger/fabric/sdkintegration/End2endLifecycleIT.java b/src/test/java/org/hyperledger/fabric/sdkintegration/End2endLifecycleIT.java index ba79cb0d..cc81f786 100644 --- a/src/test/java/org/hyperledger/fabric/sdkintegration/End2endLifecycleIT.java +++ b/src/test/java/org/hyperledger/fabric/sdkintegration/End2endLifecycleIT.java @@ -10,6 +10,7 @@ import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -17,6 +18,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Properties; @@ -28,7 +30,16 @@ import java.util.concurrent.TimeoutException; import java.util.function.Predicate; +import com.google.common.io.Closer; import com.google.protobuf.InvalidProtocolBufferException; +import io.grpc.Server; +import io.grpc.Status; +import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder; +import io.grpc.stub.StreamObserver; +import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; +import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse; +import io.opentelemetry.proto.collector.trace.v1.TraceServiceGrpc; +import io.opentelemetry.proto.trace.v1.ResourceSpans; import org.hyperledger.fabric.sdk.BlockEvent.TransactionEvent; import org.hyperledger.fabric.sdk.ChaincodeCollectionConfiguration; import org.hyperledger.fabric.sdk.ChaincodeResponse; @@ -116,6 +127,38 @@ public class End2endLifecycleIT { TX_EXPECTED.put("writeset1", "Missing writeset for channel bar block 1"); } + private static final class FakeCollector extends TraceServiceGrpc.TraceServiceImplBase { + private final List receivedSpans = new ArrayList<>(); + private Status returnedStatus = Status.OK; + + @Override + public void export( + final ExportTraceServiceRequest request, + final StreamObserver responseObserver) { + receivedSpans.addAll(request.getResourceSpansList()); + responseObserver.onNext(ExportTraceServiceResponse.newBuilder().build()); + if (!returnedStatus.isOk()) { + if (returnedStatus.getCode() == Status.Code.DEADLINE_EXCEEDED) { + // Do not call onCompleted to simulate a deadline exceeded. + return; + } + responseObserver.onError(returnedStatus.asRuntimeException()); + return; + } + responseObserver.onCompleted(); + } + + List getReceivedSpans() { + return receivedSpans; + } + + void setReturnedStatus(final Status returnedStatus) { + this.returnedStatus = returnedStatus; + } + } + + private final Closer closer = Closer.create(); + private final FakeCollector fakeTracesCollector = new FakeCollector(); private final TestConfigHelper configHelper = new TestConfigHelper(); String testName = "End2endLifecycleIT"; @@ -144,8 +187,15 @@ static void out(String format, Object... args) { @Before public void checkConfig() throws Exception { out("\n\n\nRUNNING: %s.\n", testName); + Server server = + NettyServerBuilder.forPort(4317) + .addService(fakeTracesCollector) + .build() + .start(); + closer.register(server::shutdownNow); // configHelper.clearConfig(); // assertEquals(256, Config.getConfig().getSecurityLevel()); + System.setProperty("OTEL_TRACES_SAMPLER", "always_on"); resetConfig(); configHelper.customizeConfig(); @@ -336,6 +386,8 @@ public void runFabricTest() throws Exception { assertNull(org1Client.getChannel(CHANNEL_NAME)); out("\n"); + assertFalse(fakeTracesCollector.receivedSpans.isEmpty()); + out("That's all folks!"); }