Forráskód Böngészése

Add smoke test for the OTel Spring starter (#8965)

Co-authored-by: Mateusz Rzeszutek <mrzeszutek@splunk.com>
Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
Jean Bisutti 1 éve
szülő
commit
2535a758f7

+ 9 - 1
docs/contributing/running-tests.md

@@ -55,14 +55,22 @@ If you are on Windows and you want to run the tests using linux containers:
 USE_LINUX_CONTAINERS=1 ./gradlew :smoke-tests:test -PsmokeTestSuite=payara
 USE_LINUX_CONTAINERS=1 ./gradlew :smoke-tests:test -PsmokeTestSuite=payara
 ```
 ```
 
 
+# Smoke OpenTelemetry starter tests
+
+Smoke tests for the [OpenTelemetry Spring starter](../../instrumentation/spring/starters/spring-boot-starter/README.md).
+
+You can execute the tests in a JVM (`./gradlew smoke-tests-otel-starter:test`) or as Spring native tests (`./gradlew smoke-tests-otel-starter:nativeTest`).
+
 ## GraalVM native test
 ## GraalVM native test
 
 
-Some tests can be executed as GraalVM native executables:
+To execute all the instrumentation tests runnable as GraalVM native executables:
 
 
 ```
 ```
 ./gradlew nativeTest
 ./gradlew nativeTest
 ```
 ```
 
 
+[A Github workflow](../../.github/workflows/native-tests-daily.yml) executes the native tests every day.
+
 ## Docker disk space
 ## Docker disk space
 
 
 Some of the instrumentation tests (and all of the smoke tests) spin up docker containers via
 Some of the instrumentation tests (and all of the smoke tests) spin up docker containers via

+ 2 - 0
settings.gradle.kts

@@ -132,6 +132,8 @@ hideFromDependabot(":smoke-tests:images:servlet:servlet-3.0")
 hideFromDependabot(":smoke-tests:images:servlet:servlet-5.0")
 hideFromDependabot(":smoke-tests:images:servlet:servlet-5.0")
 hideFromDependabot(":smoke-tests:images:spring-boot")
 hideFromDependabot(":smoke-tests:images:spring-boot")
 
 
+include(":smoke-tests-otel-starter")
+
 hideFromDependabot("instrumentation:akka:akka-actor-2.3:javaagent")
 hideFromDependabot("instrumentation:akka:akka-actor-2.3:javaagent")
 hideFromDependabot(":instrumentation:akka:akka-actor-fork-join-2.5:javaagent")
 hideFromDependabot(":instrumentation:akka:akka-actor-fork-join-2.5:javaagent")
 hideFromDependabot(":instrumentation:akka:akka-http-10.0:javaagent")
 hideFromDependabot(":instrumentation:akka:akka-http-10.0:javaagent")

+ 54 - 0
smoke-tests-otel-starter/build.gradle.kts

@@ -0,0 +1,54 @@
+plugins {
+  id("otel.java-conventions")
+  id("org.springframework.boot") version "3.1.0"
+  id("org.graalvm.buildtools.native")
+}
+
+description = "smoke-tests-otel-starter"
+
+otelJava {
+  minJavaVersionSupported.set(JavaVersion.VERSION_17)
+}
+
+dependencies {
+  implementation("org.springframework.boot:spring-boot-starter-web")
+  implementation("org.springframework.boot:spring-boot-starter-data-jdbc")
+  implementation("com.h2database:h2")
+  implementation("org.apache.commons:commons-dbcp2")
+  implementation(project(":instrumentation:jdbc:library"))
+  implementation(project(":instrumentation:spring:starters:spring-boot-starter"))
+  implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES))
+
+  testImplementation("org.springframework.boot:spring-boot-starter-test")
+  testImplementation(project(":testing-common"))
+}
+
+tasks {
+  compileAotJava {
+    with(options) {
+      compilerArgs.add("-Xlint:-deprecation,-unchecked,none")
+      // To disable warnings/failure coming from the Java compiler during the Spring AOT processing
+      // -deprecation,-unchecked and none are required (none is not enough)
+    }
+  }
+  compileAotTestJava {
+    with(options) {
+      compilerArgs.add("-Xlint:-deprecation,-unchecked,none")
+      // To disable warnings/failure coming from the Java compiler during the Spring AOT processing
+      // -deprecation,-unchecked and none are required (none is not enough)
+    }
+  }
+  checkstyleAot {
+    isEnabled = false
+  }
+  checkstyleAotTest {
+    isEnabled = false
+  }
+}
+
+// To be able to execute the tests as GraalVM native executables
+configurations.configureEach {
+  exclude("org.apache.groovy", "groovy")
+  exclude("org.apache.groovy", "groovy-json")
+  exclude("org.spockframework", "spock-core")
+}

