Browse Source

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 year ago
parent
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
 ```
 
+# 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
 
-Some tests can be executed as GraalVM native executables:
+To execute all the instrumentation tests runnable as GraalVM native executables:
 
 ```
 ./gradlew nativeTest
 ```
 
+[A Github workflow](../../.github/workflows/native-tests-daily.yml) executes the native tests every day.
+
 ## Docker disk space
 
 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:spring-boot")
 
+include(":smoke-tests-otel-starter")
+
 hideFromDependabot("instrumentation:akka:akka-actor-2.3:javaagent")
 hideFromDependabot(":instrumentation:akka:akka-actor-fork-join-2.5: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'");
+  }
+}