Use endpoint scheme in OTLP exporters (#2536)

* Use endpoint scheme in OTLP exporters

* Tests with authy authority
This commit is contained in:
Anuraag Agrawal 2021-01-17 14:15:00 +09:00 committed by GitHub
parent 4720911d66
commit cd1dbe5395
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 185 additions and 83 deletions

View File

@ -13,19 +13,27 @@ import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
/** Builder utility for this exporter. */
public final class OtlpGrpcMetricExporterBuilder {
private static final String DEFAULT_ENDPOINT = "localhost:4317";
private static final Logger logger =
Logger.getLogger(OtlpGrpcMetricExporterBuilder.class.getName());
private static final String DEFAULT_ENDPOINT_URL = "http://localhost:4317";
private static final URI DEFAULT_ENDPOINT = URI.create(DEFAULT_ENDPOINT_URL);
private static final long DEFAULT_TIMEOUT_SECS = 10;
private ManagedChannel channel;
private long timeoutNanos = TimeUnit.SECONDS.toNanos(DEFAULT_TIMEOUT_SECS);
private String endpoint = DEFAULT_ENDPOINT;
private URI endpoint = DEFAULT_ENDPOINT;
private boolean useTls = false;
@Nullable private Metadata metadata;
@ -63,13 +71,30 @@ public final class OtlpGrpcMetricExporterBuilder {
}
/**
* Sets the OTLP endpoint to connect to. Optional, defaults to "localhost:4317".
*
* @param endpoint endpoint to connect to
* @return this builder's instance
* Sets the OTLP endpoint to connect to. If unset, defaults to {@value DEFAULT_ENDPOINT_URL}. The
* endpoint must start with either http:// or https://.
*/
public OtlpGrpcMetricExporterBuilder setEndpoint(String endpoint) {
this.endpoint = endpoint;
requireNonNull(endpoint, "endpoint");
final URI uri;
try {
uri = new URI(endpoint);
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Invalid endpoint, must be a URL: " + endpoint, e);
}
// TODO(anuraaga): Remove after announcing deprecation of schemaless URLs.
if (uri.getScheme() != null) {
if (!uri.getScheme().equals("http") && !uri.getScheme().equals("https")) {
throw new IllegalArgumentException("Invalid scheme, must be http or https: " + endpoint);
}
} else {
logger.log(
Level.WARNING,
"Endpoints must have a scheme of http:// or https://. Endpoints without schemes will "
+ "not be permitted in a future version of the SDK.",
new Throwable());
}
this.endpoint = uri;
return this;
}
@ -79,7 +104,9 @@ public final class OtlpGrpcMetricExporterBuilder {
*
* @param useTls use TLS or not
* @return this builder's instance
* @deprecated Pass a URL starting with https:// to {@link #setEndpoint(String)}
*/
@Deprecated
public OtlpGrpcMetricExporterBuilder setUseTls(boolean useTls) {
this.useTls = useTls;
return this;
@ -109,9 +136,9 @@ public final class OtlpGrpcMetricExporterBuilder {
public OtlpGrpcMetricExporter build() {
if (channel == null) {
final ManagedChannelBuilder<?> managedChannelBuilder =
ManagedChannelBuilder.forTarget(endpoint);
ManagedChannelBuilder.forTarget(endpoint.getAuthority());
if (useTls) {
if (endpoint.getScheme().equals("https") || useTls) {
managedChannelBuilder.useTransportSecurity();
} else {
managedChannelBuilder.usePlaintext();

View File

@ -30,6 +30,7 @@ import io.opentelemetry.sdk.metrics.data.LongSumData;
import io.opentelemetry.sdk.metrics.data.MetricData;
import io.opentelemetry.sdk.resources.Resource;
import java.io.IOException;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
@ -78,6 +79,17 @@ class OtlpGrpcMetricExporterTest {
assertThatThrownBy(() -> OtlpGrpcMetricExporter.builder().setTimeout(null))
.isInstanceOf(NullPointerException.class)
.hasMessage("timeout");
assertThatThrownBy(() -> OtlpGrpcMetricExporter.builder().setEndpoint(null))
.isInstanceOf(NullPointerException.class)
.hasMessage("endpoint");
assertThatThrownBy(() -> OtlpGrpcMetricExporter.builder().setEndpoint("😺://localhost"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Invalid endpoint, must be a URL: 😺://localhost")
.hasCauseInstanceOf(URISyntaxException.class);
assertThatThrownBy(() -> OtlpGrpcMetricExporter.builder().setEndpoint("ftp://localhost"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Invalid scheme, must be http or https: ftp://localhost");
}
@Test

View File

@ -16,20 +16,28 @@ import io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.NettyChannelBuilder;
import io.grpc.stub.MetadataUtils;
import java.io.ByteArrayInputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.net.ssl.SSLException;
/** Builder utility for this exporter. */
public final class OtlpGrpcSpanExporterBuilder {
private static final String DEFAULT_ENDPOINT = "localhost:4317";
private static final Logger logger =
Logger.getLogger(OtlpGrpcSpanExporterBuilder.class.getName());
private static final String DEFAULT_ENDPOINT_URL = "http://localhost:4317";
private static final URI DEFAULT_ENDPOINT = URI.create(DEFAULT_ENDPOINT_URL);
private static final long DEFAULT_TIMEOUT_SECS = 10;
private ManagedChannel channel;
private long timeoutNanos = TimeUnit.SECONDS.toNanos(DEFAULT_TIMEOUT_SECS);
private String endpoint = DEFAULT_ENDPOINT;
private URI endpoint = DEFAULT_ENDPOINT;
private boolean useTls = false;
@Nullable private Metadata metadata;
@Nullable private byte[] trustedCertificatesPem;
@ -67,13 +75,30 @@ public final class OtlpGrpcSpanExporterBuilder {
}
/**
* Sets the OTLP endpoint to connect to. Optional, defaults to "localhost:4317".
*
* @param endpoint endpoint to connect to
* @return this builder's instance
* Sets the OTLP endpoint to connect to. If unset, defaults to {@value DEFAULT_ENDPOINT_URL}. The
* endpoint must start with either http:// or https://.
*/
public OtlpGrpcSpanExporterBuilder setEndpoint(String endpoint) {
this.endpoint = endpoint;
requireNonNull(endpoint, "endpoint");
final URI uri;
try {
uri = new URI(endpoint);
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Invalid endpoint, must be a URL: " + endpoint, e);
}
// TODO(anuraaga): Remove after announcing deprecation of schemaless URLs.
if (uri.getScheme() != null) {
if (!uri.getScheme().equals("http") && !uri.getScheme().equals("https")) {
throw new IllegalArgumentException("Invalid scheme, must be http or https: " + endpoint);
}
} else {
logger.log(
Level.WARNING,
"Endpoints must have a scheme of http:// or https://. Endpoints without schemes will "
+ "not be permitted in a future version of the SDK.",
new Throwable());
}
this.endpoint = uri;
return this;
}
@ -83,7 +108,9 @@ public final class OtlpGrpcSpanExporterBuilder {
*
* @param useTls use TLS or not
* @return this builder's instance
* @deprecated Pass a URL starting with https:// to {@link #setEndpoint(String)}
*/
@Deprecated
public OtlpGrpcSpanExporterBuilder setUseTls(boolean useTls) {
this.useTls = useTls;
return this;
@ -123,9 +150,9 @@ public final class OtlpGrpcSpanExporterBuilder {
public OtlpGrpcSpanExporter build() {
if (channel == null) {
final ManagedChannelBuilder<?> managedChannelBuilder =
ManagedChannelBuilder.forTarget(endpoint);
ManagedChannelBuilder.forTarget(endpoint.getAuthority());
if (useTls) {
if (endpoint.getScheme().equals("https") || useTls) {
managedChannelBuilder.useTransportSecurity();
} else {
managedChannelBuilder.usePlaintext();

View File

@ -28,6 +28,7 @@ import io.opentelemetry.sdk.testing.trace.TestSpanData;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.data.StatusData;
import java.io.IOException;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
@ -78,6 +79,17 @@ class OtlpGrpcSpanExporterTest {
assertThatThrownBy(() -> OtlpGrpcSpanExporter.builder().setTimeout(null))
.isInstanceOf(NullPointerException.class)
.hasMessage("timeout");
assertThatThrownBy(() -> OtlpGrpcSpanExporter.builder().setEndpoint(null))
.isInstanceOf(NullPointerException.class)
.hasMessage("endpoint");
assertThatThrownBy(() -> OtlpGrpcSpanExporter.builder().setEndpoint("😺://localhost"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Invalid endpoint, must be a URL: 😺://localhost")
.hasCauseInstanceOf(URISyntaxException.class);
assertThatThrownBy(() -> OtlpGrpcSpanExporter.builder().setEndpoint("ftp://localhost"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Invalid scheme, must be http or https: ftp://localhost");
}
@Test

View File

@ -20,16 +20,31 @@ 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.sdk.testing.trace.TestSpanData;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.data.StatusData;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
class TlsExportTest {
class ExportTest {
private static final List<SpanData> SPANS =
Collections.singletonList(
TestSpanData.builder()
.setTraceId(TraceId.getInvalid())
.setSpanId(SpanId.getInvalid())
.setName("name")
.setKind(Span.Kind.CLIENT)
.setStartEpochNanos(1)
.setEndEpochNanos(2)
.setStatus(StatusData.ok())
.setHasEnded(true)
.build());
@RegisterExtension
@Order(1)
@ -54,61 +69,45 @@ class TlsExportTest {
}
})
.build());
sb.http(0);
sb.https(0);
sb.tls(certificate.certificateFile(), certificate.privateKeyFile());
}
};
@Test
void testTlsExport() throws Exception {
void plainTextExport() {
OtlpGrpcSpanExporter exporter =
OtlpGrpcSpanExporter.builder()
.setEndpoint("localhost:" + server.httpsPort())
.setUseTls(true)
.setTrustedCertificates(Files.readAllBytes(certificate.certificateFile().toPath()))
.build();
assertThat(
exporter
.export(
Arrays.asList(
TestSpanData.builder()
.setTraceId(TraceId.getInvalid())
.setSpanId(SpanId.getInvalid())
.setName("name")
.setKind(Span.Kind.CLIENT)
.setStartEpochNanos(1)
.setEndEpochNanos(2)
.setStatus(StatusData.ok())
.setHasEnded(true)
.build()))
.join(10, TimeUnit.SECONDS)
.isSuccess())
.isTrue();
OtlpGrpcSpanExporter.builder().setEndpoint("http://localhost:" + server.httpPort()).build();
assertThat(exporter.export(SPANS).join(10, TimeUnit.SECONDS).isSuccess()).isTrue();
}
@Test
void testTlsExport_untrusted() throws Exception {
void authorityWithAuth() {
OtlpGrpcSpanExporter exporter =
OtlpGrpcSpanExporter.builder()
.setEndpoint("localhost:" + server.httpsPort())
.setUseTls(true)
.setEndpoint("http://foo:bar@localhost:" + server.httpPort())
.build();
assertThat(
exporter
.export(
Arrays.asList(
TestSpanData.builder()
.setTraceId(TraceId.getInvalid())
.setSpanId(SpanId.getInvalid())
.setName("name")
.setKind(Span.Kind.CLIENT)
.setStartEpochNanos(1)
.setEndEpochNanos(2)
.setStatus(StatusData.ok())
.setHasEnded(true)
.build()))
.join(10, TimeUnit.SECONDS)
.isSuccess())
.isFalse();
assertThat(exporter.export(SPANS).join(10, TimeUnit.SECONDS).isSuccess()).isTrue();
}
@Test
void testTlsExport() throws Exception {
OtlpGrpcSpanExporter exporter =
OtlpGrpcSpanExporter.builder()
.setEndpoint("https://localhost:" + server.httpsPort())
.setTrustedCertificates(Files.readAllBytes(certificate.certificateFile().toPath()))
.build();
assertThat(exporter.export(SPANS).join(10, TimeUnit.SECONDS).isSuccess()).isTrue();
}
@Test
void testTlsExport_untrusted() {
OtlpGrpcSpanExporter exporter =
OtlpGrpcSpanExporter.builder()
.setEndpoint("https://localhost:" + server.httpsPort())
.build();
assertThat(exporter.export(SPANS).join(10, TimeUnit.SECONDS).isSuccess()).isFalse();
}
@Test

View File

@ -62,8 +62,7 @@ class TlsExportTest {
void testTlsExport() throws Exception {
OtlpGrpcSpanExporter exporter =
OtlpGrpcSpanExporter.builder()
.setEndpoint("localhost:" + server.httpsPort())
.setUseTls(true)
.setEndpoint("https://localhost:" + server.httpsPort())
.setTrustedCertificates(Files.readAllBytes(certificate.certificateFile().toPath()))
.build();
assertThat(
@ -89,8 +88,7 @@ class TlsExportTest {
void testTlsExport_untrusted() throws Exception {
OtlpGrpcSpanExporter exporter =
OtlpGrpcSpanExporter.builder()
.setEndpoint("localhost:" + server.httpsPort())
.setUseTls(true)
.setEndpoint("https://localhost:" + server.httpsPort())
.build();
assertThat(
exporter

View File

@ -5,6 +5,7 @@
package io.opentelemetry.exporter.otlp.trace;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import com.linecorp.armeria.server.ServerBuilder;
@ -12,15 +13,37 @@ import com.linecorp.armeria.server.grpc.GrpcService;
import com.linecorp.armeria.testing.junit5.server.SelfSignedCertificateExtension;
import com.linecorp.armeria.testing.junit5.server.ServerExtension;
import io.grpc.stub.StreamObserver;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanId;
import io.opentelemetry.api.trace.TraceId;
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.sdk.testing.trace.TestSpanData;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.data.StatusData;
import java.nio.file.Files;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
class TlsExportTest {
class ExportTest {
private static final List<SpanData> SPANS =
Collections.singletonList(
TestSpanData.builder()
.setTraceId(TraceId.getInvalid())
.setSpanId(SpanId.getInvalid())
.setName("name")
.setKind(Span.Kind.CLIENT)
.setStartEpochNanos(1)
.setEndEpochNanos(2)
.setStatus(StatusData.ok())
.setHasEnded(true)
.build());
@RegisterExtension
@Order(1)
@ -45,10 +68,28 @@ class TlsExportTest {
}
})
.build());
sb.http(0);
sb.https(0);
sb.tls(certificate.certificateFile(), certificate.privateKeyFile());
}
};
@Test
void plainTextExport() {
OtlpGrpcSpanExporter exporter =
OtlpGrpcSpanExporter.builder().setEndpoint("http://localhost:" + server.httpPort()).build();
assertThat(exporter.export(SPANS).join(10, TimeUnit.SECONDS).isSuccess()).isTrue();
}
@Test
void authorityWithAuth() {
OtlpGrpcSpanExporter exporter =
OtlpGrpcSpanExporter.builder()
.setEndpoint("http://foo:bar@localhost:" + server.httpPort())
.build();
assertThat(exporter.export(SPANS).join(10, TimeUnit.SECONDS).isSuccess()).isTrue();
}
@Test
void testTlsExport() {
// Currently not supported.

View File

@ -47,11 +47,6 @@ final class MetricExporterConfiguration {
builder.setEndpoint(endpoint);
}
boolean insecure = config.getBoolean("otel.exporter.otlp.insecure");
if (!insecure) {
builder.setUseTls(true);
}
config.getCommaSeparatedMap("otel.exporter.otlp.headers").forEach(builder::addHeader);
Long timeoutMillis = config.getLong("otel.exporter.otlp.timeout");

View File

@ -52,11 +52,6 @@ final class SpanExporterConfiguration {
builder.setEndpoint(endpoint);
}
boolean insecure = config.getBoolean("otel.exporter.otlp.insecure");
if (!insecure) {
builder.setUseTls(true);
}
config.getCommaSeparatedMap("otel.exporter.otlp.headers").forEach(builder::addHeader);
Long timeoutMillis = config.getLong("otel.exporter.otlp.timeout");

View File

@ -111,14 +111,10 @@ class FullConfigTest {
@Test
void configures() throws Exception {
String endpoint = "localhost:" + server.httpPort();
String endpoint = "http://localhost:" + server.httpPort();
System.setProperty("otel.exporter.otlp.endpoint", endpoint);
System.setProperty("otel.exporter.otlp.insecure", "true");
System.setProperty("otel.exporter.otlp.timeout", "10000");
System.setProperty("otel.exporter.jaeger.endpoint", endpoint);
System.setProperty("otel.exporter.zipkin.endpoint", "http://" + endpoint + "/api/v2/spans");
OpenTelemetrySdkAutoConfiguration.initialize();
Collection<String> fields =
GlobalOpenTelemetry.get().getPropagators().getTextMapPropagator().fields();