+ 27 - 0
smoke-tests-otel-starter/src/main/java/io/opentelemetry/spring/smoketest/DatasourceConfig.java

@@ -0,0 +1,27 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.spring.smoketest;
+
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.instrumentation.jdbc.datasource.OpenTelemetryDataSource;
+import javax.sql.DataSource;
+import org.apache.commons.dbcp2.BasicDataSource;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class DatasourceConfig {
+
+  @Bean
+  public DataSource dataSource(OpenTelemetry openTelemetry) {
+    BasicDataSource dataSource = new BasicDataSource();
+    dataSource.setDriverClassName("org.h2.Driver");
+    dataSource.setUrl("jdbc:h2:mem:db");
+    dataSource.setUsername("username");
+    dataSource.setPassword("pwd");
+    return new OpenTelemetryDataSource(dataSource, openTelemetry);
+  }
+}

+ 21 - 0
smoke-tests-otel-starter/src/main/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTestApplication.java

@@ -0,0 +1,21 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.spring.smoketest;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ImportRuntimeHints;
+
+@SpringBootApplication
+@ImportRuntimeHints(RuntimeHints.class)
+public class OtelSpringStarterSmokeTestApplication {
+
+  public OtelSpringStarterSmokeTestApplication() {}
+
+  public static void main(String[] args) {
+    SpringApplication.run(OtelSpringStarterSmokeTestApplication.class);
+  }
+}

+ 31 - 0
smoke-tests-otel-starter/src/main/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTestController.java

@@ -0,0 +1,31 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.spring.smoketest;
+
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.metrics.LongHistogram;
+import io.opentelemetry.api.metrics.Meter;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class OtelSpringStarterSmokeTestController {
+
+  public static final String URL = "/ping";
+  public static final String TEST_HISTOGRAM = "histogram-test-otel-spring-starter";
+  private final LongHistogram histogram;
+
+  public OtelSpringStarterSmokeTestController(OpenTelemetry openTelemetry) {
+    Meter meter = openTelemetry.getMeter(OtelSpringStarterSmokeTestApplication.class.getName());
+    histogram = meter.histogramBuilder(TEST_HISTOGRAM).ofLongs().build();
+  }
+
+  @GetMapping(URL)
+  public String ping() {
+    histogram.record(10);
+    return "pong";
+  }
+}

+ 18 - 0
smoke-tests-otel-starter/src/main/java/io/opentelemetry/spring/smoketest/RuntimeHints.java

@@ -0,0 +1,18 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.spring.smoketest;
+
+import org.springframework.aot.hint.RuntimeHintsRegistrar;
+
+// Necessary for GraalVM native test
+public class RuntimeHints implements RuntimeHintsRegistrar {
+
+  @Override
+  public void registerHints(
+      org.springframework.aot.hint.RuntimeHints hints, ClassLoader classLoader) {
+    hints.resources().registerResourceBundle("org.apache.commons.dbcp2.LocalStrings");
+  }
+}

+ 26 - 0
smoke-tests-otel-starter/src/main/java/io/opentelemetry/spring/smoketest/SqlExecutor.java

@@ -0,0 +1,26 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.spring.smoketest;
+
+import org.springframework.boot.context.event.ApplicationReadyEvent;
+import org.springframework.context.event.EventListener;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SqlExecutor {
+
+  private final JdbcTemplate jdbcTemplate;
+
+  public SqlExecutor(JdbcTemplate jdbcTemplate) {
+    this.jdbcTemplate = jdbcTemplate;
+  }
+
+  @EventListener(ApplicationReadyEvent.class)
+  public void loadData() {
+    jdbcTemplate.execute("create table test_table (id bigint not null, primary key (id))");
+  }
+}

+ 12 - 0
smoke-tests-otel-starter/src/main/resources/logback.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+
+  <appender name="OpenTelemetry"
+            class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender">
+  </appender>
+
+  <root level="INFO">
+    <appender-ref ref="OpenTelemetry"/>
+  </root>
+
+</configuration>

