From 2834b03b8866b3bb110a36e0e2ff11beff0cb984 Mon Sep 17 00:00:00 2001 From: jack-berg <34418638+jack-berg@users.noreply.github.com> Date: Thu, 29 Dec 2022 11:07:47 -0600 Subject: [PATCH] Move autoconfigure otlp tests (#5060) * Unify OtlpHttp{Signal}Exporter testing * Add header and compression tests * Move SPI otlp tests to :exporters:otlp * Improve test coverage * Improve test coverage --- exporters/otlp/all/build.gradle.kts | 2 + .../internal/OtlpMetricExporterProvider.java | 14 +- .../internal/OtlpSpanExporterProvider.java | 14 +- .../metrics/OtlpHttpMetricExporterTest.java | 424 +++--------- .../http/trace/OtlpHttpSpanExporterTest.java | 422 +++--------- .../OtlpMetricExporterProviderTest.java | 278 ++++++++ .../OtlpSpanExporterProviderTest.java | 277 ++++++++ .../metrics/OtlpGrpcMetricExporterTest.java | 25 +- .../otlp/trace/OtlpGrpcSpanExporterTest.java | 35 +- .../OtlpGrpcNettyMetricExporterTest.java | 26 +- .../trace/OtlpGrpcNettySpanExporterTest.java | 35 +- ...OtlpGrpcNettyShadedMetricExporterTest.java | 26 +- .../OtlpGrpcNettyShadedSpanExporterTest.java | 35 +- .../OtlpGrpcOkHttpMetricExporterTest.java | 26 +- .../trace/OtlpGrpcOkHttpSpanExporterTest.java | 35 +- exporters/otlp/logs/build.gradle.kts | 2 + .../OtlpLogRecordExporterProvider.java | 14 +- .../logs/OtlpHttpLogRecordExporterTest.java | 432 +++--------- .../OtlpLogRecordExporterProviderTest.java | 277 ++++++++ .../logs/OtlpGrpcLogRecordExporterTest.java | 18 +- .../OtlpGrpcNettyLogRecordExporterTest.java | 18 +- ...pGrpcNettyShadedLogRecordExporterTest.java | 18 +- ...pGrpcNettyOkHttpLogRecordExporterTest.java | 18 +- .../otlp/testing-internal/build.gradle.kts | 2 + .../AbstractGrpcTelemetryExporterTest.java | 29 +- .../AbstractHttpTelemetryExporterTest.java | 640 ++++++++++++++++++ .../testing/internal/FakeTelemetryUtil.java | 98 +++ sdk-extensions/autoconfigure/build.gradle.kts | 19 - .../sdk/autoconfigure/OtlpGrpcConfigTest.java | 410 ----------- .../sdk/autoconfigure/OtlpGrpcRetryTest.java | 172 ----- .../OtlpGrpcServerExtension.java | 174 ----- .../sdk/autoconfigure/OtlpHttpConfigTest.java | 410 ----------- .../sdk/autoconfigure/OtlpHttpRetryTest.java | 179 ----- .../OtlpHttpServerExtension.java | 157 ----- 34 files changed, 1914 insertions(+), 2847 deletions(-) create mode 100644 exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/internal/OtlpMetricExporterProviderTest.java create mode 100644 exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/internal/OtlpSpanExporterProviderTest.java create mode 100644 exporters/otlp/logs/src/test/java/io/opentelemetry/exporter/otlp/internal/OtlpLogRecordExporterProviderTest.java create mode 100644 exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/AbstractHttpTelemetryExporterTest.java create mode 100644 exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/FakeTelemetryUtil.java delete mode 100644 sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpGrpcConfigTest.java delete mode 100644 sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpGrpcRetryTest.java delete mode 100644 sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpGrpcServerExtension.java delete mode 100644 sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpHttpConfigTest.java delete mode 100644 sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpHttpRetryTest.java delete mode 100644 sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpHttpServerExtension.java diff --git a/exporters/otlp/all/build.gradle.kts b/exporters/otlp/all/build.gradle.kts index e3529c57ca9..1968e109ce7 100644 --- a/exporters/otlp/all/build.gradle.kts +++ b/exporters/otlp/all/build.gradle.kts @@ -19,6 +19,8 @@ dependencies { compileOnly("io.grpc:grpc-stub") + testImplementation("io.grpc:grpc-stub") + testImplementation(project(":exporters:otlp:testing-internal")) testImplementation("com.linecorp.armeria:armeria-junit5") testImplementation("com.google.api.grpc:proto-google-common-protos") diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpMetricExporterProvider.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpMetricExporterProvider.java index daa54293903..5b84a9ea3d1 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpMetricExporterProvider.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpMetricExporterProvider.java @@ -33,7 +33,7 @@ public MetricExporter createExporter(ConfigProperties config) { String protocol = OtlpConfigUtil.getOtlpProtocol(DATA_TYPE_METRICS, config); if (protocol.equals(PROTOCOL_HTTP_PROTOBUF)) { - OtlpHttpMetricExporterBuilder builder = OtlpHttpMetricExporter.builder(); + OtlpHttpMetricExporterBuilder builder = httpBuilder(); OtlpConfigUtil.configureOtlpExporterBuilder( DATA_TYPE_METRICS, @@ -52,7 +52,7 @@ public MetricExporter createExporter(ConfigProperties config) { return builder.build(); } else if (protocol.equals(PROTOCOL_GRPC)) { - OtlpGrpcMetricExporterBuilder builder = OtlpGrpcMetricExporter.builder(); + OtlpGrpcMetricExporterBuilder builder = grpcBuilder(); OtlpConfigUtil.configureOtlpExporterBuilder( DATA_TYPE_METRICS, @@ -78,4 +78,14 @@ public MetricExporter createExporter(ConfigProperties config) { public String getName() { return "otlp"; } + + // Visible for testing + OtlpHttpMetricExporterBuilder httpBuilder() { + return OtlpHttpMetricExporter.builder(); + } + + // Visible for testing + OtlpGrpcMetricExporterBuilder grpcBuilder() { + return OtlpGrpcMetricExporter.builder(); + } } diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpSpanExporterProvider.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpSpanExporterProvider.java index 084998f4f8a..89caa922785 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpSpanExporterProvider.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpSpanExporterProvider.java @@ -32,7 +32,7 @@ public class OtlpSpanExporterProvider implements ConfigurableSpanExporterProvide public SpanExporter createExporter(ConfigProperties config) { String protocol = OtlpConfigUtil.getOtlpProtocol(DATA_TYPE_TRACES, config); if (protocol.equals(PROTOCOL_HTTP_PROTOBUF)) { - OtlpHttpSpanExporterBuilder builder = OtlpHttpSpanExporter.builder(); + OtlpHttpSpanExporterBuilder builder = httpBuilder(); OtlpConfigUtil.configureOtlpExporterBuilder( DATA_TYPE_TRACES, @@ -47,7 +47,7 @@ public SpanExporter createExporter(ConfigProperties config) { return builder.build(); } else if (protocol.equals(PROTOCOL_GRPC)) { - OtlpGrpcSpanExporterBuilder builder = OtlpGrpcSpanExporter.builder(); + OtlpGrpcSpanExporterBuilder builder = grpcBuilder(); OtlpConfigUtil.configureOtlpExporterBuilder( DATA_TYPE_TRACES, @@ -69,4 +69,14 @@ public SpanExporter createExporter(ConfigProperties config) { public String getName() { return "otlp"; } + + // Visible for testing + OtlpHttpSpanExporterBuilder httpBuilder() { + return OtlpHttpSpanExporter.builder(); + } + + // Visible for testing + OtlpGrpcSpanExporterBuilder grpcBuilder() { + return OtlpGrpcSpanExporter.builder(); + } } diff --git a/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/http/metrics/OtlpHttpMetricExporterTest.java b/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/http/metrics/OtlpHttpMetricExporterTest.java index 3fddc70113d..0242c6baa2c 100644 --- a/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/http/metrics/OtlpHttpMetricExporterTest.java +++ b/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/http/metrics/OtlpHttpMetricExporterTest.java @@ -5,153 +5,40 @@ package io.opentelemetry.exporter.otlp.http.metrics; -import static io.opentelemetry.api.common.AttributeKey.stringKey; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import com.google.protobuf.InvalidProtocolBufferException; -import com.google.protobuf.Message; -import com.google.rpc.Status; -import com.linecorp.armeria.common.AggregatedHttpRequest; -import com.linecorp.armeria.common.HttpMethod; -import com.linecorp.armeria.common.HttpResponse; -import com.linecorp.armeria.common.HttpStatus; -import com.linecorp.armeria.common.MediaType; -import com.linecorp.armeria.server.ServerBuilder; -import com.linecorp.armeria.testing.junit5.server.mock.MockWebServerExtension; -import io.github.netmikey.logunit.api.LogCapturer; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.exporter.internal.okhttp.OkHttpExporter; -import io.opentelemetry.exporter.internal.otlp.metrics.MetricsRequestMarshaler; +import io.opentelemetry.exporter.internal.marshal.Marshaler; import io.opentelemetry.exporter.internal.otlp.metrics.ResourceMetricsMarshaler; import io.opentelemetry.exporter.internal.retry.RetryPolicy; import io.opentelemetry.exporter.internal.retry.RetryUtil; -import io.opentelemetry.internal.testing.slf4j.SuppressLogger; -import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest; -import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse; +import io.opentelemetry.exporter.otlp.testing.internal.AbstractHttpTelemetryExporterTest; +import io.opentelemetry.exporter.otlp.testing.internal.FakeTelemetryUtil; +import io.opentelemetry.exporter.otlp.testing.internal.TelemetryExporter; +import io.opentelemetry.exporter.otlp.testing.internal.TelemetryExporterBuilder; import io.opentelemetry.proto.metrics.v1.ResourceMetrics; -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.metrics.Aggregation; import io.opentelemetry.sdk.metrics.InstrumentType; import io.opentelemetry.sdk.metrics.data.AggregationTemporality; import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.metrics.export.AggregationTemporalitySelector; import io.opentelemetry.sdk.metrics.export.DefaultAggregationSelector; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableLongPointData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableMetricData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableSumData; -import io.opentelemetry.sdk.resources.Resource; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; -import okhttp3.tls.HeldCertificate; -import okio.Buffer; -import okio.GzipSource; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.slf4j.event.Level; -import org.slf4j.event.LoggingEvent; -class OtlpHttpMetricExporterTest { +class OtlpHttpMetricExporterTest + extends AbstractHttpTelemetryExporterTest { - private static final MediaType APPLICATION_PROTOBUF = - MediaType.create("application", "x-protobuf"); - private static final MediaType APPLICATION_JSON = MediaType.create("application", "json"); - private static final HeldCertificate HELD_CERTIFICATE; - private static final String canonicalHostName; - - static { - try { - canonicalHostName = InetAddress.getByName("localhost").getCanonicalHostName(); - HELD_CERTIFICATE = - new HeldCertificate.Builder() - .commonName("localhost") - .addSubjectAlternativeName(canonicalHostName) - .build(); - } catch (UnknownHostException e) { - throw new IllegalStateException("Error building certificate.", e); - } - } - - @RegisterExtension - static MockWebServerExtension server = - new MockWebServerExtension() { - @Override - protected void configureServer(ServerBuilder sb) { - sb.tls(HELD_CERTIFICATE.keyPair().getPrivate(), HELD_CERTIFICATE.certificate()); - } - }; - - @RegisterExtension LogCapturer logs = LogCapturer.create().captureForType(OkHttpExporter.class); - - private OtlpHttpMetricExporterBuilder builder; - - @BeforeEach - void setup() { - builder = - OtlpHttpMetricExporter.builder() - .setEndpoint("https://" + canonicalHostName + ":" + server.httpsPort() + "/v1/metrics") - .addHeader("foo", "bar") - .setTrustedCertificates( - HELD_CERTIFICATE.certificatePem().getBytes(StandardCharsets.UTF_8)); + protected OtlpHttpMetricExporterTest() { + super("metric", "/v1/metrics", ResourceMetrics.getDefaultInstance()); } + /** Test configuration specific to metric exporter. */ @Test - @SuppressWarnings("PreferJavaTimeOverload") - void validConfig() { - assertThatCode(() -> OtlpHttpMetricExporter.builder().setTimeout(0, TimeUnit.MILLISECONDS)) - .doesNotThrowAnyException(); - assertThatCode(() -> OtlpHttpMetricExporter.builder().setTimeout(Duration.ofMillis(0))) - .doesNotThrowAnyException(); - assertThatCode(() -> OtlpHttpMetricExporter.builder().setTimeout(10, TimeUnit.MILLISECONDS)) - .doesNotThrowAnyException(); - assertThatCode(() -> OtlpHttpMetricExporter.builder().setTimeout(Duration.ofMillis(10))) - .doesNotThrowAnyException(); - - assertThatCode(() -> OtlpHttpMetricExporter.builder().setEndpoint("http://localhost:4318")) - .doesNotThrowAnyException(); - assertThatCode(() -> OtlpHttpMetricExporter.builder().setEndpoint("http://localhost")) - .doesNotThrowAnyException(); - assertThatCode(() -> OtlpHttpMetricExporter.builder().setEndpoint("https://localhost")) - .doesNotThrowAnyException(); - assertThatCode(() -> OtlpHttpMetricExporter.builder().setEndpoint("http://foo:bar@localhost")) - .doesNotThrowAnyException(); - - assertThatCode(() -> OtlpHttpMetricExporter.builder().setCompression("gzip")) - .doesNotThrowAnyException(); - assertThatCode(() -> OtlpHttpMetricExporter.builder().setCompression("none")) - .doesNotThrowAnyException(); - - assertThatCode( - () -> OtlpHttpMetricExporter.builder().addHeader("foo", "bar").addHeader("baz", "qux")) - .doesNotThrowAnyException(); - - assertThatCode( - () -> - OtlpHttpMetricExporter.builder() - .setTrustedCertificates("foobar".getBytes(StandardCharsets.UTF_8))) - .doesNotThrowAnyException(); - - assertThatCode( - () -> - OtlpHttpMetricExporter.builder() - .setClientTls( - "foobar".getBytes(StandardCharsets.UTF_8), - "foobar".getBytes(StandardCharsets.UTF_8))) - .doesNotThrowAnyException(); - + void validMetricConfig() { assertThatCode( () -> OtlpHttpMetricExporter.builder() @@ -180,40 +67,9 @@ void validConfig() { .isEqualTo(Aggregation.drop()); } + /** Test configuration specific to metric exporter. */ @Test - @SuppressWarnings("PreferJavaTimeOverload") - void invalidConfig() { - assertThatThrownBy(() -> OtlpHttpMetricExporter.builder().setTimeout(-1, TimeUnit.MILLISECONDS)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("timeout must be non-negative"); - assertThatThrownBy(() -> OtlpHttpMetricExporter.builder().setTimeout(1, null)) - .isInstanceOf(NullPointerException.class) - .hasMessage("unit"); - assertThatThrownBy(() -> OtlpHttpMetricExporter.builder().setTimeout(null)) - .isInstanceOf(NullPointerException.class) - .hasMessage("timeout"); - - assertThatThrownBy(() -> OtlpHttpMetricExporter.builder().setEndpoint(null)) - .isInstanceOf(NullPointerException.class) - .hasMessage("endpoint"); - assertThatThrownBy(() -> OtlpHttpMetricExporter.builder().setEndpoint("😺://localhost")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Invalid endpoint, must be a URL: 😺://localhost"); - assertThatThrownBy(() -> OtlpHttpMetricExporter.builder().setEndpoint("localhost")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Invalid endpoint, must start with http:// or https://: localhost"); - assertThatThrownBy(() -> OtlpHttpMetricExporter.builder().setEndpoint("gopher://localhost")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Invalid endpoint, must start with http:// or https://: gopher://localhost"); - - assertThatThrownBy(() -> OtlpHttpMetricExporter.builder().setCompression(null)) - .isInstanceOf(NullPointerException.class) - .hasMessage("compressionMethod"); - assertThatThrownBy(() -> OtlpHttpMetricExporter.builder().setCompression("foo")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage( - "Unsupported compression method. Supported compression methods include: gzip, none."); - + void invalidMetricConfig() { assertThatThrownBy( () -> OtlpHttpMetricExporter.builder().setAggregationTemporalitySelector(null)) .isInstanceOf(NullPointerException.class) @@ -224,186 +80,78 @@ void invalidConfig() { .hasMessage("defaultAggregationSelector"); } - @Test - void testSetRetryPolicyOnDelegate() { - assertThatCode( - () -> - RetryUtil.setRetryPolicyOnDelegate( - OtlpHttpMetricExporter.builder(), RetryPolicy.getDefault())) - .doesNotThrowAnyException(); - } - - @Test - void testExportUncompressed() { - server.enqueue(successResponse()); - OtlpHttpMetricExporter exporter = builder.build(); - - ExportMetricsServiceRequest payload = - exportAndAssertResult(exporter, /* expectedResult= */ true); - AggregatedHttpRequest request = server.takeRequest().request(); - assertRequestCommon(request); - assertThat(parseRequestBody(request.content().array())).isEqualTo(payload); - } - - @Test - void testExportGzipCompressed() { - server.enqueue(successResponse()); - OtlpHttpMetricExporter exporter = builder.setCompression("gzip").build(); - - ExportMetricsServiceRequest payload = - exportAndAssertResult(exporter, /* expectedResult= */ true); - AggregatedHttpRequest request = server.takeRequest().request(); - assertRequestCommon(request); - assertThat(request.headers().get("Content-Encoding")).isEqualTo("gzip"); - assertThat(parseRequestBody(gzipDecompress(request.content().array()))).isEqualTo(payload); - } - - @Test - void testExportAsJson() { - server.enqueue(successResponse()); - OtlpHttpMetricExporter exporter = builder.exportAsJson().build(); - - String payload = exportJsonAndAssertResult(exporter, /* expectedResult= */ true); - AggregatedHttpRequest request = server.takeRequest().request(); - assertRequestCommon(request, /* isJson= */ true); - assertThat(parseJsonRequestBody(request.content().array())).isEqualTo(payload); - } - - @Test - void testExport_flush() { - OtlpHttpMetricExporter exporter = OtlpHttpMetricExporter.builder().build(); - try { - assertThat(exporter.flush().isSuccess()).isTrue(); - } finally { - exporter.shutdown(); - } - } - - private static void assertRequestCommon(AggregatedHttpRequest request) { - assertRequestCommon(request, /* isJson= */ false); - } - - private static void assertRequestCommon(AggregatedHttpRequest request, boolean isJson) { - assertThat(request.method()).isEqualTo(HttpMethod.POST); - assertThat(request.path()).isEqualTo("/v1/metrics"); - assertThat(request.headers().get("foo")).isEqualTo("bar"); - MediaType mediaType = isJson ? APPLICATION_JSON : APPLICATION_PROTOBUF; - assertThat(request.headers().get("Content-Type")).isEqualTo(mediaType.toString()); - } - - private static ExportMetricsServiceRequest parseRequestBody(byte[] bytes) { - try { - return ExportMetricsServiceRequest.parseFrom(bytes); - } catch (InvalidProtocolBufferException e) { - throw new IllegalStateException("Unable to parse Protobuf request body.", e); - } - } - - private static String parseJsonRequestBody(byte[] bytes) { - return new String(bytes, StandardCharsets.UTF_8); - } - - private static byte[] gzipDecompress(byte[] bytes) { - try { - Buffer result = new Buffer(); - GzipSource source = new GzipSource(new Buffer().write(bytes)); - while (source.read(result, Integer.MAX_VALUE) != -1) {} - return result.readByteArray(); - } catch (IOException e) { - throw new IllegalStateException("Unable to decompress payload.", e); - } - } - - @Test - @SuppressLogger(OkHttpExporter.class) - void testServerError() { - server.enqueue( - buildResponse( - HttpStatus.INTERNAL_SERVER_ERROR, - Status.newBuilder().setMessage("Server error!").build())); - OtlpHttpMetricExporter exporter = builder.build(); - - exportAndAssertResult(exporter, /* expectedResult= */ false); - LoggingEvent log = - logs.assertContains( - "Failed to export metrics. Server responded with HTTP status code 500. Error message: Server error!"); - assertThat(log.getLevel()).isEqualTo(Level.WARN); - } - - @Test - @SuppressLogger(OkHttpExporter.class) - void testServerErrorParseError() { - server.enqueue( - HttpResponse.of(HttpStatus.INTERNAL_SERVER_ERROR, APPLICATION_PROTOBUF, "Server error!")); - OtlpHttpMetricExporter exporter = builder.build(); - - exportAndAssertResult(exporter, /* expectedResult= */ false); - LoggingEvent log = - logs.assertContains( - "Failed to export metrics. Server responded with HTTP status code 500. Error message: Unable to parse response body, HTTP status message:"); - assertThat(log.getLevel()).isEqualTo(Level.WARN); - } - - private static ExportMetricsServiceRequest exportAndAssertResult( - OtlpHttpMetricExporter otlpHttpMetricExporter, boolean expectedResult) { - List metrics = Collections.singletonList(generateFakeMetric()); - CompletableResultCode resultCode = otlpHttpMetricExporter.export(metrics); - resultCode.join(10, TimeUnit.SECONDS); - assertThat(resultCode.isSuccess()).isEqualTo(expectedResult); - List resourceMetrics = - Arrays.stream(ResourceMetricsMarshaler.create(metrics)) - .map( - marshaler -> { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - try { - marshaler.writeBinaryTo(bos); - return ResourceMetrics.parseFrom(bos.toByteArray()); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - }) - .collect(Collectors.toList()); - return ExportMetricsServiceRequest.newBuilder().addAllResourceMetrics(resourceMetrics).build(); - } - - private static String exportJsonAndAssertResult( - OtlpHttpMetricExporter otlpHttpMetricExporter, boolean expectedResult) { - List metrics = Collections.singletonList(generateFakeMetric()); - CompletableResultCode resultCode = otlpHttpMetricExporter.export(metrics); - resultCode.join(10, TimeUnit.SECONDS); - assertThat(resultCode.isSuccess()).isEqualTo(expectedResult); - try (ByteArrayOutputStream jsonBos = new ByteArrayOutputStream()) { - MetricsRequestMarshaler.create(metrics).writeJsonTo(jsonBos); - return new String(jsonBos.toByteArray(), StandardCharsets.UTF_8); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - private static HttpResponse successResponse() { - ExportMetricsServiceResponse exportMetricsServiceResponse = - ExportMetricsServiceResponse.newBuilder().build(); - return buildResponse(HttpStatus.OK, exportMetricsServiceResponse); - } - - private static HttpResponse buildResponse(HttpStatus httpStatus, T message) { - return HttpResponse.of(httpStatus, APPLICATION_PROTOBUF, message.toByteArray()); - } - - private static MetricData generateFakeMetric() { - long startNs = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()); - long endNs = startNs + TimeUnit.MILLISECONDS.toNanos(900); - return ImmutableMetricData.createLongSum( - Resource.empty(), - InstrumentationScopeInfo.empty(), - "name", - "description", - "1", - ImmutableSumData.create( - /* isMonotonic= */ true, - AggregationTemporality.CUMULATIVE, - Collections.singletonList( - ImmutableLongPointData.create( - startNs, endNs, Attributes.of(stringKey("k"), "v"), 5)))); + @Override + protected TelemetryExporterBuilder exporterBuilder() { + OtlpHttpMetricExporterBuilder builder = OtlpHttpMetricExporter.builder(); + return new TelemetryExporterBuilder() { + @Override + public TelemetryExporterBuilder setEndpoint(String endpoint) { + builder.setEndpoint(endpoint); + return this; + } + + @Override + public TelemetryExporterBuilder setTimeout(long timeout, TimeUnit unit) { + builder.setTimeout(timeout, unit); + return this; + } + + @Override + public TelemetryExporterBuilder setTimeout(Duration timeout) { + builder.setTimeout(timeout); + return this; + } + + @Override + public TelemetryExporterBuilder setCompression(String compression) { + builder.setCompression(compression); + return this; + } + + @Override + public TelemetryExporterBuilder addHeader(String key, String value) { + builder.addHeader(key, value); + return this; + } + + @Override + public TelemetryExporterBuilder setTrustedCertificates(byte[] certificates) { + builder.setTrustedCertificates(certificates); + return this; + } + + @Override + public TelemetryExporterBuilder setClientTls( + byte[] privateKeyPem, byte[] certificatePem) { + builder.setClientTls(privateKeyPem, certificatePem); + return this; + } + + @Override + public TelemetryExporterBuilder setRetryPolicy(RetryPolicy retryPolicy) { + RetryUtil.setRetryPolicyOnDelegate(builder, retryPolicy); + return this; + } + + @Override + public TelemetryExporterBuilder setChannel(io.grpc.ManagedChannel channel) { + throw new UnsupportedOperationException("Not implemented"); + } + + @Override + public TelemetryExporter build() { + return TelemetryExporter.wrap(builder.build()); + } + }; + } + + @Override + protected MetricData generateFakeTelemetry() { + return FakeTelemetryUtil.generateFakeMetricData(); + } + + @Override + protected Marshaler[] toMarshalers(List telemetry) { + return ResourceMetricsMarshaler.create(telemetry); } } diff --git a/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/http/trace/OtlpHttpSpanExporterTest.java b/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/http/trace/OtlpHttpSpanExporterTest.java index 140a279b963..2948dfe380d 100644 --- a/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/http/trace/OtlpHttpSpanExporterTest.java +++ b/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/http/trace/OtlpHttpSpanExporterTest.java @@ -5,352 +5,98 @@ package io.opentelemetry.exporter.otlp.http.trace; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import com.google.protobuf.InvalidProtocolBufferException; -import com.google.protobuf.Message; -import com.google.rpc.Status; -import com.linecorp.armeria.common.AggregatedHttpRequest; -import com.linecorp.armeria.common.HttpMethod; -import com.linecorp.armeria.common.HttpResponse; -import com.linecorp.armeria.common.HttpStatus; -import com.linecorp.armeria.common.MediaType; -import com.linecorp.armeria.server.ServerBuilder; -import com.linecorp.armeria.testing.junit5.server.mock.MockWebServerExtension; -import com.linecorp.armeria.testing.junit5.server.mock.RecordedRequest; -import io.github.netmikey.logunit.api.LogCapturer; -import io.opentelemetry.api.trace.SpanContext; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.TraceFlags; -import io.opentelemetry.api.trace.TraceState; -import io.opentelemetry.exporter.internal.okhttp.OkHttpExporter; +import io.opentelemetry.exporter.internal.marshal.Marshaler; import io.opentelemetry.exporter.internal.otlp.traces.ResourceSpansMarshaler; import io.opentelemetry.exporter.internal.retry.RetryPolicy; import io.opentelemetry.exporter.internal.retry.RetryUtil; -import io.opentelemetry.internal.testing.slf4j.SuppressLogger; -import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; -import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse; +import io.opentelemetry.exporter.otlp.testing.internal.AbstractHttpTelemetryExporterTest; +import io.opentelemetry.exporter.otlp.testing.internal.FakeTelemetryUtil; +import io.opentelemetry.exporter.otlp.testing.internal.TelemetryExporter; +import io.opentelemetry.exporter.otlp.testing.internal.TelemetryExporterBuilder; import io.opentelemetry.proto.trace.v1.ResourceSpans; -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.common.InstrumentationScopeInfo; -import io.opentelemetry.sdk.testing.trace.TestSpanData; import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.data.StatusData; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; -import okhttp3.tls.HeldCertificate; -import okio.Buffer; -import okio.GzipSource; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.slf4j.event.Level; -import org.slf4j.event.LoggingEvent; - -class OtlpHttpSpanExporterTest { - - private static final MediaType APPLICATION_PROTOBUF = - MediaType.create("application", "x-protobuf"); - private static final HeldCertificate HELD_CERTIFICATE; - private static final String canonicalHostName; - - static { - try { - canonicalHostName = InetAddress.getByName("localhost").getCanonicalHostName(); - HELD_CERTIFICATE = - new HeldCertificate.Builder() - .commonName("localhost") - .addSubjectAlternativeName(canonicalHostName) - .build(); - } catch (UnknownHostException e) { - throw new IllegalStateException("Error building certificate.", e); - } - } - - @RegisterExtension - static MockWebServerExtension server = - new MockWebServerExtension() { - @Override - protected void configureServer(ServerBuilder sb) { - sb.http(0); - sb.https(0); - sb.tls(HELD_CERTIFICATE.keyPair().getPrivate(), HELD_CERTIFICATE.certificate()); - } - }; - - @RegisterExtension LogCapturer logs = LogCapturer.create().captureForType(OkHttpExporter.class); - - private OtlpHttpSpanExporterBuilder builder; - - @BeforeEach - void setup() { - builder = - OtlpHttpSpanExporter.builder() - .setEndpoint("http://" + canonicalHostName + ":" + server.httpPort() + "/v1/traces") - .addHeader("foo", "bar"); - } - - @Test - @SuppressWarnings("PreferJavaTimeOverload") - void validConfig() { - assertThatCode(() -> OtlpHttpSpanExporter.builder().setTimeout(0, TimeUnit.MILLISECONDS)) - .doesNotThrowAnyException(); - assertThatCode(() -> OtlpHttpSpanExporter.builder().setTimeout(Duration.ofMillis(0))) - .doesNotThrowAnyException(); - assertThatCode(() -> OtlpHttpSpanExporter.builder().setTimeout(10, TimeUnit.MILLISECONDS)) - .doesNotThrowAnyException(); - assertThatCode(() -> OtlpHttpSpanExporter.builder().setTimeout(Duration.ofMillis(10))) - .doesNotThrowAnyException(); - - assertThatCode(() -> OtlpHttpSpanExporter.builder().setEndpoint("http://localhost:4318")) - .doesNotThrowAnyException(); - assertThatCode(() -> OtlpHttpSpanExporter.builder().setEndpoint("http://localhost")) - .doesNotThrowAnyException(); - assertThatCode(() -> OtlpHttpSpanExporter.builder().setEndpoint("https://localhost")) - .doesNotThrowAnyException(); - assertThatCode(() -> OtlpHttpSpanExporter.builder().setEndpoint("http://foo:bar@localhost")) - .doesNotThrowAnyException(); - - assertThatCode(() -> OtlpHttpSpanExporter.builder().setCompression("gzip")) - .doesNotThrowAnyException(); - assertThatCode(() -> OtlpHttpSpanExporter.builder().setCompression("none")) - .doesNotThrowAnyException(); - - assertThatCode( - () -> OtlpHttpSpanExporter.builder().addHeader("foo", "bar").addHeader("baz", "qux")) - .doesNotThrowAnyException(); - - assertThatCode( - () -> - OtlpHttpSpanExporter.builder() - .setTrustedCertificates("foobar".getBytes(StandardCharsets.UTF_8))) - .doesNotThrowAnyException(); - - assertThatCode( - () -> - OtlpHttpSpanExporter.builder() - .setClientTls( - "foobar".getBytes(StandardCharsets.UTF_8), - "foobar".getBytes(StandardCharsets.UTF_8))) - .doesNotThrowAnyException(); - } - - @Test - @SuppressWarnings("PreferJavaTimeOverload") - void invalidConfig() { - assertThatThrownBy(() -> OtlpHttpSpanExporter.builder().setTimeout(-1, TimeUnit.MILLISECONDS)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("timeout must be non-negative"); - assertThatThrownBy(() -> OtlpHttpSpanExporter.builder().setTimeout(1, null)) - .isInstanceOf(NullPointerException.class) - .hasMessage("unit"); - assertThatThrownBy(() -> OtlpHttpSpanExporter.builder().setTimeout(null)) - .isInstanceOf(NullPointerException.class) - .hasMessage("timeout"); - - assertThatThrownBy(() -> OtlpHttpSpanExporter.builder().setEndpoint(null)) - .isInstanceOf(NullPointerException.class) - .hasMessage("endpoint"); - assertThatThrownBy(() -> OtlpHttpSpanExporter.builder().setEndpoint("😺://localhost")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Invalid endpoint, must be a URL: 😺://localhost"); - assertThatThrownBy(() -> OtlpHttpSpanExporter.builder().setEndpoint("localhost")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Invalid endpoint, must start with http:// or https://: localhost"); - assertThatThrownBy(() -> OtlpHttpSpanExporter.builder().setEndpoint("gopher://localhost")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Invalid endpoint, must start with http:// or https://: gopher://localhost"); - - assertThatThrownBy(() -> OtlpHttpSpanExporter.builder().setCompression(null)) - .isInstanceOf(NullPointerException.class) - .hasMessage("compressionMethod"); - assertThatThrownBy(() -> OtlpHttpSpanExporter.builder().setCompression("foo")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage( - "Unsupported compression method. Supported compression methods include: gzip, none."); - } - - @Test - void testSetRetryPolicyOnDelegate() { - assertThatCode( - () -> - RetryUtil.setRetryPolicyOnDelegate( - OtlpHttpSpanExporter.builder(), RetryPolicy.getDefault())) - .doesNotThrowAnyException(); - } - - @Test - void testExportUncompressed() { - server.enqueue(successResponse()); - OtlpHttpSpanExporter exporter = builder.build(); - - ExportTraceServiceRequest payload = exportAndAssertResult(exporter, /* expectedResult= */ true); - RecordedRequest recorded = server.takeRequest(); - AggregatedHttpRequest request = recorded.request(); - assertRequestCommon(request); - assertThat(parseRequestBody(request.content().array())).isEqualTo(payload); - - // OkHttp does not support HTTP/2 upgrade on plaintext. - assertThat(recorded.context().sessionProtocol().isMultiplex()).isFalse(); - } - - @Test - void testExportTls() { - server.enqueue(successResponse()); - OtlpHttpSpanExporter exporter = - builder - .setEndpoint("https://" + canonicalHostName + ":" + server.httpsPort() + "/v1/traces") - .setTrustedCertificates( - HELD_CERTIFICATE.certificatePem().getBytes(StandardCharsets.UTF_8)) - .build(); - - ExportTraceServiceRequest payload = exportAndAssertResult(exporter, /* expectedResult= */ true); - RecordedRequest recorded = server.takeRequest(); - AggregatedHttpRequest request = recorded.request(); - assertRequestCommon(request); - assertThat(parseRequestBody(request.content().array())).isEqualTo(payload); - - // OkHttp does support HTTP/2 upgrade on TLS. - assertThat(recorded.context().sessionProtocol().isMultiplex()).isTrue(); - } - - @Test - void testExportGzipCompressed() { - server.enqueue(successResponse()); - OtlpHttpSpanExporter exporter = builder.setCompression("gzip").build(); - - ExportTraceServiceRequest payload = exportAndAssertResult(exporter, /* expectedResult= */ true); - AggregatedHttpRequest request = server.takeRequest().request(); - assertRequestCommon(request); - assertThat(request.headers().get("Content-Encoding")).isEqualTo("gzip"); - assertThat(parseRequestBody(gzipDecompress(request.content().array()))).isEqualTo(payload); - } - - private static void assertRequestCommon(AggregatedHttpRequest request) { - assertThat(request.method()).isEqualTo(HttpMethod.POST); - assertThat(request.path()).isEqualTo("/v1/traces"); - assertThat(request.headers().get("foo")).isEqualTo("bar"); - assertThat(request.headers().get("Content-Type")).isEqualTo(APPLICATION_PROTOBUF.toString()); - } - - private static ExportTraceServiceRequest parseRequestBody(byte[] bytes) { - try { - return ExportTraceServiceRequest.parseFrom(bytes); - } catch (InvalidProtocolBufferException e) { - throw new IllegalStateException("Unable to parse Protobuf request body.", e); - } - } - - private static byte[] gzipDecompress(byte[] bytes) { - try { - Buffer result = new Buffer(); - GzipSource source = new GzipSource(new Buffer().write(bytes)); - while (source.read(result, Integer.MAX_VALUE) != -1) {} - return result.readByteArray(); - } catch (IOException e) { - throw new IllegalStateException("Unable to decompress payload.", e); - } - } - - @Test - @SuppressLogger(OkHttpExporter.class) - void testServerError() { - server.enqueue( - buildResponse( - HttpStatus.INTERNAL_SERVER_ERROR, - Status.newBuilder().setMessage("Server error!").build())); - OtlpHttpSpanExporter exporter = builder.build(); - - exportAndAssertResult(exporter, /* expectedResult= */ false); - LoggingEvent log = - logs.assertContains( - "Failed to export spans. Server responded with HTTP status code 500. Error message: Server error!"); - assertThat(log.getLevel()).isEqualTo(Level.WARN); - } - - @Test - @SuppressLogger(OkHttpExporter.class) - void testServerErrorParseError() { - server.enqueue( - HttpResponse.of(HttpStatus.INTERNAL_SERVER_ERROR, APPLICATION_PROTOBUF, "Server error!")); - OtlpHttpSpanExporter exporter = builder.build(); - - exportAndAssertResult(exporter, /* expectedResult= */ false); - LoggingEvent log = - logs.assertContains( - "Failed to export spans. Server responded with HTTP status code 500. Error message: Unable to parse response body, HTTP status message:"); - assertThat(log.getLevel()).isEqualTo(Level.WARN); - } - - private static ExportTraceServiceRequest exportAndAssertResult( - OtlpHttpSpanExporter otlpHttpSpanExporter, boolean expectedResult) { - List spans = Collections.singletonList(generateFakeSpan()); - CompletableResultCode resultCode = otlpHttpSpanExporter.export(spans); - resultCode.join(10, TimeUnit.SECONDS); - assertThat(resultCode.isSuccess()).isEqualTo(expectedResult); - List resourceSpans = - Arrays.stream(ResourceSpansMarshaler.create(spans)) - .map( - marshaler -> { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - try { - marshaler.writeBinaryTo(bos); - return ResourceSpans.parseFrom(bos.toByteArray()); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - }) - .collect(Collectors.toList()); - return ExportTraceServiceRequest.newBuilder().addAllResourceSpans(resourceSpans).build(); - } - - private static HttpResponse successResponse() { - ExportTraceServiceResponse exportTraceServiceResponse = - ExportTraceServiceResponse.newBuilder().build(); - return buildResponse(HttpStatus.OK, exportTraceServiceResponse); - } - - private static HttpResponse buildResponse(HttpStatus httpStatus, T message) { - return HttpResponse.of(httpStatus, APPLICATION_PROTOBUF, message.toByteArray()); - } - private static SpanData generateFakeSpan() { - long duration = TimeUnit.MILLISECONDS.toNanos(900); - long startNs = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()); - long endNs = startNs + duration; - return TestSpanData.builder() - .setHasEnded(true) - .setSpanContext( - SpanContext.create( - "00000000000000000000000000abc123", - "0000000000def456", - TraceFlags.getDefault(), - TraceState.getDefault())) - .setName("GET /api/endpoint") - .setStartEpochNanos(startNs) - .setEndEpochNanos(endNs) - .setStatus(StatusData.ok()) - .setKind(SpanKind.SERVER) - .setLinks(Collections.emptyList()) - .setTotalRecordedLinks(0) - .setTotalRecordedEvents(0) - .setInstrumentationScopeInfo( - InstrumentationScopeInfo.builder("testLib") - .setVersion("1.0") - .setSchemaUrl("http://url") - .build()) - .build(); +class OtlpHttpSpanExporterTest extends AbstractHttpTelemetryExporterTest { + + protected OtlpHttpSpanExporterTest() { + super("span", "/v1/traces", ResourceSpans.getDefaultInstance()); + } + + @Override + protected TelemetryExporterBuilder exporterBuilder() { + OtlpHttpSpanExporterBuilder builder = OtlpHttpSpanExporter.builder(); + return new TelemetryExporterBuilder() { + @Override + public TelemetryExporterBuilder setEndpoint(String endpoint) { + builder.setEndpoint(endpoint); + return this; + } + + @Override + public TelemetryExporterBuilder setTimeout(long timeout, TimeUnit unit) { + builder.setTimeout(timeout, unit); + return this; + } + + @Override + public TelemetryExporterBuilder setTimeout(Duration timeout) { + builder.setTimeout(timeout); + return this; + } + + @Override + public TelemetryExporterBuilder setCompression(String compression) { + builder.setCompression(compression); + return this; + } + + @Override + public TelemetryExporterBuilder addHeader(String key, String value) { + builder.addHeader(key, value); + return this; + } + + @Override + public TelemetryExporterBuilder setTrustedCertificates(byte[] certificates) { + builder.setTrustedCertificates(certificates); + return this; + } + + @Override + public TelemetryExporterBuilder setClientTls( + byte[] privateKeyPem, byte[] certificatePem) { + builder.setClientTls(privateKeyPem, certificatePem); + return this; + } + + @Override + public TelemetryExporterBuilder setRetryPolicy(RetryPolicy retryPolicy) { + RetryUtil.setRetryPolicyOnDelegate(builder, retryPolicy); + return this; + } + + @Override + public TelemetryExporterBuilder setChannel(io.grpc.ManagedChannel channel) { + throw new UnsupportedOperationException("Not implemented"); + } + + @Override + public TelemetryExporter build() { + return TelemetryExporter.wrap(builder.build()); + } + }; + } + + @Override + protected SpanData generateFakeTelemetry() { + return FakeTelemetryUtil.generateFakeSpanData(); + } + + @Override + protected Marshaler[] toMarshalers(List telemetry) { + return ResourceSpansMarshaler.create(telemetry); } } diff --git a/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/internal/OtlpMetricExporterProviderTest.java b/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/internal/OtlpMetricExporterProviderTest.java new file mode 100644 index 00000000000..bc898af7f99 --- /dev/null +++ b/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/internal/OtlpMetricExporterProviderTest.java @@ -0,0 +1,278 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.otlp.internal; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import com.linecorp.armeria.testing.junit5.server.SelfSignedCertificateExtension; +import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter; +import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporterBuilder; +import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; +import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import io.opentelemetry.sdk.metrics.export.MetricExporter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.cert.CertificateEncodingException; +import java.time.Duration; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class OtlpMetricExporterProviderTest { + + @RegisterExtension + static final SelfSignedCertificateExtension serverTls = new SelfSignedCertificateExtension(); + + @RegisterExtension + static final SelfSignedCertificateExtension clientTls = new SelfSignedCertificateExtension(); + + @Spy private OtlpMetricExporterProvider provider; + + @Spy private OtlpHttpMetricExporterBuilder httpBuilder; + + @Spy private OtlpGrpcMetricExporterBuilder grpcBuilder; + + private String certificatePath; + private String clientKeyPath; + private String clientCertificatePath; + + @BeforeEach + void setup(@TempDir Path tempDir) throws IOException, CertificateEncodingException { + doReturn(httpBuilder).when(provider).httpBuilder(); + doReturn(grpcBuilder).when(provider).grpcBuilder(); + + certificatePath = + createTempFileWithContent( + tempDir, "certificate.cert", serverTls.certificate().getEncoded()); + clientKeyPath = + createTempFileWithContent(tempDir, "clientKey.key", clientTls.privateKey().getEncoded()); + clientCertificatePath = + createTempFileWithContent( + tempDir, "clientCertificate.cert", clientTls.certificate().getEncoded()); + } + + private static String createTempFileWithContent(Path dir, String filename, byte[] content) + throws IOException { + Path path = dir.resolve(filename); + Files.write(path, content); + return path.toString(); + } + + @Test + void getName() { + assertThat(provider.getName()).isEqualTo("otlp"); + } + + @Test + void createExporter_UnsupportedProtocol() { + assertThatThrownBy( + () -> + provider.createExporter( + DefaultConfigProperties.createForTest( + Collections.singletonMap("otel.exporter.otlp.protocol", "foo")))) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining("Unsupported OTLP metrics protocol: foo"); + } + + @Test + void createExporter_NoMocks() { + // Verifies createExporter after resetting the spy overrides + Mockito.reset(provider); + try (MetricExporter exporter = + provider.createExporter(DefaultConfigProperties.createForTest(Collections.emptyMap()))) { + assertThat(exporter).isInstanceOf(OtlpGrpcMetricExporter.class); + } + try (MetricExporter exporter = + provider.createExporter( + DefaultConfigProperties.createForTest( + Collections.singletonMap("otel.exporter.otlp.protocol", "http/protobuf")))) { + assertThat(exporter).isInstanceOf(OtlpHttpMetricExporter.class); + } + } + + @Test + void createExporter_GrpcDefaults() { + try (MetricExporter exporter = + provider.createExporter(DefaultConfigProperties.createForTest(Collections.emptyMap()))) { + assertThat(exporter).isInstanceOf(OtlpGrpcMetricExporter.class); + verify(grpcBuilder, times(1)).build(); + verify(grpcBuilder, never()).setEndpoint(any()); + verify(grpcBuilder, never()).addHeader(any(), any()); + verify(grpcBuilder, never()).setCompression(any()); + verify(grpcBuilder, never()).setTimeout(any()); + verify(grpcBuilder, never()).setTrustedCertificates(any()); + verify(grpcBuilder, never()).setClientTls(any(), any()); + assertThat(grpcBuilder).extracting("delegate").extracting("retryPolicy").isNull(); + } + Mockito.verifyNoInteractions(httpBuilder); + } + + @Test + void createExporter_GrpcWithGeneralConfiguration() throws CertificateEncodingException { + Map config = new HashMap<>(); + config.put("otel.exporter.otlp.endpoint", "https://localhost:443/"); + config.put("otel.exporter.otlp.certificate", certificatePath); + config.put("otel.exporter.otlp.client.key", clientKeyPath); + config.put("otel.exporter.otlp.client.certificate", clientCertificatePath); + config.put("otel.exporter.otlp.headers", "header-key=header-value"); + config.put("otel.exporter.otlp.compression", "gzip"); + config.put("otel.exporter.otlp.timeout", "15s"); + config.put("otel.experimental.exporter.otlp.retry.enabled", "true"); + + try (MetricExporter exporter = + provider.createExporter(DefaultConfigProperties.createForTest(config))) { + assertThat(exporter).isInstanceOf(OtlpGrpcMetricExporter.class); + verify(grpcBuilder, times(1)).build(); + verify(grpcBuilder).setEndpoint("https://localhost:443/"); + verify(grpcBuilder).addHeader("header-key", "header-value"); + verify(grpcBuilder).setCompression("gzip"); + verify(grpcBuilder).setTimeout(Duration.ofSeconds(15)); + verify(grpcBuilder).setTrustedCertificates(serverTls.certificate().getEncoded()); + verify(grpcBuilder) + .setClientTls(clientTls.privateKey().getEncoded(), clientTls.certificate().getEncoded()); + assertThat(grpcBuilder).extracting("delegate").extracting("retryPolicy").isNotNull(); + } + Mockito.verifyNoInteractions(httpBuilder); + } + + @Test + void createExporter_GrpcWithSignalConfiguration() throws CertificateEncodingException { + Map config = new HashMap<>(); + config.put("otel.exporter.otlp.endpoint", "https://dummy:443/"); + config.put("otel.exporter.otlp.metrics.endpoint", "https://localhost:443/"); + config.put("otel.exporter.otlp.certificate", "dummy.cert"); + config.put("otel.exporter.otlp.metrics.certificate", certificatePath); + config.put("otel.exporter.otlp.client.key", "dummy.key"); + config.put("otel.exporter.otlp.metrics.client.key", clientKeyPath); + config.put("otel.exporter.otlp.client.certificate", "dummy-client.cert"); + config.put("otel.exporter.otlp.metrics.client.certificate", clientCertificatePath); + config.put("otel.exporter.otlp.headers", "dummy=value"); + config.put("otel.exporter.otlp.metrics.headers", "header-key=header-value"); + config.put("otel.exporter.otlp.compression", "none"); + config.put("otel.exporter.otlp.metrics.compression", "gzip"); + config.put("otel.exporter.otlp.timeout", "1s"); + config.put("otel.exporter.otlp.metrics.timeout", "15s"); + + try (MetricExporter exporter = + provider.createExporter(DefaultConfigProperties.createForTest(config))) { + assertThat(exporter).isInstanceOf(OtlpGrpcMetricExporter.class); + verify(grpcBuilder, times(1)).build(); + verify(grpcBuilder).setEndpoint("https://localhost:443/"); + verify(grpcBuilder).addHeader("header-key", "header-value"); + verify(grpcBuilder).setCompression("gzip"); + verify(grpcBuilder).setTimeout(Duration.ofSeconds(15)); + verify(grpcBuilder).setTrustedCertificates(serverTls.certificate().getEncoded()); + verify(grpcBuilder) + .setClientTls(clientTls.privateKey().getEncoded(), clientTls.certificate().getEncoded()); + } + Mockito.verifyNoInteractions(httpBuilder); + } + + @Test + void createExporter_HttpDefaults() { + try (MetricExporter exporter = + provider.createExporter( + DefaultConfigProperties.createForTest( + Collections.singletonMap( + "otel.exporter.otlp.metrics.protocol", "http/protobuf")))) { + assertThat(exporter).isInstanceOf(OtlpHttpMetricExporter.class); + verify(httpBuilder, times(1)).build(); + verify(httpBuilder, never()).setEndpoint(any()); + verify(httpBuilder, never()).addHeader(any(), any()); + verify(httpBuilder, never()).setCompression(any()); + verify(httpBuilder, never()).setTimeout(any()); + verify(httpBuilder, never()).setTrustedCertificates(any()); + verify(httpBuilder, never()).setClientTls(any(), any()); + assertThat(httpBuilder).extracting("delegate").extracting("retryPolicy").isNull(); + } + Mockito.verifyNoInteractions(grpcBuilder); + } + + @Test + void createExporter_HttpWithGeneralConfiguration() throws CertificateEncodingException { + Map config = new HashMap<>(); + config.put("otel.exporter.otlp.protocol", "http/protobuf"); + config.put("otel.exporter.otlp.endpoint", "https://localhost:443/"); + config.put("otel.exporter.otlp.certificate", certificatePath); + config.put("otel.exporter.otlp.client.key", clientKeyPath); + config.put("otel.exporter.otlp.client.certificate", clientCertificatePath); + config.put("otel.exporter.otlp.headers", "header-key=header-value"); + config.put("otel.exporter.otlp.compression", "gzip"); + config.put("otel.exporter.otlp.timeout", "15s"); + config.put("otel.experimental.exporter.otlp.retry.enabled", "true"); + + try (MetricExporter exporter = + provider.createExporter(DefaultConfigProperties.createForTest(config))) { + assertThat(exporter).isInstanceOf(OtlpHttpMetricExporter.class); + verify(httpBuilder, times(1)).build(); + verify(httpBuilder).setEndpoint("https://localhost:443/v1/metrics"); + verify(httpBuilder).addHeader("header-key", "header-value"); + verify(httpBuilder).setCompression("gzip"); + verify(httpBuilder).setTimeout(Duration.ofSeconds(15)); + verify(httpBuilder).setTrustedCertificates(serverTls.certificate().getEncoded()); + verify(httpBuilder) + .setClientTls(clientTls.privateKey().getEncoded(), clientTls.certificate().getEncoded()); + assertThat(httpBuilder).extracting("delegate").extracting("retryPolicy").isNotNull(); + } + Mockito.verifyNoInteractions(grpcBuilder); + } + + @Test + void createExporter_HttpWithSignalConfiguration() throws CertificateEncodingException { + Map config = new HashMap<>(); + config.put("otel.exporter.otlp.protocol", "grpc"); + config.put("otel.exporter.otlp.metrics.protocol", "http/protobuf"); + config.put("otel.exporter.otlp.endpoint", "https://dummy:443/"); + config.put("otel.exporter.otlp.metrics.endpoint", "https://localhost:443/v1/metrics"); + config.put("otel.exporter.otlp.certificate", "dummy.cert"); + config.put("otel.exporter.otlp.metrics.certificate", certificatePath); + config.put("otel.exporter.otlp.client.key", "dummy.key"); + config.put("otel.exporter.otlp.metrics.client.key", clientKeyPath); + config.put("otel.exporter.otlp.client.certificate", "dummy-client.cert"); + config.put("otel.exporter.otlp.metrics.client.certificate", clientCertificatePath); + config.put("otel.exporter.otlp.headers", "dummy=value"); + config.put("otel.exporter.otlp.metrics.headers", "header-key=header-value"); + config.put("otel.exporter.otlp.compression", "none"); + config.put("otel.exporter.otlp.metrics.compression", "gzip"); + config.put("otel.exporter.otlp.timeout", "1s"); + config.put("otel.exporter.otlp.metrics.timeout", "15s"); + + try (MetricExporter exporter = + provider.createExporter(DefaultConfigProperties.createForTest(config))) { + assertThat(exporter).isInstanceOf(OtlpHttpMetricExporter.class); + verify(httpBuilder, times(1)).build(); + verify(httpBuilder).setEndpoint("https://localhost:443/v1/metrics"); + verify(httpBuilder).addHeader("header-key", "header-value"); + verify(httpBuilder).setCompression("gzip"); + verify(httpBuilder).setTimeout(Duration.ofSeconds(15)); + verify(httpBuilder).setTrustedCertificates(serverTls.certificate().getEncoded()); + verify(httpBuilder) + .setClientTls(clientTls.privateKey().getEncoded(), clientTls.certificate().getEncoded()); + } + Mockito.verifyNoInteractions(grpcBuilder); + } +} diff --git a/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/internal/OtlpSpanExporterProviderTest.java b/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/internal/OtlpSpanExporterProviderTest.java new file mode 100644 index 00000000000..0ae50db5a9e --- /dev/null +++ b/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/internal/OtlpSpanExporterProviderTest.java @@ -0,0 +1,277 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.otlp.internal; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import com.linecorp.armeria.testing.junit5.server.SelfSignedCertificateExtension; +import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; +import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporterBuilder; +import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; +import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporterBuilder; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.cert.CertificateEncodingException; +import java.time.Duration; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class OtlpSpanExporterProviderTest { + + @RegisterExtension + static final SelfSignedCertificateExtension serverTls = new SelfSignedCertificateExtension(); + + @RegisterExtension + static final SelfSignedCertificateExtension clientTls = new SelfSignedCertificateExtension(); + + @Spy private OtlpSpanExporterProvider provider; + + @Spy private OtlpHttpSpanExporterBuilder httpBuilder; + + @Spy private OtlpGrpcSpanExporterBuilder grpcBuilder; + + private String certificatePath; + private String clientKeyPath; + private String clientCertificatePath; + + @BeforeEach + void setup(@TempDir Path tempDir) throws IOException, CertificateEncodingException { + doReturn(httpBuilder).when(provider).httpBuilder(); + doReturn(grpcBuilder).when(provider).grpcBuilder(); + + certificatePath = + createTempFileWithContent( + tempDir, "certificate.cert", serverTls.certificate().getEncoded()); + clientKeyPath = + createTempFileWithContent(tempDir, "clientKey.key", clientTls.privateKey().getEncoded()); + clientCertificatePath = + createTempFileWithContent( + tempDir, "clientCertificate.cert", clientTls.certificate().getEncoded()); + } + + private static String createTempFileWithContent(Path dir, String filename, byte[] content) + throws IOException { + Path path = dir.resolve(filename); + Files.write(path, content); + return path.toString(); + } + + @Test + void getName() { + assertThat(provider.getName()).isEqualTo("otlp"); + } + + @Test + void createExporter_UnsupportedProtocol() { + assertThatThrownBy( + () -> + provider.createExporter( + DefaultConfigProperties.createForTest( + Collections.singletonMap("otel.exporter.otlp.protocol", "foo")))) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining("Unsupported OTLP traces protocol: foo"); + } + + @Test + void createExporter_NoMocks() { + // Verifies createExporter after resetting the spy overrides + Mockito.reset(provider); + try (SpanExporter exporter = + provider.createExporter(DefaultConfigProperties.createForTest(Collections.emptyMap()))) { + assertThat(exporter).isInstanceOf(OtlpGrpcSpanExporter.class); + } + try (SpanExporter exporter = + provider.createExporter( + DefaultConfigProperties.createForTest( + Collections.singletonMap("otel.exporter.otlp.protocol", "http/protobuf")))) { + assertThat(exporter).isInstanceOf(OtlpHttpSpanExporter.class); + } + } + + @Test + void createExporter_GrpcDefaults() { + try (SpanExporter exporter = + provider.createExporter(DefaultConfigProperties.createForTest(Collections.emptyMap()))) { + assertThat(exporter).isInstanceOf(OtlpGrpcSpanExporter.class); + verify(grpcBuilder, times(1)).build(); + verify(grpcBuilder, never()).setEndpoint(any()); + verify(grpcBuilder, never()).addHeader(any(), any()); + verify(grpcBuilder, never()).setCompression(any()); + verify(grpcBuilder, never()).setTimeout(any()); + verify(grpcBuilder, never()).setTrustedCertificates(any()); + verify(grpcBuilder, never()).setClientTls(any(), any()); + assertThat(grpcBuilder).extracting("delegate").extracting("retryPolicy").isNull(); + } + Mockito.verifyNoInteractions(httpBuilder); + } + + @Test + void createExporter_GrpcWithGeneralConfiguration() throws CertificateEncodingException { + Map config = new HashMap<>(); + config.put("otel.exporter.otlp.endpoint", "https://localhost:443/"); + config.put("otel.exporter.otlp.certificate", certificatePath); + config.put("otel.exporter.otlp.client.key", clientKeyPath); + config.put("otel.exporter.otlp.client.certificate", clientCertificatePath); + config.put("otel.exporter.otlp.headers", "header-key=header-value"); + config.put("otel.exporter.otlp.compression", "gzip"); + config.put("otel.exporter.otlp.timeout", "15s"); + config.put("otel.experimental.exporter.otlp.retry.enabled", "true"); + + try (SpanExporter exporter = + provider.createExporter(DefaultConfigProperties.createForTest(config))) { + assertThat(exporter).isInstanceOf(OtlpGrpcSpanExporter.class); + verify(grpcBuilder, times(1)).build(); + verify(grpcBuilder).setEndpoint("https://localhost:443/"); + verify(grpcBuilder).addHeader("header-key", "header-value"); + verify(grpcBuilder).setCompression("gzip"); + verify(grpcBuilder).setTimeout(Duration.ofSeconds(15)); + verify(grpcBuilder).setTrustedCertificates(serverTls.certificate().getEncoded()); + verify(grpcBuilder) + .setClientTls(clientTls.privateKey().getEncoded(), clientTls.certificate().getEncoded()); + assertThat(grpcBuilder).extracting("delegate").extracting("retryPolicy").isNotNull(); + } + Mockito.verifyNoInteractions(httpBuilder); + } + + @Test + void createExporter_GrpcWithSignalConfiguration() throws CertificateEncodingException { + Map config = new HashMap<>(); + config.put("otel.exporter.otlp.endpoint", "https://dummy:443/"); + config.put("otel.exporter.otlp.traces.endpoint", "https://localhost:443/"); + config.put("otel.exporter.otlp.certificate", "dummy.cert"); + config.put("otel.exporter.otlp.traces.certificate", certificatePath); + config.put("otel.exporter.otlp.client.key", "dummy.key"); + config.put("otel.exporter.otlp.traces.client.key", clientKeyPath); + config.put("otel.exporter.otlp.client.certificate", "dummy-client.cert"); + config.put("otel.exporter.otlp.traces.client.certificate", clientCertificatePath); + config.put("otel.exporter.otlp.headers", "dummy=value"); + config.put("otel.exporter.otlp.traces.headers", "header-key=header-value"); + config.put("otel.exporter.otlp.compression", "none"); + config.put("otel.exporter.otlp.traces.compression", "gzip"); + config.put("otel.exporter.otlp.timeout", "1s"); + config.put("otel.exporter.otlp.traces.timeout", "15s"); + + try (SpanExporter exporter = + provider.createExporter(DefaultConfigProperties.createForTest(config))) { + assertThat(exporter).isInstanceOf(OtlpGrpcSpanExporter.class); + verify(grpcBuilder, times(1)).build(); + verify(grpcBuilder).setEndpoint("https://localhost:443/"); + verify(grpcBuilder).addHeader("header-key", "header-value"); + verify(grpcBuilder).setCompression("gzip"); + verify(grpcBuilder).setTimeout(Duration.ofSeconds(15)); + verify(grpcBuilder).setTrustedCertificates(serverTls.certificate().getEncoded()); + verify(grpcBuilder) + .setClientTls(clientTls.privateKey().getEncoded(), clientTls.certificate().getEncoded()); + } + Mockito.verifyNoInteractions(httpBuilder); + } + + @Test + void createExporter_HttpDefaults() { + try (SpanExporter exporter = + provider.createExporter( + DefaultConfigProperties.createForTest( + Collections.singletonMap("otel.exporter.otlp.traces.protocol", "http/protobuf")))) { + assertThat(exporter).isInstanceOf(OtlpHttpSpanExporter.class); + verify(httpBuilder, times(1)).build(); + verify(httpBuilder, never()).setEndpoint(any()); + verify(httpBuilder, never()).addHeader(any(), any()); + verify(httpBuilder, never()).setCompression(any()); + verify(httpBuilder, never()).setTimeout(any()); + verify(httpBuilder, never()).setTrustedCertificates(any()); + verify(httpBuilder, never()).setClientTls(any(), any()); + assertThat(httpBuilder).extracting("delegate").extracting("retryPolicy").isNull(); + } + Mockito.verifyNoInteractions(grpcBuilder); + } + + @Test + void createExporter_HttpWithGeneralConfiguration() throws CertificateEncodingException { + Map config = new HashMap<>(); + config.put("otel.exporter.otlp.protocol", "http/protobuf"); + config.put("otel.exporter.otlp.endpoint", "https://localhost:443/"); + config.put("otel.exporter.otlp.certificate", certificatePath); + config.put("otel.exporter.otlp.client.key", clientKeyPath); + config.put("otel.exporter.otlp.client.certificate", clientCertificatePath); + config.put("otel.exporter.otlp.headers", "header-key=header-value"); + config.put("otel.exporter.otlp.compression", "gzip"); + config.put("otel.exporter.otlp.timeout", "15s"); + config.put("otel.experimental.exporter.otlp.retry.enabled", "true"); + + try (SpanExporter exporter = + provider.createExporter(DefaultConfigProperties.createForTest(config))) { + assertThat(exporter).isInstanceOf(OtlpHttpSpanExporter.class); + verify(httpBuilder, times(1)).build(); + verify(httpBuilder).setEndpoint("https://localhost:443/v1/traces"); + verify(httpBuilder).addHeader("header-key", "header-value"); + verify(httpBuilder).setCompression("gzip"); + verify(httpBuilder).setTimeout(Duration.ofSeconds(15)); + verify(httpBuilder).setTrustedCertificates(serverTls.certificate().getEncoded()); + verify(httpBuilder) + .setClientTls(clientTls.privateKey().getEncoded(), clientTls.certificate().getEncoded()); + assertThat(httpBuilder).extracting("delegate").extracting("retryPolicy").isNotNull(); + } + Mockito.verifyNoInteractions(grpcBuilder); + } + + @Test + void createExporter_HttpWithSignalConfiguration() throws CertificateEncodingException { + Map config = new HashMap<>(); + config.put("otel.exporter.otlp.protocol", "grpc"); + config.put("otel.exporter.otlp.traces.protocol", "http/protobuf"); + config.put("otel.exporter.otlp.endpoint", "https://dummy:443/"); + config.put("otel.exporter.otlp.traces.endpoint", "https://localhost:443/v1/traces"); + config.put("otel.exporter.otlp.certificate", "dummy.cert"); + config.put("otel.exporter.otlp.traces.certificate", certificatePath); + config.put("otel.exporter.otlp.client.key", "dummy.key"); + config.put("otel.exporter.otlp.traces.client.key", clientKeyPath); + config.put("otel.exporter.otlp.client.certificate", "dummy-client.cert"); + config.put("otel.exporter.otlp.traces.client.certificate", clientCertificatePath); + config.put("otel.exporter.otlp.headers", "dummy=value"); + config.put("otel.exporter.otlp.traces.headers", "header-key=header-value"); + config.put("otel.exporter.otlp.compression", "none"); + config.put("otel.exporter.otlp.traces.compression", "gzip"); + config.put("otel.exporter.otlp.timeout", "1s"); + config.put("otel.exporter.otlp.traces.timeout", "15s"); + + try (SpanExporter exporter = + provider.createExporter(DefaultConfigProperties.createForTest(config))) { + assertThat(exporter).isInstanceOf(OtlpHttpSpanExporter.class); + verify(httpBuilder, times(1)).build(); + verify(httpBuilder).setEndpoint("https://localhost:443/v1/traces"); + verify(httpBuilder).addHeader("header-key", "header-value"); + verify(httpBuilder).setCompression("gzip"); + verify(httpBuilder).setTimeout(Duration.ofSeconds(15)); + verify(httpBuilder).setTrustedCertificates(serverTls.certificate().getEncoded()); + verify(httpBuilder) + .setClientTls(clientTls.privateKey().getEncoded(), clientTls.certificate().getEncoded()); + } + Mockito.verifyNoInteractions(grpcBuilder); + } +} diff --git a/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcMetricExporterTest.java b/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcMetricExporterTest.java index cdf13fda16d..8cbad81ea58 100644 --- a/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcMetricExporterTest.java +++ b/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcMetricExporterTest.java @@ -5,35 +5,27 @@ package io.opentelemetry.exporter.otlp.metrics; -import static io.opentelemetry.api.common.AttributeKey.stringKey; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import io.opentelemetry.api.common.Attributes; import io.opentelemetry.exporter.internal.grpc.OkHttpGrpcExporter; import io.opentelemetry.exporter.internal.marshal.Marshaler; import io.opentelemetry.exporter.internal.otlp.metrics.ResourceMetricsMarshaler; import io.opentelemetry.exporter.internal.retry.RetryPolicy; import io.opentelemetry.exporter.internal.retry.RetryUtil; import io.opentelemetry.exporter.otlp.testing.internal.AbstractGrpcTelemetryExporterTest; +import io.opentelemetry.exporter.otlp.testing.internal.FakeTelemetryUtil; import io.opentelemetry.exporter.otlp.testing.internal.TelemetryExporterBuilder; import io.opentelemetry.proto.metrics.v1.ResourceMetrics; -import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.metrics.Aggregation; import io.opentelemetry.sdk.metrics.InstrumentType; import io.opentelemetry.sdk.metrics.data.AggregationTemporality; import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.metrics.export.AggregationTemporalitySelector; import io.opentelemetry.sdk.metrics.export.DefaultAggregationSelector; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableLongPointData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableMetricData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableSumData; -import io.opentelemetry.sdk.resources.Resource; import java.io.Closeable; -import java.util.Collections; import java.util.List; -import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; class OtlpGrpcMetricExporterTest @@ -110,20 +102,7 @@ protected TelemetryExporterBuilder exporterBuilder() { @Override protected MetricData generateFakeTelemetry() { - long startNs = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()); - long endNs = startNs + TimeUnit.MILLISECONDS.toNanos(900); - return ImmutableMetricData.createLongSum( - Resource.empty(), - InstrumentationScopeInfo.empty(), - "name", - "description", - "1", - ImmutableSumData.create( - /* isMonotonic= */ true, - AggregationTemporality.CUMULATIVE, - Collections.singletonList( - ImmutableLongPointData.create( - startNs, endNs, Attributes.of(stringKey("k"), "v"), 5)))); + return FakeTelemetryUtil.generateFakeMetricData(); } @Override diff --git a/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/trace/OtlpGrpcSpanExporterTest.java b/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/trace/OtlpGrpcSpanExporterTest.java index f411e7f6fb0..3ed15a1ac79 100644 --- a/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/trace/OtlpGrpcSpanExporterTest.java +++ b/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/trace/OtlpGrpcSpanExporterTest.java @@ -8,33 +8,22 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; -import io.opentelemetry.api.trace.SpanContext; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.TraceFlags; -import io.opentelemetry.api.trace.TraceState; import io.opentelemetry.exporter.internal.grpc.OkHttpGrpcExporter; import io.opentelemetry.exporter.internal.marshal.Marshaler; import io.opentelemetry.exporter.internal.otlp.traces.ResourceSpansMarshaler; import io.opentelemetry.exporter.internal.retry.RetryPolicy; import io.opentelemetry.exporter.internal.retry.RetryUtil; import io.opentelemetry.exporter.otlp.testing.internal.AbstractGrpcTelemetryExporterTest; +import io.opentelemetry.exporter.otlp.testing.internal.FakeTelemetryUtil; import io.opentelemetry.exporter.otlp.testing.internal.TelemetryExporterBuilder; import io.opentelemetry.proto.trace.v1.ResourceSpans; -import io.opentelemetry.sdk.common.InstrumentationScopeInfo; -import io.opentelemetry.sdk.testing.trace.TestSpanData; import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.data.StatusData; import java.io.Closeable; -import java.util.Collections; import java.util.List; -import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; class OtlpGrpcSpanExporterTest extends AbstractGrpcTelemetryExporterTest { - private static final String TRACE_ID = "00000000000000000000000000abc123"; - private static final String SPAN_ID = "0000000000def456"; - OtlpGrpcSpanExporterTest() { super("span", ResourceSpans.getDefaultInstance()); } @@ -62,27 +51,7 @@ protected TelemetryExporterBuilder exporterBuilder() { @Override protected SpanData generateFakeTelemetry() { - long duration = TimeUnit.MILLISECONDS.toNanos(900); - long startNs = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()); - long endNs = startNs + duration; - return TestSpanData.builder() - .setHasEnded(true) - .setSpanContext( - SpanContext.create(TRACE_ID, SPAN_ID, TraceFlags.getDefault(), TraceState.getDefault())) - .setName("GET /api/endpoint") - .setStartEpochNanos(startNs) - .setEndEpochNanos(endNs) - .setStatus(StatusData.ok()) - .setKind(SpanKind.SERVER) - .setLinks(Collections.emptyList()) - .setTotalRecordedLinks(0) - .setTotalRecordedEvents(0) - .setInstrumentationScopeInfo( - InstrumentationScopeInfo.builder("testLib") - .setVersion("1.0") - .setSchemaUrl("http://url") - .build()) - .build(); + return FakeTelemetryUtil.generateFakeSpanData(); } @Override diff --git a/exporters/otlp/all/src/testGrpcNetty/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcNettyMetricExporterTest.java b/exporters/otlp/all/src/testGrpcNetty/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcNettyMetricExporterTest.java index 7c1bbc5fb54..b2468379c15 100644 --- a/exporters/otlp/all/src/testGrpcNetty/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcNettyMetricExporterTest.java +++ b/exporters/otlp/all/src/testGrpcNetty/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcNettyMetricExporterTest.java @@ -5,32 +5,23 @@ package io.opentelemetry.exporter.otlp.metrics; -import static io.opentelemetry.api.common.AttributeKey.stringKey; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import io.grpc.inprocess.InProcessChannelBuilder; -import io.opentelemetry.api.common.Attributes; import io.opentelemetry.exporter.internal.grpc.UpstreamGrpcExporter; import io.opentelemetry.exporter.internal.marshal.Marshaler; import io.opentelemetry.exporter.internal.otlp.metrics.ResourceMetricsMarshaler; import io.opentelemetry.exporter.internal.retry.RetryPolicy; import io.opentelemetry.exporter.internal.retry.RetryUtil; import io.opentelemetry.exporter.otlp.testing.internal.AbstractGrpcTelemetryExporterTest; +import io.opentelemetry.exporter.otlp.testing.internal.FakeTelemetryUtil; import io.opentelemetry.exporter.otlp.testing.internal.ManagedChannelTelemetryExporterBuilder; import io.opentelemetry.exporter.otlp.testing.internal.TelemetryExporterBuilder; import io.opentelemetry.proto.metrics.v1.ResourceMetrics; -import io.opentelemetry.sdk.common.InstrumentationScopeInfo; -import io.opentelemetry.sdk.metrics.data.AggregationTemporality; import io.opentelemetry.sdk.metrics.data.MetricData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableLongPointData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableMetricData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableSumData; -import io.opentelemetry.sdk.resources.Resource; import java.io.Closeable; -import java.util.Collections; import java.util.List; -import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; class OtlpGrpcNettyMetricExporterTest @@ -68,20 +59,7 @@ protected TelemetryExporterBuilder exporterBuilder() { @Override protected MetricData generateFakeTelemetry() { - long startNs = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()); - long endNs = startNs + TimeUnit.MILLISECONDS.toNanos(900); - return ImmutableMetricData.createLongSum( - Resource.empty(), - InstrumentationScopeInfo.empty(), - "name", - "description", - "1", - ImmutableSumData.create( - /* isMonotonic= */ true, - AggregationTemporality.CUMULATIVE, - Collections.singletonList( - ImmutableLongPointData.create( - startNs, endNs, Attributes.of(stringKey("k"), "v"), 5)))); + return FakeTelemetryUtil.generateFakeMetricData(); } @Override diff --git a/exporters/otlp/all/src/testGrpcNetty/java/io/opentelemetry/exporter/otlp/trace/OtlpGrpcNettySpanExporterTest.java b/exporters/otlp/all/src/testGrpcNetty/java/io/opentelemetry/exporter/otlp/trace/OtlpGrpcNettySpanExporterTest.java index 764f924dd8f..956a73354cb 100644 --- a/exporters/otlp/all/src/testGrpcNetty/java/io/opentelemetry/exporter/otlp/trace/OtlpGrpcNettySpanExporterTest.java +++ b/exporters/otlp/all/src/testGrpcNetty/java/io/opentelemetry/exporter/otlp/trace/OtlpGrpcNettySpanExporterTest.java @@ -9,35 +9,24 @@ import static org.assertj.core.api.Assertions.assertThatCode; import io.grpc.inprocess.InProcessChannelBuilder; -import io.opentelemetry.api.trace.SpanContext; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.TraceFlags; -import io.opentelemetry.api.trace.TraceState; import io.opentelemetry.exporter.internal.grpc.UpstreamGrpcExporter; import io.opentelemetry.exporter.internal.marshal.Marshaler; import io.opentelemetry.exporter.internal.otlp.traces.ResourceSpansMarshaler; import io.opentelemetry.exporter.internal.retry.RetryPolicy; import io.opentelemetry.exporter.internal.retry.RetryUtil; import io.opentelemetry.exporter.otlp.testing.internal.AbstractGrpcTelemetryExporterTest; +import io.opentelemetry.exporter.otlp.testing.internal.FakeTelemetryUtil; import io.opentelemetry.exporter.otlp.testing.internal.ManagedChannelTelemetryExporterBuilder; import io.opentelemetry.exporter.otlp.testing.internal.TelemetryExporterBuilder; import io.opentelemetry.proto.trace.v1.ResourceSpans; -import io.opentelemetry.sdk.common.InstrumentationScopeInfo; -import io.opentelemetry.sdk.testing.trace.TestSpanData; import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.data.StatusData; import java.io.Closeable; -import java.util.Collections; import java.util.List; -import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; class OtlpGrpcNettySpanExporterTest extends AbstractGrpcTelemetryExporterTest { - private static final String TRACE_ID = "00000000000000000000000000abc123"; - private static final String SPAN_ID = "0000000000def456"; - OtlpGrpcNettySpanExporterTest() { super("span", ResourceSpans.getDefaultInstance()); } @@ -70,27 +59,7 @@ protected TelemetryExporterBuilder exporterBuilder() { @Override protected SpanData generateFakeTelemetry() { - long duration = TimeUnit.MILLISECONDS.toNanos(900); - long startNs = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()); - long endNs = startNs + duration; - return TestSpanData.builder() - .setHasEnded(true) - .setSpanContext( - SpanContext.create(TRACE_ID, SPAN_ID, TraceFlags.getDefault(), TraceState.getDefault())) - .setName("GET /api/endpoint") - .setStartEpochNanos(startNs) - .setEndEpochNanos(endNs) - .setStatus(StatusData.ok()) - .setKind(SpanKind.SERVER) - .setLinks(Collections.emptyList()) - .setTotalRecordedLinks(0) - .setTotalRecordedEvents(0) - .setInstrumentationScopeInfo( - InstrumentationScopeInfo.builder("testLib") - .setVersion("1.0") - .setSchemaUrl("http://url") - .build()) - .build(); + return FakeTelemetryUtil.generateFakeSpanData(); } @Override diff --git a/exporters/otlp/all/src/testGrpcNettyShaded/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcNettyShadedMetricExporterTest.java b/exporters/otlp/all/src/testGrpcNettyShaded/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcNettyShadedMetricExporterTest.java index 49c154b4fff..5a143d7a08c 100644 --- a/exporters/otlp/all/src/testGrpcNettyShaded/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcNettyShadedMetricExporterTest.java +++ b/exporters/otlp/all/src/testGrpcNettyShaded/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcNettyShadedMetricExporterTest.java @@ -5,29 +5,20 @@ package io.opentelemetry.exporter.otlp.metrics; -import static io.opentelemetry.api.common.AttributeKey.stringKey; import static org.assertj.core.api.Assertions.assertThat; import io.grpc.inprocess.InProcessChannelBuilder; -import io.opentelemetry.api.common.Attributes; import io.opentelemetry.exporter.internal.grpc.UpstreamGrpcExporter; import io.opentelemetry.exporter.internal.marshal.Marshaler; import io.opentelemetry.exporter.internal.otlp.metrics.ResourceMetricsMarshaler; import io.opentelemetry.exporter.otlp.testing.internal.AbstractGrpcTelemetryExporterTest; +import io.opentelemetry.exporter.otlp.testing.internal.FakeTelemetryUtil; import io.opentelemetry.exporter.otlp.testing.internal.ManagedChannelTelemetryExporterBuilder; import io.opentelemetry.exporter.otlp.testing.internal.TelemetryExporterBuilder; import io.opentelemetry.proto.metrics.v1.ResourceMetrics; -import io.opentelemetry.sdk.common.InstrumentationScopeInfo; -import io.opentelemetry.sdk.metrics.data.AggregationTemporality; import io.opentelemetry.sdk.metrics.data.MetricData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableLongPointData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableMetricData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableSumData; -import io.opentelemetry.sdk.resources.Resource; import java.io.Closeable; -import java.util.Collections; import java.util.List; -import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; class OtlpGrpcNettyShadedMetricExporterTest @@ -56,20 +47,7 @@ protected TelemetryExporterBuilder exporterBuilder() { @Override protected MetricData generateFakeTelemetry() { - long startNs = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()); - long endNs = startNs + TimeUnit.MILLISECONDS.toNanos(900); - return ImmutableMetricData.createLongSum( - Resource.empty(), - InstrumentationScopeInfo.empty(), - "name", - "description", - "1", - ImmutableSumData.create( - /* isMonotonic= */ true, - AggregationTemporality.CUMULATIVE, - Collections.singletonList( - ImmutableLongPointData.create( - startNs, endNs, Attributes.of(stringKey("k"), "v"), 5)))); + return FakeTelemetryUtil.generateFakeMetricData(); } @Override diff --git a/exporters/otlp/all/src/testGrpcNettyShaded/java/io/opentelemetry/exporter/otlp/trace/OtlpGrpcNettyShadedSpanExporterTest.java b/exporters/otlp/all/src/testGrpcNettyShaded/java/io/opentelemetry/exporter/otlp/trace/OtlpGrpcNettyShadedSpanExporterTest.java index e876c1e9917..2be7d950171 100644 --- a/exporters/otlp/all/src/testGrpcNettyShaded/java/io/opentelemetry/exporter/otlp/trace/OtlpGrpcNettyShadedSpanExporterTest.java +++ b/exporters/otlp/all/src/testGrpcNettyShaded/java/io/opentelemetry/exporter/otlp/trace/OtlpGrpcNettyShadedSpanExporterTest.java @@ -8,33 +8,22 @@ import static org.assertj.core.api.Assertions.assertThat; import io.grpc.inprocess.InProcessChannelBuilder; -import io.opentelemetry.api.trace.SpanContext; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.TraceFlags; -import io.opentelemetry.api.trace.TraceState; import io.opentelemetry.exporter.internal.grpc.UpstreamGrpcExporter; import io.opentelemetry.exporter.internal.marshal.Marshaler; import io.opentelemetry.exporter.internal.otlp.traces.ResourceSpansMarshaler; import io.opentelemetry.exporter.otlp.testing.internal.AbstractGrpcTelemetryExporterTest; +import io.opentelemetry.exporter.otlp.testing.internal.FakeTelemetryUtil; import io.opentelemetry.exporter.otlp.testing.internal.ManagedChannelTelemetryExporterBuilder; import io.opentelemetry.exporter.otlp.testing.internal.TelemetryExporterBuilder; import io.opentelemetry.proto.trace.v1.ResourceSpans; -import io.opentelemetry.sdk.common.InstrumentationScopeInfo; -import io.opentelemetry.sdk.testing.trace.TestSpanData; import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.data.StatusData; import java.io.Closeable; -import java.util.Collections; import java.util.List; -import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; class OtlpGrpcNettyShadedSpanExporterTest extends AbstractGrpcTelemetryExporterTest { - private static final String TRACE_ID = "00000000000000000000000000abc123"; - private static final String SPAN_ID = "0000000000def456"; - OtlpGrpcNettyShadedSpanExporterTest() { super("span", ResourceSpans.getDefaultInstance()); } @@ -58,27 +47,7 @@ protected TelemetryExporterBuilder exporterBuilder() { @Override protected SpanData generateFakeTelemetry() { - long duration = TimeUnit.MILLISECONDS.toNanos(900); - long startNs = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()); - long endNs = startNs + duration; - return TestSpanData.builder() - .setHasEnded(true) - .setSpanContext( - SpanContext.create(TRACE_ID, SPAN_ID, TraceFlags.getDefault(), TraceState.getDefault())) - .setName("GET /api/endpoint") - .setStartEpochNanos(startNs) - .setEndEpochNanos(endNs) - .setStatus(StatusData.ok()) - .setKind(SpanKind.SERVER) - .setLinks(Collections.emptyList()) - .setTotalRecordedLinks(0) - .setTotalRecordedEvents(0) - .setInstrumentationScopeInfo( - InstrumentationScopeInfo.builder("testLib") - .setVersion("1.0") - .setSchemaUrl("http://url") - .build()) - .build(); + return FakeTelemetryUtil.generateFakeSpanData(); } @Override diff --git a/exporters/otlp/all/src/testGrpcOkhttp/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcOkHttpMetricExporterTest.java b/exporters/otlp/all/src/testGrpcOkhttp/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcOkHttpMetricExporterTest.java index e9a871eba69..4aeb1191cb2 100644 --- a/exporters/otlp/all/src/testGrpcOkhttp/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcOkHttpMetricExporterTest.java +++ b/exporters/otlp/all/src/testGrpcOkhttp/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcOkHttpMetricExporterTest.java @@ -5,29 +5,20 @@ package io.opentelemetry.exporter.otlp.metrics; -import static io.opentelemetry.api.common.AttributeKey.stringKey; import static org.assertj.core.api.Assertions.assertThat; import io.grpc.inprocess.InProcessChannelBuilder; -import io.opentelemetry.api.common.Attributes; import io.opentelemetry.exporter.internal.grpc.UpstreamGrpcExporter; import io.opentelemetry.exporter.internal.marshal.Marshaler; import io.opentelemetry.exporter.internal.otlp.metrics.ResourceMetricsMarshaler; import io.opentelemetry.exporter.otlp.testing.internal.AbstractGrpcTelemetryExporterTest; +import io.opentelemetry.exporter.otlp.testing.internal.FakeTelemetryUtil; import io.opentelemetry.exporter.otlp.testing.internal.ManagedChannelTelemetryExporterBuilder; import io.opentelemetry.exporter.otlp.testing.internal.TelemetryExporterBuilder; import io.opentelemetry.proto.metrics.v1.ResourceMetrics; -import io.opentelemetry.sdk.common.InstrumentationScopeInfo; -import io.opentelemetry.sdk.metrics.data.AggregationTemporality; import io.opentelemetry.sdk.metrics.data.MetricData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableLongPointData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableMetricData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableSumData; -import io.opentelemetry.sdk.resources.Resource; import java.io.Closeable; -import java.util.Collections; import java.util.List; -import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; class OtlpGrpcOkHttpMetricExporterTest @@ -56,20 +47,7 @@ protected TelemetryExporterBuilder exporterBuilder() { @Override protected MetricData generateFakeTelemetry() { - long startNs = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()); - long endNs = startNs + TimeUnit.MILLISECONDS.toNanos(900); - return ImmutableMetricData.createLongSum( - Resource.empty(), - InstrumentationScopeInfo.empty(), - "name", - "description", - "1", - ImmutableSumData.create( - /* isMonotonic= */ true, - AggregationTemporality.CUMULATIVE, - Collections.singletonList( - ImmutableLongPointData.create( - startNs, endNs, Attributes.of(stringKey("k"), "v"), 5)))); + return FakeTelemetryUtil.generateFakeMetricData(); } @Override diff --git a/exporters/otlp/all/src/testGrpcOkhttp/java/io/opentelemetry/exporter/otlp/trace/OtlpGrpcOkHttpSpanExporterTest.java b/exporters/otlp/all/src/testGrpcOkhttp/java/io/opentelemetry/exporter/otlp/trace/OtlpGrpcOkHttpSpanExporterTest.java index 550a8f4524a..7b7f338f21f 100644 --- a/exporters/otlp/all/src/testGrpcOkhttp/java/io/opentelemetry/exporter/otlp/trace/OtlpGrpcOkHttpSpanExporterTest.java +++ b/exporters/otlp/all/src/testGrpcOkhttp/java/io/opentelemetry/exporter/otlp/trace/OtlpGrpcOkHttpSpanExporterTest.java @@ -8,33 +8,22 @@ import static org.assertj.core.api.Assertions.assertThat; import io.grpc.inprocess.InProcessChannelBuilder; -import io.opentelemetry.api.trace.SpanContext; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.TraceFlags; -import io.opentelemetry.api.trace.TraceState; import io.opentelemetry.exporter.internal.grpc.UpstreamGrpcExporter; import io.opentelemetry.exporter.internal.marshal.Marshaler; import io.opentelemetry.exporter.internal.otlp.traces.ResourceSpansMarshaler; import io.opentelemetry.exporter.otlp.testing.internal.AbstractGrpcTelemetryExporterTest; +import io.opentelemetry.exporter.otlp.testing.internal.FakeTelemetryUtil; import io.opentelemetry.exporter.otlp.testing.internal.ManagedChannelTelemetryExporterBuilder; import io.opentelemetry.exporter.otlp.testing.internal.TelemetryExporterBuilder; import io.opentelemetry.proto.trace.v1.ResourceSpans; -import io.opentelemetry.sdk.common.InstrumentationScopeInfo; -import io.opentelemetry.sdk.testing.trace.TestSpanData; import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.data.StatusData; import java.io.Closeable; -import java.util.Collections; import java.util.List; -import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; class OtlpGrpcOkHttpSpanExporterTest extends AbstractGrpcTelemetryExporterTest { - private static final String TRACE_ID = "00000000000000000000000000abc123"; - private static final String SPAN_ID = "0000000000def456"; - OtlpGrpcOkHttpSpanExporterTest() { super("span", ResourceSpans.getDefaultInstance()); } @@ -58,27 +47,7 @@ protected TelemetryExporterBuilder exporterBuilder() { @Override protected SpanData generateFakeTelemetry() { - long duration = TimeUnit.MILLISECONDS.toNanos(900); - long startNs = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()); - long endNs = startNs + duration; - return TestSpanData.builder() - .setHasEnded(true) - .setSpanContext( - SpanContext.create(TRACE_ID, SPAN_ID, TraceFlags.getDefault(), TraceState.getDefault())) - .setName("GET /api/endpoint") - .setStartEpochNanos(startNs) - .setEndEpochNanos(endNs) - .setStatus(StatusData.ok()) - .setKind(SpanKind.SERVER) - .setLinks(Collections.emptyList()) - .setTotalRecordedLinks(0) - .setTotalRecordedEvents(0) - .setInstrumentationScopeInfo( - InstrumentationScopeInfo.builder("testLib") - .setVersion("1.0") - .setSchemaUrl("http://url") - .build()) - .build(); + return FakeTelemetryUtil.generateFakeSpanData(); } @Override diff --git a/exporters/otlp/logs/build.gradle.kts b/exporters/otlp/logs/build.gradle.kts index 2f33efe34d7..df738afcdbf 100644 --- a/exporters/otlp/logs/build.gradle.kts +++ b/exporters/otlp/logs/build.gradle.kts @@ -16,6 +16,8 @@ dependencies { compileOnly("io.grpc:grpc-stub") + testImplementation("io.grpc:grpc-stub") + testImplementation(project(":exporters:otlp:testing-internal")) testImplementation(project(":sdk:logs-testing")) diff --git a/exporters/otlp/logs/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpLogRecordExporterProvider.java b/exporters/otlp/logs/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpLogRecordExporterProvider.java index d212b80e5d3..008b49c3418 100644 --- a/exporters/otlp/logs/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpLogRecordExporterProvider.java +++ b/exporters/otlp/logs/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpLogRecordExporterProvider.java @@ -33,7 +33,7 @@ public LogRecordExporter createExporter(ConfigProperties config) { String protocol = OtlpConfigUtil.getOtlpProtocol(DATA_TYPE_LOGS, config); if (protocol.equals(PROTOCOL_HTTP_PROTOBUF)) { - OtlpHttpLogRecordExporterBuilder builder = OtlpHttpLogRecordExporter.builder(); + OtlpHttpLogRecordExporterBuilder builder = httpBuilder(); OtlpConfigUtil.configureOtlpExporterBuilder( DATA_TYPE_LOGS, @@ -48,7 +48,7 @@ public LogRecordExporter createExporter(ConfigProperties config) { return builder.build(); } else if (protocol.equals(PROTOCOL_GRPC)) { - OtlpGrpcLogRecordExporterBuilder builder = OtlpGrpcLogRecordExporter.builder(); + OtlpGrpcLogRecordExporterBuilder builder = grpcBuilder(); OtlpConfigUtil.configureOtlpExporterBuilder( DATA_TYPE_LOGS, @@ -70,4 +70,14 @@ public LogRecordExporter createExporter(ConfigProperties config) { public String getName() { return "otlp"; } + + // Visible for testing + OtlpHttpLogRecordExporterBuilder httpBuilder() { + return OtlpHttpLogRecordExporter.builder(); + } + + // Visible for testing + OtlpGrpcLogRecordExporterBuilder grpcBuilder() { + return OtlpGrpcLogRecordExporter.builder(); + } } diff --git a/exporters/otlp/logs/src/test/java/io/opentelemetry/exporter/otlp/http/logs/OtlpHttpLogRecordExporterTest.java b/exporters/otlp/logs/src/test/java/io/opentelemetry/exporter/otlp/http/logs/OtlpHttpLogRecordExporterTest.java index 050fcdde658..e263422b619 100644 --- a/exporters/otlp/logs/src/test/java/io/opentelemetry/exporter/otlp/http/logs/OtlpHttpLogRecordExporterTest.java +++ b/exporters/otlp/logs/src/test/java/io/opentelemetry/exporter/otlp/http/logs/OtlpHttpLogRecordExporterTest.java @@ -5,361 +5,99 @@ package io.opentelemetry.exporter.otlp.http.logs; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import com.google.protobuf.InvalidProtocolBufferException; -import com.google.protobuf.Message; -import com.google.rpc.Status; -import com.linecorp.armeria.common.AggregatedHttpRequest; -import com.linecorp.armeria.common.HttpMethod; -import com.linecorp.armeria.common.HttpResponse; -import com.linecorp.armeria.common.HttpStatus; -import com.linecorp.armeria.common.MediaType; -import com.linecorp.armeria.server.ServerBuilder; -import com.linecorp.armeria.testing.junit5.server.mock.MockWebServerExtension; -import com.linecorp.armeria.testing.junit5.server.mock.RecordedRequest; -import io.github.netmikey.logunit.api.LogCapturer; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.logs.Severity; -import io.opentelemetry.exporter.internal.okhttp.OkHttpExporter; +import io.opentelemetry.exporter.internal.marshal.Marshaler; import io.opentelemetry.exporter.internal.otlp.logs.ResourceLogsMarshaler; import io.opentelemetry.exporter.internal.retry.RetryPolicy; import io.opentelemetry.exporter.internal.retry.RetryUtil; -import io.opentelemetry.internal.testing.slf4j.SuppressLogger; -import io.opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest; -import io.opentelemetry.proto.collector.logs.v1.ExportLogsServiceResponse; +import io.opentelemetry.exporter.otlp.testing.internal.AbstractHttpTelemetryExporterTest; +import io.opentelemetry.exporter.otlp.testing.internal.FakeTelemetryUtil; +import io.opentelemetry.exporter.otlp.testing.internal.TelemetryExporter; +import io.opentelemetry.exporter.otlp.testing.internal.TelemetryExporterBuilder; import io.opentelemetry.proto.logs.v1.ResourceLogs; -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.logs.data.LogRecordData; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.testing.logs.TestLogRecordData; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.time.Instant; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; -import okhttp3.tls.HeldCertificate; -import okio.Buffer; -import okio.GzipSource; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.slf4j.event.Level; -import org.slf4j.event.LoggingEvent; - -class OtlpHttpLogRecordExporterTest { - - private static final MediaType APPLICATION_PROTOBUF = - MediaType.create("application", "x-protobuf"); - private static final HeldCertificate HELD_CERTIFICATE; - private static final String canonicalHostName; - - static { - try { - canonicalHostName = InetAddress.getByName("localhost").getCanonicalHostName(); - HELD_CERTIFICATE = - new HeldCertificate.Builder() - .commonName("localhost") - .addSubjectAlternativeName(canonicalHostName) - .build(); - } catch (UnknownHostException e) { - throw new IllegalStateException("Error building certificate.", e); - } - } - - @RegisterExtension - static MockWebServerExtension server = - new MockWebServerExtension() { - @Override - protected void configureServer(ServerBuilder sb) { - sb.http(0); - sb.https(0); - sb.tls(HELD_CERTIFICATE.keyPair().getPrivate(), HELD_CERTIFICATE.certificate()); - } - }; - - @RegisterExtension LogCapturer logs = LogCapturer.create().captureForType(OkHttpExporter.class); - - private OtlpHttpLogRecordExporterBuilder builder; - - @BeforeEach - void setup() { - builder = - OtlpHttpLogRecordExporter.builder() - .setEndpoint("http://" + canonicalHostName + ":" + server.httpPort() + "/v1/logs") - .addHeader("foo", "bar"); - } - - @Test - @SuppressWarnings("PreferJavaTimeOverload") - void validConfig() { - assertThatCode(() -> OtlpHttpLogRecordExporter.builder().setTimeout(0, TimeUnit.MILLISECONDS)) - .doesNotThrowAnyException(); - assertThatCode(() -> OtlpHttpLogRecordExporter.builder().setTimeout(Duration.ofMillis(0))) - .doesNotThrowAnyException(); - assertThatCode(() -> OtlpHttpLogRecordExporter.builder().setTimeout(10, TimeUnit.MILLISECONDS)) - .doesNotThrowAnyException(); - assertThatCode(() -> OtlpHttpLogRecordExporter.builder().setTimeout(Duration.ofMillis(10))) - .doesNotThrowAnyException(); - - assertThatCode( - () -> - OtlpHttpLogRecordExporter.builder() - .setEndpoint("http://" + canonicalHostName + ":4317")) - .doesNotThrowAnyException(); - assertThatCode( - () -> - OtlpHttpLogRecordExporter.builder().setEndpoint("http://" + canonicalHostName + "")) - .doesNotThrowAnyException(); - assertThatCode( - () -> - OtlpHttpLogRecordExporter.builder() - .setEndpoint("https://" + canonicalHostName + "")) - .doesNotThrowAnyException(); - assertThatCode( - () -> - OtlpHttpLogRecordExporter.builder() - .setEndpoint("http://foo:bar@" + canonicalHostName + "")) - .doesNotThrowAnyException(); - - assertThatCode(() -> OtlpHttpLogRecordExporter.builder().setCompression("gzip")) - .doesNotThrowAnyException(); - assertThatCode(() -> OtlpHttpLogRecordExporter.builder().setCompression("none")) - .doesNotThrowAnyException(); - - assertThatCode( - () -> - OtlpHttpLogRecordExporter.builder().addHeader("foo", "bar").addHeader("baz", "qux")) - .doesNotThrowAnyException(); - - assertThatCode( - () -> - OtlpHttpLogRecordExporter.builder() - .setTrustedCertificates("foobar".getBytes(StandardCharsets.UTF_8))) - .doesNotThrowAnyException(); - - assertThatCode( - () -> - OtlpHttpLogRecordExporter.builder() - .setClientTls( - "foobar".getBytes(StandardCharsets.UTF_8), - "foobar".getBytes(StandardCharsets.UTF_8))) - .doesNotThrowAnyException(); - } - - @Test - @SuppressWarnings("PreferJavaTimeOverload") - void invalidConfig() { - assertThatThrownBy( - () -> OtlpHttpLogRecordExporter.builder().setTimeout(-1, TimeUnit.MILLISECONDS)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("timeout must be non-negative"); - assertThatThrownBy(() -> OtlpHttpLogRecordExporter.builder().setTimeout(1, null)) - .isInstanceOf(NullPointerException.class) - .hasMessage("unit"); - assertThatThrownBy(() -> OtlpHttpLogRecordExporter.builder().setTimeout(null)) - .isInstanceOf(NullPointerException.class) - .hasMessage("timeout"); - - assertThatThrownBy(() -> OtlpHttpLogRecordExporter.builder().setEndpoint(null)) - .isInstanceOf(NullPointerException.class) - .hasMessage("endpoint"); - assertThatThrownBy( - () -> OtlpHttpLogRecordExporter.builder().setEndpoint("😺://" + canonicalHostName + "")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Invalid endpoint, must be a URL: 😺://" + canonicalHostName + ""); - assertThatThrownBy( - () -> OtlpHttpLogRecordExporter.builder().setEndpoint("" + canonicalHostName + "")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage( - "Invalid endpoint, must start with http:// or https://: " + canonicalHostName + ""); - assertThatThrownBy( - () -> - OtlpHttpLogRecordExporter.builder() - .setEndpoint("gopher://" + canonicalHostName + "")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage( - "Invalid endpoint, must start with http:// or https://: gopher://" - + canonicalHostName - + ""); - - assertThatThrownBy(() -> OtlpHttpLogRecordExporter.builder().setCompression(null)) - .isInstanceOf(NullPointerException.class) - .hasMessage("compressionMethod"); - assertThatThrownBy(() -> OtlpHttpLogRecordExporter.builder().setCompression("foo")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage( - "Unsupported compression method. Supported compression methods include: gzip, none."); - } - - @Test - void testSetRetryPolicyOnDelegate() { - assertThatCode( - () -> - RetryUtil.setRetryPolicyOnDelegate( - OtlpHttpLogRecordExporter.builder(), RetryPolicy.getDefault())) - .doesNotThrowAnyException(); - } - - @Test - void testExportUncompressed() { - server.enqueue(successResponse()); - OtlpHttpLogRecordExporter exporter = builder.build(); - - ExportLogsServiceRequest payload = exportAndAssertResult(exporter, /* expectedResult= */ true); - RecordedRequest recorded = server.takeRequest(); - AggregatedHttpRequest request = recorded.request(); - assertRequestCommon(request); - assertThat(parseRequestBody(request.content().array())).isEqualTo(payload); - - // OkHttp does not support HTTP/2 upgrade on plaintext. - assertThat(recorded.context().sessionProtocol().isMultiplex()).isFalse(); - } - - @Test - void testExportTls() { - server.enqueue(successResponse()); - OtlpHttpLogRecordExporter exporter = - builder - .setEndpoint("https://" + canonicalHostName + ":" + server.httpsPort() + "/v1/logs") - .setTrustedCertificates( - HELD_CERTIFICATE.certificatePem().getBytes(StandardCharsets.UTF_8)) - .build(); - - ExportLogsServiceRequest payload = exportAndAssertResult(exporter, /* expectedResult= */ true); - RecordedRequest recorded = server.takeRequest(); - AggregatedHttpRequest request = recorded.request(); - assertRequestCommon(request); - assertThat(parseRequestBody(request.content().array())).isEqualTo(payload); - - // OkHttp does support HTTP/2 upgrade on TLS. - assertThat(recorded.context().sessionProtocol().isMultiplex()).isTrue(); - } - - @Test - void testExportGzipCompressed() { - server.enqueue(successResponse()); - OtlpHttpLogRecordExporter exporter = builder.setCompression("gzip").build(); - - ExportLogsServiceRequest payload = exportAndAssertResult(exporter, /* expectedResult= */ true); - AggregatedHttpRequest request = server.takeRequest().request(); - assertRequestCommon(request); - assertThat(request.headers().get("Content-Encoding")).isEqualTo("gzip"); - assertThat(parseRequestBody(gzipDecompress(request.content().array()))).isEqualTo(payload); - } - - private static void assertRequestCommon(AggregatedHttpRequest request) { - assertThat(request.method()).isEqualTo(HttpMethod.POST); - assertThat(request.path()).isEqualTo("/v1/logs"); - assertThat(request.headers().get("foo")).isEqualTo("bar"); - assertThat(request.headers().get("Content-Type")).isEqualTo(APPLICATION_PROTOBUF.toString()); - } - - private static ExportLogsServiceRequest parseRequestBody(byte[] bytes) { - try { - return ExportLogsServiceRequest.parseFrom(bytes); - } catch (InvalidProtocolBufferException e) { - throw new IllegalStateException("Unable to parse Protobuf request body.", e); - } - } - - private static byte[] gzipDecompress(byte[] bytes) { - try { - Buffer result = new Buffer(); - GzipSource source = new GzipSource(new Buffer().write(bytes)); - while (source.read(result, Integer.MAX_VALUE) != -1) {} - return result.readByteArray(); - } catch (IOException e) { - throw new IllegalStateException("Unable to decompress payload.", e); - } - } - - @Test - @SuppressLogger(OkHttpExporter.class) - void testServerError() { - server.enqueue( - buildResponse( - HttpStatus.INTERNAL_SERVER_ERROR, - Status.newBuilder().setMessage("Server error!").build())); - OtlpHttpLogRecordExporter exporter = builder.build(); - - exportAndAssertResult(exporter, /* expectedResult= */ false); - LoggingEvent log = - logs.assertContains( - "Failed to export logs. Server responded with HTTP status code 500. Error message: Server error!"); - assertThat(log.getLevel()).isEqualTo(Level.WARN); - } - - @Test - @SuppressLogger(OkHttpExporter.class) - void testServerErrorParseError() { - server.enqueue( - HttpResponse.of(HttpStatus.INTERNAL_SERVER_ERROR, APPLICATION_PROTOBUF, "Server error!")); - OtlpHttpLogRecordExporter exporter = builder.build(); - - exportAndAssertResult(exporter, /* expectedResult= */ false); - LoggingEvent log = - logs.assertContains( - "Failed to export logs. Server responded with HTTP status code 500. Error message: Unable to parse response body, HTTP status message:"); - assertThat(log.getLevel()).isEqualTo(Level.WARN); - } - - private static ExportLogsServiceRequest exportAndAssertResult( - OtlpHttpLogRecordExporter otlpHttpLogRecordExporter, boolean expectedResult) { - List logs = Collections.singletonList(generateFakeLog()); - CompletableResultCode resultCode = otlpHttpLogRecordExporter.export(logs); - resultCode.join(10, TimeUnit.SECONDS); - assertThat(resultCode.isSuccess()).isEqualTo(expectedResult); - List resourceLogs = - Arrays.stream(ResourceLogsMarshaler.create(logs)) - .map( - marshaler -> { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - try { - marshaler.writeBinaryTo(bos); - return ResourceLogs.parseFrom(bos.toByteArray()); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - }) - .collect(Collectors.toList()); - return ExportLogsServiceRequest.newBuilder().addAllResourceLogs(resourceLogs).build(); - } - - private static HttpResponse successResponse() { - ExportLogsServiceResponse exportLogsServiceResponse = - ExportLogsServiceResponse.newBuilder().build(); - return buildResponse(HttpStatus.OK, exportLogsServiceResponse); - } - - private static HttpResponse buildResponse(HttpStatus httpStatus, T message) { - return HttpResponse.of(httpStatus, APPLICATION_PROTOBUF, message.toByteArray()); - } - private static LogRecordData generateFakeLog() { - return TestLogRecordData.builder() - .setResource(Resource.getDefault()) - .setInstrumentationScopeInfo( - InstrumentationScopeInfo.builder("testLib") - .setVersion("1.0") - .setSchemaUrl("http://url") - .build()) - .setBody("log body") - .setAttributes(Attributes.builder().put("key", "value").build()) - .setSeverity(Severity.INFO) - .setSeverityText(Severity.INFO.name()) - .setEpoch(Instant.now()) - .build(); +class OtlpHttpLogRecordExporterTest + extends AbstractHttpTelemetryExporterTest { + + protected OtlpHttpLogRecordExporterTest() { + super("log", "/v1/logs", ResourceLogs.getDefaultInstance()); + } + + @Override + protected TelemetryExporterBuilder exporterBuilder() { + OtlpHttpLogRecordExporterBuilder builder = OtlpHttpLogRecordExporter.builder(); + return new TelemetryExporterBuilder() { + @Override + public TelemetryExporterBuilder setEndpoint(String endpoint) { + builder.setEndpoint(endpoint); + return this; + } + + @Override + public TelemetryExporterBuilder setTimeout(long timeout, TimeUnit unit) { + builder.setTimeout(timeout, unit); + return this; + } + + @Override + public TelemetryExporterBuilder setTimeout(Duration timeout) { + builder.setTimeout(timeout); + return this; + } + + @Override + public TelemetryExporterBuilder setCompression(String compression) { + builder.setCompression(compression); + return this; + } + + @Override + public TelemetryExporterBuilder addHeader(String key, String value) { + builder.addHeader(key, value); + return this; + } + + @Override + public TelemetryExporterBuilder setTrustedCertificates(byte[] certificates) { + builder.setTrustedCertificates(certificates); + return this; + } + + @Override + public TelemetryExporterBuilder setClientTls( + byte[] privateKeyPem, byte[] certificatePem) { + builder.setClientTls(privateKeyPem, certificatePem); + return this; + } + + @Override + public TelemetryExporterBuilder setRetryPolicy(RetryPolicy retryPolicy) { + RetryUtil.setRetryPolicyOnDelegate(builder, retryPolicy); + return this; + } + + @Override + public TelemetryExporterBuilder setChannel(io.grpc.ManagedChannel channel) { + throw new UnsupportedOperationException("Not implemented"); + } + + @Override + public TelemetryExporter build() { + return TelemetryExporter.wrap(builder.build()); + } + }; + } + + @Override + protected LogRecordData generateFakeTelemetry() { + return FakeTelemetryUtil.generateFakeLogRecordData(); + } + + @Override + protected Marshaler[] toMarshalers(List telemetry) { + return ResourceLogsMarshaler.create(telemetry); } } diff --git a/exporters/otlp/logs/src/test/java/io/opentelemetry/exporter/otlp/internal/OtlpLogRecordExporterProviderTest.java b/exporters/otlp/logs/src/test/java/io/opentelemetry/exporter/otlp/internal/OtlpLogRecordExporterProviderTest.java new file mode 100644 index 00000000000..d96a5b4d6dc --- /dev/null +++ b/exporters/otlp/logs/src/test/java/io/opentelemetry/exporter/otlp/internal/OtlpLogRecordExporterProviderTest.java @@ -0,0 +1,277 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.otlp.internal; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import com.linecorp.armeria.testing.junit5.server.SelfSignedCertificateExtension; +import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter; +import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporterBuilder; +import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter; +import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporterBuilder; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import io.opentelemetry.sdk.logs.export.LogRecordExporter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.cert.CertificateEncodingException; +import java.time.Duration; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class OtlpLogRecordExporterProviderTest { + + @RegisterExtension + static final SelfSignedCertificateExtension serverTls = new SelfSignedCertificateExtension(); + + @RegisterExtension + static final SelfSignedCertificateExtension clientTls = new SelfSignedCertificateExtension(); + + @Spy private OtlpLogRecordExporterProvider provider; + + @Spy private OtlpHttpLogRecordExporterBuilder httpBuilder; + + @Spy private OtlpGrpcLogRecordExporterBuilder grpcBuilder; + + private String certificatePath; + private String clientKeyPath; + private String clientCertificatePath; + + @BeforeEach + void setup(@TempDir Path tempDir) throws IOException, CertificateEncodingException { + doReturn(httpBuilder).when(provider).httpBuilder(); + doReturn(grpcBuilder).when(provider).grpcBuilder(); + + certificatePath = + createTempFileWithContent( + tempDir, "certificate.cert", serverTls.certificate().getEncoded()); + clientKeyPath = + createTempFileWithContent(tempDir, "clientKey.key", clientTls.privateKey().getEncoded()); + clientCertificatePath = + createTempFileWithContent( + tempDir, "clientCertificate.cert", clientTls.certificate().getEncoded()); + } + + private static String createTempFileWithContent(Path dir, String filename, byte[] content) + throws IOException { + Path path = dir.resolve(filename); + Files.write(path, content); + return path.toString(); + } + + @Test + void getName() { + assertThat(provider.getName()).isEqualTo("otlp"); + } + + @Test + void createExporter_UnsupportedProtocol() { + assertThatThrownBy( + () -> + provider.createExporter( + DefaultConfigProperties.createForTest( + Collections.singletonMap("otel.exporter.otlp.protocol", "foo")))) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining("Unsupported OTLP logs protocol: foo"); + } + + @Test + void createExporter_NoMocks() { + // Verifies createExporter after resetting the spy overrides + Mockito.reset(provider); + try (LogRecordExporter exporter = + provider.createExporter(DefaultConfigProperties.createForTest(Collections.emptyMap()))) { + assertThat(exporter).isInstanceOf(OtlpGrpcLogRecordExporter.class); + } + try (LogRecordExporter exporter = + provider.createExporter( + DefaultConfigProperties.createForTest( + Collections.singletonMap("otel.exporter.otlp.protocol", "http/protobuf")))) { + assertThat(exporter).isInstanceOf(OtlpHttpLogRecordExporter.class); + } + } + + @Test + void createExporter_GrpcDefaults() { + try (LogRecordExporter exporter = + provider.createExporter(DefaultConfigProperties.createForTest(Collections.emptyMap()))) { + assertThat(exporter).isInstanceOf(OtlpGrpcLogRecordExporter.class); + verify(grpcBuilder, times(1)).build(); + verify(grpcBuilder, never()).setEndpoint(any()); + verify(grpcBuilder, never()).addHeader(any(), any()); + verify(grpcBuilder, never()).setCompression(any()); + verify(grpcBuilder, never()).setTimeout(any()); + verify(grpcBuilder, never()).setTrustedCertificates(any()); + verify(grpcBuilder, never()).setClientTls(any(), any()); + assertThat(grpcBuilder).extracting("delegate").extracting("retryPolicy").isNull(); + } + Mockito.verifyNoInteractions(httpBuilder); + } + + @Test + void createExporter_GrpcWithGeneralConfiguration() throws CertificateEncodingException { + Map config = new HashMap<>(); + config.put("otel.exporter.otlp.endpoint", "https://localhost:443/"); + config.put("otel.exporter.otlp.certificate", certificatePath); + config.put("otel.exporter.otlp.client.key", clientKeyPath); + config.put("otel.exporter.otlp.client.certificate", clientCertificatePath); + config.put("otel.exporter.otlp.headers", "header-key=header-value"); + config.put("otel.exporter.otlp.compression", "gzip"); + config.put("otel.exporter.otlp.timeout", "15s"); + config.put("otel.experimental.exporter.otlp.retry.enabled", "true"); + + try (LogRecordExporter exporter = + provider.createExporter(DefaultConfigProperties.createForTest(config))) { + assertThat(exporter).isInstanceOf(OtlpGrpcLogRecordExporter.class); + verify(grpcBuilder, times(1)).build(); + verify(grpcBuilder).setEndpoint("https://localhost:443/"); + verify(grpcBuilder).addHeader("header-key", "header-value"); + verify(grpcBuilder).setCompression("gzip"); + verify(grpcBuilder).setTimeout(Duration.ofSeconds(15)); + verify(grpcBuilder).setTrustedCertificates(serverTls.certificate().getEncoded()); + verify(grpcBuilder) + .setClientTls(clientTls.privateKey().getEncoded(), clientTls.certificate().getEncoded()); + assertThat(grpcBuilder).extracting("delegate").extracting("retryPolicy").isNotNull(); + } + Mockito.verifyNoInteractions(httpBuilder); + } + + @Test + void createExporter_GrpcWithSignalConfiguration() throws CertificateEncodingException { + Map config = new HashMap<>(); + config.put("otel.exporter.otlp.endpoint", "https://dummy:443/"); + config.put("otel.exporter.otlp.logs.endpoint", "https://localhost:443/"); + config.put("otel.exporter.otlp.certificate", "dummy.cert"); + config.put("otel.exporter.otlp.logs.certificate", certificatePath); + config.put("otel.exporter.otlp.client.key", "dummy.key"); + config.put("otel.exporter.otlp.logs.client.key", clientKeyPath); + config.put("otel.exporter.otlp.client.certificate", "dummy-client.cert"); + config.put("otel.exporter.otlp.logs.client.certificate", clientCertificatePath); + config.put("otel.exporter.otlp.headers", "dummy=value"); + config.put("otel.exporter.otlp.logs.headers", "header-key=header-value"); + config.put("otel.exporter.otlp.compression", "none"); + config.put("otel.exporter.otlp.logs.compression", "gzip"); + config.put("otel.exporter.otlp.timeout", "1s"); + config.put("otel.exporter.otlp.logs.timeout", "15s"); + + try (LogRecordExporter exporter = + provider.createExporter(DefaultConfigProperties.createForTest(config))) { + assertThat(exporter).isInstanceOf(OtlpGrpcLogRecordExporter.class); + verify(grpcBuilder, times(1)).build(); + verify(grpcBuilder).setEndpoint("https://localhost:443/"); + verify(grpcBuilder).addHeader("header-key", "header-value"); + verify(grpcBuilder).setCompression("gzip"); + verify(grpcBuilder).setTimeout(Duration.ofSeconds(15)); + verify(grpcBuilder).setTrustedCertificates(serverTls.certificate().getEncoded()); + verify(grpcBuilder) + .setClientTls(clientTls.privateKey().getEncoded(), clientTls.certificate().getEncoded()); + } + Mockito.verifyNoInteractions(httpBuilder); + } + + @Test + void createExporter_HttpDefaults() { + try (LogRecordExporter exporter = + provider.createExporter( + DefaultConfigProperties.createForTest( + Collections.singletonMap("otel.exporter.otlp.logs.protocol", "http/protobuf")))) { + assertThat(exporter).isInstanceOf(OtlpHttpLogRecordExporter.class); + verify(httpBuilder, times(1)).build(); + verify(httpBuilder, never()).setEndpoint(any()); + verify(httpBuilder, never()).addHeader(any(), any()); + verify(httpBuilder, never()).setCompression(any()); + verify(httpBuilder, never()).setTimeout(any()); + verify(httpBuilder, never()).setTrustedCertificates(any()); + verify(httpBuilder, never()).setClientTls(any(), any()); + assertThat(httpBuilder).extracting("delegate").extracting("retryPolicy").isNull(); + } + Mockito.verifyNoInteractions(grpcBuilder); + } + + @Test + void createExporter_HttpWithGeneralConfiguration() throws CertificateEncodingException { + Map config = new HashMap<>(); + config.put("otel.exporter.otlp.protocol", "http/protobuf"); + config.put("otel.exporter.otlp.endpoint", "https://localhost:443/"); + config.put("otel.exporter.otlp.certificate", certificatePath); + config.put("otel.exporter.otlp.client.key", clientKeyPath); + config.put("otel.exporter.otlp.client.certificate", clientCertificatePath); + config.put("otel.exporter.otlp.headers", "header-key=header-value"); + config.put("otel.exporter.otlp.compression", "gzip"); + config.put("otel.exporter.otlp.timeout", "15s"); + config.put("otel.experimental.exporter.otlp.retry.enabled", "true"); + + try (LogRecordExporter exporter = + provider.createExporter(DefaultConfigProperties.createForTest(config))) { + assertThat(exporter).isInstanceOf(OtlpHttpLogRecordExporter.class); + verify(httpBuilder, times(1)).build(); + verify(httpBuilder).setEndpoint("https://localhost:443/v1/logs"); + verify(httpBuilder).addHeader("header-key", "header-value"); + verify(httpBuilder).setCompression("gzip"); + verify(httpBuilder).setTimeout(Duration.ofSeconds(15)); + verify(httpBuilder).setTrustedCertificates(serverTls.certificate().getEncoded()); + verify(httpBuilder) + .setClientTls(clientTls.privateKey().getEncoded(), clientTls.certificate().getEncoded()); + assertThat(httpBuilder).extracting("delegate").extracting("retryPolicy").isNotNull(); + } + Mockito.verifyNoInteractions(grpcBuilder); + } + + @Test + void createExporter_HttpWithSignalConfiguration() throws CertificateEncodingException { + Map config = new HashMap<>(); + config.put("otel.exporter.otlp.protocol", "grpc"); + config.put("otel.exporter.otlp.logs.protocol", "http/protobuf"); + config.put("otel.exporter.otlp.endpoint", "https://dummy:443/"); + config.put("otel.exporter.otlp.logs.endpoint", "https://localhost:443/v1/logs"); + config.put("otel.exporter.otlp.certificate", "dummy.cert"); + config.put("otel.exporter.otlp.logs.certificate", certificatePath); + config.put("otel.exporter.otlp.client.key", "dummy.key"); + config.put("otel.exporter.otlp.logs.client.key", clientKeyPath); + config.put("otel.exporter.otlp.client.certificate", "dummy-client.cert"); + config.put("otel.exporter.otlp.logs.client.certificate", clientCertificatePath); + config.put("otel.exporter.otlp.headers", "dummy=value"); + config.put("otel.exporter.otlp.logs.headers", "header-key=header-value"); + config.put("otel.exporter.otlp.compression", "none"); + config.put("otel.exporter.otlp.logs.compression", "gzip"); + config.put("otel.exporter.otlp.timeout", "1s"); + config.put("otel.exporter.otlp.logs.timeout", "15s"); + + try (LogRecordExporter exporter = + provider.createExporter(DefaultConfigProperties.createForTest(config))) { + assertThat(exporter).isInstanceOf(OtlpHttpLogRecordExporter.class); + verify(httpBuilder, times(1)).build(); + verify(httpBuilder).setEndpoint("https://localhost:443/v1/logs"); + verify(httpBuilder).addHeader("header-key", "header-value"); + verify(httpBuilder).setCompression("gzip"); + verify(httpBuilder).setTimeout(Duration.ofSeconds(15)); + verify(httpBuilder).setTrustedCertificates(serverTls.certificate().getEncoded()); + verify(httpBuilder) + .setClientTls(clientTls.privateKey().getEncoded(), clientTls.certificate().getEncoded()); + } + Mockito.verifyNoInteractions(grpcBuilder); + } +} diff --git a/exporters/otlp/logs/src/test/java/io/opentelemetry/exporter/otlp/logs/OtlpGrpcLogRecordExporterTest.java b/exporters/otlp/logs/src/test/java/io/opentelemetry/exporter/otlp/logs/OtlpGrpcLogRecordExporterTest.java index acf485b243d..452acfb1022 100644 --- a/exporters/otlp/logs/src/test/java/io/opentelemetry/exporter/otlp/logs/OtlpGrpcLogRecordExporterTest.java +++ b/exporters/otlp/logs/src/test/java/io/opentelemetry/exporter/otlp/logs/OtlpGrpcLogRecordExporterTest.java @@ -8,22 +8,17 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.logs.Severity; import io.opentelemetry.exporter.internal.grpc.OkHttpGrpcExporter; import io.opentelemetry.exporter.internal.marshal.Marshaler; import io.opentelemetry.exporter.internal.otlp.logs.ResourceLogsMarshaler; import io.opentelemetry.exporter.internal.retry.RetryPolicy; import io.opentelemetry.exporter.internal.retry.RetryUtil; import io.opentelemetry.exporter.otlp.testing.internal.AbstractGrpcTelemetryExporterTest; +import io.opentelemetry.exporter.otlp.testing.internal.FakeTelemetryUtil; import io.opentelemetry.exporter.otlp.testing.internal.TelemetryExporterBuilder; import io.opentelemetry.proto.logs.v1.ResourceLogs; -import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.logs.data.LogRecordData; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.testing.logs.TestLogRecordData; import java.io.Closeable; -import java.time.Instant; import java.util.List; import org.junit.jupiter.api.Test; @@ -57,16 +52,7 @@ protected TelemetryExporterBuilder exporterBuilder() { @Override protected LogRecordData generateFakeTelemetry() { - return TestLogRecordData.builder() - .setResource(Resource.create(Attributes.builder().put("testKey", "testValue").build())) - .setInstrumentationScopeInfo( - InstrumentationScopeInfo.builder("instrumentation").setVersion("1").build()) - .setEpoch(Instant.now()) - .setSeverity(Severity.ERROR) - .setSeverityText("really severe") - .setBody("message") - .setAttributes(Attributes.builder().put("animal", "cat").build()) - .build(); + return FakeTelemetryUtil.generateFakeLogRecordData(); } @Override diff --git a/exporters/otlp/logs/src/testGrpcNetty/java/io/opentelemetry/exporter/otlp/logs/OtlpGrpcNettyLogRecordExporterTest.java b/exporters/otlp/logs/src/testGrpcNetty/java/io/opentelemetry/exporter/otlp/logs/OtlpGrpcNettyLogRecordExporterTest.java index 00ae6db3e00..85d9e67235e 100644 --- a/exporters/otlp/logs/src/testGrpcNetty/java/io/opentelemetry/exporter/otlp/logs/OtlpGrpcNettyLogRecordExporterTest.java +++ b/exporters/otlp/logs/src/testGrpcNetty/java/io/opentelemetry/exporter/otlp/logs/OtlpGrpcNettyLogRecordExporterTest.java @@ -9,23 +9,18 @@ import static org.assertj.core.api.Assertions.assertThatCode; import io.grpc.inprocess.InProcessChannelBuilder; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.logs.Severity; import io.opentelemetry.exporter.internal.grpc.UpstreamGrpcExporter; import io.opentelemetry.exporter.internal.marshal.Marshaler; import io.opentelemetry.exporter.internal.otlp.logs.ResourceLogsMarshaler; import io.opentelemetry.exporter.internal.retry.RetryPolicy; import io.opentelemetry.exporter.internal.retry.RetryUtil; import io.opentelemetry.exporter.otlp.testing.internal.AbstractGrpcTelemetryExporterTest; +import io.opentelemetry.exporter.otlp.testing.internal.FakeTelemetryUtil; import io.opentelemetry.exporter.otlp.testing.internal.ManagedChannelTelemetryExporterBuilder; import io.opentelemetry.exporter.otlp.testing.internal.TelemetryExporterBuilder; import io.opentelemetry.proto.logs.v1.ResourceLogs; -import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.logs.data.LogRecordData; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.testing.logs.TestLogRecordData; import java.io.Closeable; -import java.time.Instant; import java.util.List; import org.junit.jupiter.api.Test; @@ -64,16 +59,7 @@ protected TelemetryExporterBuilder exporterBuilder() { @Override protected LogRecordData generateFakeTelemetry() { - return TestLogRecordData.builder() - .setResource(Resource.create(Attributes.builder().put("testKey", "testValue").build())) - .setInstrumentationScopeInfo( - InstrumentationScopeInfo.builder("instrumentation").setVersion("1").build()) - .setEpoch(Instant.now()) - .setSeverity(Severity.ERROR) - .setSeverityText("really severe") - .setBody("message") - .setAttributes(Attributes.builder().put("animal", "cat").build()) - .build(); + return FakeTelemetryUtil.generateFakeLogRecordData(); } @Override diff --git a/exporters/otlp/logs/src/testGrpcNettyShaded/java/io/opentelemetry/exporter/otlp/logs/OtlpGrpcNettyShadedLogRecordExporterTest.java b/exporters/otlp/logs/src/testGrpcNettyShaded/java/io/opentelemetry/exporter/otlp/logs/OtlpGrpcNettyShadedLogRecordExporterTest.java index 0bb1300a87e..502e6b64e34 100644 --- a/exporters/otlp/logs/src/testGrpcNettyShaded/java/io/opentelemetry/exporter/otlp/logs/OtlpGrpcNettyShadedLogRecordExporterTest.java +++ b/exporters/otlp/logs/src/testGrpcNettyShaded/java/io/opentelemetry/exporter/otlp/logs/OtlpGrpcNettyShadedLogRecordExporterTest.java @@ -8,21 +8,16 @@ import static org.assertj.core.api.Assertions.assertThat; import io.grpc.inprocess.InProcessChannelBuilder; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.logs.Severity; import io.opentelemetry.exporter.internal.grpc.UpstreamGrpcExporter; import io.opentelemetry.exporter.internal.marshal.Marshaler; import io.opentelemetry.exporter.internal.otlp.logs.ResourceLogsMarshaler; import io.opentelemetry.exporter.otlp.testing.internal.AbstractGrpcTelemetryExporterTest; +import io.opentelemetry.exporter.otlp.testing.internal.FakeTelemetryUtil; import io.opentelemetry.exporter.otlp.testing.internal.ManagedChannelTelemetryExporterBuilder; import io.opentelemetry.exporter.otlp.testing.internal.TelemetryExporterBuilder; import io.opentelemetry.proto.logs.v1.ResourceLogs; -import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.logs.data.LogRecordData; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.testing.logs.TestLogRecordData; import java.io.Closeable; -import java.time.Instant; import java.util.List; import org.junit.jupiter.api.Test; @@ -52,16 +47,7 @@ protected TelemetryExporterBuilder exporterBuilder() { @Override protected LogRecordData generateFakeTelemetry() { - return TestLogRecordData.builder() - .setResource(Resource.create(Attributes.builder().put("testKey", "testValue").build())) - .setInstrumentationScopeInfo( - InstrumentationScopeInfo.builder("instrumentation").setVersion("1").build()) - .setEpoch(Instant.now()) - .setSeverity(Severity.ERROR) - .setSeverityText("really severe") - .setBody("message") - .setAttributes(Attributes.builder().put("animal", "cat").build()) - .build(); + return FakeTelemetryUtil.generateFakeLogRecordData(); } @Override diff --git a/exporters/otlp/logs/src/testGrpcOkhttp/java/io/opentelemetry/exporter/otlp/logs/OtlpGrpcNettyOkHttpLogRecordExporterTest.java b/exporters/otlp/logs/src/testGrpcOkhttp/java/io/opentelemetry/exporter/otlp/logs/OtlpGrpcNettyOkHttpLogRecordExporterTest.java index 01a73674286..c5fc358915d 100644 --- a/exporters/otlp/logs/src/testGrpcOkhttp/java/io/opentelemetry/exporter/otlp/logs/OtlpGrpcNettyOkHttpLogRecordExporterTest.java +++ b/exporters/otlp/logs/src/testGrpcOkhttp/java/io/opentelemetry/exporter/otlp/logs/OtlpGrpcNettyOkHttpLogRecordExporterTest.java @@ -8,21 +8,16 @@ import static org.assertj.core.api.Assertions.assertThat; import io.grpc.inprocess.InProcessChannelBuilder; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.logs.Severity; import io.opentelemetry.exporter.internal.grpc.UpstreamGrpcExporter; import io.opentelemetry.exporter.internal.marshal.Marshaler; import io.opentelemetry.exporter.internal.otlp.logs.ResourceLogsMarshaler; import io.opentelemetry.exporter.otlp.testing.internal.AbstractGrpcTelemetryExporterTest; +import io.opentelemetry.exporter.otlp.testing.internal.FakeTelemetryUtil; import io.opentelemetry.exporter.otlp.testing.internal.ManagedChannelTelemetryExporterBuilder; import io.opentelemetry.exporter.otlp.testing.internal.TelemetryExporterBuilder; import io.opentelemetry.proto.logs.v1.ResourceLogs; -import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.logs.data.LogRecordData; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.testing.logs.TestLogRecordData; import java.io.Closeable; -import java.time.Instant; import java.util.List; import org.junit.jupiter.api.Test; @@ -52,16 +47,7 @@ protected TelemetryExporterBuilder exporterBuilder() { @Override protected LogRecordData generateFakeTelemetry() { - return TestLogRecordData.builder() - .setResource(Resource.create(Attributes.builder().put("testKey", "testValue").build())) - .setInstrumentationScopeInfo( - InstrumentationScopeInfo.builder("instrumentation").setVersion("1").build()) - .setEpoch(Instant.now()) - .setSeverity(Severity.ERROR) - .setSeverityText("really severe") - .setBody("message") - .setAttributes(Attributes.builder().put("animal", "cat").build()) - .build(); + return FakeTelemetryUtil.generateFakeLogRecordData(); } @Override diff --git a/exporters/otlp/testing-internal/build.gradle.kts b/exporters/otlp/testing-internal/build.gradle.kts index 074a300c45d..f420d877790 100644 --- a/exporters/otlp/testing-internal/build.gradle.kts +++ b/exporters/otlp/testing-internal/build.gradle.kts @@ -11,6 +11,7 @@ dependencies { api(project(":sdk:metrics")) api(project(":sdk:trace")) api(project(":sdk:testing")) + api(project(":sdk:logs-testing")) api(project(":exporters:otlp:all")) api(project(":exporters:otlp:logs")) @@ -22,6 +23,7 @@ dependencies { api("io.opentelemetry.proto:opentelemetry-proto") api("org.junit.jupiter:junit-jupiter-api") + implementation("com.squareup.okhttp3:okhttp") implementation("org.junit.jupiter:junit-jupiter-params") implementation("com.linecorp.armeria:armeria-grpc-protocol") diff --git a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/AbstractGrpcTelemetryExporterTest.java b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/AbstractGrpcTelemetryExporterTest.java index f07b0ea320a..a009c7c61fd 100644 --- a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/AbstractGrpcTelemetryExporterTest.java +++ b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/AbstractGrpcTelemetryExporterTest.java @@ -222,9 +222,9 @@ void export() { assertThat(httpRequests) .singleElement() .satisfies( - req -> { - assertThat(req.headers().get("User-Agent")).matches("OTel OTLP Exporter Java/1\\..*"); - }); + req -> + assertThat(req.headers().get("User-Agent")) + .matches("OTel OTLP Exporter Java/1\\..*")); } @Test @@ -273,6 +273,25 @@ void authorityWithAuth() { } } + @Test + void withHeaders() { + TelemetryExporter exporter = + exporterBuilder() + .setEndpoint(server.httpUri().toString()) + .addHeader("key", "value") + .build(); + try { + CompletableResultCode result = + exporter.export(Collections.singletonList(generateFakeTelemetry())); + assertThat(result.join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); + assertThat(httpRequests) + .singleElement() + .satisfies(req -> assertThat(req.headers().get("key")).isEqualTo("value")); + } finally { + exporter.shutdown(); + } + } + @Test void tls() throws Exception { TelemetryExporter exporter = @@ -305,8 +324,6 @@ void tls_untrusted() { } @Test - @SuppressLogger(OkHttpGrpcExporter.class) - @SuppressLogger(UpstreamGrpcExporter.class) void tls_badCert() { assertThatThrownBy( () -> @@ -711,7 +728,7 @@ private static void addGrpcError(int code, @Nullable String message) { private static boolean usingOkHttp() { try { - Class.forName("io.grpc.stub.AbstractStub"); + Class.forName("io.grpc.internal.AbstractManagedChannelImplBuilder"); return false; } catch (ClassNotFoundException e) { return true; diff --git a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/AbstractHttpTelemetryExporterTest.java b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/AbstractHttpTelemetryExporterTest.java new file mode 100644 index 00000000000..99b3ba914a2 --- /dev/null +++ b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/AbstractHttpTelemetryExporterTest.java @@ -0,0 +1,640 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.otlp.testing.internal; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assumptions.assumeThat; +import static org.junit.jupiter.api.Named.named; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.Message; +import com.linecorp.armeria.common.HttpRequest; +import com.linecorp.armeria.common.HttpResponse; +import com.linecorp.armeria.common.HttpStatus; +import com.linecorp.armeria.common.MediaType; +import com.linecorp.armeria.common.RequestHeaders; +import com.linecorp.armeria.server.HttpService; +import com.linecorp.armeria.server.ServerBuilder; +import com.linecorp.armeria.server.ServiceRequestContext; +import com.linecorp.armeria.server.logging.LoggingService; +import com.linecorp.armeria.testing.junit5.server.SelfSignedCertificateExtension; +import com.linecorp.armeria.testing.junit5.server.ServerExtension; +import io.github.netmikey.logunit.api.LogCapturer; +import io.opentelemetry.exporter.internal.grpc.UpstreamGrpcExporter; +import io.opentelemetry.exporter.internal.marshal.Marshaler; +import io.opentelemetry.exporter.internal.okhttp.OkHttpExporter; +import io.opentelemetry.exporter.internal.retry.RetryPolicy; +import io.opentelemetry.internal.testing.slf4j.SuppressLogger; +import io.opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest; +import io.opentelemetry.proto.collector.logs.v1.ExportLogsServiceResponse; +import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest; +import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse; +import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; +import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse; +import io.opentelemetry.sdk.common.CompletableResultCode; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import okio.Buffer; +import okio.GzipSource; +import okio.Okio; +import org.assertj.core.api.iterable.ThrowingExtractor; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.slf4j.event.Level; +import org.slf4j.event.LoggingEvent; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public abstract class AbstractHttpTelemetryExporterTest { + + private static final ConcurrentLinkedQueue exportedResourceTelemetry = + new ConcurrentLinkedQueue<>(); + + private static final ConcurrentLinkedQueue httpErrors = + new ConcurrentLinkedQueue<>(); + + private static final AtomicInteger attempts = new AtomicInteger(); + + private static final ConcurrentLinkedQueue httpRequests = + new ConcurrentLinkedQueue<>(); + + @RegisterExtension + @Order(1) + static final SelfSignedCertificateExtension certificate = new SelfSignedCertificateExtension(); + + @RegisterExtension + @Order(2) + static final SelfSignedCertificateExtension clientCertificate = + new SelfSignedCertificateExtension(); + + @RegisterExtension + @Order(3) + static final ServerExtension server = + new ServerExtension() { + @Override + protected void configure(ServerBuilder sb) { + sb.service( + "/v1/traces", + new CollectorService<>( + ExportTraceServiceRequest::parseFrom, + ExportTraceServiceRequest::getResourceSpansList, + ExportTraceServiceResponse.getDefaultInstance().toByteArray())); + sb.service( + "/v1/metrics", + new CollectorService<>( + ExportMetricsServiceRequest::parseFrom, + ExportMetricsServiceRequest::getResourceMetricsList, + ExportMetricsServiceResponse.getDefaultInstance().toByteArray())); + sb.service( + "/v1/logs", + new CollectorService<>( + ExportLogsServiceRequest::parseFrom, + ExportLogsServiceRequest::getResourceLogsList, + ExportLogsServiceResponse.getDefaultInstance().toByteArray())); + + sb.http(0); + sb.https(0); + sb.tls(certificate.certificateFile(), certificate.privateKeyFile()); + sb.tlsCustomizer(ssl -> ssl.trustManager(clientCertificate.certificate())); + sb.decorator(LoggingService.newDecorator()); + } + }; + + private static class CollectorService implements HttpService { + private final ThrowingExtractor parse; + private final Function> getResourceTelemetry; + private final byte[] successResponse; + + private CollectorService( + ThrowingExtractor parse, + Function> getResourceTelemetry, + byte[] successResponse) { + this.parse = parse; + this.getResourceTelemetry = getResourceTelemetry; + this.successResponse = successResponse; + } + + @Override + public HttpResponse serve(ServiceRequestContext ctx, HttpRequest req) { + httpRequests.add(ctx.request()); + attempts.incrementAndGet(); + CompletableFuture responseFuture = + req.aggregate() + .thenApply( + aggReq -> { + T request; + try { + byte[] requestBody = + maybeGzipInflate(aggReq.headers(), aggReq.content().array()); + request = parse.extractThrows(requestBody); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + exportedResourceTelemetry.addAll(getResourceTelemetry.apply(request)); + HttpResponse errorResponse = httpErrors.poll(); + return errorResponse != null + ? errorResponse + : HttpResponse.of( + HttpStatus.OK, + MediaType.parse("application/x-protobuf"), + successResponse); + }); + return HttpResponse.from(responseFuture); + } + + private static byte[] maybeGzipInflate(RequestHeaders requestHeaders, byte[] content) + throws IOException { + if (!requestHeaders.contains("content-encoding", "gzip")) { + return content; + } + Buffer buffer = new Buffer(); + GzipSource gzipSource = new GzipSource(Okio.source(new ByteArrayInputStream(content))); + gzipSource.read(buffer, Integer.MAX_VALUE); + return buffer.readByteArray(); + } + } + + @RegisterExtension LogCapturer logs = LogCapturer.create().captureForType(OkHttpExporter.class); + + private final String type; + private final String path; + private final U resourceTelemetryInstance; + + private TelemetryExporter exporter; + + protected AbstractHttpTelemetryExporterTest( + String type, String path, U resourceTelemetryInstance) { + this.type = type; + this.path = path; + this.resourceTelemetryInstance = resourceTelemetryInstance; + } + + @BeforeAll + void setUp() { + exporter = exporterBuilder().setEndpoint(server.httpUri() + path).build(); + + // Sanity check that TLS files are in PEM format. + assertThat(certificate.certificateFile()) + .binaryContent() + .asString(StandardCharsets.UTF_8) + .startsWith("-----BEGIN CERTIFICATE-----"); + assertThat(certificate.privateKeyFile()) + .binaryContent() + .asString(StandardCharsets.UTF_8) + .startsWith("-----BEGIN PRIVATE KEY-----"); + assertThat(clientCertificate.certificateFile()) + .binaryContent() + .asString(StandardCharsets.UTF_8) + .startsWith("-----BEGIN CERTIFICATE-----"); + assertThat(clientCertificate.privateKeyFile()) + .binaryContent() + .asString(StandardCharsets.UTF_8) + .startsWith("-----BEGIN PRIVATE KEY-----"); + } + + @AfterAll + void tearDown() { + exporter.shutdown(); + } + + @AfterEach + void reset() { + exportedResourceTelemetry.clear(); + httpErrors.clear(); + attempts.set(0); + httpRequests.clear(); + } + + @Test + void export() { + List telemetry = Collections.singletonList(generateFakeTelemetry()); + assertThat(exporter.export(telemetry).join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); + List expectedResourceTelemetry = toProto(telemetry); + assertThat(exportedResourceTelemetry).containsExactlyElementsOf(expectedResourceTelemetry); + + // Assert request contains OTLP spec compliant User-Agent header + assertThat(httpRequests) + .singleElement() + .satisfies( + req -> { + assertThat(req.headers().get("User-Agent")).matches("OTel OTLP Exporter Java/1\\..*"); + }); + } + + @Test + void multipleItems() { + List telemetry = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + telemetry.add(generateFakeTelemetry()); + } + assertThat(exporter.export(telemetry).join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); + List expectedResourceTelemetry = toProto(telemetry); + assertThat(exportedResourceTelemetry).containsExactlyElementsOf(expectedResourceTelemetry); + } + + @Test + void compressionWithNone() { + TelemetryExporter exporter = + exporterBuilder().setEndpoint(server.httpUri() + path).setCompression("none").build(); + assertThat(exporter.unwrap()).extracting("delegate.compressionEnabled").isEqualTo(false); + try { + CompletableResultCode result = + exporter.export(Collections.singletonList(generateFakeTelemetry())); + assertThat(result.join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); + assertThat(httpRequests) + .singleElement() + .satisfies(req -> assertThat(req.headers().get("content-encoding")).isNull()); + } finally { + exporter.shutdown(); + } + } + + @Test + void compressionWithGzip() { + TelemetryExporter exporter = + exporterBuilder().setEndpoint(server.httpUri() + path).setCompression("gzip").build(); + // UpstreamGrpcExporter doesn't support compression, so we skip the assertion + assumeThat(exporter.unwrap()) + .extracting("delegate") + .isNotInstanceOf(UpstreamGrpcExporter.class); + assertThat(exporter.unwrap()).extracting("delegate.compressionEnabled").isEqualTo(true); + try { + CompletableResultCode result = + exporter.export(Collections.singletonList(generateFakeTelemetry())); + assertThat(result.join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); + assertThat(httpRequests) + .singleElement() + .satisfies(req -> assertThat(req.headers().get("content-encoding")).isEqualTo("gzip")); + } finally { + exporter.shutdown(); + } + } + + @Test + void authorityWithAuth() { + TelemetryExporter exporter = + exporterBuilder() + .setEndpoint("http://foo:bar@localhost:" + server.httpPort() + path) + .build(); + try { + CompletableResultCode result = + exporter.export(Collections.singletonList(generateFakeTelemetry())); + assertThat(result.join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); + } finally { + exporter.shutdown(); + } + } + + @Test + void withHeaders() { + TelemetryExporter exporter = + exporterBuilder().setEndpoint(server.httpUri() + path).addHeader("key", "value").build(); + try { + CompletableResultCode result = + exporter.export(Collections.singletonList(generateFakeTelemetry())); + assertThat(result.join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); + assertThat(httpRequests) + .singleElement() + .satisfies(req -> assertThat(req.headers().get("key")).isEqualTo("value")); + } finally { + exporter.shutdown(); + } + } + + @Test + void tls() throws Exception { + TelemetryExporter exporter = + exporterBuilder() + .setEndpoint(server.httpsUri() + path) + .setTrustedCertificates(Files.readAllBytes(certificate.certificateFile().toPath())) + .build(); + try { + CompletableResultCode result = + exporter.export(Collections.singletonList(generateFakeTelemetry())); + assertThat(result.join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); + } finally { + exporter.shutdown(); + } + } + + @Test + @SuppressLogger(OkHttpExporter.class) + void tls_untrusted() { + TelemetryExporter exporter = exporterBuilder().setEndpoint(server.httpsUri() + path).build(); + try { + CompletableResultCode result = + exporter.export(Collections.singletonList(generateFakeTelemetry())); + assertThat(result.join(10, TimeUnit.SECONDS).isSuccess()).isFalse(); + } finally { + exporter.shutdown(); + } + } + + @Test + void tls_badCert() { + assertThatThrownBy( + () -> + exporterBuilder() + .setEndpoint(server.httpsUri() + path) + .setTrustedCertificates("foobar".getBytes(StandardCharsets.UTF_8)) + .build()) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Could not set trusted certificate"); + } + + @ParameterizedTest + @ArgumentsSource(ClientPrivateKeyProvider.class) + void clientTls(byte[] privateKey) throws Exception { + TelemetryExporter exporter = + exporterBuilder() + .setEndpoint(server.httpsUri() + path) + .setTrustedCertificates(Files.readAllBytes(certificate.certificateFile().toPath())) + .setClientTls( + privateKey, Files.readAllBytes(clientCertificate.certificateFile().toPath())) + .build(); + try { + CompletableResultCode result = + exporter.export(Collections.singletonList(generateFakeTelemetry())); + assertThat(result.join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); + } finally { + exporter.shutdown(); + } + } + + private static class ClientPrivateKeyProvider implements ArgumentsProvider { + @Override + @SuppressWarnings("PrimitiveArrayPassedToVarargsMethod") + public Stream provideArguments(ExtensionContext context) throws Exception { + return Stream.of( + arguments(named("PEM", Files.readAllBytes(clientCertificate.privateKeyFile().toPath()))), + arguments(named("DER", clientCertificate.privateKey().getEncoded()))); + } + } + + @Test + void deadlineSetPerExport() throws InterruptedException { + TelemetryExporter exporter = + exporterBuilder() + .setEndpoint(server.httpUri() + path) + .setTimeout(Duration.ofMillis(1500)) + .build(); + try { + TimeUnit.MILLISECONDS.sleep(2000); + CompletableResultCode result = + exporter.export(Collections.singletonList(generateFakeTelemetry())); + assertThat(result.join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); + } finally { + exporter.shutdown(); + } + } + + @Test + @SuppressLogger(OkHttpExporter.class) + void exportAfterShutdown() { + TelemetryExporter exporter = exporterBuilder().setEndpoint(server.httpUri() + path).build(); + exporter.shutdown(); + assertThat( + exporter + .export(Collections.singletonList(generateFakeTelemetry())) + .join(10, TimeUnit.SECONDS) + .isSuccess()) + .isFalse(); + } + + @Test + void doubleShutdown() { + TelemetryExporter exporter = exporterBuilder().setEndpoint(server.httpUri() + path).build(); + assertThat(exporter.shutdown().join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); + assertThat(exporter.shutdown().join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); + } + + @Test + @SuppressLogger(OkHttpExporter.class) + void error() { + addHttpError(500); + assertThat( + exporter + .export(Collections.singletonList(generateFakeTelemetry())) + .join(10, TimeUnit.SECONDS) + .isSuccess()) + .isFalse(); + LoggingEvent log = + logs.assertContains( + "Failed to export " + + type + + "s. Server responded with HTTP status code 500. Error message:"); + assertThat(log.getLevel()).isEqualTo(Level.WARN); + } + + @ParameterizedTest + @ValueSource(ints = {429, 502, 503, 504}) + void retryableError(int code) { + addHttpError(code); + + TelemetryExporter exporter = retryingExporter(); + + try { + assertThat( + exporter + .export(Collections.singletonList(generateFakeTelemetry())) + .join(10, TimeUnit.SECONDS) + .isSuccess()) + .isTrue(); + } finally { + exporter.shutdown(); + } + + assertThat(attempts).hasValue(2); + } + + @Test + @SuppressLogger(OkHttpExporter.class) + void retryableError_tooManyAttempts() { + addHttpError(502); + addHttpError(502); + + TelemetryExporter exporter = retryingExporter(); + + try { + assertThat( + exporter + .export(Collections.singletonList(generateFakeTelemetry())) + .join(10, TimeUnit.SECONDS) + .isSuccess()) + .isFalse(); + } finally { + exporter.shutdown(); + } + + assertThat(attempts).hasValue(2); + } + + @ParameterizedTest + @SuppressLogger(OkHttpExporter.class) + @ValueSource(ints = {400, 401, 403, 500, 501}) + void nonRetryableError(int code) { + addHttpError(code); + + TelemetryExporter exporter = retryingExporter(); + + try { + assertThat( + exporter + .export(Collections.singletonList(generateFakeTelemetry())) + .join(10, TimeUnit.SECONDS) + .isSuccess()) + .isFalse(); + } finally { + exporter.shutdown(); + } + + assertThat(attempts).hasValue(1); + } + + @Test + @SuppressWarnings("PreferJavaTimeOverload") + void validConfig() { + assertThatCode(() -> exporterBuilder().setTimeout(0, TimeUnit.MILLISECONDS)) + .doesNotThrowAnyException(); + assertThatCode(() -> exporterBuilder().setTimeout(Duration.ofMillis(0))) + .doesNotThrowAnyException(); + assertThatCode(() -> exporterBuilder().setTimeout(10, TimeUnit.MILLISECONDS)) + .doesNotThrowAnyException(); + assertThatCode(() -> exporterBuilder().setTimeout(Duration.ofMillis(10))) + .doesNotThrowAnyException(); + + assertThatCode(() -> exporterBuilder().setEndpoint("http://localhost:4318")) + .doesNotThrowAnyException(); + assertThatCode(() -> exporterBuilder().setEndpoint("http://localhost/")) + .doesNotThrowAnyException(); + assertThatCode(() -> exporterBuilder().setEndpoint("https://localhost/")) + .doesNotThrowAnyException(); + assertThatCode(() -> exporterBuilder().setEndpoint("http://foo:bar@localhost/")) + .doesNotThrowAnyException(); + + assertThatCode(() -> exporterBuilder().setCompression("gzip")).doesNotThrowAnyException(); + assertThatCode(() -> exporterBuilder().setCompression("none")).doesNotThrowAnyException(); + + assertThatCode(() -> exporterBuilder().addHeader("foo", "bar").addHeader("baz", "qux")) + .doesNotThrowAnyException(); + + assertThatCode( + () -> + exporterBuilder().setTrustedCertificates("foobar".getBytes(StandardCharsets.UTF_8))) + .doesNotThrowAnyException(); + } + + @Test + @SuppressWarnings({"PreferJavaTimeOverload", "NullAway"}) + void invalidConfig() { + assertThatThrownBy(() -> exporterBuilder().setTimeout(-1, TimeUnit.MILLISECONDS)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("timeout must be non-negative"); + assertThatThrownBy(() -> exporterBuilder().setTimeout(1, null)) + .isInstanceOf(NullPointerException.class) + .hasMessage("unit"); + assertThatThrownBy(() -> exporterBuilder().setTimeout(null)) + .isInstanceOf(NullPointerException.class) + .hasMessage("timeout"); + + assertThatThrownBy(() -> exporterBuilder().setEndpoint(null)) + .isInstanceOf(NullPointerException.class) + .hasMessage("endpoint"); + assertThatThrownBy(() -> exporterBuilder().setEndpoint("😺://localhost")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Invalid endpoint, must be a URL: 😺://localhost"); + assertThatThrownBy(() -> exporterBuilder().setEndpoint("localhost")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Invalid endpoint, must start with http:// or https://: localhost"); + assertThatThrownBy(() -> exporterBuilder().setEndpoint("gopher://localhost")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Invalid endpoint, must start with http:// or https://: gopher://localhost"); + + assertThatThrownBy(() -> exporterBuilder().setCompression(null)) + .isInstanceOf(NullPointerException.class) + .hasMessage("compressionMethod"); + assertThatThrownBy(() -> exporterBuilder().setCompression("foo")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage( + "Unsupported compression method. Supported compression methods include: gzip, none."); + } + + protected abstract TelemetryExporterBuilder exporterBuilder(); + + protected abstract T generateFakeTelemetry(); + + protected abstract Marshaler[] toMarshalers(List telemetry); + + private List toProto(List telemetry) { + return Arrays.stream(toMarshalers(telemetry)) + .map( + marshaler -> { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try { + marshaler.writeBinaryTo(bos); + @SuppressWarnings("unchecked") + U result = + (U) + resourceTelemetryInstance + .newBuilderForType() + .mergeFrom(bos.toByteArray()) + .build(); + return result; + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }) + .collect(Collectors.toList()); + } + + private TelemetryExporter retryingExporter() { + return exporterBuilder() + .setEndpoint(server.httpUri() + path) + .setRetryPolicy( + RetryPolicy.builder() + .setMaxAttempts(2) + // We don't validate backoff time itself in these tests, just that retries + // occur. Keep the tests fast by using minimal backoff. + .setInitialBackoff(Duration.ofMillis(1)) + .setMaxBackoff(Duration.ofMillis(1)) + .setBackoffMultiplier(1) + .build()) + .build(); + } + + private static void addHttpError(int code) { + httpErrors.add(HttpResponse.of(code)); + } +} diff --git a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/FakeTelemetryUtil.java b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/FakeTelemetryUtil.java new file mode 100644 index 00000000000..d35420202b6 --- /dev/null +++ b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/FakeTelemetryUtil.java @@ -0,0 +1,98 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.otlp.testing.internal; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.logs.Severity; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.TraceFlags; +import io.opentelemetry.api.trace.TraceState; +import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.metrics.data.AggregationTemporality; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableLongPointData; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableMetricData; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableSumData; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.testing.logs.TestLogRecordData; +import io.opentelemetry.sdk.testing.trace.TestSpanData; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.data.StatusData; +import java.time.Instant; +import java.util.Collections; +import java.util.concurrent.TimeUnit; + +public class FakeTelemetryUtil { + + private static final String TRACE_ID = "00000000000000000000000000abc123"; + private static final String SPAN_ID = "0000000000def456"; + + /** Generate a fake {@link MetricData}. */ + public static MetricData generateFakeMetricData() { + long startNs = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()); + long endNs = startNs + TimeUnit.MILLISECONDS.toNanos(900); + return ImmutableMetricData.createLongSum( + Resource.empty(), + InstrumentationScopeInfo.empty(), + "name", + "description", + "1", + ImmutableSumData.create( + /* isMonotonic= */ true, + AggregationTemporality.CUMULATIVE, + Collections.singletonList( + ImmutableLongPointData.create( + startNs, endNs, Attributes.of(stringKey("k"), "v"), 5)))); + } + + /** Generate a fake {@link SpanData}. */ + public static SpanData generateFakeSpanData() { + long duration = TimeUnit.MILLISECONDS.toNanos(900); + long startNs = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()); + long endNs = startNs + duration; + return TestSpanData.builder() + .setHasEnded(true) + .setSpanContext( + SpanContext.create(TRACE_ID, SPAN_ID, TraceFlags.getDefault(), TraceState.getDefault())) + .setName("GET /api/endpoint") + .setStartEpochNanos(startNs) + .setEndEpochNanos(endNs) + .setStatus(StatusData.ok()) + .setKind(SpanKind.SERVER) + .setLinks(Collections.emptyList()) + .setTotalRecordedLinks(0) + .setTotalRecordedEvents(0) + .setInstrumentationScopeInfo( + InstrumentationScopeInfo.builder("testLib") + .setVersion("1.0") + .setSchemaUrl("http://url") + .build()) + .build(); + } + + /** Generate a fake {@link LogRecordData}. */ + public static LogRecordData generateFakeLogRecordData() { + return TestLogRecordData.builder() + .setResource(Resource.getDefault()) + .setInstrumentationScopeInfo( + InstrumentationScopeInfo.builder("testLib") + .setVersion("1.0") + .setSchemaUrl("http://url") + .build()) + .setBody("log body") + .setAttributes(Attributes.builder().put("key", "value").build()) + .setSeverity(Severity.INFO) + .setSeverityText(Severity.INFO.name()) + .setEpoch(Instant.now()) + .build(); + } + + private FakeTelemetryUtil() {} +} diff --git a/sdk-extensions/autoconfigure/build.gradle.kts b/sdk-extensions/autoconfigure/build.gradle.kts index 37134675c47..2a9fa1592a2 100644 --- a/sdk-extensions/autoconfigure/build.gradle.kts +++ b/sdk-extensions/autoconfigure/build.gradle.kts @@ -54,11 +54,8 @@ testing { val testConfigError by registering(JvmTestSuite::class) { dependencies { implementation(project(":extensions:trace-propagators")) - implementation(project(":exporters:jaeger")) - implementation(project(":exporters:logging")) implementation(project(":exporters:otlp:all")) implementation(project(":exporters:otlp:logs")) - implementation(project(":exporters:zipkin")) } } val testFullConfig by registering(JvmTestSuite::class) { @@ -110,22 +107,6 @@ testing { } } } - val testOtlp by registering(JvmTestSuite::class) { - dependencies { - implementation(project(":exporters:otlp:all")) - implementation(project(":exporters:otlp:logs")) - implementation(project(":exporters:otlp:common")) - implementation(project(":sdk:testing")) - implementation(project(":sdk:logs-testing")) - - implementation("io.opentelemetry.proto:opentelemetry-proto") - implementation("com.linecorp.armeria:armeria-junit5") - implementation("com.linecorp.armeria:armeria-grpc") - implementation("com.squareup.okhttp3:okhttp") - implementation("com.squareup.okhttp3:okhttp-tls") - runtimeOnly("io.grpc:grpc-netty-shaded") - } - } } } diff --git a/sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpGrpcConfigTest.java b/sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpGrpcConfigTest.java deleted file mode 100644 index ca497d18cbf..00000000000 --- a/sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpGrpcConfigTest.java +++ /dev/null @@ -1,410 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.autoconfigure; - -import static io.opentelemetry.sdk.autoconfigure.OtlpGrpcServerExtension.generateFakeLog; -import static io.opentelemetry.sdk.autoconfigure.OtlpGrpcServerExtension.generateFakeMetric; -import static io.opentelemetry.sdk.autoconfigure.OtlpGrpcServerExtension.generateFakeSpan; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.api.InstanceOfAssertFactories.INTEGER; -import static org.awaitility.Awaitility.await; - -import com.google.common.collect.Lists; -import com.linecorp.armeria.testing.junit5.server.SelfSignedCertificateExtension; -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.logs.GlobalLoggerProvider; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; -import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; -import io.opentelemetry.sdk.logs.data.LogRecordData; -import io.opentelemetry.sdk.logs.export.LogRecordExporter; -import io.opentelemetry.sdk.metrics.InstrumentType; -import io.opentelemetry.sdk.metrics.data.AggregationTemporality; -import io.opentelemetry.sdk.metrics.data.MetricData; -import io.opentelemetry.sdk.metrics.export.MetricExporter; -import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.export.SpanExporter; -import java.lang.reflect.Field; -import java.nio.file.Paths; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.junitpioneer.jupiter.SetSystemProperty; - -class OtlpGrpcConfigTest { - - private static final List SPAN_DATA = Lists.newArrayList(generateFakeSpan()); - private static final List METRIC_DATA = Lists.newArrayList(generateFakeMetric()); - private static final List LOG_RECORD_DATA = Lists.newArrayList(generateFakeLog()); - - @RegisterExtension - @Order(1) - public static final SelfSignedCertificateExtension certificate = - new SelfSignedCertificateExtension(); - - @RegisterExtension - @Order(2) - public static final OtlpGrpcServerExtension server = new OtlpGrpcServerExtension(certificate); - - @BeforeEach - void setUp() { - GlobalOpenTelemetry.resetForTest(); - GlobalLoggerProvider.resetForTest(); - } - - @AfterEach - public void tearDown() { - server.reset(); - shutdownGlobalSdk(); - GlobalOpenTelemetry.resetForTest(); - GlobalLoggerProvider.resetForTest(); - } - - @Test - void configureExportersGeneral() { - Map props = new HashMap<>(); - props.put("otel.exporter.otlp.endpoint", "https://localhost:" + server.httpsPort()); - props.put("otel.exporter.otlp.certificate", certificate.certificateFile().getAbsolutePath()); - props.put("otel.exporter.otlp.headers", "header-key=header-value"); - props.put("otel.exporter.otlp.compression", "gzip"); - props.put("otel.exporter.otlp.timeout", "15s"); - ConfigProperties properties = DefaultConfigProperties.createForTest(props); - try (SpanExporter spanExporter = - SpanExporterConfiguration.configureExporter( - "otlp", - SpanExporterConfiguration.spanExporterSpiManager( - properties, OtlpGrpcConfigTest.class.getClassLoader())); - MetricExporter metricExporter = - MetricExporterConfiguration.configureExporter( - "otlp", - MetricExporterConfiguration.metricExporterSpiManager( - properties, OtlpGrpcConfigTest.class.getClassLoader())); - LogRecordExporter logRecordExporter = - LogRecordExporterConfiguration.configureExporter( - "otlp", - LogRecordExporterConfiguration.logRecordExporterSpiManager( - properties, OtlpGrpcConfigTest.class.getClassLoader()))) { - assertThat(spanExporter) - .extracting("delegate.client.callTimeoutMillis", INTEGER) - .isEqualTo(TimeUnit.SECONDS.toMillis(15)); - assertThat(spanExporter.export(SPAN_DATA).join(15, TimeUnit.SECONDS).isSuccess()).isTrue(); - assertThat(server.traceRequests).hasSize(1); - assertThat(server.requestHeaders) - .anyMatch( - headers -> - headers.contains( - ":path", "/opentelemetry.proto.collector.trace.v1.TraceService/Export") - && headers.contains("header-key", "header-value") - && headers.contains("grpc-encoding", "gzip")); - - assertThat(metricExporter) - .extracting("delegate.client.callTimeoutMillis", INTEGER) - .isEqualTo(TimeUnit.SECONDS.toMillis(15)); - assertThat(metricExporter.export(METRIC_DATA).join(15, TimeUnit.SECONDS).isSuccess()) - .isTrue(); - assertThat(server.metricRequests).hasSize(1); - assertThat(server.requestHeaders) - .anyMatch( - headers -> - headers.contains( - ":path", - "/opentelemetry.proto.collector.metrics.v1.MetricsService/Export") - && headers.contains("header-key", "header-value") - && headers.contains("grpc-encoding", "gzip")); - - assertThat(logRecordExporter) - .extracting("delegate.client.callTimeoutMillis", INTEGER) - .isEqualTo(TimeUnit.SECONDS.toMillis(15)); - assertThat(logRecordExporter.export(LOG_RECORD_DATA).join(15, TimeUnit.SECONDS).isSuccess()) - .isTrue(); - assertThat(server.logRequests).hasSize(1); - assertThat(server.requestHeaders) - .anyMatch( - headers -> - headers.contains( - ":path", "/opentelemetry.proto.collector.logs.v1.LogsService/Export") - && headers.contains("header-key", "header-value") - && headers.contains("grpc-encoding", "gzip")); - } - } - - @Test - void configureSpanExporter() { - // Set values for general and signal specific properties. Signal specific should override - // general. - Map props = new HashMap<>(); - props.put("otel.exporter.otlp.endpoint", "http://foo.bar"); - props.put("otel.exporter.otlp.certificate", Paths.get("foo", "bar", "baz").toString()); - props.put("otel.exporter.otlp.headers", "header-key=dummy-value"); - props.put("otel.exporter.otlp.compression", "foo"); - props.put("otel.exporter.otlp.timeout", "10s"); - props.put("otel.exporter.otlp.traces.endpoint", "https://localhost:" + server.httpsPort()); - props.put( - "otel.exporter.otlp.traces.certificate", certificate.certificateFile().getAbsolutePath()); - props.put("otel.exporter.otlp.traces.headers", "header-key=header-value"); - props.put("otel.exporter.otlp.traces.compression", "gzip"); - props.put("otel.exporter.otlp.traces.timeout", "15s"); - ConfigProperties properties = DefaultConfigProperties.createForTest(props); - try (SpanExporter spanExporter = - SpanExporterConfiguration.configureExporter( - "otlp", - SpanExporterConfiguration.spanExporterSpiManager( - properties, OtlpGrpcConfigTest.class.getClassLoader()))) { - assertThat(spanExporter) - .extracting("delegate.client.callTimeoutMillis", INTEGER) - .isEqualTo(TimeUnit.SECONDS.toMillis(15)); - assertThat(spanExporter.export(SPAN_DATA).join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); - assertThat(server.traceRequests).hasSize(1); - assertThat(server.requestHeaders) - .anyMatch( - headers -> - headers.contains( - ":path", "/opentelemetry.proto.collector.trace.v1.TraceService/Export") - && headers.contains("header-key", "header-value")); - } - } - - @Test - public void configureMetricExporter() { - // Set values for general and signal specific properties. Signal specific should override - // general. - Map props = new HashMap<>(); - props.put("otel.exporter.otlp.endpoint", "http://foo.bar"); - props.put("otel.exporter.otlp.certificate", Paths.get("foo", "bar", "baz").toString()); - props.put("otel.exporter.otlp.headers", "header-key=dummy-value"); - props.put("otel.exporter.otlp.compression", "gzip"); - props.put("otel.exporter.otlp.timeout", "10s"); - props.put("otel.exporter.otlp.metrics.endpoint", "https://localhost:" + server.httpsPort()); - props.put( - "otel.exporter.otlp.metrics.certificate", certificate.certificateFile().getAbsolutePath()); - props.put("otel.exporter.otlp.metrics.headers", "header-key=header-value"); - props.put("otel.exporter.otlp.metrics.compression", "gzip"); - props.put("otel.exporter.otlp.metrics.timeout", "15s"); - props.put("otel.exporter.otlp.metrics.temporality.preference", "DELTA"); - ConfigProperties properties = DefaultConfigProperties.createForTest(props); - try (MetricExporter metricExporter = - MetricExporterConfiguration.configureExporter( - "otlp", - MetricExporterConfiguration.metricExporterSpiManager( - properties, OtlpGrpcConfigTest.class.getClassLoader()))) { - - assertThat(metricExporter) - .extracting("delegate.client.callTimeoutMillis", INTEGER) - .isEqualTo(TimeUnit.SECONDS.toMillis(15)); - assertThat(metricExporter.getAggregationTemporality(InstrumentType.COUNTER)) - .isEqualTo(AggregationTemporality.DELTA); - assertThat(metricExporter.getAggregationTemporality(InstrumentType.UP_DOWN_COUNTER)) - .isEqualTo(AggregationTemporality.CUMULATIVE); - assertThat(metricExporter.export(METRIC_DATA).join(15, TimeUnit.SECONDS).isSuccess()) - .isTrue(); - assertThat(server.metricRequests).hasSize(1); - assertThat(server.requestHeaders) - .anyMatch( - headers -> - headers.contains( - ":path", - "/opentelemetry.proto.collector.metrics.v1.MetricsService/Export") - && headers.contains("header-key", "header-value") - && headers.contains("grpc-encoding", "gzip")); - } - } - - @Test - public void configureLogRecordExporter() { - // Set values for general and signal specific properties. Signal specific should override - // general. - Map props = new HashMap<>(); - props.put("otel.exporter.otlp.endpoint", "http://foo.bar"); - props.put("otel.exporter.otlp.certificate", Paths.get("foo", "bar", "baz").toString()); - props.put("otel.exporter.otlp.headers", "header-key=dummy-value"); - props.put("otel.exporter.otlp.compression", "gzip"); - props.put("otel.exporter.otlp.timeout", "10s"); - props.put("otel.exporter.otlp.logs.endpoint", "https://localhost:" + server.httpsPort()); - props.put( - "otel.exporter.otlp.logs.certificate", certificate.certificateFile().getAbsolutePath()); - props.put("otel.exporter.otlp.logs.headers", "header-key=header-value"); - props.put("otel.exporter.otlp.logs.compression", "gzip"); - props.put("otel.exporter.otlp.logs.timeout", "15s"); - ConfigProperties properties = DefaultConfigProperties.createForTest(props); - try (LogRecordExporter logRecordExporter = - LogRecordExporterConfiguration.configureExporter( - "otlp", - LogRecordExporterConfiguration.logRecordExporterSpiManager( - properties, OtlpGrpcConfigTest.class.getClassLoader()))) { - - assertThat(logRecordExporter) - .extracting("delegate.client.callTimeoutMillis", INTEGER) - .isEqualTo(TimeUnit.SECONDS.toMillis(15)); - assertThat(logRecordExporter.export(LOG_RECORD_DATA).join(15, TimeUnit.SECONDS).isSuccess()) - .isTrue(); - assertThat(server.logRequests).hasSize(1); - assertThat(server.requestHeaders) - .anyMatch( - headers -> - headers.contains( - ":path", "/opentelemetry.proto.collector.logs.v1.LogsService/Export") - && headers.contains("header-key", "header-value") - && headers.contains("grpc-encoding", "gzip")); - } - } - - @Test - void configureTlsInvalidCertificatePath() { - Map props = new HashMap<>(); - props.put("otel.exporter.otlp.certificate", Paths.get("foo", "bar", "baz").toString()); - ConfigProperties properties = DefaultConfigProperties.createForTest(props); - - assertThatThrownBy( - () -> - SpanExporterConfiguration.configureExporter( - "otlp", - SpanExporterConfiguration.spanExporterSpiManager( - properties, OtlpGrpcConfigTest.class.getClassLoader()))) - .isInstanceOf(ConfigurationException.class) - .hasMessageContaining("Invalid OTLP certificate/key path:"); - - assertThatThrownBy( - () -> - MetricExporterConfiguration.configureExporter( - "otlp", - MetricExporterConfiguration.metricExporterSpiManager( - properties, OtlpGrpcConfigTest.class.getClassLoader()))) - .isInstanceOf(ConfigurationException.class) - .hasMessageContaining("Invalid OTLP certificate/key path:"); - - assertThatThrownBy( - () -> - LogRecordExporterConfiguration.configureExporter( - "otlp", - LogRecordExporterConfiguration.logRecordExporterSpiManager( - properties, OtlpGrpcConfigTest.class.getClassLoader()))) - .isInstanceOf(ConfigurationException.class) - .hasMessageContaining("Invalid OTLP certificate/key path:"); - } - - @Test - void configureTlsMissingClientCertificatePath() { - Map props = new HashMap<>(); - props.put("otel.exporter.otlp.client.key", Paths.get("foo", "bar", "baz").toString()); - ConfigProperties properties = DefaultConfigProperties.createForTest(props); - - assertThatThrownBy( - () -> - SpanExporterConfiguration.configureExporter( - "otlp", - SpanExporterConfiguration.spanExporterSpiManager( - properties, OtlpGrpcConfigTest.class.getClassLoader()))) - .isInstanceOf(ConfigurationException.class) - .hasMessageContaining("Client key provided but certification chain is missing"); - - assertThatThrownBy( - () -> - MetricExporterConfiguration.configureExporter( - "otlp", - MetricExporterConfiguration.metricExporterSpiManager( - properties, OtlpGrpcConfigTest.class.getClassLoader()))) - .isInstanceOf(ConfigurationException.class) - .hasMessageContaining("Client key provided but certification chain is missing"); - - assertThatThrownBy( - () -> - LogRecordExporterConfiguration.configureExporter( - "otlp", - LogRecordExporterConfiguration.logRecordExporterSpiManager( - properties, OtlpGrpcConfigTest.class.getClassLoader()))) - .isInstanceOf(ConfigurationException.class) - .hasMessageContaining("Client key provided but certification chain is missing"); - } - - @Test - void configureTlsMissingClientKeyPath() { - Map props = new HashMap<>(); - props.put("otel.exporter.otlp.client.certificate", Paths.get("foo", "bar", "baz").toString()); - ConfigProperties properties = DefaultConfigProperties.createForTest(props); - - assertThatThrownBy( - () -> - SpanExporterConfiguration.configureExporter( - "otlp", - SpanExporterConfiguration.spanExporterSpiManager( - properties, OtlpGrpcConfigTest.class.getClassLoader()))) - .isInstanceOf(ConfigurationException.class) - .hasMessageContaining("Client key chain provided but key is missing"); - - assertThatThrownBy( - () -> - MetricExporterConfiguration.configureExporter( - "otlp", - MetricExporterConfiguration.metricExporterSpiManager( - properties, OtlpGrpcConfigTest.class.getClassLoader()))) - .isInstanceOf(ConfigurationException.class) - .hasMessageContaining("Client key chain provided but key is missing"); - - assertThatThrownBy( - () -> - LogRecordExporterConfiguration.configureExporter( - "otlp", - LogRecordExporterConfiguration.logRecordExporterSpiManager( - properties, OtlpGrpcConfigTest.class.getClassLoader()))) - .isInstanceOf(ConfigurationException.class) - .hasMessageContaining("Client key chain provided but key is missing"); - } - - @Test - @SetSystemProperty(key = "otel.java.global-autoconfigure.enabled", value = "true") - void configuresGlobal() { - System.setProperty("otel.exporter.otlp.endpoint", "https://localhost:" + server.httpsPort()); - System.setProperty( - "otel.exporter.otlp.certificate", certificate.certificateFile().getAbsolutePath()); - System.setProperty("otel.metric.export.interval", "1s"); - - GlobalOpenTelemetry.get().getTracer("test").spanBuilder("test").startSpan().end(); - - await() - .untilAsserted( - () -> { - assertThat(server.traceRequests).hasSize(1); - - // Not well defined how many metric exports would have happened by now, check that - // any did. Metrics are recorded by OtlpGrpcSpanExporter, BatchSpanProcessor, and - // potentially others. - assertThat(server.metricRequests).isNotEmpty(); - }); - } - - static void shutdownGlobalSdk() { - try { - Field globalOpenTelemetryField = - GlobalOpenTelemetry.class.getDeclaredField("globalOpenTelemetry"); - globalOpenTelemetryField.setAccessible(true); - Object globalOpenTelemetry = globalOpenTelemetryField.get(null); - if (globalOpenTelemetry == null) { - return; - } - Field delegateField = - Class.forName("io.opentelemetry.api.GlobalOpenTelemetry$ObfuscatedOpenTelemetry") - .getDeclaredField("delegate"); - delegateField.setAccessible(true); - Object delegate = delegateField.get(globalOpenTelemetry); - if (delegate instanceof OpenTelemetrySdk) { - OpenTelemetrySdk sdk = ((OpenTelemetrySdk) delegate); - sdk.getSdkTracerProvider().shutdown().join(10, TimeUnit.SECONDS); - sdk.getSdkMeterProvider().shutdown().join(10, TimeUnit.SECONDS); - sdk.getSdkLoggerProvider().shutdown().join(10, TimeUnit.SECONDS); - } - } catch (NoSuchFieldException | IllegalAccessException | ClassNotFoundException e) { - throw new IllegalStateException("Error shutting down global SDK.", e); - } - } -} diff --git a/sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpGrpcRetryTest.java b/sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpGrpcRetryTest.java deleted file mode 100644 index 31ef7c1c0a1..00000000000 --- a/sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpGrpcRetryTest.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.autoconfigure; - -import static io.opentelemetry.sdk.autoconfigure.OtlpGrpcServerExtension.generateFakeLog; -import static io.opentelemetry.sdk.autoconfigure.OtlpGrpcServerExtension.generateFakeMetric; -import static io.opentelemetry.sdk.autoconfigure.OtlpGrpcServerExtension.generateFakeSpan; -import static org.assertj.core.api.Assertions.assertThat; - -import com.google.common.collect.Lists; -import com.linecorp.armeria.testing.junit5.server.SelfSignedCertificateExtension; -import io.grpc.Status; -import io.opentelemetry.exporter.internal.grpc.OkHttpGrpcExporter; -import io.opentelemetry.exporter.internal.retry.RetryPolicy; -import io.opentelemetry.exporter.internal.retry.RetryUtil; -import io.opentelemetry.internal.testing.slf4j.SuppressLogger; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; -import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.logs.data.LogRecordData; -import io.opentelemetry.sdk.logs.export.LogRecordExporter; -import io.opentelemetry.sdk.metrics.data.MetricData; -import io.opentelemetry.sdk.metrics.export.MetricExporter; -import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.export.SpanExporter; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; -import java.util.function.Supplier; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -@SuppressLogger(OkHttpGrpcExporter.class) -class OtlpGrpcRetryTest { - - private static final List SPAN_DATA = Lists.newArrayList(generateFakeSpan()); - private static final List METRIC_DATA = Lists.newArrayList(generateFakeMetric()); - private static final List LOG_RECORD_DATA = Lists.newArrayList(generateFakeLog()); - - @RegisterExtension - @Order(1) - public static final SelfSignedCertificateExtension certificate = - new SelfSignedCertificateExtension(); - - @RegisterExtension - @Order(2) - public static final OtlpGrpcServerExtension server = new OtlpGrpcServerExtension(certificate); - - @Test - @SuppressLogger(OkHttpGrpcExporter.class) - void configureSpanExporterRetryPolicy() { - Map props = new HashMap<>(); - props.put("otel.exporter.otlp.traces.endpoint", "https://localhost:" + server.httpsPort()); - props.put( - "otel.exporter.otlp.traces.certificate", certificate.certificateFile().getAbsolutePath()); - props.put("otel.experimental.exporter.otlp.retry.enabled", "true"); - ConfigProperties properties = DefaultConfigProperties.createForTest(props); - try (SpanExporter spanExporter = - SpanExporterConfiguration.configureExporter( - "otlp", - SpanExporterConfiguration.spanExporterSpiManager( - properties, OtlpGrpcRetryTest.class.getClassLoader()))) { - - testRetryableStatusCodes(() -> SPAN_DATA, spanExporter::export, server.traceRequests::size); - testDefaultRetryPolicy(() -> SPAN_DATA, spanExporter::export, server.traceRequests::size); - } - } - - @Test - @SuppressLogger(OkHttpGrpcExporter.class) - void configureMetricExporterRetryPolicy() { - Map props = new HashMap<>(); - props.put("otel.exporter.otlp.metrics.endpoint", "https://localhost:" + server.httpsPort()); - props.put( - "otel.exporter.otlp.metrics.certificate", certificate.certificateFile().getAbsolutePath()); - props.put("otel.experimental.exporter.otlp.retry.enabled", "true"); - try (MetricExporter metricExporter = - MetricExporterConfiguration.configureExporter( - "otlp", - MetricExporterConfiguration.metricExporterSpiManager( - DefaultConfigProperties.createForTest(props), - OtlpGrpcRetryTest.class.getClassLoader()))) { - - testRetryableStatusCodes( - () -> METRIC_DATA, metricExporter::export, server.metricRequests::size); - testDefaultRetryPolicy( - () -> METRIC_DATA, metricExporter::export, server.metricRequests::size); - } - } - - @Test - @SuppressLogger(OkHttpGrpcExporter.class) - void configureLogRecordExporterRetryPolicy() { - Map props = new HashMap<>(); - props.put("otel.exporter.otlp.logs.endpoint", "https://localhost:" + server.httpsPort()); - props.put( - "otel.exporter.otlp.logs.certificate", certificate.certificateFile().getAbsolutePath()); - props.put("otel.experimental.exporter.otlp.retry.enabled", "true"); - try (LogRecordExporter logRecordExporter = - LogRecordExporterConfiguration.configureExporter( - "otlp", - LogRecordExporterConfiguration.logRecordExporterSpiManager( - DefaultConfigProperties.createForTest(props), - OtlpGrpcConfigTest.class.getClassLoader()))) { - testRetryableStatusCodes( - () -> LOG_RECORD_DATA, logRecordExporter::export, server.logRequests::size); - testDefaultRetryPolicy( - () -> LOG_RECORD_DATA, logRecordExporter::export, server.logRequests::size); - } - } - - private static void testRetryableStatusCodes( - Supplier dataSupplier, - Function exporter, - Supplier serverRequestCountSupplier) { - for (Status.Code code : Status.Code.values()) { - server.reset(); - - server.responseStatuses.add(Status.fromCode(code)); - server.responseStatuses.add(Status.OK); - - CompletableResultCode resultCode = - exporter.apply(dataSupplier.get()).join(10, TimeUnit.SECONDS); - assertThat(resultCode.isDone()) - .as("Exporter didn't complete in time. Consider increasing join timeout?") - .isTrue(); - - boolean retryable = - RetryUtil.retryableGrpcStatusCodes().contains(String.valueOf(code.value())); - boolean expectedResult = retryable || code == Status.Code.OK; - assertThat(resultCode.isSuccess()) - .as( - "status code %s should export %s", - code, expectedResult ? "successfully" : "unsuccessfully") - .isEqualTo(expectedResult); - int expectedRequests = retryable ? 2 : 1; - assertThat(serverRequestCountSupplier.get()) - .as("status code %s should make %s requests", code, expectedRequests) - .isEqualTo(expectedRequests); - } - } - - private static void testDefaultRetryPolicy( - Supplier dataSupplier, - Function exporter, - Supplier serverRequestCountSupplier) { - server.reset(); - - // Set the server to fail with a retryable status code for the max attempts - int maxAttempts = RetryPolicy.getDefault().getMaxAttempts(); - int retryableCode = - RetryUtil.retryableGrpcStatusCodes().stream().map(Integer::parseInt).findFirst().get(); - for (int i = 0; i < maxAttempts; i++) { - server.responseStatuses.add(Status.fromCodeValue(retryableCode)); - } - - // Result should be failure, sever should have received maxAttempts requests - CompletableResultCode resultCode = - exporter.apply(dataSupplier.get()).join(10, TimeUnit.SECONDS); - assertThat(resultCode.isDone()) - .as("Exporter didn't complete in time. Consider increasing join timeout?") - .isTrue(); - assertThat(resultCode.isSuccess()).isFalse(); - assertThat(serverRequestCountSupplier.get()).isEqualTo(maxAttempts); - } -} diff --git a/sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpGrpcServerExtension.java b/sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpGrpcServerExtension.java deleted file mode 100644 index 5becf18380d..00000000000 --- a/sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpGrpcServerExtension.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.autoconfigure; - -import static io.opentelemetry.api.common.AttributeKey.stringKey; -import static java.util.concurrent.TimeUnit.MILLISECONDS; - -import com.linecorp.armeria.common.RequestHeaders; -import com.linecorp.armeria.server.ServerBuilder; -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.Status; -import io.grpc.stub.StreamObserver; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest; -import io.opentelemetry.proto.collector.logs.v1.ExportLogsServiceResponse; -import io.opentelemetry.proto.collector.logs.v1.LogsServiceGrpc; -import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest; -import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse; -import io.opentelemetry.proto.collector.metrics.v1.MetricsServiceGrpc; -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.common.InstrumentationScopeInfo; -import io.opentelemetry.sdk.logs.data.LogRecordData; -import io.opentelemetry.sdk.metrics.data.AggregationTemporality; -import io.opentelemetry.sdk.metrics.data.MetricData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableLongPointData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableMetricData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableSumData; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.testing.logs.TestLogRecordData; -import io.opentelemetry.sdk.testing.trace.TestSpanData; -import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.data.StatusData; -import java.time.Instant; -import java.util.ArrayDeque; -import java.util.Collections; -import java.util.Queue; - -class OtlpGrpcServerExtension extends ServerExtension { - - final Queue traceRequests = new ArrayDeque<>(); - final Queue metricRequests = new ArrayDeque<>(); - final Queue logRequests = new ArrayDeque<>(); - final Queue responseStatuses = new ArrayDeque<>(); - final Queue requestHeaders = new ArrayDeque<>(); - private final SelfSignedCertificateExtension certificate; - - OtlpGrpcServerExtension(SelfSignedCertificateExtension certificate) { - this.certificate = certificate; - } - - @Override - protected void configure(ServerBuilder sb) { - sb.service( - GrpcService.builder() - .addService( - new TraceServiceGrpc.TraceServiceImplBase() { - @Override - public void export( - ExportTraceServiceRequest request, - StreamObserver responseObserver) { - exportHelper( - traceRequests, - ExportTraceServiceResponse.getDefaultInstance(), - request, - responseObserver); - } - }) - .addService( - new MetricsServiceGrpc.MetricsServiceImplBase() { - @Override - public void export( - ExportMetricsServiceRequest request, - StreamObserver responseObserver) { - exportHelper( - metricRequests, - ExportMetricsServiceResponse.getDefaultInstance(), - request, - responseObserver); - } - }) - .addService( - new LogsServiceGrpc.LogsServiceImplBase() { - @Override - public void export( - ExportLogsServiceRequest request, - StreamObserver responseObserver) { - exportHelper( - logRequests, - ExportLogsServiceResponse.getDefaultInstance(), - request, - responseObserver); - } - }) - .useBlockingTaskExecutor(true) - .build()); - sb.decorator( - (delegate, ctx, req) -> { - requestHeaders.add(req.headers()); - return delegate.serve(ctx, req); - }); - sb.tls(certificate.certificateFile(), certificate.privateKeyFile()); - } - - private void exportHelper( - Queue requests, - ResponseT defaultResponse, - RequestT request, - StreamObserver responseObserver) { - requests.add(request); - Status responseStatus = responseStatuses.peek() != null ? responseStatuses.poll() : Status.OK; - if (responseStatus.isOk()) { - responseObserver.onNext(defaultResponse); - responseObserver.onCompleted(); - return; - } - responseObserver.onError(responseStatus.asRuntimeException()); - } - - void reset() { - traceRequests.clear(); - metricRequests.clear(); - logRequests.clear(); - requestHeaders.clear(); - responseStatuses.clear(); - } - - static SpanData generateFakeSpan() { - return TestSpanData.builder() - .setHasEnded(true) - .setName("name") - .setStartEpochNanos(MILLISECONDS.toNanos(System.currentTimeMillis())) - .setEndEpochNanos(MILLISECONDS.toNanos(System.currentTimeMillis())) - .setKind(SpanKind.SERVER) - .setStatus(StatusData.error()) - .setTotalRecordedEvents(0) - .setTotalRecordedLinks(0) - .build(); - } - - static MetricData generateFakeMetric() { - return ImmutableMetricData.createLongSum( - Resource.empty(), - InstrumentationScopeInfo.empty(), - "metric_name", - "metric_description", - "ms", - ImmutableSumData.create( - false, - AggregationTemporality.CUMULATIVE, - Collections.singletonList( - ImmutableLongPointData.create( - MILLISECONDS.toNanos(System.currentTimeMillis()), - MILLISECONDS.toNanos(System.currentTimeMillis()), - Attributes.of(stringKey("key"), "value"), - 10)))); - } - - static LogRecordData generateFakeLog() { - return TestLogRecordData.builder() - .setResource(Resource.empty()) - .setInstrumentationScopeInfo(InstrumentationScopeInfo.empty()) - .setEpoch(Instant.now()) - .setBody("log body") - .build(); - } -} diff --git a/sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpHttpConfigTest.java b/sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpHttpConfigTest.java deleted file mode 100644 index 801c5cc9709..00000000000 --- a/sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpHttpConfigTest.java +++ /dev/null @@ -1,410 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.autoconfigure; - -import static io.opentelemetry.sdk.autoconfigure.OtlpGrpcConfigTest.shutdownGlobalSdk; -import static io.opentelemetry.sdk.autoconfigure.OtlpHttpServerExtension.generateFakeLog; -import static io.opentelemetry.sdk.autoconfigure.OtlpHttpServerExtension.generateFakeMetric; -import static io.opentelemetry.sdk.autoconfigure.OtlpHttpServerExtension.generateFakeSpan; -import static org.assertj.core.api.Assertions.as; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.awaitility.Awaitility.await; - -import com.google.common.collect.Lists; -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.logs.GlobalLoggerProvider; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; -import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; -import io.opentelemetry.sdk.logs.export.LogRecordExporter; -import io.opentelemetry.sdk.metrics.InstrumentType; -import io.opentelemetry.sdk.metrics.data.AggregationTemporality; -import io.opentelemetry.sdk.metrics.export.MetricExporter; -import io.opentelemetry.sdk.trace.export.SpanExporter; -import java.nio.file.Paths; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import okhttp3.OkHttpClient; -import org.assertj.core.api.InstanceOfAssertFactories; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.junitpioneer.jupiter.SetSystemProperty; - -class OtlpHttpConfigTest { - - @RegisterExtension - public static final OtlpHttpServerExtension server = new OtlpHttpServerExtension(); - - @BeforeEach - void setUp() { - server.reset(); - GlobalOpenTelemetry.resetForTest(); - GlobalLoggerProvider.resetForTest(); - } - - @AfterEach - public void tearDown() { - shutdownGlobalSdk(); - GlobalOpenTelemetry.resetForTest(); - GlobalLoggerProvider.resetForTest(); - } - - @Test - void configureExportersGeneral() { - Map props = new HashMap<>(); - props.put("otel.exporter.otlp.protocol", "http/protobuf"); - props.put("otel.exporter.otlp.endpoint", "https://localhost:" + server.httpsPort()); - props.put( - "otel.exporter.otlp.certificate", server.selfSignedCertificate.certificate().getPath()); - props.put("otel.exporter.otlp.headers", "header-key=header-value"); - props.put("otel.exporter.otlp.compression", "gzip"); - props.put("otel.exporter.otlp.timeout", "15s"); - ConfigProperties properties = DefaultConfigProperties.createForTest(props); - SpanExporter spanExporter = - SpanExporterConfiguration.configureExporter( - "otlp", - SpanExporterConfiguration.spanExporterSpiManager( - properties, OtlpHttpConfigTest.class.getClassLoader())); - MetricExporter metricExporter = - MetricExporterConfiguration.configureExporter( - "otlp", - MetricExporterConfiguration.metricExporterSpiManager( - properties, OtlpHttpConfigTest.class.getClassLoader())); - LogRecordExporter logRecordExporter = - LogRecordExporterConfiguration.configureExporter( - "otlp", - LogRecordExporterConfiguration.logRecordExporterSpiManager( - properties, OtlpHttpConfigTest.class.getClassLoader())); - - assertThat(spanExporter) - .extracting("delegate.client", as(InstanceOfAssertFactories.type(OkHttpClient.class))) - .extracting(OkHttpClient::callTimeoutMillis) - .isEqualTo((int) TimeUnit.SECONDS.toMillis(15)); - assertThat( - spanExporter - .export(Lists.newArrayList(generateFakeSpan())) - .join(15, TimeUnit.SECONDS) - .isSuccess()) - .isTrue(); - assertThat(server.traceRequests).hasSize(1); - assertThat(server.requestHeaders) - .anyMatch( - headers -> - headers.contains(":path", "/v1/traces") - && headers.contains("header-key", "header-value") - && headers.contains("content-encoding", "gzip")); - - assertThat(metricExporter) - .extracting("delegate.client", as(InstanceOfAssertFactories.type(OkHttpClient.class))) - .extracting(OkHttpClient::callTimeoutMillis) - .isEqualTo((int) TimeUnit.SECONDS.toMillis(15)); - assertThat( - metricExporter - .export(Lists.newArrayList(generateFakeMetric())) - .join(15, TimeUnit.SECONDS) - .isSuccess()) - .isTrue(); - assertThat(server.metricRequests).hasSize(1); - assertThat(server.requestHeaders) - .anyMatch( - headers -> - headers.contains(":path", "/v1/metrics") - && headers.contains("header-key", "header-value") - && headers.contains("content-encoding", "gzip")); - - assertThat(logRecordExporter) - .extracting("delegate.client", as(InstanceOfAssertFactories.type(OkHttpClient.class))) - .extracting(OkHttpClient::callTimeoutMillis) - .isEqualTo((int) TimeUnit.SECONDS.toMillis(15)); - assertThat( - logRecordExporter - .export(Lists.newArrayList(generateFakeLog())) - .join(15, TimeUnit.SECONDS) - .isSuccess()) - .isTrue(); - assertThat(server.logRequests).hasSize(1); - assertThat(server.requestHeaders) - .anyMatch( - headers -> - headers.contains(":path", "/v1/logs") - && headers.contains("header-key", "header-value") - && headers.contains("content-encoding", "gzip")); - } - - @Test - void configureSpanExporter() { - // Set values for general and signal specific properties. Signal specific should override - // general. - Map props = new HashMap<>(); - props.put("otel.exporter.otlp.protocol", "grpc"); - props.put("otel.exporter.otlp.endpoint", "http://foo.bar"); - props.put("otel.exporter.otlp.certificate", Paths.get("foo", "bar", "baz").toString()); - props.put("otel.exporter.otlp.headers", "header-key=dummy-value"); - props.put("otel.exporter.otlp.compression", "foo"); - props.put("otel.exporter.otlp.timeout", "10s"); - props.put("otel.exporter.otlp.traces.protocol", "http/protobuf"); - props.put( - "otel.exporter.otlp.traces.endpoint", - "https://localhost:" + server.httpsPort() + "/v1/traces"); - props.put( - "otel.exporter.otlp.traces.certificate", - server.selfSignedCertificate.certificate().getPath()); - props.put("otel.exporter.otlp.traces.headers", "header-key=header-value"); - props.put("otel.exporter.otlp.traces.compression", "gzip"); - props.put("otel.exporter.otlp.traces.timeout", "15s"); - ConfigProperties properties = DefaultConfigProperties.createForTest(props); - SpanExporter spanExporter = - SpanExporterConfiguration.configureExporter( - "otlp", - SpanExporterConfiguration.spanExporterSpiManager( - properties, OtlpHttpConfigTest.class.getClassLoader())); - - assertThat(spanExporter) - .extracting("delegate.client", as(InstanceOfAssertFactories.type(OkHttpClient.class))) - .extracting(OkHttpClient::callTimeoutMillis) - .isEqualTo((int) TimeUnit.SECONDS.toMillis(15)); - assertThat( - spanExporter - .export(Lists.newArrayList(generateFakeSpan())) - .join(10, TimeUnit.SECONDS) - .isSuccess()) - .isTrue(); - assertThat(server.traceRequests).hasSize(1); - assertThat(server.requestHeaders) - .anyMatch( - headers -> - headers.contains(":path", "/v1/traces") - && headers.contains("header-key", "header-value") - && headers.contains("content-encoding", "gzip")); - } - - @Test - public void configureMetricExporter() { - // Set values for general and signal specific properties. Signal specific should override - // general. - Map props = new HashMap<>(); - props.put("otel.exporter.otlp.protocol", "grpc"); - props.put("otel.exporter.otlp.endpoint", "http://foo.bar"); - props.put("otel.exporter.otlp.certificate", Paths.get("foo", "bar", "baz").toString()); - props.put("otel.exporter.otlp.headers", "header-key=dummy-value"); - props.put("otel.exporter.otlp.compression", "foo"); - props.put("otel.exporter.otlp.timeout", "10s"); - props.put("otel.exporter.otlp.metrics.protocol", "http/protobuf"); - props.put( - "otel.exporter.otlp.metrics.endpoint", - "https://localhost:" + server.httpsPort() + "/v1/metrics"); - props.put( - "otel.exporter.otlp.metrics.certificate", - server.selfSignedCertificate.certificate().getPath()); - props.put("otel.exporter.otlp.metrics.headers", "header-key=header-value"); - props.put("otel.exporter.otlp.metrics.compression", "gzip"); - props.put("otel.exporter.otlp.metrics.timeout", "15s"); - props.put("otel.exporter.otlp.metrics.temporality.preference", "DELTA"); - MetricExporter metricExporter = - MetricExporterConfiguration.configureExporter( - "otlp", - MetricExporterConfiguration.metricExporterSpiManager( - DefaultConfigProperties.createForTest(props), - OtlpHttpConfigTest.class.getClassLoader())); - - assertThat(metricExporter) - .extracting("delegate.client", as(InstanceOfAssertFactories.type(OkHttpClient.class))) - .extracting(OkHttpClient::callTimeoutMillis) - .isEqualTo((int) TimeUnit.SECONDS.toMillis(15)); - assertThat(metricExporter.getAggregationTemporality(InstrumentType.COUNTER)) - .isEqualTo(AggregationTemporality.DELTA); - assertThat( - metricExporter - .export(Lists.newArrayList(generateFakeMetric())) - .join(15, TimeUnit.SECONDS) - .isSuccess()) - .isTrue(); - assertThat(server.metricRequests).hasSize(1); - assertThat(server.requestHeaders) - .anyMatch( - headers -> - headers.contains(":path", "/v1/metrics") - && headers.contains("header-key", "header-value")); - } - - @Test - public void configureLogRecordExporter() { - // Set values for general and signal specific properties. Signal specific should override - // general. - Map props = new HashMap<>(); - props.put("otel.exporter.otlp.protocol", "grpc"); - props.put("otel.exporter.otlp.endpoint", "http://foo.bar"); - props.put("otel.exporter.otlp.certificate", Paths.get("foo", "bar", "baz").toString()); - props.put("otel.exporter.otlp.headers", "header-key=dummy-value"); - props.put("otel.exporter.otlp.compression", "foo"); - props.put("otel.exporter.otlp.timeout", "10s"); - props.put("otel.exporter.otlp.logs.protocol", "http/protobuf"); - props.put( - "otel.exporter.otlp.logs.endpoint", "https://localhost:" + server.httpsPort() + "/v1/logs"); - props.put( - "otel.exporter.otlp.logs.certificate", - server.selfSignedCertificate.certificate().getPath()); - props.put("otel.exporter.otlp.logs.headers", "header-key=header-value"); - props.put("otel.exporter.otlp.logs.compression", "gzip"); - props.put("otel.exporter.otlp.logs.timeout", "15s"); - LogRecordExporter logRecordExporter = - LogRecordExporterConfiguration.configureExporter( - "otlp", - LogRecordExporterConfiguration.logRecordExporterSpiManager( - DefaultConfigProperties.createForTest(props), - OtlpHttpConfigTest.class.getClassLoader())); - - assertThat(logRecordExporter) - .extracting("delegate.client", as(InstanceOfAssertFactories.type(OkHttpClient.class))) - .extracting(OkHttpClient::callTimeoutMillis) - .isEqualTo((int) TimeUnit.SECONDS.toMillis(15)); - assertThat( - logRecordExporter - .export(Lists.newArrayList(generateFakeLog())) - .join(15, TimeUnit.SECONDS) - .isSuccess()) - .isTrue(); - assertThat(server.logRequests).hasSize(1); - assertThat(server.requestHeaders) - .anyMatch( - headers -> - headers.contains(":path", "/v1/logs") - && headers.contains("header-key", "header-value")); - } - - @Test - void configureTlsInvalidCertificatePath() { - Map props = new HashMap<>(); - props.put("otel.exporter.otlp.protocol", "http/protobuf"); - props.put("otel.exporter.otlp.certificate", Paths.get("foo", "bar", "baz").toString()); - ConfigProperties properties = DefaultConfigProperties.createForTest(props); - - assertThatThrownBy( - () -> - SpanExporterConfiguration.configureExporter( - "otlp", - SpanExporterConfiguration.spanExporterSpiManager( - properties, OtlpHttpConfigTest.class.getClassLoader()))) - .isInstanceOf(ConfigurationException.class) - .hasMessageContaining("Invalid OTLP certificate/key path:"); - - assertThatThrownBy( - () -> - MetricExporterConfiguration.configureExporter( - "otlp", - MetricExporterConfiguration.metricExporterSpiManager( - properties, OtlpHttpConfigTest.class.getClassLoader()))) - .isInstanceOf(ConfigurationException.class) - .hasMessageContaining("Invalid OTLP certificate/key path:"); - - assertThatThrownBy( - () -> - LogRecordExporterConfiguration.configureExporter( - "otlp", - LogRecordExporterConfiguration.logRecordExporterSpiManager( - properties, OtlpHttpConfigTest.class.getClassLoader()))) - .isInstanceOf(ConfigurationException.class) - .hasMessageContaining("Invalid OTLP certificate/key path:"); - } - - @Test - void configureTlsMissingClientCertificatePath() { - Map props = new HashMap<>(); - props.put("otel.exporter.otlp.protocol", "http/protobuf"); - props.put("otel.exporter.otlp.client.key", Paths.get("foo", "bar", "baz").toString()); - ConfigProperties properties = DefaultConfigProperties.createForTest(props); - - assertThatThrownBy( - () -> - SpanExporterConfiguration.configureExporter( - "otlp", - SpanExporterConfiguration.spanExporterSpiManager( - properties, OtlpHttpConfigTest.class.getClassLoader()))) - .isInstanceOf(ConfigurationException.class) - .hasMessageContaining("Client key provided but certification chain is missing"); - - assertThatThrownBy( - () -> - MetricExporterConfiguration.configureExporter( - "otlp", - MetricExporterConfiguration.metricExporterSpiManager( - properties, OtlpHttpConfigTest.class.getClassLoader()))) - .isInstanceOf(ConfigurationException.class) - .hasMessageContaining("Client key provided but certification chain is missing"); - - assertThatThrownBy( - () -> - LogRecordExporterConfiguration.configureExporter( - "otlp", - LogRecordExporterConfiguration.logRecordExporterSpiManager( - properties, OtlpHttpConfigTest.class.getClassLoader()))) - .isInstanceOf(ConfigurationException.class) - .hasMessageContaining("Client key provided but certification chain is missing"); - } - - @Test - void configureTlsMissingClientKeyPath() { - Map props = new HashMap<>(); - props.put("otel.exporter.otlp.protocol", "http/protobuf"); - props.put("otel.exporter.otlp.client.certificate", Paths.get("foo", "bar", "baz").toString()); - ConfigProperties properties = DefaultConfigProperties.createForTest(props); - - assertThatThrownBy( - () -> - SpanExporterConfiguration.configureExporter( - "otlp", - SpanExporterConfiguration.spanExporterSpiManager( - properties, OtlpHttpConfigTest.class.getClassLoader()))) - .isInstanceOf(ConfigurationException.class) - .hasMessageContaining("Client key chain provided but key is missing"); - - assertThatThrownBy( - () -> - MetricExporterConfiguration.configureExporter( - "otlp", - MetricExporterConfiguration.metricExporterSpiManager( - properties, OtlpHttpConfigTest.class.getClassLoader()))) - .isInstanceOf(ConfigurationException.class) - .hasMessageContaining("Client key chain provided but key is missing"); - - assertThatThrownBy( - () -> - LogRecordExporterConfiguration.configureExporter( - "otlp", - LogRecordExporterConfiguration.logRecordExporterSpiManager( - properties, OtlpHttpConfigTest.class.getClassLoader()))) - .isInstanceOf(ConfigurationException.class) - .hasMessageContaining("Client key chain provided but key is missing"); - } - - @Test - @SetSystemProperty(key = "otel.java.global-autoconfigure.enabled", value = "true") - void configuresGlobal() { - System.setProperty("otel.exporter.otlp.protocol", "http/protobuf"); - System.setProperty( - "otel.exporter.otlp.endpoint", "https://localhost:" + server.httpsPort() + "/"); - System.setProperty( - "otel.exporter.otlp.certificate", server.selfSignedCertificate.certificate().getPath()); - System.setProperty("otel.metric.export.interval", "1s"); - - GlobalOpenTelemetry.get().getTracer("test").spanBuilder("test").startSpan().end(); - - await() - .untilAsserted( - () -> { - assertThat(server.traceRequests).hasSize(1); - - // Not well defined how many metric exports would have happened by now, check that - // any did. Metrics are recorded by OtlpHttpSpanExporter, BatchSpanProcessor, and - // potentially others. - assertThat(server.metricRequests).isNotEmpty(); - }); - } -} diff --git a/sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpHttpRetryTest.java b/sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpHttpRetryTest.java deleted file mode 100644 index bf778816ba6..00000000000 --- a/sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpHttpRetryTest.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.autoconfigure; - -import static io.opentelemetry.sdk.autoconfigure.OtlpHttpServerExtension.generateFakeLog; -import static io.opentelemetry.sdk.autoconfigure.OtlpHttpServerExtension.generateFakeMetric; -import static io.opentelemetry.sdk.autoconfigure.OtlpHttpServerExtension.generateFakeSpan; -import static org.assertj.core.api.Assertions.assertThat; - -import com.google.common.collect.Lists; -import com.linecorp.armeria.common.HttpResponse; -import com.linecorp.armeria.common.HttpStatus; -import io.opentelemetry.exporter.internal.grpc.OkHttpGrpcExporter; -import io.opentelemetry.exporter.internal.okhttp.OkHttpExporter; -import io.opentelemetry.exporter.internal.retry.RetryPolicy; -import io.opentelemetry.exporter.internal.retry.RetryUtil; -import io.opentelemetry.internal.testing.slf4j.SuppressLogger; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; -import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.logs.data.LogRecordData; -import io.opentelemetry.sdk.logs.export.LogRecordExporter; -import io.opentelemetry.sdk.metrics.data.MetricData; -import io.opentelemetry.sdk.metrics.export.MetricExporter; -import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.export.SpanExporter; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; -import java.util.function.Supplier; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -@SuppressLogger(OkHttpExporter.class) -class OtlpHttpRetryTest { - - private static final List SPAN_DATA = Lists.newArrayList(generateFakeSpan()); - private static final List METRIC_DATA = Lists.newArrayList(generateFakeMetric()); - private static final List LOG_RECORD_DATA = Lists.newArrayList(generateFakeLog()); - - @RegisterExtension - public static final OtlpHttpServerExtension server = new OtlpHttpServerExtension(); - - @Test - @SuppressLogger(OkHttpGrpcExporter.class) - void configureSpanExporterRetryPolicy() { - Map props = new HashMap<>(); - props.put("otel.exporter.otlp.traces.protocol", "http/protobuf"); - props.put( - "otel.exporter.otlp.traces.endpoint", - "https://localhost:" + server.httpsPort() + "/v1/traces"); - props.put( - "otel.exporter.otlp.traces.certificate", - server.selfSignedCertificate.certificate().getPath()); - props.put("otel.experimental.exporter.otlp.retry.enabled", "true"); - ConfigProperties properties = DefaultConfigProperties.createForTest(props); - try (SpanExporter spanExporter = - SpanExporterConfiguration.configureExporter( - "otlp", - SpanExporterConfiguration.spanExporterSpiManager( - properties, OtlpHttpRetryTest.class.getClassLoader()))) { - - testRetryableStatusCodes(() -> SPAN_DATA, spanExporter::export, server.traceRequests::size); - testDefaultRetryPolicy(() -> SPAN_DATA, spanExporter::export, server.traceRequests::size); - } - } - - @Test - @SuppressLogger(OkHttpGrpcExporter.class) - void configureMetricExporterRetryPolicy() { - Map props = new HashMap<>(); - props.put("otel.exporter.otlp.metrics.protocol", "http/protobuf"); - props.put( - "otel.exporter.otlp.metrics.endpoint", - "https://localhost:" + server.httpsPort() + "/v1/metrics"); - props.put( - "otel.exporter.otlp.metrics.certificate", - server.selfSignedCertificate.certificate().getPath()); - props.put("otel.experimental.exporter.otlp.retry.enabled", "true"); - try (MetricExporter metricExporter = - MetricExporterConfiguration.configureExporter( - "otlp", - MetricExporterConfiguration.metricExporterSpiManager( - DefaultConfigProperties.createForTest(props), - OtlpHttpRetryTest.class.getClassLoader()))) { - - testRetryableStatusCodes( - () -> METRIC_DATA, metricExporter::export, server.metricRequests::size); - testDefaultRetryPolicy( - () -> METRIC_DATA, metricExporter::export, server.metricRequests::size); - } - } - - @Test - @SuppressLogger(OkHttpGrpcExporter.class) - void configureLogRecordExporterRetryPolicy() { - Map props = new HashMap<>(); - props.put("otel.exporter.otlp.logs.protocol", "http/protobuf"); - props.put( - "otel.exporter.otlp.logs.endpoint", "https://localhost:" + server.httpsPort() + "/v1/logs"); - props.put( - "otel.exporter.otlp.logs.certificate", - server.selfSignedCertificate.certificate().getPath()); - props.put("otel.experimental.exporter.otlp.retry.enabled", "true"); - try (LogRecordExporter logRecordExporter = - LogRecordExporterConfiguration.configureExporter( - "otlp", - LogRecordExporterConfiguration.logRecordExporterSpiManager( - DefaultConfigProperties.createForTest(props), - OtlpHttpRetryTest.class.getClassLoader()))) { - testRetryableStatusCodes( - () -> LOG_RECORD_DATA, logRecordExporter::export, server.logRequests::size); - testDefaultRetryPolicy( - () -> LOG_RECORD_DATA, logRecordExporter::export, server.logRequests::size); - } - } - - private static void testRetryableStatusCodes( - Supplier dataSupplier, - Function exporter, - Supplier serverRequestCountSupplier) { - - List statusCodes = Arrays.asList(200, 400, 401, 403, 429, 500, 501, 502, 503); - - for (Integer code : statusCodes) { - server.reset(); - - server.responses.add(HttpResponse.of(HttpStatus.valueOf(code))); - server.responses.add(HttpResponse.of(HttpStatus.OK)); - - CompletableResultCode resultCode = - exporter.apply(dataSupplier.get()).join(10, TimeUnit.SECONDS); - assertThat(resultCode.isDone()) - .as("Exporter didn't complete in time. Consider increasing join timeout?") - .isTrue(); - - boolean retryable = code != 200 && RetryUtil.retryableHttpResponseCodes().contains(code); - boolean expectedResult = retryable || code == 200; - assertThat(resultCode.isSuccess()) - .as( - "status code %s should export %s", - code, expectedResult ? "successfully" : "unsuccessfully") - .isEqualTo(expectedResult); - int expectedRequests = retryable ? 2 : 1; - assertThat(serverRequestCountSupplier.get()) - .as("status code %s should make %s requests", code, expectedRequests) - .isEqualTo(expectedRequests); - } - } - - private static void testDefaultRetryPolicy( - Supplier dataSupplier, - Function exporter, - Supplier serverRequestCountSupplier) { - server.reset(); - - // Set the server to fail with a retryable status code for the max attempts - int maxAttempts = RetryPolicy.getDefault().getMaxAttempts(); - int retryableCode = 503; - for (int i = 0; i < maxAttempts; i++) { - server.responses.add(HttpResponse.of(retryableCode)); - } - - // Result should be failure, sever should have received maxAttempts requests - CompletableResultCode resultCode = - exporter.apply(dataSupplier.get()).join(10, TimeUnit.SECONDS); - assertThat(resultCode.isDone()) - .as("Exporter didn't complete in time. Consider increasing join timeout?") - .isTrue(); - assertThat(resultCode.isSuccess()).isFalse(); - assertThat(serverRequestCountSupplier.get()).isEqualTo(maxAttempts); - } -} diff --git a/sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpHttpServerExtension.java b/sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpHttpServerExtension.java deleted file mode 100644 index 3b774f313fc..00000000000 --- a/sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpHttpServerExtension.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.autoconfigure; - -import static io.opentelemetry.api.common.AttributeKey.stringKey; -import static java.util.concurrent.TimeUnit.MILLISECONDS; - -import com.google.protobuf.Message; -import com.linecorp.armeria.common.HttpResponse; -import com.linecorp.armeria.common.HttpStatus; -import com.linecorp.armeria.common.RequestHeaders; -import com.linecorp.armeria.internal.common.util.SelfSignedCertificate; -import com.linecorp.armeria.server.HttpService; -import com.linecorp.armeria.server.ServerBuilder; -import com.linecorp.armeria.testing.junit5.server.ServerExtension; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest; -import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest; -import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; -import io.opentelemetry.sdk.common.InstrumentationScopeInfo; -import io.opentelemetry.sdk.logs.data.LogRecordData; -import io.opentelemetry.sdk.metrics.data.AggregationTemporality; -import io.opentelemetry.sdk.metrics.data.MetricData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableLongPointData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableMetricData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableSumData; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.testing.logs.TestLogRecordData; -import io.opentelemetry.sdk.testing.trace.TestSpanData; -import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.data.StatusData; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.security.cert.CertificateException; -import java.time.Instant; -import java.util.ArrayDeque; -import java.util.Collections; -import java.util.Queue; -import okio.Buffer; -import okio.GzipSource; -import okio.Okio; - -class OtlpHttpServerExtension extends ServerExtension { - - final SelfSignedCertificate selfSignedCertificate; - - final Queue traceRequests = new ArrayDeque<>(); - final Queue metricRequests = new ArrayDeque<>(); - final Queue logRequests = new ArrayDeque<>(); - final Queue responses = new ArrayDeque<>(); - final Queue requestHeaders = new ArrayDeque<>(); - - OtlpHttpServerExtension() { - try { - selfSignedCertificate = new SelfSignedCertificate(); - } catch (CertificateException e) { - throw new IllegalStateException("Unable to setup certificate.", e); - } - } - - @Override - protected void configure(ServerBuilder sb) { - sb.service( - "/v1/traces", - httpService(traceRequests, ExportTraceServiceRequest.getDefaultInstance())) - .service( - "/v1/metrics", - httpService(metricRequests, ExportMetricsServiceRequest.getDefaultInstance())) - .service( - "/v1/logs", httpService(logRequests, ExportLogsServiceRequest.getDefaultInstance())); - sb.tls(selfSignedCertificate.certificate(), selfSignedCertificate.privateKey()); - } - - @SuppressWarnings("unchecked") - private HttpService httpService(Queue queue, T defaultMessage) { - return (ctx, req) -> - HttpResponse.from( - req.aggregate() - .thenApply( - aggReq -> { - requestHeaders.add(aggReq.headers()); - try { - byte[] requestBody = - maybeGzipInflate(aggReq.headers(), aggReq.content().array()); - queue.add((T) defaultMessage.getParserForType().parseFrom(requestBody)); - } catch (IOException e) { - return HttpResponse.of(HttpStatus.BAD_REQUEST); - } - return responses.peek() != null - ? responses.poll() - : HttpResponse.of(HttpStatus.OK); - })); - } - - private static byte[] maybeGzipInflate(RequestHeaders requestHeaders, byte[] content) - throws IOException { - if (!requestHeaders.contains("content-encoding", "gzip")) { - return content; - } - Buffer buffer = new Buffer(); - GzipSource gzipSource = new GzipSource(Okio.source(new ByteArrayInputStream(content))); - gzipSource.read(buffer, Integer.MAX_VALUE); - return buffer.readByteArray(); - } - - void reset() { - traceRequests.clear(); - metricRequests.clear(); - logRequests.clear(); - requestHeaders.clear(); - responses.clear(); - } - - static SpanData generateFakeSpan() { - return TestSpanData.builder() - .setHasEnded(true) - .setName("name") - .setStartEpochNanos(MILLISECONDS.toNanos(System.currentTimeMillis())) - .setEndEpochNanos(MILLISECONDS.toNanos(System.currentTimeMillis())) - .setKind(SpanKind.SERVER) - .setStatus(StatusData.error()) - .setTotalRecordedEvents(0) - .setTotalRecordedLinks(0) - .build(); - } - - static MetricData generateFakeMetric() { - return ImmutableMetricData.createLongSum( - Resource.empty(), - InstrumentationScopeInfo.empty(), - "metric_name", - "metric_description", - "ms", - ImmutableSumData.create( - false, - AggregationTemporality.CUMULATIVE, - Collections.singletonList( - ImmutableLongPointData.create( - MILLISECONDS.toNanos(System.currentTimeMillis()), - MILLISECONDS.toNanos(System.currentTimeMillis()), - Attributes.of(stringKey("key"), "value"), - 10)))); - } - - static LogRecordData generateFakeLog() { - return TestLogRecordData.builder() - .setResource(Resource.empty()) - .setInstrumentationScopeInfo(InstrumentationScopeInfo.empty()) - .setEpoch(Instant.now()) - .setBody("log body") - .build(); - } -}