Skip to content

Commit

Permalink
Add Java Lambda E2E test (#353)
Browse files Browse the repository at this point in the history
*Issue description:* Run E2E test for Application Signals Java Lambda
support

*Description of changes:*
- Github workflows:
- Java Lambda Canary - Should be disabled since we haven't released the
layers yet.
- Java Lambda Test - Actual E2E test workflow that is called by the
canary or the main build of the ADOT Java repo
- Java Sample App S3 Deploy - Updated to also uplaod the Java sample app
for Lambda testing
- Terraform files - To deploy the APIGW-Lambda integration sample app
for E2E testing.
- Validation templates for validating trace, EMF Logs, and Application
Signals metrics data.

*Testing:* 
- See this GH action run where I executed the E2E test workflow in a dev
branch from another dev branch in the ADOT Java repo.
-
https://github.com/aws-observability/aws-otel-java-instrumentation/actions/runs/12720398142/job/35462971301


By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache 2.0 license.
  • Loading branch information
srprash authored Jan 14, 2025
1 parent 82ab58c commit e4958f4
Show file tree
Hide file tree
Showing 19 changed files with 931 additions and 0 deletions.
29 changes: 29 additions & 0 deletions .github/workflows/java-lambda-canary.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
## 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 on Lambda.
name: Java Lambda Enablement Canary Testing
on:
schedule:
- cron: '20,45 * * * *' # run the workflow at 20th and 45th minute of every hour
workflow_dispatch: # be able to run the workflow on demand

permissions:
id-token: write
contents: read

jobs:
github:
strategy:
fail-fast: false
matrix:
aws-region: ['af-south-1','ap-east-1','ap-northeast-1','ap-northeast-2','ap-northeast-3','ap-south-1','ap-south-2','ap-southeast-1',
'ap-southeast-2','ap-southeast-3','ap-southeast-4','ca-central-1','eu-central-1','eu-central-2','eu-north-1',
'eu-south-1','eu-south-2','eu-west-1','eu-west-2','eu-west-3','il-central-1','me-central-1','me-south-1', 'sa-east-1',
'us-east-1','us-east-2', 'us-west-1', 'us-west-2']
uses: ./.github/workflows/java-lambda-retry.yml
secrets: inherit
with:
aws-region: ${{ matrix.aws-region }}
caller-workflow-name: 'appsignals-java-e2e-lambda-canary-test'
57 changes: 57 additions & 0 deletions .github/workflows/java-lambda-retry.yml
Original file line number Diff line number Diff line change
@@ -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 Java Lambda Canary with retries.
# 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 Lambda Default 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-lambda-attempt-1:
uses: ./.github/workflows/java-lambda-test.yml
secrets: inherit
with:
aws-region: ${{ inputs.aws-region }}
caller-workflow-name: ${{ inputs.caller-workflow-name }}

java-lambda-attempt-2:
needs: [ java-lambda-attempt-1 ]
if: ${{ needs.java-lambda-attempt-1.outputs.job-started != 'true' }}
uses: ./.github/workflows/java-lambda-test.yml
secrets: inherit
with:
aws-region: ${{ inputs.aws-region }}
caller-workflow-name: ${{ inputs.caller-workflow-name }}

publish-metric-attempt-1:
needs: [ java-lambda-attempt-1, java-lambda-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-lambda-attempt-1.outputs.validation-result || needs.java-lambda-attempt-2.outputs.validation-result }}

publish-metric-attempt-2:
needs: [ java-lambda-attempt-1, java-lambda-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-lambda-attempt-1.outputs.validation-result || needs.java-lambda-attempt-2.outputs.validation-result }}
231 changes: 231 additions & 0 deletions .github/workflows/java-lambda-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
## Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
## SPDX-License-Identifier: Apache-2.0

# This is a reusable workflow for running the Java Enablement Canary test for Application Signals on Lambda.
# 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 Lambda Default Use Case
on:
workflow_call:
inputs:
aws-region:
required: true
type: string
caller-workflow-name:
required: true
type: string
outputs:
job-started:
value: ${{ jobs.java-lambda-default.outputs.job-started }}
validation-result:
value: ${{ jobs.java-lambda-default.outputs.validation-result }}

permissions:
id-token: write
contents: read

