Skip to content

Commit 878eb8d

Browse files
authored
Setup NodeJS Lambda Layer E2E Test (#275)
*Description of changes:* Setup Node JS Lambda layer E2E tests. Current the data validation steps are failing due to the known quality issue. This changes are mainly for `main-build` validation in https://github.com/aws-observability/aws-otel-js-instrumentation . it will not trigger any alarm or block anything. #### Test workflow * https://github.com/mxiamxia/aws-otel-js-instrumentation/actions/runs/11190639442/job/31113369923 EMF validation failed as expected due to the known EMF data quality issue WIP. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent b53cd5d commit 878eb8d

File tree

19 files changed

+912
-0
lines changed

19 files changed

+912
-0
lines changed
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
## Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
## SPDX-License-Identifier: Apache-2.0
3+
4+
# This is a reusable workflow for running the Enablement test for App Signals.
5+
# It is meant to be called from another workflow.
6+
# Read more about reusable workflows: https://docs.github.com/en/actions/using-workflows/reusing-workflows#overview
7+
name: Node Lambda Use Case
8+
on:
9+
workflow_call:
10+
inputs:
11+
aws-region:
12+
required: true
13+
type: string
14+
caller-workflow-name:
15+
required: true
16+
type: string
17+
staging-instrumentation-name:
18+
required: false
19+
default: '@aws/aws-distro-opentelemetry-node-autoinstrumentation'
20+
type: string
21+
outputs:
22+
job-started:
23+
value: ${{ jobs.node-lambda-default.outputs.job-started }}
24+
validation-result:
25+
value: ${{ jobs.node-lambda-default.outputs.validation-result }}
26+
27+
permissions:
28+
id-token: write
29+
contents: read
30+
31+
env:
32+
E2E_TEST_AWS_REGION: ${{ inputs.aws-region }}
33+
CALLER_WORKFLOW_NAME: ${{ inputs.caller-workflow-name }}
34+
E2E_TEST_ACCOUNT_ID: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ACCOUNT_ID }}
35+
E2E_TEST_ROLE_NAME: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ROLE_NAME }}
36+
METRIC_NAMESPACE: ApplicationSignals
37+
LOG_GROUP_NAME: /aws/application-signals/data
38+
TEST_RESOURCES_FOLDER: ${GITHUB_WORKSPACE}
39+
40+
jobs:
41+
node-lambda-default:
42+
runs-on: ubuntu-latest
43+
timeout-minutes: 30
44+
outputs:
45+
job-started: ${{ steps.job-started.outputs.job-started }}
46+
validation-result: ${{ steps.validation-result.outputs.validation-result }}
47+
steps:
48+
- name: Check if the job started
49+
id: job-started
50+
run: echo "job-started=true" >> $GITHUB_OUTPUT
51+
52+
- name: Generate testing id
53+
run: echo TESTING_ID="${{ github.job }}-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}" >> $GITHUB_ENV
54+
55+
- uses: actions/checkout@v4
56+
with:
57+
repository: 'aws-observability/aws-application-signals-test-framework'
58+
ref: ${{ env.CALLER_WORKFLOW_NAME == 'main-build' && 'main' || github.ref }}
59+
fetch-depth: 0
60+
61+
# We initialize Gradlew Daemon early on during the workflow because sometimes initialization
62+
# fails due to transient issues. If it fails here, then we will try again later before the validators
63+
- name: Initiate Gradlew Daemon
64+
id: initiate-gradlew
65+
uses: ./.github/workflows/actions/execute_and_retry
66+
continue-on-error: true
67+
with:
68+
command: "./gradlew :validator:build"
69+
cleanup: "./gradlew clean"
70+
max_retry: 3
71+
sleep_time: 60
72+
73+
- name: Configure AWS Credentials
74+
uses: aws-actions/configure-aws-credentials@v4
75+
with:
76+
role-to-assume: arn:aws:iam::${{ env.E2E_TEST_ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }}
77+
aws-region: us-east-1
78+
79+
- name: Retrieve account
80+
uses: aws-actions/aws-secretsmanager-get-secrets@v1
81+
with:
82+
secret-ids:
83+
ACCOUNT_ID, region-account/${{ env.E2E_TEST_AWS_REGION }}
84+
85+
# If the workflow is running as a canary, then we want to log in to the aws account in the appropriate region
86+
- name: Configure AWS Credentials
87+
if: ${{ github.event.repository.name == 'aws-application-signals-test-framework' }}
88+
uses: aws-actions/configure-aws-credentials@v4
89+
with:
90+
role-to-assume: arn:aws:iam::${{ env.ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }}
91+
aws-region: ${{ env.E2E_TEST_AWS_REGION }}
92+
93+
- name: Set Lambda Layer artifact directory path
94+
run: echo ARTIFACTS_DIR="${{ github.workspace }}/lambda_artifacts" >> $GITHUB_ENV
95+
96+
- name: Download Lambda Layer and Function artifacts
97+
run: |
98+
aws s3 cp s3://adot-autoinstrumentation-node-staging/layer-${{ github.run_id }}.zip ${{ env.ARTIFACTS_DIR }}/layer.zip |
99+
aws s3 cp s3://adot-autoinstrumentation-node-staging/function-${{ github.run_id }}.zip ${{ env.ARTIFACTS_DIR }}/function.zip
100+
101+
- name: Set up terraform
102+
uses: ./.github/workflows/actions/execute_and_retry
103+
with:
104+
command: "wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg"
105+
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
106+
&& sudo apt update && sudo apt install terraform'
107+
sleep_time: 60
108+
109+
- name: Initiate Terraform
110+
uses: ./.github/workflows/actions/execute_and_retry
111+
with:
112+
command: "cd ${{ env.TEST_RESOURCES_FOLDER }}/terraform/node/lambda/lambda && terraform init && terraform validate"
113+
cleanup: "rm -rf .terraform && rm -rf .terraform.lock.hcl"
114+
max_retry: 6
115+
sleep_time: 60
116+
117+
- name: Get terraform Lambda function name
118+
shell: bash
119+
run: |
120+
echo TERRAFORM_LAMBDA_FUNCTION_NAME="AdotLambdaNodeJsSampleApp-${{ github.run_id }}"|
121+
tee --append $GITHUB_ENV
122+
- name: Apply terraform
123+
uses: ./.github/workflows/actions/execute_and_retry
124+
with:
125+
command: 'cd ${{ env.TEST_RESOURCES_FOLDER }}/terraform/node/lambda/lambda && terraform apply -auto-approve
126+
-var="sdk_layer_name=AWSOpenTelemetryDistroJs-${{ github.run_id }}"
127+
-var="function_name=${{env.TERRAFORM_LAMBDA_FUNCTION_NAME}}" -var="layer_artifacts_directory=${{ env.ARTIFACTS_DIR }}"'
128+
max_retry: 6
129+
sleep_time: 60
130+
- name: Extract endpoint
131+
id: extract-endpoint
132+
shell: bash
133+
run: cd ${{ env.TEST_RESOURCES_FOLDER }}/terraform/node/lambda/lambda && echo API_GATEWAY_URL=$(terraform output -raw api-gateway-url) >> $GITHUB_ENV
134+
- name: Send request to endpoint
135+
shell: bash
136+
run: sleep 30s; curl -sS ${{ env.API_GATEWAY_URL }}
137+
138+
# Validation for pulse telemetry data
139+
- name: Validate generated EMF logs
140+
id: log-validation
141+
run: ./gradlew validator:run --args='-c node/lambda/log-validation.yml
142+
--testing-id ${{ env.TESTING_ID }}
143+
--endpoint http://${{ env.API_GATEWAY_URL }}
144+
--region ${{ inputs.aws-region }}
145+
--account-id ${{ env.ACCOUNT_ID }}
146+
--metric-namespace ${{ env.METRIC_NAMESPACE }}
147+
--log-group ${{ env.LOG_GROUP_NAME }}
148+
--service-name ${{ env.TERRAFORM_LAMBDA_FUNCTION_NAME }}
149+
--rollup'
150+
151+
- name: Validate generated metrics
152+
id: metric-validation
153+
if: (success() || steps.log-validation.outcome == 'failure') && !cancelled()
154+
run: ./gradlew validator:run --args='-c node/lambda/metric-validation.yml
155+
--testing-id ${{ env.TESTING_ID }}
156+
--endpoint http://${{ env.API_GATEWAY_URL }}
157+
--region ${{ inputs.aws-region }}
158+
--account-id ${{ env.ACCOUNT_ID }}
159+
--metric-namespace ${{ env.METRIC_NAMESPACE }}
160+
--log-group ${{ env.LOG_GROUP_NAME }}
161+
--service-name ${{ env.TERRAFORM_LAMBDA_FUNCTION_NAME }}
162+
--rollup'
163+
164+
- name: Validate generated traces
165+
id: trace-validation
166+
if: (success() || steps.log-validation.outcome == 'failure' || steps.metric-validation.outcome == 'failure') && !cancelled()
167+
run: ./gradlew validator:run --args='-c node/lambda/trace-validation.yml
168+
--testing-id ${{ env.TESTING_ID }}
169+
--endpoint http://${{ env.API_GATEWAY_URL }}
170+
--region ${{ inputs.aws-region }}
171+
--account-id ${{ env.ACCOUNT_ID }}
172+
--metric-namespace ${{ env.METRIC_NAMESPACE }}
173+
--log-group ${{ env.LOG_GROUP_NAME }}
174+
--service-name ${{ env.TERRAFORM_LAMBDA_FUNCTION_NAME }}
175+
--rollup'
176+
177+
- name: Refresh AWS Credentials
178+
if: ${{ always() && github.event.repository.name == 'aws-application-signals-test-framework' }}
179+
uses: aws-actions/configure-aws-credentials@v4
180+
with:
181+
role-to-assume: arn:aws:iam::${{ env.ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }}
182+
aws-region: ${{ env.E2E_TEST_AWS_REGION }}
183+
184+
- name: Save test results
185+
if: always()
186+
id: validation-result
187+
run: |
188+
if [ "${{ steps.log-validation.outcome }}" = "success" ] && [ "${{ steps.metric-validation.outcome }}" = "success" ] && [ "${{ steps.trace-validation.outcome }}" = "success" ]; then
189+
echo "validation-result=success" >> $GITHUB_OUTPUT
190+
else
191+
echo "validation-result=failure" >> $GITHUB_OUTPUT
192+
fi
193+
194+
# Clean up Procedures
195+
- name: Terraform destroy
196+
if: always()
197+
continue-on-error: true
198+
run: |
199+
cd ${{ env.TEST_RESOURCES_FOLDER }}/terraform/node/lambda/lambda && terraform destroy -auto-approve \
200+
-var="sdk_layer_name=AWSOpenTelemetryDistroJs-${{ github.run_id }}" \
201+
-var="function_name=${{env.TERRAFORM_LAMBDA_FUNCTION_NAME}}" \
202+
-var="layer_artifacts_directory=${{ env.ARTIFACTS_DIR }}"
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
resource "aws_api_gateway_rest_api" "lambda_api_proxy" {
2+
name = var.name
3+
}
4+
5+
resource "aws_api_gateway_resource" "lambda_api_proxy" {
6+
rest_api_id = aws_api_gateway_rest_api.lambda_api_proxy.id
7+
parent_id = aws_api_gateway_rest_api.lambda_api_proxy.root_resource_id
8+
path_part = "{proxy+}"
9+
}
10+
11+
resource "aws_api_gateway_method" "lambda_api_proxy" {
12+
rest_api_id = aws_api_gateway_rest_api.lambda_api_proxy.id
13+
resource_id = aws_api_gateway_resource.lambda_api_proxy.id
14+
http_method = "ANY"
15+
authorization = "NONE"
16+
}
17+
18+
resource "aws_api_gateway_integration" "lambda_api" {
19+
rest_api_id = aws_api_gateway_rest_api.lambda_api_proxy.id
20+
resource_id = aws_api_gateway_method.lambda_api_proxy.resource_id
21+
http_method = aws_api_gateway_method.lambda_api_proxy.http_method
22+
23+
integration_http_method = "POST"
24+
type = "AWS_PROXY"
25+
uri = var.function_invoke_arn
26+
}
27+
28+
resource "aws_api_gateway_method" "lambda_api_proxy_root_nodejs" {
29+
rest_api_id = aws_api_gateway_rest_api.lambda_api_proxy.id
30+
resource_id = aws_api_gateway_rest_api.lambda_api_proxy.root_resource_id
31+
http_method = "ANY"
32+
authorization = "NONE"
33+
}
34+
35+
resource "aws_api_gateway_integration" "lambda_api_root_nodejs" {
36+
rest_api_id = aws_api_gateway_rest_api.lambda_api_proxy.id
37+
resource_id = aws_api_gateway_method.lambda_api_proxy_root_nodejs.resource_id
38+
http_method = aws_api_gateway_method.lambda_api_proxy_root_nodejs.http_method
39+
40+
integration_http_method = "POST"
41+
type = "AWS_PROXY"
42+
uri = var.function_invoke_arn
43+
}
44+
45+
resource "aws_api_gateway_deployment" "lambda_api_proxy" {
46+
depends_on = [
47+
aws_api_gateway_integration.lambda_api,
48+
aws_api_gateway_integration.lambda_api_root_nodejs,
49+
]
50+
51+
rest_api_id = aws_api_gateway_rest_api.lambda_api_proxy.id
52+
}
53+
54+
resource "aws_api_gateway_stage" "test" {
55+
stage_name = "default"
56+
rest_api_id = aws_api_gateway_rest_api.lambda_api_proxy.id
57+
deployment_id = aws_api_gateway_deployment.lambda_api_proxy.id
58+
xray_tracing_enabled = var.enable_xray_tracing
59+
}
60+
61+
resource "aws_lambda_permission" "lambda_api_allow_gateway_nodejs" {
62+
action = "lambda:InvokeFunction"
63+
function_name = var.function_name
64+
qualifier = var.function_qualifier
65+
principal = "apigateway.amazonaws.com"
66+
source_arn = "${aws_api_gateway_rest_api.lambda_api_proxy.execution_arn}/*/*"
67+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
output "api_gateway_url" {
2+
value = aws_api_gateway_stage.test.invoke_url
3+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
variable "name" {
2+
type = string
3+
description = "Name of API gateway to create"
4+
}
5+
6+
variable "function_name" {
7+
type = string
8+
description = "Name of function to proxy to"
9+
}
10+
11+
variable "function_qualifier" {
12+
type = string
13+
default = null
14+
description = "Qualifier of function to proxy to"
15+
}
16+
17+
variable "function_invoke_arn" {
18+
type = string
19+
description = "Invoke ARN of function to proxy to"
20+
}
21+
22+
variable "enable_xray_tracing" {
23+
type = bool
24+
description = "Whether to enable xray tracing of the API gateway"
25+
default = true
26+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
locals {
2+
sdk_layer_arns_amd64 = {
3+
# TODO: to be updated
4+
"ap-northeast-1" = "arn:aws:lambda:ap-northeast-1:901920570463:layer:aws-otel-nodejs-amd64-ver-1-18-1:4"
5+
"ap-northeast-2" = "arn:aws:lambda:ap-northeast-2:901920570463:layer:aws-otel-nodejs-amd64-ver-1-18-1:4"
6+
"ap-south-1" = "arn:aws:lambda:ap-south-1:901920570463:layer:aws-otel-nodejs-amd64-ver-1-18-1:4"
7+
"ap-southeast-1" = "arn:aws:lambda:ap-southeast-1:901920570463:layer:aws-otel-nodejs-amd64-ver-1-18-1:4"
8+
"ap-southeast-2" = "arn:aws:lambda:ap-southeast-2:901920570463:layer:aws-otel-nodejs-amd64-ver-1-18-1:4"
9+
"ca-central-1" = "arn:aws:lambda:ca-central-1:901920570463:layer:aws-otel-nodejs-amd64-ver-1-18-1:4"
10+
"eu-central-1" = "arn:aws:lambda:eu-central-1:901920570463:layer:aws-otel-nodejs-amd64-ver-1-18-1:4"
11+
"eu-north-1" = "arn:aws:lambda:eu-north-1:901920570463:layer:aws-otel-nodejs-amd64-ver-1-18-1:4"
12+
"eu-west-1" = "arn:aws:lambda:eu-west-1:901920570463:layer:aws-otel-nodejs-amd64-ver-1-18-1:4"
13+
"eu-west-2" = "arn:aws:lambda:eu-west-2:901920570463:layer:aws-otel-nodejs-amd64-ver-1-18-1:4"
14+
"eu-west-3" = "arn:aws:lambda:eu-west-3:901920570463:layer:aws-otel-nodejs-amd64-ver-1-18-1:4"
15+
"sa-east-1" = "arn:aws:lambda:sa-east-1:901920570463:layer:aws-otel-nodejs-amd64-ver-1-18-1:4"
16+
"us-east-1" = "arn:aws:lambda:us-west-1:252610625673:layer:AWSOpenTelemetryDistroJs:1"
17+
"us-east-2" = "arn:aws:lambda:us-east-2:901920570463:layer:aws-otel-nodejs-amd64-ver-1-18-1:4"
18+
"us-west-1" = "arn:aws:lambda:us-west-1:252610625673:layer:AWSOpenTelemetryDistroJs:1"
19+
"us-west-2" = "arn:aws:lambda:us-west-1:252610625673:layer:AWSOpenTelemetryDistroJs:1"
20+
}
21+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
locals {
2+
architecture = var.architecture == "x86_64" ? "amd64" : "arm64"
3+
}
4+
resource "aws_lambda_layer_version" "sdk_layer" {
5+
count = var.is_canary ? 0 : 1
6+
layer_name = var.sdk_layer_name
7+
filename = "${var.layer_artifacts_directory}/layer.zip"
8+
compatible_runtimes = ["nodejs14.x", "nodejs16.x", "nodejs18.x"]
9+
license_info = "Apache-2.0"
10+
source_code_hash = filebase64sha256("${var.layer_artifacts_directory}/layer.zip")
11+
# filename = "${var.kube_directory_path}/config"
12+
}
13+
14+
module "hello-lambda-function" {
15+
source = "terraform-aws-modules/lambda/aws"
16+
version = ">= 2.24.0"
17+
18+
architectures = compact([var.architecture])
19+
function_name = var.function_name
20+
handler = "index.handler"
21+
runtime = var.runtime
22+
23+
create_package = false
24+
local_existing_package = "${var.layer_artifacts_directory}/function.zip"
25+
26+
memory_size = 512
27+
timeout = 30
28+
29+
layers = var.is_canary ? [local.sdk_layer_arns_amd64.us-west-1] : [aws_lambda_layer_version.sdk_layer[0].arn]
30+
31+
environment_variables = {
32+
AWS_LAMBDA_EXEC_WRAPPER = "/opt/otel-instrument"
33+
OTEL_AWS_APPLICATION_SIGNALS_ENABLED = "true"
34+
OTEL_METRICS_EXPORTER = "none"
35+
}
36+
37+
tracing_mode = var.tracing_mode
38+
39+
attach_policy_statements = true
40+
policy_statements = {
41+
s3 = {
42+
effect = "Allow"
43+
actions = [
44+
"s3:ListAllMyBuckets"
45+
]
46+
resources = [
47+
"*"
48+
]
49+
}
50+
}
51+
}
52+
53+
module "api-gateway" {
54+
source = "../api-gateway-proxy"
55+
56+
name = var.function_name
57+
function_name = module.hello-lambda-function.lambda_function_name
58+
function_invoke_arn = module.hello-lambda-function.lambda_function_invoke_arn
59+
enable_xray_tracing = var.tracing_mode == "Active"
60+
}
61+
62+
resource "aws_iam_role_policy_attachment" "hello-lambda-cloudwatch" {
63+
role = module.hello-lambda-function.lambda_function_name
64+
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
65+
}
66+
67+
resource "aws_iam_role_policy_attachment" "test_xray" {
68+
role = module.hello-lambda-function.lambda_function_name
69+
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
70+
}

0 commit comments

Comments
 (0)