From af40b9b144f98538bb5c24ccc39f7ac8c001ae6a Mon Sep 17 00:00:00 2001 From: Chris Thain <32781396+cthain@users.noreply.github.com> Date: Thu, 21 Jul 2022 09:54:56 -0700 Subject: [PATCH] Add Consul Lambda integration tests (#13770) --- .circleci/config.yml | 5 ++ .../envoy/case-mesh-to-lambda/capture.sh | 4 ++ .../case-mesh-to-lambda/config_entries.hcl | 12 +++++ .../envoy/case-mesh-to-lambda/lambda_l1.json | 12 +++++ .../envoy/case-mesh-to-lambda/lambda_l2.json | 12 +++++ .../envoy/case-mesh-to-lambda/serverless.hcl | 3 ++ .../service_defaults_l1.json | 11 ++++ .../service_defaults_l2.json | 11 ++++ .../case-mesh-to-lambda/service_gateway.hcl | 5 ++ .../envoy/case-mesh-to-lambda/service_s1.hcl | 20 +++++++ .../envoy/case-mesh-to-lambda/setup.sh | 19 +++++++ .../connect/envoy/case-mesh-to-lambda/vars.sh | 15 ++++++ .../envoy/case-mesh-to-lambda/verify.bats | 39 ++++++++++++++ test/integration/connect/envoy/helpers.bash | 52 +++++++++++++++++++ test/integration/connect/envoy/run-tests.sh | 17 ++++++ 15 files changed, 237 insertions(+) create mode 100644 test/integration/connect/envoy/case-mesh-to-lambda/capture.sh create mode 100644 test/integration/connect/envoy/case-mesh-to-lambda/config_entries.hcl create mode 100644 test/integration/connect/envoy/case-mesh-to-lambda/lambda_l1.json create mode 100644 test/integration/connect/envoy/case-mesh-to-lambda/lambda_l2.json create mode 100644 test/integration/connect/envoy/case-mesh-to-lambda/serverless.hcl create mode 100644 test/integration/connect/envoy/case-mesh-to-lambda/service_defaults_l1.json create mode 100644 test/integration/connect/envoy/case-mesh-to-lambda/service_defaults_l2.json create mode 100644 test/integration/connect/envoy/case-mesh-to-lambda/service_gateway.hcl create mode 100644 test/integration/connect/envoy/case-mesh-to-lambda/service_s1.hcl create mode 100644 test/integration/connect/envoy/case-mesh-to-lambda/setup.sh create mode 100644 test/integration/connect/envoy/case-mesh-to-lambda/vars.sh create mode 100644 test/integration/connect/envoy/case-mesh-to-lambda/verify.bats diff --git a/.circleci/config.yml b/.circleci/config.yml index de96204868..af1a2f5c66 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -876,8 +876,13 @@ jobs: environment: ENVOY_VERSION: << parameters.envoy-version >> XDS_TARGET: << parameters.xds-target >> + AWS_LAMBDA_REGION: us-west-2 steps: &ENVOY_INTEGRATION_TEST_STEPS - checkout + - assume-role: + access-key: AWS_ACCESS_KEY_ID_LAMBDA + secret-key: AWS_SECRET_ACCESS_KEY_LAMBDA + role-arn: ROLE_ARN_LAMBDA # Get go binary from workspace - attach_workspace: at: . diff --git a/test/integration/connect/envoy/case-mesh-to-lambda/capture.sh b/test/integration/connect/envoy/case-mesh-to-lambda/capture.sh new file mode 100644 index 0000000000..19ddf49f6b --- /dev/null +++ b/test/integration/connect/envoy/case-mesh-to-lambda/capture.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +snapshot_envoy_admin localhost:19000 s1 primary || true +snapshot_envoy_admin localhost:20000 terminating-gateway primary || true diff --git a/test/integration/connect/envoy/case-mesh-to-lambda/config_entries.hcl b/test/integration/connect/envoy/case-mesh-to-lambda/config_entries.hcl new file mode 100644 index 0000000000..2cf6a2e28d --- /dev/null +++ b/test/integration/connect/envoy/case-mesh-to-lambda/config_entries.hcl @@ -0,0 +1,12 @@ +config_entries { + bootstrap { + kind = "terminating-gateway" + name = "terminating-gateway" + + services = [ + { + name = "l2" + } + ] + } +} diff --git a/test/integration/connect/envoy/case-mesh-to-lambda/lambda_l1.json b/test/integration/connect/envoy/case-mesh-to-lambda/lambda_l1.json new file mode 100644 index 0000000000..f5ef7cd50a --- /dev/null +++ b/test/integration/connect/envoy/case-mesh-to-lambda/lambda_l1.json @@ -0,0 +1,12 @@ +{ + "Node": "lambdas", + "SkipNodeUpdate": true, + "NodeMeta": { + "external-node": "true", + "external-probe": "true" + }, + "Service": { + "ID": "l1", + "Service": "l1" + } +} diff --git a/test/integration/connect/envoy/case-mesh-to-lambda/lambda_l2.json b/test/integration/connect/envoy/case-mesh-to-lambda/lambda_l2.json new file mode 100644 index 0000000000..e4997bd631 --- /dev/null +++ b/test/integration/connect/envoy/case-mesh-to-lambda/lambda_l2.json @@ -0,0 +1,12 @@ +{ + "Node": "lambdas", + "SkipNodeUpdate": true, + "NodeMeta": { + "external-node": "true", + "external-probe": "true" + }, + "Service": { + "ID": "l2", + "Service": "l2" + } +} diff --git a/test/integration/connect/envoy/case-mesh-to-lambda/serverless.hcl b/test/integration/connect/envoy/case-mesh-to-lambda/serverless.hcl new file mode 100644 index 0000000000..41447a4662 --- /dev/null +++ b/test/integration/connect/envoy/case-mesh-to-lambda/serverless.hcl @@ -0,0 +1,3 @@ +connect { + enable_serverless_plugin = true +} diff --git a/test/integration/connect/envoy/case-mesh-to-lambda/service_defaults_l1.json b/test/integration/connect/envoy/case-mesh-to-lambda/service_defaults_l1.json new file mode 100644 index 0000000000..2d67c3558e --- /dev/null +++ b/test/integration/connect/envoy/case-mesh-to-lambda/service_defaults_l1.json @@ -0,0 +1,11 @@ +{ + "Kind": "service-defaults", + "Name": "l1", + "Protocol": "http", + "Meta": { + "serverless.consul.hashicorp.com/v1alpha1/lambda/enabled": "true", + "serverless.consul.hashicorp.com/v1alpha1/lambda/region": "${AWS_LAMBDA_REGION}", + "serverless.consul.hashicorp.com/v1alpha1/lambda/arn": "${AWS_LAMBDA_ARN}", + "serverless.consul.hashicorp.com/v1alpha1/lambda/payload-passthrough": "true" + } +} diff --git a/test/integration/connect/envoy/case-mesh-to-lambda/service_defaults_l2.json b/test/integration/connect/envoy/case-mesh-to-lambda/service_defaults_l2.json new file mode 100644 index 0000000000..263126060d --- /dev/null +++ b/test/integration/connect/envoy/case-mesh-to-lambda/service_defaults_l2.json @@ -0,0 +1,11 @@ +{ + "Kind": "service-defaults", + "Name": "l2", + "Protocol": "http", + "Meta": { + "serverless.consul.hashicorp.com/v1alpha1/lambda/enabled": "true", + "serverless.consul.hashicorp.com/v1alpha1/lambda/region": "${AWS_LAMBDA_REGION}", + "serverless.consul.hashicorp.com/v1alpha1/lambda/arn": "${AWS_LAMBDA_ARN}", + "serverless.consul.hashicorp.com/v1alpha1/lambda/payload-passthrough": "false" + } +} diff --git a/test/integration/connect/envoy/case-mesh-to-lambda/service_gateway.hcl b/test/integration/connect/envoy/case-mesh-to-lambda/service_gateway.hcl new file mode 100644 index 0000000000..0958221ed9 --- /dev/null +++ b/test/integration/connect/envoy/case-mesh-to-lambda/service_gateway.hcl @@ -0,0 +1,5 @@ +services { + name = "terminating-gateway" + kind = "terminating-gateway" + port = 8443 +} diff --git a/test/integration/connect/envoy/case-mesh-to-lambda/service_s1.hcl b/test/integration/connect/envoy/case-mesh-to-lambda/service_s1.hcl new file mode 100644 index 0000000000..36217f42d5 --- /dev/null +++ b/test/integration/connect/envoy/case-mesh-to-lambda/service_s1.hcl @@ -0,0 +1,20 @@ +services { + name = "s1" + port = 8080 + connect { + sidecar_service { + proxy { + upstreams = [ + { + destination_name = "l1" + local_bind_port = 1234 + }, + { + destination_name = "l2" + local_bind_port = 5678 + } + ] + } + } + } +} diff --git a/test/integration/connect/envoy/case-mesh-to-lambda/setup.sh b/test/integration/connect/envoy/case-mesh-to-lambda/setup.sh new file mode 100644 index 0000000000..c187c8df28 --- /dev/null +++ b/test/integration/connect/envoy/case-mesh-to-lambda/setup.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -eEuo pipefail + +# Copy lambda config files into the register dir +find ${CASE_DIR} -maxdepth 1 -name '*_l*.json' -type f -exec cp -f {} workdir/${CLUSTER}/register \; + +# wait for tgw config entry +wait_for_config_entry terminating-gateway terminating-gateway + +register_services primary +register_lambdas primary + +# wait for Lambda config entries +wait_for_config_entry service-defaults l1 +wait_for_config_entry service-defaults l2 + +gen_envoy_bootstrap s1 19000 primary +gen_envoy_bootstrap terminating-gateway 20000 primary true diff --git a/test/integration/connect/envoy/case-mesh-to-lambda/vars.sh b/test/integration/connect/envoy/case-mesh-to-lambda/vars.sh new file mode 100644 index 0000000000..fa47ec4ae1 --- /dev/null +++ b/test/integration/connect/envoy/case-mesh-to-lambda/vars.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# Ensure that the environment variables required to configure and invoke the Lambda function are present, otherwise skip. +# Note that `set | grep ...` is used here because we cannot check the vars directly. If they are unbound the test will +# fail instead of being skipped. +export SKIP_CASE="" +[ -n "$(set | grep '^AWS_LAMBDA_REGION=')" ] || export SKIP_CASE="AWS_LAMBDA_REGION is not present in the environment" +[ -n "$(set | grep '^AWS_LAMBDA_ARN=')" ] || export SKIP_CASE="AWS_LAMBDA_ARN is not present in the environment" +[ -n "$(set | grep '^AWS_SESSION_TOKEN=')" ] || export SKIP_CASE="AWS_SESSION_TOKEN is not present in the environment" +[ -n "$(set | grep '^AWS_SECRET_ACCESS_KEY=')" ] || export SKIP_CASE="AWS_SECRET_ACCESS_KEY is not present in the environment" +[ -n "$(set | grep '^AWS_ACCESS_KEY_ID=')" ] || export SKIP_CASE="AWS_ACCESS_KEY_ID is not present in the environment" + +[ -n "$SKIP_CASE" ] && return 0 + +export REQUIRED_SERVICES="s1 s1-sidecar-proxy terminating-gateway-primary" diff --git a/test/integration/connect/envoy/case-mesh-to-lambda/verify.bats b/test/integration/connect/envoy/case-mesh-to-lambda/verify.bats new file mode 100644 index 0000000000..6a88e83f3b --- /dev/null +++ b/test/integration/connect/envoy/case-mesh-to-lambda/verify.bats @@ -0,0 +1,39 @@ +#!/usr/bin/env bats + +load helpers + +@test "s1 has lambda cluster for l1" { + assert_lambda_envoy_dynamic_cluster_exists localhost:19000 l1 +} + +@test "s1 has lambda http filter for l1" { + assert_lambda_envoy_dynamic_http_filter_exists localhost:19000 l1 $AWS_LAMBDA_ARN +} + +@test "terminating gateway has lambda cluster for l2" { + assert_lambda_envoy_dynamic_cluster_exists localhost:20000 l2 +} + +@test "terminating gateway has lambda http filter for l2" { + assert_lambda_envoy_dynamic_http_filter_exists localhost:20000 l2 $AWS_LAMBDA_ARN +} + +@test "s1 can call l1 through its sidecar-proxy" { + run retry_default curl -s -f -H "Content-type: application/json" -d '"hello"' 'localhost:1234' + [ "$status" -eq 0 ] + + # l1 is configured with payload_passthrough = true so the response needs to be unwrapped + [ $(echo "$output" | jq -r '.statusCode') -eq 200 ] + [ $(echo "$output" | jq -r '.body') == "hello" ] +} + +@test "s1 can call l2 through the terminating gateway" { + run retry_default curl -s -f -H "Content-type: application/json" -d '"hello"' 'localhost:5678' + [ "$status" -eq 0 ] + [ "$output" == '"hello"' ] + + # Omitting the Content-type in the request will cause envoy to base64 encode the request. + run curl -s -f -d '{"message":"hello"}' 'localhost:5678' + [ "$status" -eq 0 ] + [ "$output" == '{"message":"hello"}' ] +} diff --git a/test/integration/connect/envoy/helpers.bash b/test/integration/connect/envoy/helpers.bash index 7ac897c744..2fd9be7e38 100755 --- a/test/integration/connect/envoy/helpers.bash +++ b/test/integration/connect/envoy/helpers.bash @@ -966,3 +966,55 @@ function create_peering { # echo "$output" >&3 [ "$status" == 0 ] } + +function get_lambda_envoy_http_filter { + local HOSTPORT=$1 + local NAME_PREFIX=$2 + run retry_default curl -s -f $HOSTPORT/config_dump + [ "$status" -eq 0 ] + # get the full http filter object so the individual fields can be validated. + echo "$output" | jq --raw-output ".configs[2].dynamic_listeners[] | .active_state.listener.filter_chains[].filters[] | select(.name == \"envoy.filters.network.http_connection_manager\") | .typed_config.http_filters[] | select(.name == \"envoy.filters.http.aws_lambda\") | .typed_config" +} + +function register_lambdas { + local DC=${1:-primary} + # register lambdas to the catalog + for f in $(find workdir/${DC}/register -type f -name 'lambda_*.json'); do + retry_default curl -sL -XPUT -d @${f} "http://localhost:8500/v1/catalog/register" >/dev/null && \ + echo "Registered Lambda: $(jq -r .Service.Service $f)" + done + # write service-defaults config entries for lambdas + for f in $(find workdir/${DC}/register -type f -name 'service_defaults_*.json'); do + varsub ${f} AWS_LAMBDA_REGION AWS_LAMBDA_ARN + retry_default curl -sL -XPUT -d @${f} "http://localhost:8500/v1/config" >/dev/null && \ + echo "Wrote config: $(jq -r '.Kind + " / " + .Name' $f)" + done +} + +function assert_lambda_envoy_dynamic_cluster_exists { + local HOSTPORT=$1 + local NAME_PREFIX=$2 + + local BODY=$(get_envoy_dynamic_cluster_once $HOSTPORT $NAME_PREFIX) + [ -n "$BODY" ] + + [ "$(echo $BODY | jq -r '.cluster.transport_socket.typed_config.sni')" == '*.amazonaws.com' ] +} + +function assert_lambda_envoy_dynamic_http_filter_exists { + local HOSTPORT=$1 + local NAME_PREFIX=$2 + local ARN=$3 + + local FILTER=$(get_lambda_envoy_http_filter $HOSTPORT $NAME_PREFIX) + [ -n "$FILTER" ] + + [ "$(echo $FILTER | jq -r '.arn')" == "$ARN" ] +} + +function varsub { + local file=$1 ; shift + for v in "$@"; do + sed -i "s/\${$v}/${!v}/g" $file + done +} diff --git a/test/integration/connect/envoy/run-tests.sh b/test/integration/connect/envoy/run-tests.sh index fc885f9a1a..cc678b5032 100755 --- a/test/integration/connect/envoy/run-tests.sh +++ b/test/integration/connect/envoy/run-tests.sh @@ -43,6 +43,20 @@ function network_snippet { echo "--net container:envoy_consul-${DC}_1" } +function aws_snippet { + local snippet="" + + # The Lambda integration cases assume that a Lambda function exists in $AWS_REGION with an ARN of $AWS_LAMBDA_ARN. + # The AWS credentials must have permission to invoke the Lambda function. + [ -n "$(set | grep '^AWS_ACCESS_KEY_ID=')" ] && snippet="${snippet} -e AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID" + [ -n "$(set | grep '^AWS_SECRET_ACCESS_KEY=')" ] && snippet="${snippet} -e AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY" + [ -n "$(set | grep '^AWS_SESSION_TOKEN=')" ] && snippet="${snippet} -e AWS_SESSION_TOKEN=$AWS_SESSION_TOKEN" + [ -n "$(set | grep '^AWS_LAMBDA_REGION=')" ] && snippet="${snippet} -e AWS_LAMBDA_REGION=$AWS_LAMBDA_REGION" + [ -n "$(set | grep '^AWS_LAMBDA_ARN=')" ] && snippet="${snippet} -e AWS_LAMBDA_ARN=$AWS_LAMBDA_ARN" + + echo "$snippet" +} + function init_workdir { local CLUSTER="$1" @@ -333,6 +347,7 @@ function verify { $WORKDIR_SNIPPET \ --pid=host \ $(network_snippet $CLUSTER) \ + $(aws_snippet) \ bats-verify \ --pretty /workdir/${CLUSTER}/bats ; then echogreen "✓ PASS" @@ -679,6 +694,7 @@ function common_run_container_sidecar_proxy { docker run -d --name $(container_name_prev) \ $WORKDIR_SNIPPET \ $(network_snippet $CLUSTER) \ + $(aws_snippet) \ "${HASHICORP_DOCKER_PROXY}/envoyproxy/envoy:v${ENVOY_VERSION}" \ envoy \ -c /workdir/${CLUSTER}/envoy/${service}-bootstrap.json \ @@ -765,6 +781,7 @@ function common_run_container_gateway { docker run -d --name $(container_name_prev) \ $WORKDIR_SNIPPET \ $(network_snippet $DC) \ + $(aws_snippet) \ "${HASHICORP_DOCKER_PROXY}/envoyproxy/envoy:v${ENVOY_VERSION}" \ envoy \ -c /workdir/${DC}/envoy/${name}-bootstrap.json \