env:
E2E_TEST_AWS_REGION: ${{ inputs.aws-region }}
CALLER_WORKFLOW_NAME: ${{ inputs.caller-workflow-name }}
E2E_TEST_ACCOUNT_ID: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ACCOUNT_ID }}
E2E_TEST_ROLE_NAME: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ROLE_NAME }}
METRIC_NAMESPACE: ApplicationSignals
LOG_GROUP_NAME: /aws/application-signals/data
SAMPLE_APP_ZIP: s3://aws-appsignals-sample-app-prod-${{ inputs.aws-region }}/java-lambda-function.zip
TEST_RESOURCES_FOLDER: ${GITHUB_WORKSPACE}
STAGING_S3_BUCKET: ${{ secrets.STAGING_BUCKET_NAME }}
IS_CANARY: false

jobs:
java-lambda-default:
runs-on: ubuntu-latest
timeout-minutes: 30
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

- 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: Generate testing id
run: echo TESTING_ID="${{ github.run_id }}-${{ github.run_number }}-${RANDOM}" >> $GITHUB_ENV

- 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 }}

- 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: Set Lambda Layer artifact directory path
run: echo ARTIFACTS_DIR="${{ github.workspace }}/lambda_artifacts" >> $GITHUB_ENV

- name: Download Lambda Layer and Function artifacts for E2E Test
if: ${{ env.CALLER_WORKFLOW_NAME != 'appsignals-java-e2e-lambda-canary-test' }}
run: |
aws s3 cp s3://${{ env.STAGING_S3_BUCKET }}/adot-java-lambda-layer-${{ github.run_id }}.zip ${{ env.ARTIFACTS_DIR }}/layer.zip
aws s3 cp ${{ env.SAMPLE_APP_ZIP }} ${{ env.ARTIFACTS_DIR }}/java-function.zip
- name: Set Canary Environment Variable
if: ${{ env.CALLER_WORKFLOW_NAME == 'appsignals-java-e2e-lambda-canary-test' }}
run: |
echo IS_CANARY=true >> $GITHUB_ENV |
aws s3 cp ${{ env.SAMPLE_APP_ZIP }} ${{ env.ARTIFACTS_DIR }}/javafunction.zip
- 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/lambda/lambda && terraform init && terraform validate"
cleanup: "rm -rf .terraform && rm -rf .terraform.lock.hcl"
max_retry: 6
sleep_time: 60

- name: Get terraform Lambda function name
shell: bash
run: |
echo TERRAFORM_LAMBDA_FUNCTION_NAME="AdotLambdaJavaSampleApp-${{ github.run_id }}"|
tee --append $GITHUB_ENV
- name: Apply terraform
uses: ./.github/workflows/actions/execute_and_retry
with:
command: 'cd ${{ env.TEST_RESOURCES_FOLDER }}/terraform/java/lambda/lambda && terraform apply -auto-approve
-var="sdk_layer_name=AWSOpenTelemetryDistroJava-${{ github.run_id }}"
-var="function_name=${{env.TERRAFORM_LAMBDA_FUNCTION_NAME}}"
-var="layer_artifacts_directory=${{ env.ARTIFACTS_DIR }}"
-var="region=${{ env.E2E_TEST_AWS_REGION }}"
-var="is_canary=${{ env.IS_CANARY }}"'
max_retry: 6
sleep_time: 60

- name: Extract endpoint
id: extract-endpoint
shell: bash
run: cd ${{ env.TEST_RESOURCES_FOLDER }}/terraform/java/lambda/lambda && echo API_GATEWAY_URL=$(terraform output -raw api-gateway-url) >> $GITHUB_ENV

- name: Send request to endpoint
shell: bash
run: sleep 30s; curl -sS ${{ env.API_GATEWAY_URL }}

- 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 traces
id: trace-validation
# will be removed after data quality bug fixed
continue-on-error: true
run: ./gradlew validator:run --args='-c java/lambda/trace-validation.yml
--testing-id ${{ env.TESTING_ID }}
--endpoint http://${{ env.API_GATEWAY_URL }}
--region ${{ inputs.aws-region }}
--account-id ${{ env.ACCOUNT_ID }}
--metric-namespace ${{ env.METRIC_NAMESPACE }}
--log-group ${{ env.LOG_GROUP_NAME }}
--service-name ${{ env.TERRAFORM_LAMBDA_FUNCTION_NAME }}
--rollup'

# Validation for pulse telemetry data
- name: Validate generated EMF logs
id: log-validation
if: (success() || steps.trace-validation.outcome == 'failure') && !cancelled()
# will be removed after data quality bug fixed
continue-on-error: true
run: ./gradlew validator:run --args='-c java/lambda/log-validation.yml
--testing-id ${{ env.TESTING_ID }}
--endpoint http://${{ env.API_GATEWAY_URL }}
--region ${{ inputs.aws-region }}
--account-id ${{ env.ACCOUNT_ID }}
--metric-namespace ${{ env.METRIC_NAMESPACE }}
--log-group ${{ env.LOG_GROUP_NAME }}
--service-name ${{ env.TERRAFORM_LAMBDA_FUNCTION_NAME }}
--rollup'

