Skip to content

Commit c1a887a

Browse files
authored
Add fake otlp ingestion server and smoke test for OTLP metrics (#3301)
1 parent 80b7c12 commit c1a887a

File tree

13 files changed

+313
-14
lines changed

13 files changed

+313
-14
lines changed

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -780,7 +780,6 @@ private static SdkMeterProviderBuilder configureMetrics(
780780
SdkMeterProviderBuilder builder,
781781
TelemetryClient telemetryClient,
782782
Configuration configuration) {
783-
784783
MetricDataMapper mapper =
785784
new MetricDataMapper(
786785
telemetryClient::populateDefaults, configuration.preview.captureHttpServer4xxAsError);
@@ -793,11 +792,9 @@ private static SdkMeterProviderBuilder configureMetrics(
793792
"applicationinsights.testing.metric-reader-interval-millis",
794793
configuration.metricIntervalSeconds * 1000);
795794
metricReader = readerBuilder.setInterval(Duration.ofMillis(intervalMillis)).build();
796-
797795
if (configuration.internal.preAggregatedStandardMetrics.enabled) {
798796
AiViewRegistry.registerViews(builder);
799797
}
800-
801798
return builder.registerMetricReader(metricReader);
802799
}
803800

settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ hideFromDependabot(":smoke-tests:apps:NonDaemonThreads")
125125
hideFromDependabot(":smoke-tests:apps:OpenTelemetryApiSupport")
126126
hideFromDependabot(":smoke-tests:apps:OpenTelemetryApiLogBridge")
127127
hideFromDependabot(":smoke-tests:apps:OpenTelemetryMetric")
128+
hideFromDependabot(":smoke-tests:apps:OtlpMetrics")
128129
hideFromDependabot(":smoke-tests:apps:PreAggMetricsWithRoleNameOverridesAndSampling")
129130
hideFromDependabot(":smoke-tests:apps:PreferForwardedUrlScheme")
130131
hideFromDependabot(":smoke-tests:apps:RateLimitedSampling")
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
plugins {
2+
id("ai.smoke-test-war")
3+
}
4+
5+
dependencies {
6+
implementation("org.springframework.boot:spring-boot-starter-web:2.5.12") {
7+
exclude("spring-boot-starter-tomcat")
8+
}
9+
implementation("io.opentelemetry:opentelemetry-api:1.30.0")
10+
implementation("io.opentelemetry:opentelemetry-semconv:1.30.0-alpha")
11+
implementation("io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations:1.30.0")
12+
implementation("org.springframework.boot:spring-boot-test:2.7.16")
13+
implementation("io.spring.dependency-management:io.spring.dependency-management.gradle.plugin:1.1.3")
14+
15+
// spring modules
16+
smokeTestImplementation("org.springframework.boot:spring-boot-starter-test:2.7.16")
17+
smokeTestImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
18+
smokeTestImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.0")
19+
smokeTestImplementation("org.mock-server:mockserver-netty:5.15.0:shaded")
20+
smokeTestImplementation("org.awaitility:awaitility:4.2.0")
21+
smokeTestImplementation("io.opentelemetry.proto:opentelemetry-proto:0.14.0-alpha")
22+
smokeTestImplementation("org.assertj:assertj-core:3.24.2")
23+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.microsoft.applicationinsights.smoketest;
5+
6+
import org.springframework.boot.autoconfigure.SpringBootApplication;
7+
import org.springframework.boot.builder.SpringApplicationBuilder;
8+
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
9+
10+
@SpringBootApplication
11+
public class OtlpApplication extends SpringBootServletInitializer {
12+
@Override
13+
protected SpringApplicationBuilder configure(SpringApplicationBuilder applicationBuilder) {
14+
return applicationBuilder.sources(OtlpApplication.class);
15+
}
16+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.microsoft.applicationinsights.smoketest;
5+
6+
import io.opentelemetry.api.GlobalOpenTelemetry;
7+
import io.opentelemetry.instrumentation.annotations.WithSpan;
8+
import org.springframework.web.bind.annotation.GetMapping;
9+
import org.springframework.web.bind.annotation.RestController;
10+
11+
@RestController
12+
public class OtlpController {
13+
14+
@GetMapping("/")
15+
public String root() {
16+
return "OK";
17+
}
18+
19+
@GetMapping("/ping")
20+
public String ping() {
21+
doWork();
22+
return "pong";
23+
}
24+
25+
@WithSpan
26+
private void doWork() {
27+
GlobalOpenTelemetry.get()
28+
.getMeter(OtlpController.class.getName())
29+
.histogramBuilder("histogram-test-otlp-exporter")
30+
.ofLongs()
31+
.build()
32+
.record(10);
33+
}
34+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.microsoft.applicationinsights.smoketest;
5+
6+
import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.TOMCAT_8_JAVA_8;
7+
import static org.assertj.core.api.Assertions.assertThat;
8+
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
9+
10+
import com.microsoft.applicationinsights.smoketest.schemav2.Data;
11+
import com.microsoft.applicationinsights.smoketest.schemav2.Envelope;
12+
import com.microsoft.applicationinsights.smoketest.schemav2.MetricData;
13+
import com.microsoft.applicationinsights.smoketest.schemav2.RequestData;
14+
import java.util.List;
15+
import org.junit.jupiter.api.Test;
16+
import org.junit.jupiter.api.extension.RegisterExtension;
17+
import org.springframework.boot.test.context.SpringBootTest;
18+
19+
@SpringBootTest(
20+
classes = {OtlpApplication.class},
21+
webEnvironment = RANDOM_PORT)
22+
@UseAgent
23+
abstract class OtlpTest {
24+
25+
@RegisterExtension
26+
static final SmokeTestExtension testing =
27+
SmokeTestExtension.builder().useOtlpEndpoint().setSelfDiagnosticsLevel("TRACE").build();
28+
29+
@Test
30+
@TargetUri("/ping")
31+
public void testOtlpTelemetry() throws Exception {
32+
// verify request sent to Application Insights endpoint
33+
List<Envelope> rdList = testing.mockedIngestion.waitForItems("RequestData", 1);
34+
Envelope rdEnvelope = rdList.get(0);
35+
RequestData rd = (RequestData) ((Data<?>) rdEnvelope.getData()).getBaseData();
36+
assertThat(rd.getName()).isEqualTo("GET /OtlpMetrics/ping");
37+
38+
// verify custom histogram metric sent to Application Insights endpoint
39+
List<Envelope> metricList =
40+
testing.mockedIngestion.waitForItems("MetricData", OtlpTest::isHistogramMetric, 1);
41+
Envelope metricEnvelope = metricList.get(0);
42+
MetricData metricData = (MetricData) ((Data<?>) metricEnvelope.getData()).getBaseData();
43+
assertThat(metricData.getMetrics().get(0).getName()).isEqualTo("histogram-test-otlp-exporter");
44+
45+
// verify pre-aggregated standard metric sent to Application Insights endpoint
46+
List<Envelope> standardMetricsList =
47+
testing.mockedIngestion.waitForItems("MetricData", OtlpTest::isStandardMetric, 1);
48+
Envelope standardMetricEnvelope = standardMetricsList.get(0);
49+
MetricData standardMetricData =
50+
(MetricData) ((Data<?>) standardMetricEnvelope.getData()).getBaseData();
51+
assertThat(standardMetricData.getMetrics().get(0).getName()).isEqualTo("http.server.duration");
52+
assertThat(standardMetricData.getProperties().get("_MS.IsAutocollected")).isEqualTo("True");
53+
54+
// verify custom histogram metric 'histogram-test-otlp-exporter' and otel metric
55+
// 'http.server.duration' sent to OTLP endpoint
56+
testing.mockedOtlpIngestion.verify();
57+
}
58+
59+
private static boolean isHistogramMetric(Envelope envelope) {
60+
if (envelope.getData().getBaseType().equals("MetricData")) {
61+
MetricData data = (MetricData) ((Data<?>) envelope.getData()).getBaseData();
62+
return data.getMetrics().get(0).getName().equals("histogram-test-otlp-exporter");
63+
}
64+
return false;
65+
}
66+
67+
private static boolean isStandardMetric(Envelope envelope) {
68+
if (envelope.getData().getBaseType().equals("MetricData")) {
69+
MetricData data = (MetricData) ((Data<?>) envelope.getData()).getBaseData();
70+
return data.getMetrics().get(0).getName().equals("http.server.duration");
71+
}
72+
return false;
73+
}
74+
75+
@Environment(TOMCAT_8_JAVA_8)
76+
static class Tomcat8Java8Test extends OtlpTest {}
77+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"role": {
3+
"name": "testrolename",
4+
"instance": "testroleinstance"
5+
},
6+
"sampling": {
7+
"percentage": 100
8+
},
9+
"metricIntervalSeconds": 5,
10+
"customDimensions": {
11+
"tag1": "abc",
12+
"tag2": "def",
13+
"service.version": "123"
14+
}
15+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<configuration>
3+
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
4+
<encoder>
5+
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern>
6+
</encoder>
7+
</appender>
8+
<root level="warn">
9+
<appender-ref ref="CONSOLE"/>
10+
</root>
11+
</configuration>

smoke-tests/framework/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ dependencies {
1818

1919
implementation("org.eclipse.jetty:jetty-servlet:10.0.17")
2020

21+
implementation("org.mock-server:mockserver-netty:5.15.0:shaded")
22+
implementation("io.opentelemetry.proto:opentelemetry-proto:0.14.0-alpha")
23+
2124
// this is exposed in SmokeTestExtension API
2225
api("org.testcontainers:testcontainers:1.19.1")
2326

smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/SmokeTestExtension.java

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import com.google.common.base.Stopwatch;
1010
import com.microsoft.applicationinsights.smoketest.fakeingestion.MockedAppInsightsIngestionServer;
11+
import com.microsoft.applicationinsights.smoketest.fakeingestion.MockedOtlpIngestionServer;
1112
import com.microsoft.applicationinsights.smoketest.fakeingestion.ProfilerState;
1213
import com.microsoft.applicationinsights.smoketest.schemav2.Data;
1314
import com.microsoft.applicationinsights.smoketest.schemav2.Domain;
@@ -60,7 +61,10 @@ public class SmokeTestExtension
6061

6162
private static final int TELEMETRY_RECEIVE_TIMEOUT_SECONDS = 60;
6263

63-
private static final String FAKE_INGESTION_ENDPOINT = "http://host.testcontainers.internal:6060/";
64+
private static final String FAKE_BREEZE_INGESTION_ENDPOINT =
65+
"http://host.testcontainers.internal:6060/";
66+
private static final String FAKE_OTLP_INGESTION_ENDPOINT =
67+
"http://host.testcontainers.internal:4318";
6468
private static final String FAKE_PROFILER_ENDPOINT =
6569
"http://host.testcontainers.internal:6060/profiler/";
6670

@@ -70,6 +74,8 @@ public class SmokeTestExtension
7074
protected final MockedAppInsightsIngestionServer mockedIngestion =
7175
new MockedAppInsightsIngestionServer();
7276

77+
protected final MockedOtlpIngestionServer mockedOtlpIngestion = new MockedOtlpIngestionServer();
78+
7379
private boolean useAgent;
7480
@Nullable private String agentConfigurationPath;
7581
@Nullable private List<DependencyContainer> dependencyImages;
@@ -97,6 +103,7 @@ public class SmokeTestExtension
97103
private final File javaagentFile;
98104
private final File agentExtensionFile;
99105
private final Map<String, String> httpHeaders;
106+
private final boolean useOtlpEndpoint;
100107

101108
public static SmokeTestExtension create() {
102109
return builder().build();
@@ -118,7 +125,8 @@ public static SmokeTestExtensionBuilder builder() {
118125
String selfDiagnosticsLevel,
119126
File agentExtensionFile,
120127
ProfilerState profilerState,
121-
Map<String, String> httpHeaders) {
128+
Map<String, String> httpHeaders,
129+
boolean useOtlpEndpoint) {
122130
this.skipHealthCheck = skipHealthCheck;
123131
this.readOnly = readOnly;
124132
this.dependencyContainer = dependencyContainer;
@@ -129,9 +137,9 @@ public static SmokeTestExtensionBuilder builder() {
129137
doNotSetConnectionString
130138
? ""
131139
: "InstrumentationKey=00000000-0000-0000-0000-0FEEDDADBEEF;IngestionEndpoint="
132-
+ FAKE_INGESTION_ENDPOINT
140+
+ FAKE_BREEZE_INGESTION_ENDPOINT
133141
+ ";LiveEndpoint="
134-
+ FAKE_INGESTION_ENDPOINT
142+
+ FAKE_BREEZE_INGESTION_ENDPOINT
135143
+ ";ProfilerEndpoint="
136144
+ getProfilerEndpoint(profilerState);
137145

@@ -144,6 +152,7 @@ public static SmokeTestExtensionBuilder builder() {
144152
javaagentFile = new File(System.getProperty(javaagentPathSystemProperty));
145153

146154
this.httpHeaders = httpHeaders;
155+
this.useOtlpEndpoint = useOtlpEndpoint;
147156
}
148157

149158
private static String getProfilerEndpoint(ProfilerState profilerState) {
@@ -199,13 +208,16 @@ private void prepareEnvironment(Environment environment) throws Exception {
199208
currentImageAppFileName = appFile.getName();
200209
}
201210
mockedIngestion.startServer();
211+
mockedIngestion.setRequestLoggingEnabled(true);
212+
if (useOtlpEndpoint) {
213+
mockedOtlpIngestion.startServer();
214+
}
202215
network = Network.newNetwork();
203216
allContainers = new ArrayList<>();
204217
hostnameEnvVars = new HashMap<>();
205218
startDependencyContainers();
206219
startTestApplicationContainer();
207220
clearOutAnyInitLogs();
208-
mockedIngestion.setRequestLoggingEnabled(true);
209221
}
210222

211223
@Override
@@ -301,7 +313,7 @@ private void startDependencyContainers() {
301313
dependencyContainer
302314
.withNetwork(network)
303315
.withNetworkAliases(containerName)
304-
.withStartupTimeout(Duration.ofSeconds(90));
316+
.withStartupTimeout(Duration.ofMinutes(5));
305317

306318
Stopwatch stopwatch = Stopwatch.createStarted();
307319
dependencyContainer.start();
@@ -332,7 +344,7 @@ private void startDependencyContainers() {
332344
.withNetwork(network)
333345
.withNetworkAliases(containerName)
334346
.withExposedPorts(dc.exposedPort())
335-
.withStartupTimeout(Duration.ofSeconds(90));
347+
.withStartupTimeout(Duration.ofMinutes(5));
336348
Stopwatch stopwatch = Stopwatch.createStarted();
337349
container.start();
338350
System.out.printf(
@@ -370,6 +382,7 @@ private void startTestApplicationContainer() throws Exception {
370382

371383
// TODO (trask) make this port dynamic so can run tests in parallel
372384
Testcontainers.exposeHostPorts(6060);
385+
Testcontainers.exposeHostPorts(4318);
373386

374387
GenericContainer<?> container;
375388
if (REMOTE_DEBUG) {
@@ -402,7 +415,14 @@ private void startTestApplicationContainer() throws Exception {
402415
}
403416
if (usesGlobalIngestionEndpoint) {
404417
javaToolOptions.add(
405-
"-Dapplicationinsights.testing.global-ingestion-endpoint=" + FAKE_INGESTION_ENDPOINT);
418+
"-Dapplicationinsights.testing.global-ingestion-endpoint="
419+
+ FAKE_BREEZE_INGESTION_ENDPOINT);
420+
}
421+
if (useOtlpEndpoint) {
422+
javaToolOptions.add("-Dotel.metrics.exporter=otlp");
423+
javaToolOptions.add("-Dotel.exporter.otlp.metrics.endpoint=" + FAKE_OTLP_INGESTION_ENDPOINT);
424+
javaToolOptions.add("-Dotel.exporter.otlp.protocol=http/protobuf");
425+
javaToolOptions.add("-Dotel.metric.export.interval=5000");
406426
}
407427
if (REMOTE_DEBUG) {
408428
javaToolOptions.add(
@@ -486,8 +506,12 @@ public void afterAll(ExtensionContext context) throws Exception {
486506
if (network != null) {
487507
network.close();
488508
}
509+
489510
mockedIngestion.stopServer();
490511
mockedIngestion.setRequestLoggingEnabled(false);
512+
if (useOtlpEndpoint) {
513+
mockedOtlpIngestion.stopServer();
514+
}
491515
}
492516

493517
@SuppressWarnings("TypeParameterUnusedInFormals")

0 commit comments

Comments
 (0)