Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add E2E tests for AWS XRay Exporter with and without XRay ID Generator #1474

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]
Loading