- name: Validate generated metrics
id: metric-validation
# will be removed after data quality bug fixed
continue-on-error: true
if: (success() || steps.trace-validation.outcome == 'failure' || steps.log-validation.outcome == 'failure') && !cancelled()
run: ./gradlew validator:run --args='-c java/lambda/metric-validation.yml
--testing-id ${{ env.TESTING_ID }}
--endpoint http://${{ env.API_GATEWAY_URL }}
--region ${{ inputs.aws-region }}
--account-id ${{ env.ACCOUNT_ID }}
--metric-namespace ${{ env.METRIC_NAMESPACE }}
--log-group ${{ env.LOG_GROUP_NAME }}
--service-name ${{ env.TERRAFORM_LAMBDA_FUNCTION_NAME }}
--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
run: |
cd ${{ env.TEST_RESOURCES_FOLDER }}/terraform/java/lambda/lambda && terraform destroy -auto-approve \
-var="sdk_layer_name=AWSOpenTelemetryDistroJava-${{ github.run_id }}" \
-var="function_name=${{env.TERRAFORM_LAMBDA_FUNCTION_NAME}}" \
-var="layer_artifacts_directory=${{ env.ARTIFACTS_DIR }}" \
-var="region=${{ env.E2E_TEST_AWS_REGION }}" \
-var="is_canary=${{ env.IS_CANARY }}"
67 changes: 67 additions & 0 deletions terraform/java/lambda/api-gateway-proxy/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
resource "aws_api_gateway_rest_api" "lambda_api_proxy" {
name = var.name
}

resource "aws_api_gateway_resource" "lambda_api_proxy" {
rest_api_id = aws_api_gateway_rest_api.lambda_api_proxy.id
parent_id = aws_api_gateway_rest_api.lambda_api_proxy.root_resource_id
path_part = "{proxy+}"
}

resource "aws_api_gateway_method" "lambda_api_proxy" {
rest_api_id = aws_api_gateway_rest_api.lambda_api_proxy.id
resource_id = aws_api_gateway_resource.lambda_api_proxy.id
http_method = "ANY"
authorization = "NONE"
}

resource "aws_api_gateway_integration" "lambda_api" {
rest_api_id = aws_api_gateway_rest_api.lambda_api_proxy.id
resource_id = aws_api_gateway_method.lambda_api_proxy.resource_id
http_method = aws_api_gateway_method.lambda_api_proxy.http_method

integration_http_method = "POST"
type = "AWS_PROXY"
uri = var.function_invoke_arn
}

resource "aws_api_gateway_method" "lambda_api_proxy_root" {
rest_api_id = aws_api_gateway_rest_api.lambda_api_proxy.id
resource_id = aws_api_gateway_rest_api.lambda_api_proxy.root_resource_id
http_method = "ANY"
authorization = "NONE"
}

resource "aws_api_gateway_integration" "lambda_api_root" {
rest_api_id = aws_api_gateway_rest_api.lambda_api_proxy.id
resource_id = aws_api_gateway_method.lambda_api_proxy_root.resource_id
http_method = aws_api_gateway_method.lambda_api_proxy_root.http_method

integration_http_method = "POST"
type = "AWS_PROXY"
uri = var.function_invoke_arn
}

resource "aws_api_gateway_deployment" "lambda_api_proxy" {
depends_on = [
aws_api_gateway_integration.lambda_api,
aws_api_gateway_integration.lambda_api_root,
]

rest_api_id = aws_api_gateway_rest_api.lambda_api_proxy.id
}

resource "aws_api_gateway_stage" "test" {
stage_name = "default"
rest_api_id = aws_api_gateway_rest_api.lambda_api_proxy.id
deployment_id = aws_api_gateway_deployment.lambda_api_proxy.id
xray_tracing_enabled = var.enable_xray_tracing
}

resource "aws_lambda_permission" "lambda_api_allow_gateway" {
action = "lambda:InvokeFunction"
function_name = var.function_name
qualifier = var.function_qualifier
principal = "apigateway.amazonaws.com"
source_arn = "${aws_api_gateway_rest_api.lambda_api_proxy.execution_arn}/*/*"
}
3 changes: 3 additions & 0 deletions terraform/java/lambda/api-gateway-proxy/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
output "api_gateway_url" {
value = aws_api_gateway_stage.test.invoke_url
}
Loading

0 comments on commit e4958f4

Please sign in to comment.