+ 119 - 0
smoke-tests-otel-starter/src/test/java/io/opentelemetry/smoketest/OtelSpringStarterSmokeTest.java

@@ -0,0 +1,119 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.smoketest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.sdk.logs.data.LogRecordData;
+import io.opentelemetry.sdk.logs.export.LogRecordExporter;
+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.testing.assertj.TracesAssert;
+import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter;
+import io.opentelemetry.sdk.testing.exporter.InMemoryMetricExporter;
+import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter;
+import io.opentelemetry.sdk.trace.data.SpanData;
+import io.opentelemetry.sdk.trace.export.SpanExporter;
+import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
+import io.opentelemetry.spring.smoketest.OtelSpringStarterSmokeTestApplication;
+import io.opentelemetry.spring.smoketest.OtelSpringStarterSmokeTestController;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@SpringBootTest(
+    classes = {
+      OtelSpringStarterSmokeTestApplication.class,
+      OtelSpringStarterSmokeTest.TestConfiguration.class
+    },
+    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
+    properties = {"otel.exporter.otlp.enabled=false", "otel.metric.export.interval=100"
+      // We set the export interval of the metrics to 100 ms. The default value is 1 minute.
+    })
+class OtelSpringStarterSmokeTest {
+
+  public static final InMemoryMetricExporter METRIC_EXPORTER =
+      InMemoryMetricExporter.create(AggregationTemporality.DELTA);
+  private static final InMemoryLogRecordExporter LOG_RECORD_EXPORTER =
+      InMemoryLogRecordExporter.create();
+  public static final InMemorySpanExporter SPAN_EXPORTER = InMemorySpanExporter.create();
+
+  @Autowired private TestRestTemplate testRestTemplate;
+
+  @Configuration(proxyBeanMethods = false)
+  static class TestConfiguration {
+    @Bean
+    public MetricExporter metricExporter() {
+      return METRIC_EXPORTER;
+    }
+
+    @Bean
+    public SpanExporter spanExporter() {
+      return SPAN_EXPORTER;
+    }
+
+    @Bean
+    public LogRecordExporter logRecordExporter() {
+      return LOG_RECORD_EXPORTER;
+    }
+  }
+
+  @Test
+  void shouldSendTelemetry() throws InterruptedException {
+
+    testRestTemplate.getForObject(OtelSpringStarterSmokeTestController.URL, String.class);
+
+    Thread.sleep(5_000); // Sleep time could be potentially reduced and perhaps removed with
+    // https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/8962
+    // and https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/8963
+
+    List<SpanData> exportedSpans = SPAN_EXPORTER.getFinishedSpanItems();
+
+    // Span
+    TracesAssert.assertThat(exportedSpans)
+        .hasSize(2)
+        .hasTracesSatisfyingExactly(
+            traceAssert ->
+                traceAssert.hasSpansSatisfyingExactly(
+                    spanDataAssert ->
+                        spanDataAssert
+                            .hasKind(SpanKind.CLIENT)
+                            .hasAttribute(
+                                SemanticAttributes.DB_STATEMENT,
+                                "create table test_table (id bigint not null, primary key (id))")),
+            traceAssert ->
+                traceAssert.hasSpansSatisfyingExactly(
+                    spanDataAssert ->
+                        spanDataAssert
+                            .hasKind(SpanKind.SERVER)
+                            .hasAttribute(SemanticAttributes.HTTP_METHOD, "GET")
+                            .hasAttribute(SemanticAttributes.HTTP_STATUS_CODE, 200L)
+                            .hasAttribute(SemanticAttributes.HTTP_ROUTE, "/ping")));
+
+    // Metric
+    List<MetricData> exportedMetrics = METRIC_EXPORTER.getFinishedMetricItems();
+    assertThat(exportedMetrics)
+        .as("Should contain " + OtelSpringStarterSmokeTestController.TEST_HISTOGRAM + " metric.")
+        .anySatisfy(
+            metric -> {
+              String metricName = metric.getName();
+              assertThat(metricName).isEqualTo(OtelSpringStarterSmokeTestController.TEST_HISTOGRAM);
+            });
+
+    // Log
+    List<LogRecordData> logs = LOG_RECORD_EXPORTER.getFinishedLogRecordItems();
+    LogRecordData firstLog = logs.get(0);
+    assertThat(firstLog.getBody().asString())
+        .as("Should instrument logs")
+        .isEqualTo("Initializing Spring DispatcherServlet 'dispatcherServlet'");
+  }
+}