diff --git a/.github/workflows/java-ec2-adaptive-sampling-test.yml b/.github/workflows/java-ec2-adaptive-sampling-test.yml new file mode 100644 index 000000000..31e1fb80d --- /dev/null +++ b/.github/workflows/java-ec2-adaptive-sampling-test.yml @@ -0,0 +1,192 @@ +## Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +## SPDX-License-Identifier: Apache-2.0 + +# This is a reusable workflow for running adaptive sampling validation test for App Signals. +# It is meant to be called from another workflow. +# Read more about reusable workflows: https://docs.github.com/en/actions/using-workflows/reusing-workflows#overview +name: Java EC2 Adaptive Sampling Use Case +on: + workflow_call: + inputs: + caller-workflow-name: + required: true + type: string + outputs: + job-started: + value: ${{ jobs.java-ec2-adaptive-sampling.outputs.job-started }} + validation-result: + value: ${{ jobs.java-ec2-adaptive-sampling.outputs.validation-result }} + +permissions: + id-token: write + contents: read + +env: + E2E_TEST_AWS_REGION: us-west-2 + CALLER_WORKFLOW_NAME: ${{ inputs.caller-workflow-name }} + SAMPLE_APP_FRONTEND_SERVICE_JAR: s3://aws-appsignals-sample-app-prod-us-west-2-adap/java-main-service-v11.jar + SAMPLE_APP_REMOTE_SERVICE_JAR: s3://aws-appsignals-sample-app-prod-us-west-2-adap/java-remote-service-v11.jar + E2E_TEST_ACCOUNT_ID: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ACCOUNT_ID }} + E2E_TEST_ROLE_NAME: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ROLE_NAME }} + TEST_RESOURCES_FOLDER: ${GITHUB_WORKSPACE} + +jobs: + java-ec2-adaptive-sampling: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Check if the job started + id: job-started + run: echo "job-started=true" >> $GITHUB_OUTPUT + + - name: Generate testing id + run: echo TESTING_ID="${{ github.run_id }}-${{ github.run_number }}-${RANDOM}" >> $GITHUB_ENV + + - uses: actions/checkout@v4 + with: + repository: 'aws-observability/aws-application-signals-test-framework' + ref: ${{ env.CALLER_WORKFLOW_NAME == 'main-build' && 'main' || github.ref }} + fetch-depth: 0 + + - name: Initiate Gradlew Daemon + id: initiate-gradlew + uses: ./.github/workflows/actions/execute_and_retry + continue-on-error: true + with: + command: "./gradlew :validator:build" + cleanup: "./gradlew clean" + max_retry: 3 + sleep_time: 60 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::${{ env.E2E_TEST_ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} + aws-region: us-east-1 + + - name: Retrieve account + uses: aws-actions/aws-secretsmanager-get-secrets@v1 + with: + secret-ids: | + ACCOUNT_ID, adaptive-sampling-region-account/prod-${{ env.E2E_TEST_AWS_REGION }} + + - name: Configure AWS Credentials + if: ${{ github.event.repository.name == 'aws-application-signals-test-framework' }} + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::${{ env.ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} + aws-region: ${{ env.E2E_TEST_AWS_REGION }} + + # TODO: Replace else action with getting latest ADOT release + - name: Set Get ADOT JAR command environment variable + run: | + if [ "${{ github.event.repository.name }}" = "aws-otel-java-instrumentation" ]; then + echo GET_ADOT_JAR_COMMAND="aws s3 cp s3://adot-main-build-staging-jar/aws-opentelemetry-agent.jar ./adot.jar" >> $GITHUB_ENV + else + echo GET_ADOT_JAR_COMMAND="aws s3 cp s3://aws-appsignals-sample-app-prod-us-west-2-adap/pre-release/aws-opentelemetry-agent-2.11.0-SNAPSHOT.jar ./adot.jar --region ${{ env.E2E_TEST_AWS_REGION }}" >> $GITHUB_ENV + fi + + - name: Set Get CW Agent command environment variable + run: | + echo GET_CW_AGENT_RPM_COMMAND="wget -O cw-agent.rpm https://amazoncloudwatch-agent-${{ env.E2E_TEST_AWS_REGION }}.s3.${{ env.E2E_TEST_AWS_REGION }}.amazonaws.com/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm" >> $GITHUB_ENV + + - name: Set up terraform + uses: ./.github/workflows/actions/execute_and_retry + with: + command: "wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg" + post-command: 'echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list + && sudo apt update && sudo apt install terraform' + sleep_time: 60 + + - name: Initiate Terraform + uses: ./.github/workflows/actions/execute_and_retry + with: + command: "cd ${{ env.TEST_RESOURCES_FOLDER }}/terraform/java/ec2/adaptive-sampling && terraform init && terraform validate" + cleanup: "rm -rf .terraform && rm -rf .terraform.lock.hcl" + max_retry: 6 + sleep_time: 60 + + - name: Deploy sample app via terraform and wait for endpoint to come online + working-directory: terraform/java/ec2/adaptive-sampling + run: | + retry_counter=0 + max_retry=2 + while [ $retry_counter -lt $max_retry ]; do + echo "Attempt $retry_counter" + deployment_failed=0 + terraform apply -auto-approve \ + -var="aws_region=${{ env.E2E_TEST_AWS_REGION }}" \ + -var="test_id=${{ env.TESTING_ID }}" \ + -var="sample_app_jar=${{ env.SAMPLE_APP_FRONTEND_SERVICE_JAR }}" \ + -var="sample_remote_app_jar=${{ env.SAMPLE_APP_REMOTE_SERVICE_JAR }}" \ + -var="get_cw_agent_rpm_command=${{ env.GET_CW_AGENT_RPM_COMMAND }}" \ + -var="get_adot_jar_command=${{ env.GET_ADOT_JAR_COMMAND }}" \ + || deployment_failed=$? + + if [ $deployment_failed -eq 1 ]; then + echo "Terraform deployment was unsuccessful. Will attempt to retry deployment." + echo "Destroying terraform" + terraform destroy -auto-approve \ + -var="test_id=${{ env.TESTING_ID }}" + retry_counter=$(($retry_counter+1)) + else + break + fi + + if [ $retry_counter -eq $max_retry ]; then + echo "Max retry reached, failed to deploy terraform and connect to the endpoint. Exiting code" + exit 1 + fi + done + + - name: Get the sample app and EC2 instance information + working-directory: terraform/java/ec2/adaptive-sampling + run: | + echo "MAIN_SERVICE_ENDPOINT=localhost:8080" >> $GITHUB_ENV + echo "REMOTE_SERVICE_IP=$(terraform output sample_app_remote_service_private_ip)" >> $GITHUB_ENV + echo "MAIN_SERVICE_INSTANCE_ID=$(terraform output main_service_instance_id)" >> $GITHUB_ENV + echo "EC2_INSTANCE_AMI=$(terraform output ec2_instance_ami)" >> $GITHUB_ENV + + - name: Initiate Gradlew Daemon + if: steps.initiate-gradlew == 'failure' + uses: ./.github/workflows/actions/execute_and_retry + continue-on-error: true + with: + command: "./gradlew :validator:build" + cleanup: "./gradlew clean" + max_retry: 3 + sleep_time: 60 + + - name: Validate generated adaptive-sampling + run: ./gradlew validator:run --args='-c java/ec2/adaptive-sampling/trace-validation.yml + --testing-id ${{ env.TESTING_ID }} + --endpoint http://${{ env.MAIN_SERVICE_ENDPOINT }} + --remote-service-deployment-name ${{ env.REMOTE_SERVICE_IP }}:8080 + --region ${{ env.E2E_TEST_AWS_REGION }} + --account-id ${{ env.ACCOUNT_ID }} + --service-name main-${{ env.TESTING_ID }} + --remote-service-name remote-${{ env.TESTING_ID }} + --query-string ip=${{ env.REMOTE_SERVICE_IP }} + --instance-ami ${{ env.EC2_INSTANCE_AMI }} + --instance-id ${{ env.MAIN_SERVICE_INSTANCE_ID }} + --rollup' + + - name: Refresh AWS Credentials + if: ${{ github.event.repository.name == 'aws-application-signals-test-framework' }} + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::${{ env.ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} + aws-region: ${{ env.E2E_TEST_AWS_REGION }} + + # Ensures gap between terraform apply and destroy + - name: Sleep 10 seconds + if: always() + run: sleep 10 + + - name: Terraform destroy + if: always() + continue-on-error: true + working-directory: terraform/java/ec2/adaptive-sampling + run: | + terraform destroy -auto-approve \ + -var="test_id=${{ env.TESTING_ID }}" \ No newline at end of file diff --git a/terraform/java/ec2/adaptive-sampling/amazon-cloudwatch-agent.json b/terraform/java/ec2/adaptive-sampling/amazon-cloudwatch-agent.json new file mode 100644 index 000000000..a98a40d36 --- /dev/null +++ b/terraform/java/ec2/adaptive-sampling/amazon-cloudwatch-agent.json @@ -0,0 +1,16 @@ +{ + "agent": { + "debug": true, + "region": "$REGION" + }, + "traces": { + "traces_collected": { + "application_signals": {} + } + }, + "logs": { + "metrics_collected": { + "application_signals": {} + } + } +} \ No newline at end of file diff --git a/terraform/java/ec2/adaptive-sampling/main.tf b/terraform/java/ec2/adaptive-sampling/main.tf new file mode 100644 index 000000000..f7c5a7d04 --- /dev/null +++ b/terraform/java/ec2/adaptive-sampling/main.tf @@ -0,0 +1,331 @@ +# ------------------------------------------------------------------------ +# Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). +# You may not use this file except in compliance with the License. +# A copy of the License is located at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# or in the "license" file accompanying this file. This file is distributed +# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +# express or implied. See the License for the specific language governing +# permissions and limitations under the License. +# ------------------------------------------------------------------------- + +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + } + } +} + +# Define the provider for AWS +provider "aws" {} + +resource "aws_default_vpc" "default" {} + +resource "tls_private_key" "ssh_key" { + algorithm = "RSA" + rsa_bits = 4096 +} + +resource "aws_key_pair" "aws_ssh_key" { + key_name = "instance_key-${var.test_id}" + public_key = tls_private_key.ssh_key.public_key_openssh +} + +locals { + ssh_key_name = aws_key_pair.aws_ssh_key.key_name + private_key_content = tls_private_key.ssh_key.private_key_pem +} + +data "aws_ami" "ami" { + owners = ["amazon"] + most_recent = true + filter { + name = "name" + values = ["al20*-ami-minimal-*-${var.cpu_architecture}"] + } + filter { + name = "state" + values = ["available"] + } + filter { + name = "architecture" + values = [var.cpu_architecture] + } + filter { + name = "image-type" + values = ["machine"] + } + + filter { + name = "root-device-name" + values = ["/dev/xvda"] + } + + filter { + name = "root-device-type" + values = ["ebs"] + } + + filter { + name = "virtualization-type" + values = ["hvm"] + } +} + +resource "aws_instance" "main_service_instance" { + ami = data.aws_ami.ami.id # Amazon Linux 2 (free tier) + instance_type = var.cpu_architecture == "x86_64" ? "t3.micro" : "t4g.micro" + key_name = local.ssh_key_name + iam_instance_profile = "APP_SIGNALS_EC2_TEST_ROLE" + vpc_security_group_ids = [aws_default_vpc.default.default_security_group_id] + associate_public_ip_address = true + instance_initiated_shutdown_behavior = "terminate" + + metadata_options { + http_tokens = "required" + } + + root_block_device { + volume_size = 5 + } + + tags = { + Name = "main-service-${var.test_id}" + } +} + +resource "null_resource" "main_service_setup" { + connection { + type = "ssh" + user = var.user + private_key = local.private_key_content + host = aws_instance.main_service_instance.public_ip + } + + provisioner "remote-exec" { + inline = [ + <<-EOF + # Make the Terraform fail if any step throws an error + set -ex + # Install wget + sudo yum install wget -y + # Install Java + echo + if [[ "${var.language_version}" == "8" ]]; then + sudo yum install java-1.8.0-amazon-corretto -y + else + sudo yum install java-${var.language_version}-amazon-corretto -y + fi + + # enable ec2 instance connect for debug + sudo yum install ec2-instance-connect -y + + # Copy in CW Agent configuration + agent_config='${replace(replace(file("./amazon-cloudwatch-agent.json"), "/\\s+/", ""), "$REGION", var.aws_region)}' + echo $agent_config > amazon-cloudwatch-agent.json + + # Get and run CW agent rpm + ${var.get_cw_agent_rpm_command} + sudo rpm -U ./cw-agent.rpm + sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -s -c file:./amazon-cloudwatch-agent.json + + # TODO: Remove temporary custom collector pointing to alpha stage + # This sets up an AWS Proxy to perform GetSamplingRules and GetSamplingTargets against alpha stage + aws s3 cp s3://aws-appsignals-sample-app-prod-us-west-2-adap/pre-release/collector/ ./collector/ --recursive + cd collector + chmod +x ./otelcol-appsignals/otelcol-appsignals + nohup ./otelcol-appsignals/otelcol-appsignals --config ./otel-config.yaml &> logs.out & + cd .. + + # Get ADOT + ${var.get_adot_jar_command} + + # Get and run the sample application with configuration + aws s3 cp ${var.sample_app_jar} ./main-service.jar + + # TODO: Remove temporary custom collector pointing to alpha stage + JAVA_TOOL_OPTIONS=' -javaagent:/home/ec2-user/adot.jar' \ + OTEL_METRICS_EXPORTER=none \ + OTEL_LOGS_EXPORT=none \ + OTEL_AWS_APPLICATION_SIGNALS_ENABLED=true \ + OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT=http://localhost:4316/v1/metrics \ + OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf \ + OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4316/v1/traces \ + OTEL_RESOURCE_ATTRIBUTES=service.name=main-${var.test_id} \ + OTEL_INSTRUMENTATION_COMMON_EXPERIMENTAL_CONTROLLER_TELEMETRY_ENABLED=true \ + OTEL_TRACES_SAMPLER_ARG=endpoint=http://localhost:8980 \ + AWS_XRAY_ADAPTIVE_SAMPLING_CONFIG="{version: 1.0, anomalyConditions: [{usage: sampling-boost}]}" \ + nohup java -XX:+UseG1GC -jar main-service.jar &> nohup.out & + + # The application needs time to come up and reach a steady state, this should not take longer than 30 seconds + sleep 30 + + # Check if the application is up. If it is not up, then exit 1. + attempt_counter=0 + max_attempts=30 + until $(curl --output /dev/null --silent --head --fail --max-time 5 $(echo "http://localhost:8080" | tr -d '"')); do + if [ $attempt_counter -eq $max_attempts ];then + echo "Failed to connect to endpoint." + exit 1 + fi + echo "Attempting to connect to the main endpoint. Tried $attempt_counter out of $max_attempts" + attempt_counter=$(($attempt_counter+1)) + sleep 10 + done + + echo "Successfully connected to main endpoint" + + EOF + ] + } + + depends_on = [aws_instance.main_service_instance] +} + +resource "aws_instance" "remote_service_instance" { + ami = data.aws_ami.ami.id # Amazon Linux 2 (free tier) + instance_type = var.cpu_architecture == "x86_64" ? "t3.micro" : "t4g.micro" + key_name = local.ssh_key_name + iam_instance_profile = "APP_SIGNALS_EC2_TEST_ROLE" + vpc_security_group_ids = [aws_default_vpc.default.default_security_group_id] + associate_public_ip_address = true + instance_initiated_shutdown_behavior = "terminate" + + metadata_options { + http_tokens = "required" + } + + root_block_device { + volume_size = 5 + } + + tags = { + Name = "remote-service-${var.test_id}" + } +} + +resource "null_resource" "remote_service_setup" { + connection { + type = "ssh" + user = var.user + private_key = local.private_key_content + host = aws_instance.remote_service_instance.public_ip + } + + provisioner "remote-exec" { + inline = [ + <<-EOF + # Make the Terraform fail if any step throws an error + set -ex + # Install wget + sudo yum install wget -y + # Install Java + if [[ "${var.language_version}" == "8" ]]; then + sudo yum install java-1.8.0-amazon-corretto -y + else + sudo yum install java-${var.language_version}-amazon-corretto -y + fi + + # enable ec2 instance connect for debug + sudo yum install ec2-instance-connect -y + + # Copy in CW Agent configuration + agent_config='${replace(replace(file("./amazon-cloudwatch-agent.json"), "/\\s+/", ""), "$REGION", var.aws_region)}' + echo $agent_config > amazon-cloudwatch-agent.json + + # Get and run CW agent rpm + ${var.get_cw_agent_rpm_command} + sudo rpm -U ./cw-agent.rpm + sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -s -c file:./amazon-cloudwatch-agent.json + + # TODO: Remove temporary custom collector pointing to alpha stage + # This sets up an AWS Proxy to perform GetSamplingRules and GetSamplingTargets against alpha stage + aws s3 cp s3://aws-appsignals-sample-app-prod-us-west-2-adap/pre-release/collector/ ./collector/ --recursive --region ${var.aws_region} + cd collector + chmod +x ./otelcol-appsignals/otelcol-appsignals + nohup ./otelcol-appsignals/otelcol-appsignals --config ./otel-config.yaml &> logs.out & + cd .. + + # Get ADOT + ${var.get_adot_jar_command} + + # Get and run the sample application with configuration + aws s3 cp ${var.sample_remote_app_jar} ./remote-service.jar + + JAVA_TOOL_OPTIONS=' -javaagent:/home/ec2-user/adot.jar' \ + OTEL_METRICS_EXPORTER=none \ + OTEL_LOGS_EXPORT=none \ + OTEL_AWS_APPLICATION_SIGNALS_ENABLED=true \ + OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT=http://localhost:4316/v1/metrics \ + OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf \ + OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4316/v1/traces \ + OTEL_RESOURCE_ATTRIBUTES=service.name=remote-${var.test_id} \ + OTEL_INSTRUMENTATION_COMMON_EXPERIMENTAL_CONTROLLER_TELEMETRY_ENABLED=true \ + OTEL_TRACES_SAMPLER_ARG=endpoint=http://localhost:8980 \ + AWS_XRAY_ADAPTIVE_SAMPLING_CONFIG="{version: 1.0, anomalyConditions: [{errorCodeRegex: \"^500$\", usage: both}], errorCaptureLimit: {errorSpansPerSecond: 5}}" \ + nohup java -XX:+UseG1GC -jar remote-service.jar &> nohup.out & + + # The application needs time to come up and reach a steady state, this should not take longer than 30 seconds + sleep 30 + + # Check if the application is up. If it is not up, then exit 1. + attempt_counter=0 + max_attempts=30 + until $(curl --output /dev/null --silent --head --fail --max-time 5 $(echo "http://localhost:8080/healthcheck" | tr -d '"')); do + if [ $attempt_counter -eq $max_attempts ];then + echo "Failed to connect to endpoint." + exit 1 + fi + echo "Attempting to connect to the remote endpoint. Tried $attempt_counter out of $max_attempts" + attempt_counter=$(($attempt_counter+1)) + sleep 10 + done + + echo "Successfully connected to remote endpoint" + + EOF + ] + } + + depends_on = [aws_instance.remote_service_instance] +} + +resource "null_resource" "traffic_generator_setup" { + connection { + type = "ssh" + user = var.user + private_key = local.private_key_content + host = aws_instance.main_service_instance.public_ip + } + + provisioner "remote-exec" { + inline = [ + <<-EOF + set -ex + + # Load traffic generator script from local resources + traffic_script='${file("./resources/traffic-generator.sh")}' + echo "$traffic_script" > traffic-generator.sh + + chmod +x traffic-generator.sh + + # Send traffic to trigger anomaly capture separately + curl -v "http://localhost:8080/status/500?ip=${aws_instance.remote_service_instance.private_ip}" + curl -v "http://localhost:8080/status/500?ip=${aws_instance.remote_service_instance.private_ip}" + curl -v "http://localhost:8080/status/500?ip=${aws_instance.remote_service_instance.private_ip}" + sleep 20 + + MAIN_ENDPOINT="localhost:8080" REMOTE_ENDPOINT="${aws_instance.remote_service_instance.private_ip}" nohup ./traffic-generator.sh &> traffic_generator.log & + + sleep 10 + EOF + ] + } + + depends_on = [null_resource.main_service_setup, null_resource.remote_service_setup] +} \ No newline at end of file diff --git a/terraform/java/ec2/adaptive-sampling/output.tf b/terraform/java/ec2/adaptive-sampling/output.tf new file mode 100644 index 000000000..be572868a --- /dev/null +++ b/terraform/java/ec2/adaptive-sampling/output.tf @@ -0,0 +1,26 @@ +# ------------------------------------------------------------------------ +# Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). +# You may not use this file except in compliance with the License. +# A copy of the License is located at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# or in the "license" file accompanying this file. This file is distributed +# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +# express or implied. See the License for the specific language governing +# permissions and limitations under the License. +# ------------------------------------------------------------------------- + +output "sample_app_remote_service_private_ip" { + value = aws_instance.remote_service_instance.private_ip +} + +output "main_service_instance_id" { + value = aws_instance.main_service_instance.id +} + +output "ec2_instance_ami" { + value = data.aws_ami.ami.id +} \ No newline at end of file diff --git a/terraform/java/ec2/adaptive-sampling/resources/traffic-generator.sh b/terraform/java/ec2/adaptive-sampling/resources/traffic-generator.sh new file mode 100644 index 000000000..de74e7718 --- /dev/null +++ b/terraform/java/ec2/adaptive-sampling/resources/traffic-generator.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +while true; do + for i in {1..9}; do + curl -s "http://$MAIN_ENDPOINT/status/200?ip=$REMOTE_ENDPOINT" + echo + sleep 0.5 + done + curl -s "http://$MAIN_ENDPOINT/status/500?ip=$REMOTE_ENDPOINT" + echo + sleep 0.5 +done \ No newline at end of file diff --git a/terraform/java/ec2/adaptive-sampling/variables.tf b/terraform/java/ec2/adaptive-sampling/variables.tf new file mode 100644 index 000000000..8bdfb53a0 --- /dev/null +++ b/terraform/java/ec2/adaptive-sampling/variables.tf @@ -0,0 +1,50 @@ +# ------------------------------------------------------------------------ +# Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). +# You may not use this file except in compliance with the License. +# A copy of the License is located at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# or in the "license" file accompanying this file. This file is distributed +# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +# express or implied. See the License for the specific language governing +# permissions and limitations under the License. +# ------------------------------------------------------------------------- + +variable "test_id" { + default = "dummy-123" +} + +variable "aws_region" { + default = "" +} + +variable "user" { + default = "ec2-user" +} + +variable "sample_app_jar" { + default = "s3:///" +} + +variable "sample_remote_app_jar" { + default = "s3:///" +} + +variable "get_cw_agent_rpm_command" { + default = " s3:///" +} + +variable "get_adot_jar_command" { + default = " s3:///" +} + +variable "language_version" { + default = "11" +} + +variable "cpu_architecture" { + default = "x86_64" +} \ No newline at end of file diff --git a/validator/src/main/java/com/amazon/aoc/fileconfigs/PredefinedExpectedTemplate.java b/validator/src/main/java/com/amazon/aoc/fileconfigs/PredefinedExpectedTemplate.java index 73f883c97..3523f2131 100644 --- a/validator/src/main/java/com/amazon/aoc/fileconfigs/PredefinedExpectedTemplate.java +++ b/validator/src/main/java/com/amazon/aoc/fileconfigs/PredefinedExpectedTemplate.java @@ -137,6 +137,10 @@ public enum PredefinedExpectedTemplate implements FileConfig { /** Java EC2 ADOT SigV4 Log Exporter Test Case Validation */ JAVA_EC2_ADOT_OTLP_LOG("/expected-data-template/java/ec2/adot-aws-otlp/application-log.mustache"), + /** Java EC2 X-Ray Adaptive Sampling Test Case Validation */ + JAVA_EC2_ADAPTIVE_SAMPLING_FULL_TRACE("/expected-data-template/java/ec2/adaptive-sampling/full-trace.mustache"), + JAVA_EC2_ADAPTIVE_SAMPLING_PARTIAL_TRACE("/expected-data-template/java/ec2/adaptive-sampling/partial-trace.mustache"), + /** Java EC2 K8s Test Case Validations */ JAVA_K8S_OUTGOING_HTTP_CALL_LOG("/expected-data-template/java/k8s/outgoing-http-call-log.mustache"), JAVA_K8S_OUTGOING_HTTP_CALL_METRIC("/expected-data-template/java/k8s/outgoing-http-call-metric.mustache"), diff --git a/validator/src/main/java/com/amazon/aoc/validators/TraceValidator.java b/validator/src/main/java/com/amazon/aoc/validators/TraceValidator.java index 17886beb6..ea4aaa0df 100644 --- a/validator/src/main/java/com/amazon/aoc/validators/TraceValidator.java +++ b/validator/src/main/java/com/amazon/aoc/validators/TraceValidator.java @@ -142,6 +142,9 @@ private Map getTrace() throws Exception { traceFilter += (String.format(" AND annotation.aws_local_operation = \"%s/%s\"", context.getServiceName(), "FunctionHandler")); + } else if (validationConfig.getExpectedTraceTemplate().getPath().getFile().contains("partial-trace")) { + // Main service node should NOT be present and remote service SHOULD be + traceFilter = String.format("annotation.aws_local_service != \"%s\" AND annotation.aws_local_service = \"%s\"", context.getServiceName(), context.getRemoteServiceName()); } else { traceFilter += (String.format(" AND annotation.aws_local_operation = \"%s %s\"", validationConfig.getHttpMethod().toUpperCase(), diff --git a/validator/src/main/resources/expected-data-template/java/ec2/adaptive-sampling/full-trace.mustache b/validator/src/main/resources/expected-data-template/java/ec2/adaptive-sampling/full-trace.mustache new file mode 100644 index 000000000..635ba2612 --- /dev/null +++ b/validator/src/main/resources/expected-data-template/java/ec2/adaptive-sampling/full-trace.mustache @@ -0,0 +1,101 @@ +[{ + "name": "^{{serviceName}}$", + "http": { + "request": { + "url": "^{{endpoint}}/status/(200|500)(?:\\?ip=(([0-9]{1,3}\\.){3}[0-9]{1,3}))?$", + "method": "^GET$" + }, + "response": { + "status": "^(200|500)$" + } + }, + "aws": { + "account_id": "^{{accountId}}$" + }, + "annotations": { + "aws.local.service": "^{{serviceName}}$", + "aws.local.operation": "^GET /status/.code.", + "aws.local.environment": "^ec2:default$" + }, + "metadata": { + "default": { + "EC2.InstanceId": "^{{instanceId}}$", + "PlatformType": "^AWS::EC2$", + "otel.resource.host.image.id": "^{{instanceAmi}}$", + "otel.resource.host.type": "^([a-z0-9]+\\.[a-z0-9]+)$", + "aws.span.kind": "^LOCAL_ROOT$" + } + }, + "subsegments": [ + { + "subsegments": [ + { + "name": "^{{remoteServiceDeploymentName}}$", + "http": { + "request": { + "url": "^http://{{remoteServiceDeploymentName}}/status/(200|500)$", + "method": "^GET$" + } + }, + "annotations": { + "aws.local.service": "^{{serviceName}}$", + "aws.local.operation": "^GET /status/.code.", + "aws.remote.service": "^{{remoteServiceDeploymentName}}$", + "aws.remote.operation": "^GET /status", + "aws.local.environment": "^ec2:default$" + }, + "metadata": { + "default": { + "EC2.InstanceId": "^{{instanceId}}$", + "PlatformType": "^AWS::EC2$", + "aws.span.kind": "^CLIENT$" + } + }, + "namespace": "^remote$" + } + ] + } + ] +}, +{ + "name": "^{{remoteServiceName}}$", + "http": { + "request": { + "url": "^http://{{remoteServiceDeploymentName}}/status/(200|500)$", + "method": "^GET$" + }, + "response": { + "status": "^(200|500)$" + } + }, + "annotations": { + "aws.local.service": "^{{remoteServiceName}}$", + "aws.local.operation": "^GET /status/.code.", + "aws.local.environment": "^ec2:default$" + }, + "metadata": { + "default": { + "EC2.InstanceId": "^i-[A-Za-z0-9]{17}$", + "PlatformType": "^AWS::EC2$", + "otel.resource.host.image.id": "^{{instanceAmi}}$", + "otel.resource.host.type": "^([a-z0-9]+\\.[a-z0-9]+)$", + "aws.span.kind": "^LOCAL_ROOT$", + "aws.xray.sampling_rule": "^RuleA$" + } + }, + "subsegments": [ + { + "name": "^RemoteServiceController.status$", + "annotations": { + "aws.local.operation": "^GET /status/.code.", + "aws.local.environment": "^ec2:default$" + }, + "metadata": { + "default": { + "EC2.InstanceId": "^i-[A-Za-z0-9]{17}$", + "PlatformType": "^AWS::EC2$" + } + } + } + ] +}] \ No newline at end of file diff --git a/validator/src/main/resources/expected-data-template/java/ec2/adaptive-sampling/partial-trace.mustache b/validator/src/main/resources/expected-data-template/java/ec2/adaptive-sampling/partial-trace.mustache new file mode 100644 index 000000000..be898ff57 --- /dev/null +++ b/validator/src/main/resources/expected-data-template/java/ec2/adaptive-sampling/partial-trace.mustache @@ -0,0 +1,28 @@ +[{ + "name": "^{{remoteServiceName}}$", + "http": { + "request": { + "url": "^http://{{remoteServiceDeploymentName}}/status/500$", + "method": "^GET$" + }, + "response": { + "status": "^500$" + } + }, + "annotations": { + "aws.local.service": "^{{remoteServiceName}}$", + "aws.local.operation": "^GET /status/.code.", + "aws.local.environment": "^ec2:default$" + }, + "metadata": { + "default": { + "EC2.InstanceId": "^i-[A-Za-z0-9]{17}$", + "PlatformType": "^AWS::EC2$", + "otel.resource.host.image.id": "^{{instanceAmi}}$", + "otel.resource.host.type": "^([a-z0-9]+\\.[a-z0-9]+)$", + "aws.span.kind": "^LOCAL_ROOT$", + "aws.xray.sampling_rule": "^RuleA$", + "error.type": "^500$" + } + } +}] \ No newline at end of file diff --git a/validator/src/main/resources/validations/java/ec2/adaptive-sampling/trace-validation.yml b/validator/src/main/resources/validations/java/ec2/adaptive-sampling/trace-validation.yml new file mode 100644 index 000000000..833795d0e --- /dev/null +++ b/validator/src/main/resources/validations/java/ec2/adaptive-sampling/trace-validation.yml @@ -0,0 +1,12 @@ +- + validationType: "trace" + httpPath: "/status/{code}" + httpMethod: "get" + callingType: "http-with-query" + expectedTraceTemplate: "JAVA_EC2_ADAPTIVE_SAMPLING_FULL_TRACE" +- + validationType: "trace" + httpPath: "/status/{code}" + httpMethod: "get" + callingType: "http-with-query" + expectedTraceTemplate: "JAVA_EC2_ADAPTIVE_SAMPLING_PARTIAL_TRACE" \ No newline at end of file