diff --git a/.github/workflows/java-ecs-canary.yml b/.github/workflows/java-ecs-canary.yml new file mode 100644 index 000000000..2bbf59b43 --- /dev/null +++ b/.github/workflows/java-ecs-canary.yml @@ -0,0 +1,27 @@ +## Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +## SPDX-License-Identifier: Apache-2.0 + +## This workflow aims to run the Application Signals Java end-to-end tests as a canary to +## test the artifacts for Application Signals enablement. It will deploy a sample app onto an ECS cluster, +## call the APIs, and validate the generated telemetry, including logs, metrics, and traces. +name: Java ECS Enablement Canary Testing +on: + schedule: + - cron: '*/15 * * * *' # run the workflow every 15 minutes + workflow_dispatch: # be able to run the workflow on demand + +permissions: + id-token: write + contents: read + +jobs: + ecs: + strategy: + fail-fast: false + matrix: + aws-region: ['us-east-1'] + uses: ./.github/workflows/java-ecs-retry.yml + secrets: inherit + with: + aws-region: ${{ matrix.aws-region }} + caller-workflow-name: 'appsignals-e2e-java-ecs-canary-test' \ No newline at end of file diff --git a/.github/workflows/java-ecs-retry.yml b/.github/workflows/java-ecs-retry.yml new file mode 100644 index 000000000..942f1bf17 --- /dev/null +++ b/.github/workflows/java-ecs-retry.yml @@ -0,0 +1,57 @@ +## Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +## SPDX-License-Identifier: Apache-2.0 + +# This is a reusable workflow for running the Enablement 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 ECS Retry +on: + workflow_call: + inputs: + aws-region: + required: true + type: string + caller-workflow-name: + required: true + type: string + +permissions: + id-token: write + contents: read + +jobs: + java-ecs-attempt-1: + uses: ./.github/workflows/java-ecs-test.yml + secrets: inherit + with: + aws-region: ${{ inputs.aws-region }} + caller-workflow-name: ${{ inputs.caller-workflow-name }} + + java-ecs-attempt-2: + needs: [ java-ecs-attempt-1 ] + if: ${{ needs.java-ecs-attempt-1.outputs.job-started != 'true' }} + uses: ./.github/workflows/java-ecs-test.yml + secrets: inherit + with: + aws-region: ${{ inputs.aws-region }} + caller-workflow-name: ${{ inputs.caller-workflow-name }} + + publish-metric-attempt-1: + needs: [ java-ecs-attempt-1, java-ecs-attempt-2 ] + if: always() + uses: ./.github/workflows/enablement-test-publish-result.yml + secrets: inherit + with: + aws-region: ${{ inputs.aws-region }} + caller-workflow-name: ${{ inputs.caller-workflow-name }} + validation-result: ${{ needs.java-ecs-attempt-1.outputs.validation-result || needs.java-ecs-attempt-2.outputs.validation-result }} + + publish-metric-attempt-2: + needs: [ java-ecs-attempt-1, java-ecs-attempt-2, publish-metric-attempt-1 ] + if: ${{ always() && needs.publish-metric-attempt-1.outputs.job-started != 'true' }} + uses: ./.github/workflows/enablement-test-publish-result.yml + secrets: inherit + with: + aws-region: ${{ inputs.aws-region }} + caller-workflow-name: ${{ inputs.caller-workflow-name }} + validation-result: ${{ needs.java-ecs-attempt-1.outputs.validation-result || needs.java-ecs-attempt-2.outputs.validation-result }} \ No newline at end of file diff --git a/.github/workflows/java-ecs-test.yml b/.github/workflows/java-ecs-test.yml new file mode 100644 index 000000000..3a12dfa78 --- /dev/null +++ b/.github/workflows/java-ecs-test.yml @@ -0,0 +1,276 @@ +## Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +## SPDX-License-Identifier: Apache-2.0 + +# This is a reusable workflow for running the E2E 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 ECS Use Case +on: + workflow_call: + inputs: + aws-region: + required: true + type: string + caller-workflow-name: + required: true + type: string + adot-image-name: + required: false + type: string + cwagent-image-name: + required: false + type: string + outputs: + job-started: + value: ${{ jobs.java-ecs.outputs.job-started }} + validation-result: + value: ${{ jobs.java-ecs.outputs.validation-result }} + +permissions: + id-token: write + contents: read + +env: + E2E_TEST_AWS_REGION: ${{ inputs.aws-region }} + CALLER_WORKFLOW_NAME: ${{ inputs.caller-workflow-name }} + ADOT_IMAGE_NAME: ${{ inputs.adot-image-name }} + CLUSTER_NAME: e2e-test-java + SAMPLE_APP_NAME: main-service-java + METRIC_NAMESPACE: ApplicationSignals + LOG_GROUP_NAME: /aws/application-signals/data + TEST_RESOURCES_FOLDER: ${GITHUB_WORKSPACE} + E2E_TEST_ACCOUNT_ID: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ACCOUNT_ID }} + E2E_TEST_ROLE_NAME: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ROLE_NAME }} + +jobs: + java-ecs: + runs-on: ubuntu-latest + outputs: + job-started: ${{ steps.job-started.outputs.job-started }} + validation-result: ${{ steps.validation-result.outputs.validation-result }} + steps: + - name: Check if the job started + id: job-started + run: echo "job-started=true" >> $GITHUB_OUTPUT + + - name: Generate testing id and sample app namespace + run: | + echo TESTING_ID="${{ github.job }}-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}" >> $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 + + # We initialize Gradlew Daemon early on during the workflow because sometimes initialization + # fails due to transient issues. If it fails here, then we will try again later before the validators + - 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, region-account/${{ env.E2E_TEST_AWS_REGION }} + JAVA_MAIN_SAMPLE_APP_IMAGE, e2e-test/java-main-sample-app-image + JAVA_REMOTE_SAMPLE_APP_IMAGE, e2e-test/java-remote-sample-app-image + + # If the workflow is running as a canary, then we want to log in to the aws account in the appropriate 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 }} + + - name: Initiate Terraform + uses: ./.github/workflows/actions/execute_and_retry + with: + command: "cd ${{ env.TEST_RESOURCES_FOLDER }}/terraform/java/ecs && terraform init && terraform validate" + cleanup: "rm -rf .terraform && rm -rf .terraform.lock.hcl" + max_retry: 6 + sleep_time: 60 + + - name: Set Sample App Image + run: | + echo MAIN_SAMPLE_APP_IMAGE_URI="${{ env.ACCOUNT_ID }}.dkr.ecr.${{ env.E2E_TEST_AWS_REGION }}.amazonaws.com/${{ env.JAVA_MAIN_SAMPLE_APP_IMAGE }}" >> $GITHUB_ENV + echo REMOTE_SAMPLE_APP_IMAGE_URI="${{ env.ACCOUNT_ID }}.dkr.ecr.${{ env.E2E_TEST_AWS_REGION }}.amazonaws.com/${{ env.JAVA_REMOTE_SAMPLE_APP_IMAGE }}" >> $GITHUB_ENV + + - name: Set ADOT Java image environment variable + run: | + if [ "${{ github.event.repository.name }}" = "aws-otel-java-instrumentation" ]; then + # Use the staging image build by the ADOT Java repo + echo ADOT_INSTRUMENTATION_IMAGE_URI="${{ env.ADOT_IMAGE_NAME }}" >> $GITHUB_ENV + else + ADOT_INSTRUMENTATION_IMAGE_TAG=$(curl -s -I -L 'https://github.com/aws-observability/aws-otel-java-instrumentation/releases/latest' | grep -i Location | awk -F'/tag/' '{print $2}' | tr -d '\r') + echo ADOT_INSTRUMENTATION_IMAGE_URI="public.ecr.aws/aws-observability/adot-autoinstrumentation-java:$ADOT_INSTRUMENTATION_IMAGE_TAG" >> $GITHUB_ENV + fi + + # Switch to use the public image for CW Agent + - name: Set Get CW Agent command environment variable + run: | + if [ "${{ github.event.repository.name }}" = "amazon-cloudwatch-agent" ]; then + echo CWAGENT_IMAGE_URI="${{ secrets.AWS_ECR_PRIVATE_REGISTRY }}/cwagent-integration-test:${{ github.sha }}" >> $GITHUB_ENV + else + # echo CWAGENT_IMAGE_URI="public.ecr.aws/cloudwatch-agent/cloudwatch-agent:latest" >> $GITHUB_ENV + echo CWAGENT_IMAGE_URI="public.ecr.aws/y8s3a7r9/cloudwatch-agent:latest" >> $GITHUB_ENV + fi + + - name: Deploy sample app via terraform and wait for the endpoint to come online + id: deploy-sample-app + working-directory: terraform/java/ecs + run: | + # Attempt to deploy the sample app on an EKS instance and wait for its endpoint to come online. + # There may be occasional failures due to transitivity issues, so try up to 2 times. + # deployment_failed of 0 indicates that both the terraform deployment and the endpoint are running, while 1 indicates + # that it failed at some point + 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="test_id=${{ env.TESTING_ID }}" \ + -var="aws_region=${{ env.E2E_TEST_AWS_REGION }}" \ + -var="ecs_cluster_name=${{ env.CLUSTER_NAME }}-${{ env.TESTING_ID }}" \ + -var="sample_app_name=${{ env.SAMPLE_APP_NAME }}-${{ env.TESTING_ID }}" \ + -var="sample_app_image=${{ env.MAIN_SAMPLE_APP_IMAGE_URI }}" \ + -var="sample_remote_app_image=${{ env.REMOTE_SAMPLE_APP_IMAGE_URI }}" \ + -var="adot_instrumentation_image=${{ env.ADOT_INSTRUMENTATION_IMAGE_URI }}" \ + -var="cwagent_image=${{ env.CWAGENT_IMAGE_URI }}" \ + || deployment_failed=$? + + if [ $deployment_failed -ne 0 ]; then + echo "Terraform deployment was unsuccessful. Will attempt to retry deployment." + fi + + # If the deployment_failed is 1 then either the terraform deployment or the endpoint connection failed, so first destroy the + # resources created from terraform and try again. + if [ $deployment_failed -eq 1 ]; then + echo "Destroying terraform" + terraform destroy -auto-approve \ + -var="test_id=${{ env.TESTING_ID }}" \ + -var="aws_region=${{ env.E2E_TEST_AWS_REGION }}" \ + -var="ecs_cluster_name=${{ env.CLUSTER_NAME }}-${{ env.TESTING_ID }}" \ + -var="sample_app_name=${{ env.SAMPLE_APP_NAME }}-${{ env.TESTING_ID }}" \ + -var="sample_app_image=${{ env.MAIN_SAMPLE_APP_IMAGE_URI }}" \ + -var="sample_remote_app_image=${{ env.REMOTE_SAMPLE_APP_IMAGE_URI }}" \ + -var="adot_instrumentation_image=${{ env.ADOT_INSTRUMENTATION_IMAGE_URI }}" \ + -var="cwagent_image=${{ env.CWAGENT_IMAGE_URI }}" + + retry_counter=$(($retry_counter+1)) + else + # If deployment succeeded, then exit the loop + break + fi + + if [ $retry_counter -ge $max_retry ]; then + echo "Max retry reached, failed to deploy terraform and connect to the endpoint. Exiting code" + exit 1 + fi + done + + - name: Sleep to Wait for Canary Generated and Log Artifact Versions + run: | + sleep 120 + echo "ADOT Image: ${{ env.ADOT_INSTRUMENTATION_IMAGE_URI }}"; + echo "CW Agent Image: ${{ env.CWAGENT_IMAGE_URI }}"; + + - 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 + + # Validation for app signals telemetry data + - name: Call endpoint and validate generated EMF logs + id: log-validation + if: steps.deploy-sample-app.outcome == 'success' && !cancelled() + run: ./gradlew validator:run --args='-c java/ecs/log-validation.yml + --testing-id ${{ env.TESTING_ID }} + --region ${{ env.E2E_TEST_AWS_REGION }} + --account-id ${{ env.ACCOUNT_ID }} + --metric-namespace ${{ env.METRIC_NAMESPACE }} + --log-group ${{ env.LOG_GROUP_NAME }} + --platform-info ${{ env.CLUSTER_NAME }}-${{ env.TESTING_ID }} + --service-name ${{env.SAMPLE_APP_NAME }}-${{ env.TESTING_ID }} + --rollup' + + - name: Call endpoints and validate generated metrics + id: metric-validation + if: (steps.deploy-sample-app.outcome == 'success' || steps.log-validation.outcome == 'failure') && !cancelled() + run: ./gradlew validator:run --args='-c java/ecs/metric-validation.yml + --testing-id ${{ env.TESTING_ID }} + --region ${{ env.E2E_TEST_AWS_REGION }} + --account-id ${{ env.ACCOUNT_ID }} + --metric-namespace ${{ env.METRIC_NAMESPACE }} + --log-group ${{ env.LOG_GROUP_NAME }} + --platform-info ${{ env.CLUSTER_NAME }}-${{ env.TESTING_ID }} + --service-name ${{env.SAMPLE_APP_NAME }}-${{ env.TESTING_ID }} + --rollup' + + - name: Call endpoints and validate generated traces + id: trace-validation + if: (steps.deploy-sample-app.outcome == 'success' || steps.log-validation.outcome == 'failure' || steps.metric-validation.outcome == 'failure') && !cancelled() + run: ./gradlew validator:run --args='-c java/ecs/trace-validation.yml + --testing-id ${{ env.TESTING_ID }} + --region ${{ env.E2E_TEST_AWS_REGION }} + --account-id ${{ env.ACCOUNT_ID }} + --metric-namespace ${{ env.METRIC_NAMESPACE }} + --log-group ${{ env.LOG_GROUP_NAME }} + --platform-info ${{ env.CLUSTER_NAME }}-${{ env.TESTING_ID }} + --service-name ${{env.SAMPLE_APP_NAME }}-${{ env.TESTING_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 }} + + - name: Save test results + if: always() + id: validation-result + run: | + if [ "${{ steps.log-validation.outcome }}" = "success" ] && [ "${{ steps.metric-validation.outcome }}" = "success" ] && [ "${{ steps.trace-validation.outcome }}" = "success" ]; then + echo "validation-result=success" >> $GITHUB_OUTPUT + else + echo "validation-result=failure" >> $GITHUB_OUTPUT + fi + + # Clean up Procedures + + - name: Terraform destroy + if: always() + continue-on-error: true + timeout-minutes: 5 + working-directory: terraform/java/ecs + run: | + terraform destroy -auto-approve \ + -var="test_id=${{ env.TESTING_ID }}" \ + -var="aws_region=${{ env.E2E_TEST_AWS_REGION }}" \ + -var="ecs_cluster_name=${{ env.CLUSTER_NAME }}-${{ env.TESTING_ID }}" \ + -var="sample_app_name=${{ env.SAMPLE_APP_NAME }}-${{ env.TESTING_ID }}" \ + -var="sample_app_image=${{ env.MAIN_SAMPLE_APP_IMAGE_URI }}" \ + -var="sample_remote_app_image=${{ env.REMOTE_SAMPLE_APP_IMAGE_URI }}" \ + -var="adot_instrumentation_image=${{ env.ADOT_INSTRUMENTATION_IMAGE_URI }}" \ + -var="cwagent_image=${{ env.CWAGENT_IMAGE_URI }}" \ No newline at end of file diff --git a/.github/workflows/python-ecs-canary.yml b/.github/workflows/python-ecs-canary.yml new file mode 100644 index 000000000..d2c8ffb4f --- /dev/null +++ b/.github/workflows/python-ecs-canary.yml @@ -0,0 +1,27 @@ +## Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +## SPDX-License-Identifier: Apache-2.0 + +## This workflow aims to run the Application Signals Python end-to-end tests as a canary to +## test the artifacts for Application Signals enablement. It will deploy a sample app onto an ECS cluster, +## call the APIs, and validate the generated telemetry, including logs, metrics, and traces. +name: Python ECS Enablement Canary Testing +on: + schedule: + - cron: '*/15 * * * *' # run the workflow every 15 minutes + workflow_dispatch: # be able to run the workflow on demand + +permissions: + id-token: write + contents: read + +jobs: + ecs: + strategy: + fail-fast: false + matrix: + aws-region: ['us-east-1'] + uses: ./.github/workflows/python-ecs-retry.yml + secrets: inherit + with: + aws-region: ${{ matrix.aws-region }} + caller-workflow-name: 'appsignals-e2e-python-ecs-canary-test' \ No newline at end of file diff --git a/.github/workflows/python-ecs-retry.yml b/.github/workflows/python-ecs-retry.yml new file mode 100644 index 000000000..025572ac2 --- /dev/null +++ b/.github/workflows/python-ecs-retry.yml @@ -0,0 +1,57 @@ +## Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +## SPDX-License-Identifier: Apache-2.0 + +# This is a reusable workflow for running the Enablement 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: Python ECS Retry +on: + workflow_call: + inputs: + aws-region: + required: true + type: string + caller-workflow-name: + required: true + type: string + +permissions: + id-token: write + contents: read + +jobs: + python-ecs-attempt-1: + uses: ./.github/workflows/python-ecs-test.yml + secrets: inherit + with: + aws-region: ${{ inputs.aws-region }} + caller-workflow-name: ${{ inputs.caller-workflow-name }} + + python-ecs-attempt-2: + needs: [ python-ecs-attempt-1 ] + if: ${{ needs.python-ecs-attempt-1.outputs.job-started != 'true' }} + uses: ./.github/workflows/python-ecs-test.yml + secrets: inherit + with: + aws-region: ${{ inputs.aws-region }} + caller-workflow-name: ${{ inputs.caller-workflow-name }} + + publish-metric-attempt-1: + needs: [ python-ecs-attempt-1, python-ecs-attempt-2 ] + if: always() + uses: ./.github/workflows/enablement-test-publish-result.yml + secrets: inherit + with: + aws-region: ${{ inputs.aws-region }} + caller-workflow-name: ${{ inputs.caller-workflow-name }} + validation-result: ${{ needs.python-ecs-attempt-1.outputs.validation-result || needs.python-ecs-attempt-2.outputs.validation-result }} + + publish-metric-attempt-2: + needs: [ python-ecs-attempt-1, python-ecs-attempt-2, publish-metric-attempt-1 ] + if: ${{ always() && needs.publish-metric-attempt-1.outputs.job-started != 'true' }} + uses: ./.github/workflows/enablement-test-publish-result.yml + secrets: inherit + with: + aws-region: ${{ inputs.aws-region }} + caller-workflow-name: ${{ inputs.caller-workflow-name }} + validation-result: ${{ needs.python-ecs-attempt-1.outputs.validation-result || needs.python-ecs-attempt-2.outputs.validation-result }} \ No newline at end of file diff --git a/.github/workflows/python-ecs-test.yml b/.github/workflows/python-ecs-test.yml new file mode 100644 index 000000000..5f82714f8 --- /dev/null +++ b/.github/workflows/python-ecs-test.yml @@ -0,0 +1,276 @@ +## Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +## SPDX-License-Identifier: Apache-2.0 + +# This is a reusable workflow for running the E2E 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: Python ECS Use Case +on: + workflow_call: + inputs: + aws-region: + required: true + type: string + caller-workflow-name: + required: true + type: string + adot-image-name: + required: false + type: string + cwagent-image-name: + required: false + type: string + outputs: + job-started: + value: ${{ jobs.python-ecs.outputs.job-started }} + validation-result: + value: ${{ jobs.python-ecs.outputs.validation-result }} + +permissions: + id-token: write + contents: read + +env: + E2E_TEST_AWS_REGION: ${{ inputs.aws-region }} + CALLER_WORKFLOW_NAME: ${{ inputs.caller-workflow-name }} + ADOT_IMAGE_NAME: ${{ inputs.adot-image-name }} + CLUSTER_NAME: e2e-test-python + SAMPLE_APP_NAME: main-service-python + METRIC_NAMESPACE: ApplicationSignals + LOG_GROUP_NAME: /aws/application-signals/data + TEST_RESOURCES_FOLDER: ${GITHUB_WORKSPACE} + E2E_TEST_ACCOUNT_ID: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ACCOUNT_ID }} + E2E_TEST_ROLE_NAME: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ROLE_NAME }} + +jobs: + python-ecs: + runs-on: ubuntu-latest + outputs: + job-started: ${{ steps.job-started.outputs.job-started }} + validation-result: ${{ steps.validation-result.outputs.validation-result }} + steps: + - name: Check if the job started + id: job-started + run: echo "job-started=true" >> $GITHUB_OUTPUT + + - name: Generate testing id and sample app namespace + run: | + echo TESTING_ID="${{ github.job }}-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}" >> $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 + + # We initialize Gradlew Daemon early on during the workflow because sometimes initialization + # fails due to transient issues. If it fails here, then we will try again later before the validators + - 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, region-account/${{ env.E2E_TEST_AWS_REGION }} + PYTHON_MAIN_SAMPLE_APP_IMAGE, e2e-test/python-main-sample-app-image + PYTHON_REMOTE_SAMPLE_APP_IMAGE, e2e-test/python-remote-sample-app-image + + # If the workflow is running as a canary, then we want to log in to the aws account in the appropriate 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 }} + + - name: Initiate Terraform + uses: ./.github/workflows/actions/execute_and_retry + with: + command: "cd ${{ env.TEST_RESOURCES_FOLDER }}/terraform/python/ecs && terraform init && terraform validate" + cleanup: "rm -rf .terraform && rm -rf .terraform.lock.hcl" + max_retry: 6 + sleep_time: 60 + + - name: Set Sample App Image + run: | + echo MAIN_SAMPLE_APP_IMAGE_URI="${{ env.ACCOUNT_ID }}.dkr.ecr.${{ env.E2E_TEST_AWS_REGION }}.amazonaws.com/${{ env.PYTHON_MAIN_SAMPLE_APP_IMAGE }}" >> $GITHUB_ENV + echo REMOTE_SAMPLE_APP_IMAGE_URI="${{ env.ACCOUNT_ID }}.dkr.ecr.${{ env.E2E_TEST_AWS_REGION }}.amazonaws.com/${{ env.PYTHON_REMOTE_SAMPLE_APP_IMAGE }}" >> $GITHUB_ENV + + - name: Set ADOT Python image environment variable + run: | + if [ "${{ github.event.repository.name }}" = "aws-otel-python-instrumentation" ]; then + # Use the staging image build by the ADOT Python repo + echo ADOT_INSTRUMENTATION_IMAGE_URI=="${{ env.ADOT_IMAGE_NAME }}" >> $GITHUB_ENV + else + ADOT_INSTRUMENTATION_IMAGE_TAG=$(curl -s -I -L 'https://github.com/aws-observability/aws-otel-python-instrumentation/releases/latest' | grep -i Location | awk -F'/tag/' '{print $2}' | tr -d '\r') + echo ADOT_INSTRUMENTATION_IMAGE_URI="public.ecr.aws/aws-observability/adot-autoinstrumentation-python:$ADOT_INSTRUMENTATION_IMAGE_TAG" >> $GITHUB_ENV + fi + + # Switch to use the public image for CW Agent + - name: Set Get CW Agent command environment variable + run: | + if [ "${{ github.event.repository.name }}" = "amazon-cloudwatch-agent" ]; then + echo CWAGENT_IMAGE_URI="${{ secrets.AWS_ECR_PRIVATE_REGISTRY }}/cwagent-integration-test:${{ github.sha }}" >> $GITHUB_ENV + else + # echo CWAGENT_IMAGE_URI="public.ecr.aws/cloudwatch-agent/cloudwatch-agent:latest" >> $GITHUB_ENV + echo CWAGENT_IMAGE_URI="public.ecr.aws/y8s3a7r9/cloudwatch-agent:latest" >> $GITHUB_ENV + fi + + - name: Deploy sample app via terraform and wait for the endpoint to come online + id: deploy-sample-app + working-directory: terraform/python/ecs + run: | + # Attempt to deploy the sample app on an EKS instance and wait for its endpoint to come online. + # There may be occasional failures due to transitivity issues, so try up to 2 times. + # deployment_failed of 0 indicates that both the terraform deployment and the endpoint are running, while 1 indicates + # that it failed at some point + 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="test_id=${{ env.TESTING_ID }}" \ + -var="aws_region=${{ env.E2E_TEST_AWS_REGION }}" \ + -var="ecs_cluster_name=${{ env.CLUSTER_NAME }}-${{ env.TESTING_ID }}" \ + -var="sample_app_name=${{ env.SAMPLE_APP_NAME }}-${{ env.TESTING_ID }}" \ + -var="sample_app_image=${{ env.MAIN_SAMPLE_APP_IMAGE_URI }}" \ + -var="sample_remote_app_image=${{ env.REMOTE_SAMPLE_APP_IMAGE_URI }}" \ + -var="adot_instrumentation_image=${{ env.ADOT_INSTRUMENTATION_IMAGE_URI }}" \ + -var="cwagent_image=${{ env.CWAGENT_IMAGE_URI }}" \ + || deployment_failed=$? + + if [ $deployment_failed -ne 0 ]; then + echo "Terraform deployment was unsuccessful. Will attempt to retry deployment." + fi + + # If the deployment_failed is 1 then either the terraform deployment or the endpoint connection failed, so first destroy the + # resources created from terraform and try again. + if [ $deployment_failed -eq 1 ]; then + echo "Destroying terraform" + terraform destroy -auto-approve \ + -var="test_id=${{ env.TESTING_ID }}" \ + -var="aws_region=${{ env.E2E_TEST_AWS_REGION }}" \ + -var="ecs_cluster_name=${{ env.CLUSTER_NAME }}-${{ env.TESTING_ID }}" \ + -var="sample_app_name=${{ env.SAMPLE_APP_NAME }}-${{ env.TESTING_ID }}" \ + -var="sample_app_image=${{ env.MAIN_SAMPLE_APP_IMAGE_URI }}" \ + -var="sample_remote_app_image=${{ env.REMOTE_SAMPLE_APP_IMAGE_URI }}" \ + -var="adot_instrumentation_image=${{ env.ADOT_INSTRUMENTATION_IMAGE_URI }}" \ + -var="cwagent_image=${{ env.CWAGENT_IMAGE_URI }}" + + retry_counter=$(($retry_counter+1)) + else + # If deployment succeeded, then exit the loop + break + fi + + if [ $retry_counter -ge $max_retry ]; then + echo "Max retry reached, failed to deploy terraform and connect to the endpoint. Exiting code" + exit 1 + fi + done + + - name: Sleep to Wait for Canary Generated and Log Artifact Versions + run: | + sleep 120 + echo "ADOT Image: ${{ env.ADOT_INSTRUMENTATION_IMAGE_URI }}"; + echo "CW Agent Image: ${{ env.CWAGENT_IMAGE_URI }}"; + + - 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 + + # Validation for app signals telemetry data + - name: Call endpoint and validate generated EMF logs + id: log-validation + if: steps.deploy-sample-app.outcome == 'success' && !cancelled() + run: ./gradlew validator:run --args='-c python/ecs/log-validation.yml + --testing-id ${{ env.TESTING_ID }} + --region ${{ env.E2E_TEST_AWS_REGION }} + --account-id ${{ env.ACCOUNT_ID }} + --metric-namespace ${{ env.METRIC_NAMESPACE }} + --log-group ${{ env.LOG_GROUP_NAME }} + --platform-info ${{ env.CLUSTER_NAME }}-${{ env.TESTING_ID }} + --service-name ${{env.SAMPLE_APP_NAME }}-${{ env.TESTING_ID }} + --rollup' + + - name: Call endpoints and validate generated metrics + id: metric-validation + if: (steps.deploy-sample-app.outcome == 'success' || steps.log-validation.outcome == 'failure') && !cancelled() + run: ./gradlew validator:run --args='-c python/ecs/metric-validation.yml + --testing-id ${{ env.TESTING_ID }} + --region ${{ env.E2E_TEST_AWS_REGION }} + --account-id ${{ env.ACCOUNT_ID }} + --metric-namespace ${{ env.METRIC_NAMESPACE }} + --log-group ${{ env.LOG_GROUP_NAME }} + --platform-info ${{ env.CLUSTER_NAME }}-${{ env.TESTING_ID }} + --service-name ${{env.SAMPLE_APP_NAME }}-${{ env.TESTING_ID }} + --rollup' + + - name: Call endpoints and validate generated traces + id: trace-validation + if: (steps.deploy-sample-app.outcome == 'success' || steps.log-validation.outcome == 'failure' || steps.metric-validation.outcome == 'failure') && !cancelled() + run: ./gradlew validator:run --args='-c python/ecs/trace-validation.yml + --testing-id ${{ env.TESTING_ID }} + --region ${{ env.E2E_TEST_AWS_REGION }} + --account-id ${{ env.ACCOUNT_ID }} + --metric-namespace ${{ env.METRIC_NAMESPACE }} + --log-group ${{ env.LOG_GROUP_NAME }} + --platform-info ${{ env.CLUSTER_NAME }}-${{ env.TESTING_ID }} + --service-name ${{env.SAMPLE_APP_NAME }}-${{ env.TESTING_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 }} + + - name: Save test results + if: always() + id: validation-result + run: | + if [ "${{ steps.log-validation.outcome }}" = "success" ] && [ "${{ steps.metric-validation.outcome }}" = "success" ] && [ "${{ steps.trace-validation.outcome }}" = "success" ]; then + echo "validation-result=success" >> $GITHUB_OUTPUT + else + echo "validation-result=failure" >> $GITHUB_OUTPUT + fi + + # Clean up Procedures + + - name: Terraform destroy + if: always() + continue-on-error: true + timeout-minutes: 5 + working-directory: terraform/python/ecs + run: | + terraform destroy -auto-approve \ + -var="test_id=${{ env.TESTING_ID }}" \ + -var="aws_region=${{ env.E2E_TEST_AWS_REGION }}" \ + -var="ecs_cluster_name=${{ env.CLUSTER_NAME }}-${{ env.TESTING_ID }}" \ + -var="sample_app_name=${{ env.SAMPLE_APP_NAME }}-${{ env.TESTING_ID }}" \ + -var="sample_app_image=${{ env.MAIN_SAMPLE_APP_IMAGE_URI }}" \ + -var="sample_remote_app_image=${{ env.REMOTE_SAMPLE_APP_IMAGE_URI }}" \ + -var="adot_instrumentation_image=${{ env.ADOT_INSTRUMENTATION_IMAGE_URI }}" \ + -var="cwagent_image=${{ env.CWAGENT_IMAGE_URI }}" \ No newline at end of file diff --git a/terraform/java/ecs/main.tf b/terraform/java/ecs/main.tf new file mode 100644 index 000000000..1cb98b58f --- /dev/null +++ b/terraform/java/ecs/main.tf @@ -0,0 +1,94 @@ +# ------------------------------------------------------------------------ +# 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" {} + +data "aws_subnets" "default_subnets" { + filter { + name = "vpc-id" + values = [aws_default_vpc.default.id] + } +} + +data "aws_iam_role" "e2e_test_task_role" { + # change + name = "ecsE2ETestRole" +} + +data "aws_iam_role" "e2e_test_task_execution_role" { + name = "ecsE2ETestExecutionRole" +} + +data "template_file" "main_service" { + template = file("./resources/main-service.json.tpl") + + vars = { + app_image = var.sample_app_image + app_service_name = "${var.sample_app_name}" + aws_region = var.aws_region + init_image = var.adot_instrumentation_image + cwagent_image = var.cwagent_image + } +} + +resource "aws_ecs_cluster" "e2e_test" { + name = var.ecs_cluster_name +} + +resource "aws_ecs_task_definition" "main_service" { + family = var.sample_app_name + network_mode = "awsvpc" + requires_compatibilities = ["FARGATE"] + execution_role_arn = data.aws_iam_role.e2e_test_task_execution_role.arn + task_role_arn = data.aws_iam_role.e2e_test_task_role.arn + cpu = 512 + memory = 1024 + runtime_platform { + operating_system_family = "LINUX" + cpu_architecture = "X86_64" + } + container_definitions = data.template_file.main_service.rendered + volume { + name = "opentelemetry-auto-instrumentation" + } +} + +resource "aws_ecs_service" "main_service" { + name = var.sample_app_name + cluster = aws_ecs_cluster.e2e_test.id + task_definition = aws_ecs_task_definition.main_service.arn + desired_count = 1 + enable_ecs_managed_tags = true + launch_type = "FARGATE" + + network_configuration { + security_groups = [aws_default_vpc.default.default_security_group_id] + subnets = data.aws_subnets.default_subnets.ids + assign_public_ip = true + } + + wait_for_steady_state = true +} \ No newline at end of file diff --git a/terraform/java/ecs/resources/main-service.json.tpl b/terraform/java/ecs/resources/main-service.json.tpl new file mode 100644 index 000000000..4649b5b96 --- /dev/null +++ b/terraform/java/ecs/resources/main-service.json.tpl @@ -0,0 +1,125 @@ +[ + { + "name": "app", + "image": "${app_image}", + "cpu": 0, + "portMappings": [ + { + "name": "app-8080-tcp", + "containerPort": 8080, + "hostPort": 8080, + "protocol": "tcp", + "appProtocol": "http" + } + ], + "essential": true, + "environment": [ + { + "name": "OTEL_EXPORTER_OTLP_PROTOCOL", + "value": "http/protobuf" + }, + { + "name": "OTEL_AWS_APPLICATION_SIGNALS_ENABLED", + "value": "true" + }, + { + "name": "OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT", + "value": "http://localhost:4316/v1/metrics" + }, + { + "name": "OTEL_RESOURCE_ATTRIBUTES", + "value": "service.name=${app_service_name}" + }, + { + "name": "OTEL_METRICS_EXPORTER", + "value": "none" + }, + { + "name": "JAVA_TOOL_OPTIONS", + "value": "-javaagent:/otel-auto-instrumentation/javaagent.jar" + }, + { + "name": "OTEL_LOGS_EXPORTER", + "value": "none" + }, + { + "name": "OTEL_TRACES_SAMPLER", + "value": "xray" + }, + { + "name": "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", + "value": "http://localhost:4316/v1/traces" + }, + { + "name": "OTEL_PROPAGATORS", + "value": "tracecontext,baggage,b3,xray" + } + ], + "mountPoints": [ + { + "sourceVolume": "opentelemetry-auto-instrumentation", + "containerPath": "/otel-auto-instrumentation", + "readOnly": false + } + ], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "/ecs/service-e2e-test", + "awslogs-create-group": "true", + "awslogs-region": "${aws_region}", + "awslogs-stream-prefix": "ecs" + } + } + }, + { + "name": "init", + "image": "${init_image}", + "cpu": 0, + "essential": false, + "command": [ + "cp", + "/javaagent.jar", + "/otel-auto-instrumentation/javaagent.jar" + ], + "mountPoints": [ + { + "sourceVolume": "opentelemetry-auto-instrumentation", + "containerPath": "/otel-auto-instrumentation", + "readOnly": false + } + ] + }, + { + "name": "ecs-cwagent", + "image": "${cwagent_image}", + "cpu": 0, + "essential": true, + "environment": [ + { + "name": "CW_CONFIG_CONTENT", + "value": "{\"agent\": {\"debug\": true}, \"traces\": {\"traces_collected\": {\"application_signals\": {\"enabled\": true}}}, \"logs\": {\"metrics_collected\": {\"application_signals\": {\"enabled\": true}}}}" + } + ], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "/ecs/ecs-cwagent", + "awslogs-create-group": "true", + "awslogs-region": "${aws_region}", + "awslogs-stream-prefix": "ecs" + } + } + }, + { + "name": "traffic-gen", + "image": "amazonlinux:2", + "cpu": 0, + "essential": true, + "command": [ + "sh", + "-c", + "while true; do curl http://localhost:8080/; sleep 10; done" + ] + } +] \ No newline at end of file diff --git a/terraform/java/ecs/variables.tf b/terraform/java/ecs/variables.tf new file mode 100644 index 000000000..d4aaee852 --- /dev/null +++ b/terraform/java/ecs/variables.tf @@ -0,0 +1,46 @@ +# ------------------------------------------------------------------------ +# 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 "ecs_cluster_name" { + default = "e2e-test-java" +} + +variable "sample_app_name" { + default = "" +} + +variable "sample_app_image" { + default = "" +} + +variable "sample_remote_app_image" { + default = "" +} +variable "adot_instrumentation_image" { + default = "" +} + +variable "cwagent_image" { + default = "" +} \ No newline at end of file diff --git a/terraform/python/ecs/main.tf b/terraform/python/ecs/main.tf new file mode 100644 index 000000000..0f0eab8b3 --- /dev/null +++ b/terraform/python/ecs/main.tf @@ -0,0 +1,94 @@ +# ------------------------------------------------------------------------ +# 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" {} + +data "aws_subnets" "default_subnets" { + filter { + name = "vpc-id" + values = [aws_default_vpc.default.id] + } +} + +data "aws_iam_role" "e2e_test_task_role" { + # change + name = "ecsE2ETestRole" +} + +data "aws_iam_role" "e2e_test_task_execution_role" { + # change + name = "ecsE2ETestExecutionRole" +} + +data "template_file" "main_service" { + template = file("./resources/main-service.json.tpl") + + vars = { + app_image = var.sample_app_image + app_service_name = "${var.sample_app_name}" + aws_region = var.aws_region + init_image = var.adot_instrumentation_image + cwagent_image = var.cwagent_image + } +} + +resource "aws_ecs_cluster" "e2e_test" { + name = var.ecs_cluster_name +} +resource "aws_ecs_task_definition" "main_service" { + family = var.sample_app_name + network_mode = "awsvpc" + requires_compatibilities = ["FARGATE"] + execution_role_arn = data.aws_iam_role.e2e_test_task_execution_role.arn + task_role_arn = data.aws_iam_role.e2e_test_task_role.arn + cpu = 512 + memory = 1024 + runtime_platform { + operating_system_family = "LINUX" + cpu_architecture = "X86_64" + } + container_definitions = data.template_file.main_service.rendered + volume { + name = "opentelemetry-auto-instrumentation" + } +} + +resource "aws_ecs_service" "main_service" { + name = var.sample_app_name + cluster = aws_ecs_cluster.e2e_test.id + task_definition = aws_ecs_task_definition.main_service.arn + desired_count = 1 + enable_ecs_managed_tags = true + launch_type = "FARGATE" + + network_configuration { + security_groups = [aws_default_vpc.default.default_security_group_id] + subnets = data.aws_subnets.default_subnets.ids + assign_public_ip = true + } + + wait_for_steady_state = true +} \ No newline at end of file diff --git a/terraform/python/ecs/resources/main-service.json.tpl b/terraform/python/ecs/resources/main-service.json.tpl new file mode 100644 index 000000000..9f820fb6e --- /dev/null +++ b/terraform/python/ecs/resources/main-service.json.tpl @@ -0,0 +1,152 @@ +[ + { + "name": "app", + "image": "${app_image}", + "cpu": 0, + "portMappings": [ + { + "name": "app-8080-tcp", + "containerPort": 8080, + "hostPort": 8080, + "protocol": "tcp", + "appProtocol": "http" + } + ], + "essential": true, + "command": [ + "sh", + "-c", + "python3 manage.py migrate --noinput && python3 manage.py collectstatic --noinput && python3 manage.py runserver 0.0.0.0:8080 --noreload" + ], + "environment": [ + { + "name": "PYTHONPATH", + "value": "/otel-auto-instrumentation-python/opentelemetry/instrumentation/auto_instrumentation:/django_frontend_app:/otel-auto-instrumentation-python" + }, + { + "name": "OTEL_EXPORTER_OTLP_PROTOCOL", + "value": "http/protobuf" + }, + { + "name": "OTEL_TRACES_SAMPLER_ARG", + "value": "endpoint=http://localhost:2000" + }, + { + "name": "OTEL_LOGS_EXPORTER", + "value": "none" + }, + { + "name": "OTEL_PYTHON_CONFIGURATOR", + "value": "aws_configurator" + }, + { + "name": "OTEL_TRACES_SAMPLER", + "value": "xray" + }, + { + "name": "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", + "value": "http://localhost:4316/v1/traces" + }, + { + "name": "DJANGO_SETTINGS_MODULE", + "value": "django_frontend_service.settings" + }, + { + "name": "OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT", + "value": "http://localhost:4316/v1/metrics" + }, + { + "name": "OTEL_AWS_APPLICATION_SIGNALS_ENABLED", + "value": "true" + }, + { + "name": "OTEL_RESOURCE_ATTRIBUTES", + "value": "service.name=${app_service_name}" + }, + { + "name": "OTEL_METRICS_EXPORTER", + "value": "none" + }, + { + "name": "OTEL_PYTHON_DISTRO", + "value": "aws_distro" + } + ], + "mountPoints": [ + { + "sourceVolume": "opentelemetry-auto-instrumentation", + "containerPath": "/otel-auto-instrumentation-python", + "readOnly": false + } + ], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "/ecs/service-e2e-test", + "awslogs-create-group": "true", + "awslogs-region": "${aws_region}", + "awslogs-stream-prefix": "ecs" + } + } + }, + { + "name": "init", + "image": "${init_image}", + "cpu": 0, + "essential": false, + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "/ecs/ecs-cwagent", + "awslogs-create-group": "true", + "awslogs-region": "${aws_region}", + "awslogs-stream-prefix": "ecs" + } + }, + "command": [ + "cp", + "-a", + "/autoinstrumentation/.", + "/otel-auto-instrumentation-python" + ], + "mountPoints": [ + { + "sourceVolume": "opentelemetry-auto-instrumentation", + "containerPath": "/otel-auto-instrumentation-python", + "readOnly": false + } + ] + }, + { + "name": "ecs-cwagent", + "image": "${cwagent_image}", + "cpu": 0, + "essential": true, + "environment": [ + { + "name": "CW_CONFIG_CONTENT", + "value": "{\"agent\": {\"debug\": true}, \"traces\": {\"traces_collected\": {\"application_signals\": {\"enabled\": true}}}, \"logs\": {\"metrics_collected\": {\"application_signals\": {\"enabled\": true}}}}" + } + ], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "/ecs/ecs-cwagent", + "awslogs-create-group": "true", + "awslogs-region": "${aws_region}", + "awslogs-stream-prefix": "ecs" + } + } + }, + { + "name": "traffic-gen", + "image": "amazonlinux:2", + "cpu": 0, + "essential": true, + "command": [ + "sh", + "-c", + "while true; do curl http://localhost:8080/; sleep 10; done" + ] + } +] \ No newline at end of file diff --git a/terraform/python/ecs/variables.tf b/terraform/python/ecs/variables.tf new file mode 100644 index 000000000..d4aaee852 --- /dev/null +++ b/terraform/python/ecs/variables.tf @@ -0,0 +1,46 @@ +# ------------------------------------------------------------------------ +# 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 "ecs_cluster_name" { + default = "e2e-test-java" +} + +variable "sample_app_name" { + default = "" +} + +variable "sample_app_image" { + default = "" +} + +variable "sample_remote_app_image" { + default = "" +} +variable "adot_instrumentation_image" { + default = "" +} + +variable "cwagent_image" { + default = "" +} \ 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 bf955e54d..0421f7efb 100644 --- a/validator/src/main/java/com/amazon/aoc/fileconfigs/PredefinedExpectedTemplate.java +++ b/validator/src/main/java/com/amazon/aoc/fileconfigs/PredefinedExpectedTemplate.java @@ -96,6 +96,11 @@ public enum PredefinedExpectedTemplate implements FileConfig { JAVA_K8S_CLIENT_CALL_METRIC("/expected-data-template/java/k8s/client-call-metric.mustache"), JAVA_K8S_CLIENT_CALL_TRACE("/expected-data-template/java/k8s/client-call-trace.mustache"), + /** Java ECS Test Case Validations */ + JAVA_ECS_HC_CALL_LOG("/expected-data-template/java/ecs/hc-log.mustache"), + JAVA_ECS_HC_CALL_METRIC("/expected-data-template/java/ecs/hc-metric.mustache"), + JAVA_ECS_HC_CALL_TRACE("/expected-data-template/java/ecs/hc-trace.mustache"), + /** Metric Limiter Test Case Validations */ JAVA_METRIC_LIMITER_METRIC("/expected-data-template/java/metric_limiter/metric-limiter-metric.mustache"), @@ -174,6 +179,11 @@ public enum PredefinedExpectedTemplate implements FileConfig { PYTHON_K8S_CLIENT_CALL_METRIC("/expected-data-template/python/k8s/client-call-metric.mustache"), PYTHON_K8S_CLIENT_CALL_TRACE("/expected-data-template/python/k8s/client-call-trace.mustache"), + /** Python ECS Test Case Validations */ + PYTHON_ECS_HC_CALL_LOG("/expected-data-template/python/ecs/hc-log.mustache"), + PYTHON_ECS_HC_CALL_METRIC("/expected-data-template/python/ecs/hc-metric.mustache"), + PYTHON_ECS_HC_CALL_TRACE("/expected-data-template/python/ecs/hc-trace.mustache"), + /** DotNet EC2 Default Test Case Validations */ DOTNET_EC2_DEFAULT_OUTGOING_HTTP_CALL_LOG( "/expected-data-template/dotnet/ec2/default/outgoing-http-call-log.mustache"), diff --git a/validator/src/main/resources/expected-data-template/java/ecs/hc-log.mustache b/validator/src/main/resources/expected-data-template/java/ecs/hc-log.mustache new file mode 100644 index 000000000..5eab3c70c --- /dev/null +++ b/validator/src/main/resources/expected-data-template/java/ecs/hc-log.mustache @@ -0,0 +1,13 @@ +[{ + "ECS.Cluster": "^{{platformInfo}}$", + "ECS.TaskDefinitionFamily": "^{{serviceName}}$", + "ECS.TaskDefinitionRevision": "[0-9]*", + "ECS.TaskId": "[A-Za-z0-9]*", + "Environment": "^ecs:{{platformInfo}}$", + "Operation": "GET /", + "PlatformType": "^AWS::ECS$", + "Service": "^{{serviceName}}$", + "Telemetry.Agent": "^CWAgent\/([A-Za-z0-9.-]*)$", + "Telemetry.SDK": "^opentelemetry,([A-Za-z0-9-.]*),java,Auto$", + "Telemetry.Source": "^LocalRootSpan$" +}] \ No newline at end of file diff --git a/validator/src/main/resources/expected-data-template/java/ecs/hc-metric.mustache b/validator/src/main/resources/expected-data-template/java/ecs/hc-metric.mustache new file mode 100644 index 000000000..a35005faa --- /dev/null +++ b/validator/src/main/resources/expected-data-template/java/ecs/hc-metric.mustache @@ -0,0 +1,74 @@ +- + metricName: Latency + namespace: {{metricNamespace}} + dimensions: + - + name: Operation + value: GET / + - + name: Service + value: {{serviceName}} + - + name: Environment + value: ecs:{{platformInfo}} + +- + metricName: Latency + namespace: {{metricNamespace}} + dimensions: + - + name: Service + value: {{serviceName}} + - + name: Environment + value: ecs:{{platformInfo}} + +- + metricName: Fault + namespace: {{metricNamespace}} + dimensions: + - + name: Operation + value: GET / + - + name: Service + value: {{serviceName}} + - + name: Environment + value: ecs:{{platformInfo}} + +- + metricName: Fault + namespace: {{metricNamespace}} + dimensions: + - + name: Service + value: {{serviceName}} + - + name: Environment + value: ecs:{{platformInfo}} + +- + metricName: Error + namespace: {{metricNamespace}} + dimensions: + - + name: Operation + value: GET / + - + name: Service + value: {{serviceName}} + - + name: Environment + value: ecs:{{platformInfo}} + +- + metricName: Error + namespace: {{metricNamespace}} + dimensions: + - + name: Service + value: {{serviceName}} + - + name: Environment + value: ecs:{{platformInfo}} \ No newline at end of file diff --git a/validator/src/main/resources/expected-data-template/java/ecs/hc-trace.mustache b/validator/src/main/resources/expected-data-template/java/ecs/hc-trace.mustache new file mode 100644 index 000000000..229561f4a --- /dev/null +++ b/validator/src/main/resources/expected-data-template/java/ecs/hc-trace.mustache @@ -0,0 +1,30 @@ +[ + { + "name": "^{{serviceName}}$", + "http": { + "request": { + "url": "^http://localhost:8080/$", + "method": "^GET$" + } + }, + "aws": { + "account_id": "^{{accountId}}$" + }, + "annotations": { + "aws.local.service": "^{{serviceName}}$", + "aws.local.operation": "^GET /$", + "aws.local.environment": "^ecs:{{platformInfo}}$" + }, + "metadata": { + "default": { + "otel.resource.aws.ecs.launchtype": "^fargate$", + "otel.resource.aws.ecs.task.revision": "[0-9]*", + "otel.resource.cloud.platform": "^aws_ecs$", + "aws.ecs.cluster.name": "^{{platformInfo}}$", + "aws.ecs.task.id": "[A-Za-z0-9]*", + "PlatformType": "^AWS::ECS$", + "aws.span.kind": "^LOCAL_ROOT$" + } + } + } +] \ No newline at end of file diff --git a/validator/src/main/resources/expected-data-template/python/ecs/hc-log.mustache b/validator/src/main/resources/expected-data-template/python/ecs/hc-log.mustache new file mode 100644 index 000000000..e453f0310 --- /dev/null +++ b/validator/src/main/resources/expected-data-template/python/ecs/hc-log.mustache @@ -0,0 +1,13 @@ +[{ + "ECS.Cluster": "^{{platformInfo}}$", + "ECS.TaskDefinitionFamily": "^{{serviceName}}$", + "ECS.TaskDefinitionRevision": "[0-9]*", + "ECS.TaskId": "[A-Za-z0-9]*", + "Environment": "^ecs:{{platformInfo}}$", + "Operation": "GET /", + "PlatformType": "^AWS::ECS$", + "Service": "^{{serviceName}}$", + "Telemetry.Agent": "^CWAgent\/([A-Za-z0-9.-]*)$", + "Telemetry.SDK": "^opentelemetry,([A-Za-z0-9-.]*),python,Auto$", + "Telemetry.Source": "^LocalRootSpan$" +}] \ No newline at end of file diff --git a/validator/src/main/resources/expected-data-template/python/ecs/hc-metric.mustache b/validator/src/main/resources/expected-data-template/python/ecs/hc-metric.mustache new file mode 100644 index 000000000..a35005faa --- /dev/null +++ b/validator/src/main/resources/expected-data-template/python/ecs/hc-metric.mustache @@ -0,0 +1,74 @@ +- + metricName: Latency + namespace: {{metricNamespace}} + dimensions: + - + name: Operation + value: GET / + - + name: Service + value: {{serviceName}} + - + name: Environment + value: ecs:{{platformInfo}} + +- + metricName: Latency + namespace: {{metricNamespace}} + dimensions: + - + name: Service + value: {{serviceName}} + - + name: Environment + value: ecs:{{platformInfo}} + +- + metricName: Fault + namespace: {{metricNamespace}} + dimensions: + - + name: Operation + value: GET / + - + name: Service + value: {{serviceName}} + - + name: Environment + value: ecs:{{platformInfo}} + +- + metricName: Fault + namespace: {{metricNamespace}} + dimensions: + - + name: Service + value: {{serviceName}} + - + name: Environment + value: ecs:{{platformInfo}} + +- + metricName: Error + namespace: {{metricNamespace}} + dimensions: + - + name: Operation + value: GET / + - + name: Service + value: {{serviceName}} + - + name: Environment + value: ecs:{{platformInfo}} + +- + metricName: Error + namespace: {{metricNamespace}} + dimensions: + - + name: Service + value: {{serviceName}} + - + name: Environment + value: ecs:{{platformInfo}} \ No newline at end of file diff --git a/validator/src/main/resources/expected-data-template/python/ecs/hc-trace.mustache b/validator/src/main/resources/expected-data-template/python/ecs/hc-trace.mustache new file mode 100644 index 000000000..229561f4a --- /dev/null +++ b/validator/src/main/resources/expected-data-template/python/ecs/hc-trace.mustache @@ -0,0 +1,30 @@ +[ + { + "name": "^{{serviceName}}$", + "http": { + "request": { + "url": "^http://localhost:8080/$", + "method": "^GET$" + } + }, + "aws": { + "account_id": "^{{accountId}}$" + }, + "annotations": { + "aws.local.service": "^{{serviceName}}$", + "aws.local.operation": "^GET /$", + "aws.local.environment": "^ecs:{{platformInfo}}$" + }, + "metadata": { + "default": { + "otel.resource.aws.ecs.launchtype": "^fargate$", + "otel.resource.aws.ecs.task.revision": "[0-9]*", + "otel.resource.cloud.platform": "^aws_ecs$", + "aws.ecs.cluster.name": "^{{platformInfo}}$", + "aws.ecs.task.id": "[A-Za-z0-9]*", + "PlatformType": "^AWS::ECS$", + "aws.span.kind": "^LOCAL_ROOT$" + } + } + } +] \ No newline at end of file diff --git a/validator/src/main/resources/validations/java/ecs/log-validation.yml b/validator/src/main/resources/validations/java/ecs/log-validation.yml new file mode 100644 index 000000000..0302f1965 --- /dev/null +++ b/validator/src/main/resources/validations/java/ecs/log-validation.yml @@ -0,0 +1,4 @@ +- + validationType: "cw-log" + callingType: "none" + expectedLogStructureTemplate: "JAVA_ECS_HC_CALL_LOG" \ No newline at end of file diff --git a/validator/src/main/resources/validations/java/ecs/metric-validation.yml b/validator/src/main/resources/validations/java/ecs/metric-validation.yml new file mode 100644 index 000000000..c1cea45e4 --- /dev/null +++ b/validator/src/main/resources/validations/java/ecs/metric-validation.yml @@ -0,0 +1,4 @@ +- + validationType: "cw-metric" + callingType: "none" + expectedMetricTemplate: "JAVA_ECS_HC_CALL_METRIC" \ No newline at end of file diff --git a/validator/src/main/resources/validations/java/ecs/trace-validation.yml b/validator/src/main/resources/validations/java/ecs/trace-validation.yml new file mode 100644 index 000000000..322f35a35 --- /dev/null +++ b/validator/src/main/resources/validations/java/ecs/trace-validation.yml @@ -0,0 +1,6 @@ +- + validationType: "trace" + callingType: "none" + httpMethod: "get" + httpPath: "/" + expectedTraceTemplate: "JAVA_ECS_HC_CALL_TRACE" \ No newline at end of file diff --git a/validator/src/main/resources/validations/python/ecs/log-validation.yml b/validator/src/main/resources/validations/python/ecs/log-validation.yml new file mode 100644 index 000000000..473ffb2ee --- /dev/null +++ b/validator/src/main/resources/validations/python/ecs/log-validation.yml @@ -0,0 +1,4 @@ +- + validationType: "cw-log" + callingType: "none" + expectedLogStructureTemplate: "PYTHON_ECS_HC_CALL_LOG" \ No newline at end of file diff --git a/validator/src/main/resources/validations/python/ecs/metric-validation.yml b/validator/src/main/resources/validations/python/ecs/metric-validation.yml new file mode 100644 index 000000000..d1365d4bd --- /dev/null +++ b/validator/src/main/resources/validations/python/ecs/metric-validation.yml @@ -0,0 +1,4 @@ +- + validationType: "cw-metric" + callingType: "none" + expectedMetricTemplate: "PYTHON_ECS_HC_CALL_METRIC" \ No newline at end of file diff --git a/validator/src/main/resources/validations/python/ecs/trace-validation.yml b/validator/src/main/resources/validations/python/ecs/trace-validation.yml new file mode 100644 index 000000000..1ee776efa --- /dev/null +++ b/validator/src/main/resources/validations/python/ecs/trace-validation.yml @@ -0,0 +1,6 @@ +- + validationType: "trace" + callingType: "none" + httpMethod: "get" + httpPath: "/" + expectedTraceTemplate: "PYTHON_ECS_HC_CALL_TRACE" \ No newline at end of file