Skip to content

Commit

Permalink
Add E2E tests for AWS XRay Exporter with and without XRay ID Generator (
Browse files Browse the repository at this point in the history
#1474)

* add e2e tests for w3c and xray trace id values with xray exporter

* update collector configuration

* address comments, move collector setup to base class
  • Loading branch information
jj22ee authored Dec 18, 2023
1 parent f751d85 commit 7c31e88
Show file tree
Hide file tree
Showing 5 changed files with 270 additions and 54 deletions.
8 changes: 8 additions & 0 deletions adot-testbed/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ dependencies {
testImplementation("com.github.rholder:guava-retrying:2.0.0")
testImplementation("org.assertj:assertj-core:3.24.2")
testImplementation("com.fasterxml.jackson.core:jackson-databind:2.13.0")

// Trace ID (W3C & XRay) tests with XRay Exporter
api(platform("io.opentelemetry:opentelemetry-bom-alpha:1.30.0-alpha"))
testImplementation("io.opentelemetry:opentelemetry-exporter-otlp")
testImplementation("io.opentelemetry:opentelemetry-sdk");
testImplementation("io.opentelemetry:opentelemetry-semconv");
testImplementation("io.opentelemetry.contrib:opentelemetry-aws-xray:1.30.0")
testImplementation("software.amazon.awssdk:xray")
}

// Apply a specific Java toolchain to ease working on different environments.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package software.amazon.adot.testbed;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Files;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.BindMode;
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.utility.MountableFile;
import org.testcontainers.containers.FixedHostPortGenericContainer;
import org.testcontainers.containers.InternetProtocol;

abstract class CollectorSetup {
private static final String TEST_IMAGE = System.getenv("TEST_IMAGE") != null && !System.getenv("TEST_IMAGE").isEmpty()
? System.getenv("TEST_IMAGE")
: "public.ecr.aws/aws-observability/aws-otel-collector:latest";
private final Logger collectorLogger = LoggerFactory.getLogger("collector");
protected Path logDirectory;
protected GenericContainer<?> collector;

private Map<String, String> createCollectorEnvVars(String logStreamName) {
// Create an environment variable map
Map<String, String> envVariables = new HashMap<>();
if (logStreamName != null) {
envVariables.put("LOG_STREAM_NAME", logStreamName);
}
//Set credentials
envVariables.put("AWS_REGION", System.getenv("AWS_REGION"));
envVariables.put("AWS_ACCESS_KEY_ID", System.getenv("AWS_ACCESS_KEY_ID"));
envVariables.put("AWS_SECRET_ACCESS_KEY", System.getenv("AWS_SECRET_ACCESS_KEY"));
// Check if AWS_SESSION_TOKEN is not null before adding it
if (System.getenv("AWS_SESSION_TOKEN") != null) {
envVariables.put("AWS_SESSION_TOKEN", System.getenv("AWS_SESSION_TOKEN"));
}
return envVariables;
}

protected GenericContainer<?> createAndStartCollectorForLogs(String configFilePath, String logStreamName) throws IOException {
try {
logDirectory = Files.createTempDirectory("tempLogs");
} catch (IOException e) {
throw new RuntimeException("Failed to create log directory", e);
}
var collector = new GenericContainer<>(TEST_IMAGE)
.withCopyFileToContainer(MountableFile.forClasspathResource(configFilePath), "/etc/collector/config.yaml")
.withLogConsumer(new Slf4jLogConsumer(collectorLogger))
.waitingFor(Wait.forLogMessage(".*Everything is ready. Begin running and processing data.*", 1))
.withEnv(createCollectorEnvVars(logStreamName))
.withClasspathResourceMapping("/logs", "/logs", BindMode.READ_WRITE)
.withCommand("--config", "/etc/collector/config.yaml", "--feature-gates=+adot.receiver.filelog,+adot.exporter.awscloudwatchlogs,+adot.extension.file_storage");

//Mount the Temp directory
collector.withFileSystemBind(logDirectory.toString(),"/tempLogs", BindMode.READ_WRITE);

collector.start();
return collector;
}

protected GenericContainer<?> createAndStartCollectorForXray(String configFilePath) throws IOException {
var collector = new FixedHostPortGenericContainer<>(TEST_IMAGE)
.withCopyFileToContainer(MountableFile.forClasspathResource(configFilePath), "/etc/collector/config.yaml")
.withFixedExposedPort(4317, 4317, InternetProtocol.TCP)
.withLogConsumer(new Slf4jLogConsumer(collectorLogger))
.waitingFor(Wait.forLogMessage(".*Everything is ready. Begin running and processing data.*", 1))
.withEnv(createCollectorEnvVars(null))
.withCommand("--config", "/etc/collector/config.yaml");

collector.start();
return collector;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@
import java.time.Duration;
import java.time.Instant;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import java.util.Objects;
Expand All @@ -36,67 +34,21 @@

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.BindMode;
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.MountableFile;
import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClient;
import software.amazon.awssdk.services.cloudwatchlogs.model.GetLogEventsRequest;

import static org.assertj.core.api.Assertions.assertThat;

@Testcontainers(disabledWithoutDocker = true)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class LogsTests {
private static final String TEST_IMAGE = System.getenv("TEST_IMAGE") != null && !System.getenv("TEST_IMAGE").isEmpty()
? System.getenv("TEST_IMAGE")
: "public.ecr.aws/aws-observability/aws-otel-collector:latest";
private final Logger collectorLogger = LoggerFactory.getLogger("collector");
class LogsTests extends CollectorSetup {
private static final String uniqueID = UUID.randomUUID().toString();
private Path logDirectory;
private GenericContainer<?> collector;

private GenericContainer<?> createAndStartCollector(String configFilePath, String logStreamName) throws IOException {

// Create an environment variable map
Map<String, String> envVariables = new HashMap<>();
envVariables.put("LOG_STREAM_NAME", logStreamName);
//Set credentials
envVariables.put("AWS_REGION", System.getenv("AWS_REGION"));
envVariables.put("AWS_ACCESS_KEY_ID", System.getenv("AWS_ACCESS_KEY_ID"));
envVariables.put("AWS_SECRET_ACCESS_KEY", System.getenv("AWS_SECRET_ACCESS_KEY"));
// Check if AWS_SESSION_TOKEN is not null before adding it
if (System.getenv("AWS_SESSION_TOKEN") != null) {
envVariables.put("AWS_SESSION_TOKEN", System.getenv("AWS_SESSION_TOKEN"));
}
try {
logDirectory = Files.createTempDirectory("tempLogs");
} catch (IOException e) {
throw new RuntimeException("Failed to create log directory", e);
}
var collector = new GenericContainer<>(TEST_IMAGE)
.withCopyFileToContainer(MountableFile.forClasspathResource(configFilePath), "/etc/collector/config.yaml")
.withLogConsumer(new Slf4jLogConsumer(collectorLogger))
.waitingFor(Wait.forLogMessage(".*Everything is ready. Begin running and processing data.*", 1))
.withEnv(envVariables)
.withCreateContainerCmdModifier(cmd -> cmd.withUser("root"))
.withClasspathResourceMapping("/logs", "/logs", BindMode.READ_WRITE)
.withCommand("--config", "/etc/collector/config.yaml");

//Mount the Temp directory
collector.withFileSystemBind(logDirectory.toString(),"/tempLogs", BindMode.READ_WRITE);

collector.start();
return collector;
}
@Test
void testSyslog() throws Exception {
String logStreamName = "rfcsyslog-logstream-" + uniqueID;
collector = createAndStartCollector("/configurations/config-rfcsyslog.yaml", logStreamName);
collector = createAndStartCollectorForLogs("/configurations/config-rfcsyslog.yaml", logStreamName);
List<InputStream> inputStreams = new ArrayList<>();
InputStream inputStream = getClass().getResourceAsStream("/logs/RFC5424.log");
inputStreams.add(inputStream);
Expand All @@ -109,7 +61,7 @@ void testSyslog() throws Exception {
@Test
void testLog4j() throws Exception {
String logStreamName = "log4j-logstream-" + uniqueID;
collector = createAndStartCollector("/configurations/config-log4j.yaml", logStreamName);
collector = createAndStartCollectorForLogs("/configurations/config-log4j.yaml", logStreamName);
List<InputStream> inputStreams = new ArrayList<>();
InputStream inputStream = getClass().getResourceAsStream("/logs/log4j.log");
inputStreams.add(inputStream);
Expand All @@ -121,7 +73,7 @@ void testLog4j() throws Exception {
@Test
void testJson() throws Exception {
String logStreamName = "json-logstream-" + uniqueID;
collector = createAndStartCollector("/configurations/config-json.yaml", logStreamName);
collector = createAndStartCollectorForLogs("/configurations/config-json.yaml", logStreamName);
List<InputStream> inputStreams = new ArrayList<>();
InputStream inputStream = getClass().getResourceAsStream("/logs/testingJSON.log");
inputStreams.add(inputStream);
Expand All @@ -134,7 +86,7 @@ void testJson() throws Exception {
@Test
void testCollectorRestartStorageExtension() throws Exception {
String logStreamName = "storageExtension-logstream-" + uniqueID;
collector = createAndStartCollector("/configurations/config-storageExtension.yaml", logStreamName);
collector = createAndStartCollectorForLogs("/configurations/config-storageExtension.yaml", logStreamName);
File tempFile = new File(logDirectory.toString(), "storageExtension.log");
Thread.sleep(5000);

Expand Down Expand Up @@ -178,7 +130,7 @@ void testCollectorRestartStorageExtension() throws Exception {
@Test
void testFileRotation() throws Exception {
String logStreamName = "fileRotation-logstream-" + uniqueID;
collector = createAndStartCollector("/configurations/config-fileRotation.yaml", logStreamName);
collector = createAndStartCollectorForLogs("/configurations/config-fileRotation.yaml", logStreamName);

Thread.sleep(5000);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package software.amazon.adot.testbed;

import java.util.HashSet;
import java.util.Set;
import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;

import com.github.rholder.retry.RetryerBuilder;
import com.github.rholder.retry.StopStrategies;
import com.github.rholder.retry.WaitStrategies;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.TestInstance;
import org.testcontainers.junit.jupiter.Testcontainers;

import static org.assertj.core.api.Assertions.assertThat;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.contrib.awsxray.AwsXrayIdGenerator;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.sdk.trace.IdGenerator;

import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.xray.XRayClient;
import software.amazon.awssdk.services.xray.model.BatchGetTracesRequest;
import software.amazon.awssdk.services.xray.model.BatchGetTracesResponse;

@Testcontainers(disabledWithoutDocker = true)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TraceIdsWithXRayTests extends CollectorSetup {
private static final IdGenerator XRAY_ID_GENERATOR = AwsXrayIdGenerator.getInstance();

@Test
void testXRayTraceIdSendToXRay() throws Exception {
List<String> traceIds = createTraces(XRAY_ID_GENERATOR);
validateTracesInXRay(traceIds);
}

@Test
void testW3CTraceIdSendToXRay() throws Exception {
List<String> traceIds = createTraces(null);
validateTracesInXRay(traceIds);
}

List<String> createTraces(IdGenerator idGenerator) throws Exception {
collector = createAndStartCollectorForXray("/configurations/config-xrayExporter.yaml");

OpenTelemetry otel = openTelemetry(idGenerator);
Tracer tracer = otel.getTracer("adot-trace-test");

Attributes attributes = Attributes.of(
AttributeKey.stringKey("http.method"), "GET",
AttributeKey.stringKey("http.url"), "http://localhost:8080/randomEndpoint"
);

int numOfTraces = 5;
List<String> traceIds = new ArrayList<String>();
for (int count = 0; count < numOfTraces; count++) {
Span span = tracer.spanBuilder("trace-id-test")
.setSpanKind(SpanKind.SERVER)
.setAllAttributes(attributes)
.startSpan();
//Format trace IDs to XRay format ({16 bytes} --> {1-<4-bytes>-<12-bytes>})
String id = new StringBuilder(span.getSpanContext().getTraceId()).insert(8, "-").insert(0, "1-").toString();
traceIds.add(id);
span.end();
}

assertThat(traceIds).hasSize(numOfTraces);
return traceIds;
}

void validateTracesInXRay(List<String> traceIds) throws Exception {
Region region = Region.of(System.getenv("AWS_REGION"));
XRayClient xray = XRayClient.builder()
.region(region)
.build();

RetryerBuilder.<Void>newBuilder()
.retryIfException()
.retryIfRuntimeException()
.retryIfExceptionOfType(java.lang.AssertionError.class)
.withWaitStrategy(WaitStrategies.fixedWait(10, TimeUnit.SECONDS))
.withStopStrategy(StopStrategies.stopAfterAttempt(5))
.build()
.call(() -> {
BatchGetTracesResponse tracesResponse = xray.batchGetTraces(BatchGetTracesRequest.builder()
.traceIds(traceIds)
.build());

// Assertions
Set<String> traceIdsSet = new HashSet<String>(traceIds);
assertThat(tracesResponse.traces()).hasSize(traceIds.size());
tracesResponse.traces().forEach(trace -> {
assertThat(traceIdsSet.contains(trace.id())).isTrue();
});

return null;
});
}

@AfterEach
public void resetGlobalOpenTelemetry() {
// Cleanup collector and otel sdk
collector.stop();
GlobalOpenTelemetry.resetForTest();
}

private OpenTelemetry openTelemetry(IdGenerator idGenerator) {
Resource resource = Resource.getDefault().toBuilder()
.put(ResourceAttributes.SERVICE_NAME, "xray-test")
.put(ResourceAttributes.SERVICE_VERSION, "0.1.0")
.build();

SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder()
.setSampler(Sampler.alwaysOn())
.setResource(resource)
.build();

String exporter = System.getenv().getOrDefault("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317");

SdkTracerProvider tracerProvider;
SdkTracerProviderBuilder tracerProviderBuilder = SdkTracerProvider.builder()
.addSpanProcessor(
BatchSpanProcessor.builder(
OtlpGrpcSpanExporter.builder()
.setEndpoint(exporter)
.build())
.build())
.setResource(resource);

if (idGenerator != null) {
tracerProvider = tracerProviderBuilder
.setIdGenerator(idGenerator)
.build();
} else {
tracerProvider = tracerProviderBuilder.build();
}

OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
.setTracerProvider(tracerProvider)
.buildAndRegisterGlobal();

return openTelemetry;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
receivers:
otlp:
protocols:
grpc:

exporters:
awsxray:
local_mode: true

service:
pipelines:
traces:
receivers: [otlp]
exporters: [awsxray]

0 comments on commit 7c31e88

Please sign in to comment.