consul/test/integration/connect/envoy/helpers.windows.bash

1192 lines
32 KiB
Bash
Raw Normal View History

Envoy Integration Test Windows (#18007) * [CONSUL-395] Update check_hostport and Usage (#40) * [CONSUL-397] Copy envoy binary from Image (#41) * [CONSUL-382] Support openssl in unique test dockerfile (#43) * [CONSUL-405] Add bats to single container (#44) * [CONSUL-414] Run Prometheus Test Cases and Validate Changes (#46) * [CONSUL-410] Run Jaeger in Single container (#45) * [CONSUL-412] Run test-sds-server in single container (#48) * [CONSUL-408] Clean containers (#47) * [CONSUL-384] Rebase and sync fork (#50) * [CONSUL-415] Create Scenarios Troubleshooting Docs (#49) * [CONSUL-417] Update Docs Single Container (#51) * [CONSUL-428] Add Socat to single container (#54) * [CONSUL-424] Replace pkill in kill_envoy function (#52) * [CONSUL-434] Modify Docker run functions in Helper script (#53) * [CONSUL-435] Replace docker run in set_ttl_check_state & wait_for_agent_service_register functions (#55) * [CONSUL-438] Add netcat (nc) in the Single container Dockerfile (#56) * [CONSUL-429] Replace Docker run with Docker exec (#57) * [CONSUL-436] Curl timeout and run tests (#58) * [CONSUL-443] Create dogstatsd Function (#59) * [CONSUL-431] Update Docs Netcat (#60) * [CONSUL-439] Parse nc Command in function (#61) * [CONSUL-463] Review curl Exec and get_ca_root Func (#63) * [CONSUL-453] Docker hostname in Helper functions (#64) * [CONSUL-461] Test wipe volumes without extra cont (#66) * [CONSUL-454] Check ports in the Server and Agent containers (#65) * [CONSUL-441] Update windows dockerfile with version (#62) * [CONSUL-466] Review case-grpc Failing Test (#67) * [CONSUL-494] Review case-cfg-resolver-svc-failover (#68) * [CONSUL-496] Replace docker_wget & docker_curl (#69) * [CONSUL-499] Cleanup Scripts - Remove nanoserver (#70) * [CONSUL-500] Update Troubleshooting Docs (#72) * [CONSUL-502] Pull & Tag Envoy Windows Image (#73) * [CONSUL-504] Replace docker run in docker_consul (#76) * [CONSUL-505] Change admin_bind * [CONSUL-399] Update envoy to 1.23.1 (#78) * [CONSUL-510] Support case-wanfed-gw on Windows (#79) * [CONSUL-506] Update troubleshooting Documentation (#80) * [CONSUL-512] Review debug_dump_volumes Function (#81) * [CONSUL-514] Add zipkin to Docker Image (#82) * [CONSUL-515] Update Documentation (#83) * [CONSUL-529] Support case-consul-exec (#86) * [CONSUL-530] Update Documentation (#87) * [CONSUL-530] Update default consul version 1.13.3 * [CONSUL-539] Cleanup (#91) * [CONSUL-546] Scripts Clean-up (#92) * [CONSUL-491] Support admin_access_log_path value for Windows (#71) * [CONSUL-519] Implement mkfifo Alternative (#84) * [CONSUL-542] Create OS Specific Files for Envoy Package (#88) * [CONSUL-543] Create exec_supported.go (#89) * [CONSUL-544] Test and Build Changes (#90) * Implement os.DevNull * using mmap instead of disk files * fix import in exec-unix * fix nmap open too many arguemtn * go fmt on file * changelog file * fix go mod * Update .changelog/17694.txt Co-authored-by: Dhia Ayachi <dhia@hashicorp.com> * different mmap library * fix bootstrap json * some fixes * chocolatey version fix and image fix * using different library * fix Map funciton call * fix mmap call * fix tcp dump * fix tcp dump * windows tcp dump * Fix docker run * fix tests * fix go mod * fix version 16.0 * fix version * fix version dev * sleep to debug * fix sleep * fix permission issue * fix permission issue * fix permission issue * fix command * fix command * fix funciton * fix assert config entry status command not found * fix command not found assert_cert_has_cn * fix command not found assert_upstream_missing * fix command not found assert_upstream_missing_once * fix command not found get_upstream_endpoint * fix command not found get_envoy_public_listener_once * fix command not found * fix test cases * windows integration test workflow github * made code similar to unix using npipe * fix go.mod * fix dialing of npipe * dont wait * check size of written json * fix undefined n * running * fix dep * fix syntax error * fix workflow file * windows runner * fix runner * fix from json * fix runs on * merge connect envoy * fix cin path * build * fix file name * fix file name * fix dev build * remove unwanted code * fix upload * fix bin name * fix path * checkout current branch * fix path * fix tests * fix shell bash for windows sh files * fix permission of run-test.sh * removed docker dev * added shell bash for tests * fix tag * fix win=true * fix cd * added dev * fix variable undefined * removed failing tests * fix tcp dump image * fix curl * fix curl * tcp dump path * fix tcpdump path * fix curl * fix curl install * stop removing intermediate containers * fix tcpdump docker image * revert -rm * --rm=false * makeing docker image before * fix tcpdump * removed case consul exec * removed terminating gateway simple * comment case wasm * removed data dog * comment out upload coverage * uncomment case-consul-exec * comment case consul exec * if always * logs * using consul 1.17.0 * fix quotes * revert quotes * redirect to dev null * Revert version * revert consul connect * fix version * removed envoy connect * not using function * change log * docker logs * fix logs * restructure bad authz * rmeoved dev null * output * fix file descriptor * fix cacert * fix cacert * fix ca cert * cacert does not work in windows curl * fix func * removed docker logs * added sleep * fix tls * commented case-consul-exec * removed echo * retry docker consul * fix upload bin * uncomment consul exec * copying consul.exe to docker image * copy fix * fix paths * fix path * github workspace path * latest version * Revert "latest version" This reverts commit 5a7d7b82d9e7553bcb01b02557ec8969f9deba1d. * commented consul exec * added ssl revoke best effort * revert best effort * removed unused files * rename var name and change dir * windows runner * permission * needs setup fix * swtich to github runner * fix file path * fix path * fix path * fix path * fix path * fix path * fix build paths * fix tag * nightly runs * added matrix in github workflow, renamed files * fix job * fix matrix * removed brackes * from json * without using job matrix * fix quotes * revert job matrix * fix workflow * fix comment * added comment * nightly runs * removed datadog ci as it is already measured in linux one * running test * Revert "running test" This reverts commit 7013d15a23732179d18ec5d17336e16b26fab5d4. * pr comment fixes * running test now * running subset of test * running subset of test * job matrix * shell bash * removed bash shell * linux machine for job matrix * fix output * added cat to debug * using ubuntu latest * fix job matrix * fix win true * fix go test * revert job matrix --------- Co-authored-by: Jose Ignacio Lorenzo <74208929+joselo85@users.noreply.github.com> Co-authored-by: Franco Bruno Lavayen <cocolavayen@gmail.com> Co-authored-by: Ivan K Berlot <ivanberlot@gmail.com> Co-authored-by: Ezequiel Fernández Ponce <20102608+ezfepo@users.noreply.github.com> Co-authored-by: joselo85 <joseignaciolorenzo85@gmail.com> Co-authored-by: Ezequiel Fernández Ponce <ezequiel.fernandez@southworks.com> Co-authored-by: Dhia Ayachi <dhia@hashicorp.com>
2023-07-21 14:56:00 +00:00
#!/bin/bash
CONSUL_HOSTNAME=""
MOD_ARG=""
function split_hostport {
local HOSTPORT="$@"
if [[ $HOSTPORT == *":"* ]]; then
MOD_ARG=$( <<< $HOSTPORT sed 's/:/ /' )
fi
}
function get_consul_hostname {
local DC=${1:-primary}
[[ $XDS_TARGET = "client" ]] && CONSUL_HOSTNAME="consul-$DC-client" || CONSUL_HOSTNAME="consul-$DC"
}
# retry based on
# https://github.com/fernandoacorreia/azure-docker-registry/blob/master/tools/scripts/create-registry-server
# under MIT license.
function retry {
local n=1
local max=$1
shift
local delay=$1
shift
local errtrace=0
if grep -q "errtrace" <<<"$SHELLOPTS"
then
errtrace=1
set +E
fi
if [[ $1 == "curl" ]]; then
set -- "${@}" -m 10
elif [[ $1 == "nc" ]]
then
split_hostport $3
set -- "${@:1:2}" $MOD_ARG "${@:4}"
fi
# This if block, was added to check if curl is being executed directly on a test,
# if so, we replace the url parameter with the correct one.
for ((i=1;i<=$max;i++))
do
if "$@"
then
if test $errtrace -eq 1
then
set -E
fi
return 0
else
echo "Command failed. Attempt $i/$max:"
sleep $delay
fi
done
if test $errtrace -eq 1; then
set -E
fi
return 1
}
function retry_default {
local DEFAULT_TOTAL_RETRIES=5
set +E
ret=0
retry $DEFAULT_TOTAL_RETRIES 1 "$@" || ret=1
set -E
return $ret
}
function retry_long {
retry 30 1 "$@"
}
function is_set {
# Arguments:
# $1 - string value to check its truthiness
#
# Return:
# 0 - is truthy (backwards I know but allows syntax like `if is_set <var>` to work)
# 1 - is not truthy
local val=$(tr '[:upper:]' '[:lower:]' <<<"$1")
case $val in
1 | t | true | y | yes)
return 0
;;
*)
return 1
;;
esac
}
function get_cert {
local HOSTPORT=$1
local SERVER_NAME=$2
local CA_FILE=$3
local SNI_FLAG=""
if [ -n "$SERVER_NAME" ]; then
SNI_FLAG="-servername $SERVER_NAME"
fi
CERT=$(openssl s_client -connect $HOSTPORT $SNI_FLAG -showcerts </dev/null)
openssl x509 -noout -text <<<"$CERT"
}
function assert_proxy_presents_cert_uri {
local HOSTPORT=$1
local SERVICENAME=$2
local DC=${3:-primary}
local NS=${4:-default}
local PARTITION=${5:default}
CERT=$(retry_default get_cert $HOSTPORT)
echo "WANT SERVICE: ${PARTITION}/${NS}/${SERVICENAME}"
echo "GOT CERT:"
echo "$CERT"
if [[ -z $PARTITION ]] || [[ $PARTITION = "default" ]]; then
echo "$CERT" | grep -Eo "URI:spiffe://([a-zA-Z0-9-]+).consul/ns/${NS}/dc/${DC}/svc/$SERVICENAME"
else
echo "$CERT" | grep -Eo "URI:spiffe://([a-zA-Z0-9-]+).consul/ap/${PARTITION}/ns/${NS}/dc/${DC}/svc/$SERVICENAME"
fi
}
function assert_dnssan_in_cert {
local HOSTPORT=$1
local DNSSAN=$2
local SERVER_NAME=${3:-$DNSSAN}
CERT=$(retry_default get_cert $HOSTPORT $SERVER_NAME)
echo "WANT DNSSAN: ${DNSSAN} (SNI: ${SERVER_NAME})"
echo "GOT CERT:"
echo "$CERT"
echo "$CERT" | grep -Eo "DNS:${DNSSAN}"
}
function assert_cert_signed_by_ca {
local CA_FILE=$1
local HOSTPORT=$2
local DNSSAN=$3
local SERVER_NAME=${4:-$DNSSAN}
local SNI_FLAG=""
if [ -n "$SERVER_NAME" ]; then
SNI_FLAG="-servername $SERVER_NAME"
fi
# Fix in the CA_FILE parameter: for Windows environments, the root path starts with "/c"
CERT=$(openssl s_client -connect $HOSTPORT $SNI_FLAG -CAfile "/c/$CA_FILE" -showcerts </dev/null)
echo "GOT CERT:"
echo "$CERT"
echo "$CERT" | grep 'Verify return code: 0 (ok)'
}
function assert_cert_has_cn {
local HOSTPORT=$1
local CN=$2
local SERVER_NAME=${3:-$CN}
CERT=$(openssl s_client -connect $HOSTPORT -servername $SERVER_NAME -showcerts </dev/null 2>/dev/null)
echo "WANT CN: ${CN} (SNI: ${SERVER_NAME})"
echo "GOT CERT:"
echo "$CERT"
echo "$CERT" | grep "CN = ${CN}"
}
function get_upstream_endpoint {
local HOSTPORT=$1
local CLUSTER_NAME=$2
run curl -s -f "http://${HOSTPORT}/clusters?format=json"
[ "$status" -eq 0 ]
echo "$output" | jq --raw-output "
.cluster_statuses[]
| select(.name|startswith(\"${CLUSTER_NAME}\"))"
}
function assert_upstream_missing_once {
local HOSTPORT=$1
local CLUSTER_NAME=$2
run get_upstream_endpoint $HOSTPORT $CLUSTER_NAME
[ "$status" -eq 0 ]
echo "$output"
[ "" == "$output" ]
}
function assert_upstream_missing {
local HOSTPORT=$1
local CLUSTER_NAME=$2
run retry_long assert_upstream_missing_once $HOSTPORT $CLUSTER_NAME
echo "OUTPUT: $output $status"
[ "$status" -eq 0 ]
}
function assert_envoy_version {
local ADMINPORT=$1
run retry_default curl -f -s localhost:$ADMINPORT/server_info
[ "$status" -eq 0 ]
# Envoy 1.8.0 returns a plain text line like
# envoy 5d25f466c3410c0dfa735d7d4358beb76b2da507/1.8.0/Clean/DEBUG live 3 3 0
# Later versions return JSON.
if (echo $output | grep '^envoy'); then
VERSION=$(echo $output | cut -d ' ' -f 2)
else
VERSION=$(echo $output | jq -r '.version')
fi
echo "Status=$status"
echo "Output=$output"
echo "---"
echo "Got version=$VERSION"
echo "Want version=$ENVOY_VERSION"
# 1.20.2, 1.19.3 and 1.18.6 are special snowflakes in that the version for
# the release is reported with a '-dev' suffix (eg 1.20.2-dev).
if [ "$ENVOY_VERSION" = "1.20.2" ]; then
ENVOY_VERSION="1.20.2-dev"
elif [ "$ENVOY_VERSION" = "1.19.3" ]; then
ENVOY_VERSION="1.19.3-dev"
elif [ "$ENVOY_VERSION" = "1.18.6" ]; then
ENVOY_VERSION="1.18.6-dev"
fi
echo $VERSION | grep "/$ENVOY_VERSION/"
}
function assert_envoy_expose_checks_listener_count {
local HOSTPORT=$1
local EXPECT_PATH=$2
# scrape this once
BODY=$(get_envoy_expose_checks_listener_once $HOSTPORT)
echo "BODY = $BODY"
CHAINS=$(echo "$BODY" | jq '.active_state.listener.filter_chains | length')
echo "CHAINS = $CHAINS (expect 1)"
[ "${CHAINS:-0}" -eq 1 ]
RANGES=$(echo "$BODY" | jq '.active_state.listener.filter_chains[0].filter_chain_match.source_prefix_ranges | length')
echo "RANGES = $RANGES (expect 3)"
# note: if IPv6 is not supported in the kernel per
# agent/xds:kernelSupportsIPv6() then this will only be 2
[ "${RANGES:-0}" -eq 3 ]
HCM=$(echo "$BODY" | jq '.active_state.listener.filter_chains[0].filters[0]')
HCM_NAME=$(echo "$HCM" | jq -r '.name')
HCM_PATH=$(echo "$HCM" | jq -r '.typed_config.route_config.virtual_hosts[0].routes[0].match.path')
echo "HCM = $HCM"
[ "${HCM_NAME:-}" == "envoy.filters.network.http_connection_manager" ]
[ "${HCM_PATH:-}" == "${EXPECT_PATH}" ]
}
function get_envoy_expose_checks_listener_once {
local HOSTPORT=$1
run curl -m 5 -s -f $HOSTPORT/config_dump
[ "$status" -eq 0 ]
echo "$output" | jq --raw-output '.configs[] | select(.["@type"] == "type.googleapis.com/envoy.admin.v3.ListenersConfigDump") | .dynamic_listeners[] | select(.name | startswith("exposed_path_"))'
}
function get_envoy_public_listener_once {
local HOSTPORT=$1
run curl -s -f $HOSTPORT/config_dump
[ "$status" -eq 0 ]
echo "$output" | jq --raw-output '.configs[] | select(.["@type"] == "type.googleapis.com/envoy.admin.v3.ListenersConfigDump") | .dynamic_listeners[] | select(.name | startswith("public_listener:"))'
}
function assert_envoy_http_rbac_policy_count {
local HOSTPORT=$1
local EXPECT_COUNT=$2
GOT_COUNT=$(get_envoy_http_rbac_once $HOSTPORT | jq '.rules.policies | length')
echo "GOT_COUNT = $GOT_COUNT"
[ "${GOT_COUNT:-0}" -eq $EXPECT_COUNT ]
}
function get_envoy_http_rbac_once {
local HOSTPORT=$1
run curl -m 5 -s -f $HOSTPORT/config_dump
[ "$status" -eq 0 ]
echo "$output" | jq --raw-output '.configs[2].dynamic_listeners[].active_state.listener.filter_chains[0].filters[0].typed_config.http_filters[] | select(.name == "envoy.filters.http.rbac") | .typed_config'
}
function assert_envoy_network_rbac_policy_count {
local HOSTPORT=$1
local EXPECT_COUNT=$2
GOT_COUNT=$(get_envoy_network_rbac_once $HOSTPORT | jq '.rules.policies | length')
echo "GOT_COUNT = $GOT_COUNT"
[ "${GOT_COUNT:-0}" -eq $EXPECT_COUNT ]
}
function get_envoy_network_rbac_once {
local HOSTPORT=$1
run curl -m 5 -s -f $HOSTPORT/config_dump
[ "$status" -eq 0 ]
echo "$output" | jq --raw-output '.configs[2].dynamic_listeners[].active_state.listener.filter_chains[0].filters[] | select(.name == "envoy.filters.network.rbac") | .typed_config'
}
function get_envoy_listener_filters {
local HOSTPORT=$1
run retry_default curl -s -f $HOSTPORT/config_dump
[ "$status" -eq 0 ]
echo "$output" | jq --raw-output '.configs[2].dynamic_listeners[].active_state.listener | "\(.name) \( .filter_chains[0].filters | map(.name) | join(","))"'
}
function get_envoy_http_filter {
local HOSTPORT=$1
local FILTER_NAME=$2
run retry_default curl -s -f $HOSTPORT/config_dump
[ "$status" -eq 0 ]
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 == \"${FILTER_NAME}\")"
}
function get_envoy_http_filters {
local HOSTPORT=$1
run retry_default curl -s -f $HOSTPORT/config_dump
[ "$status" -eq 0 ]
echo "$output" | jq --raw-output '.configs[2].dynamic_listeners[].active_state.listener | "\(.name) \( .filter_chains[0].filters[] | select(.name == "envoy.filters.network.http_connection_manager") | .typed_config.http_filters | map(.name) | join(","))"'
}
function get_envoy_dynamic_cluster_once {
local HOSTPORT=$1
local NAME_PREFIX=$2
run curl -m 5 -s -f $HOSTPORT/config_dump
[ "$status" -eq 0 ]
echo "$output" | jq --raw-output ".configs[] | select (.[\"@type\"] == \"type.googleapis.com/envoy.admin.v3.ClustersConfigDump\") | .dynamic_active_clusters[] | select(.cluster.name | startswith(\"${NAME_PREFIX}\"))"
}
function assert_envoy_dynamic_cluster_exists_once {
local HOSTPORT=$1
local NAME_PREFIX=$2
local EXPECT_SNI=$3
BODY="$(get_envoy_dynamic_cluster_once $HOSTPORT $NAME_PREFIX)"
[ -n "$BODY" ]
SNI="$(echo "$BODY" | jq --raw-output ".cluster.transport_socket.typed_config.sni | select(. | startswith(\"${EXPECT_SNI}\"))")"
[ -n "$SNI" ]
}
function assert_envoy_dynamic_cluster_exists {
local HOSTPORT=$1
local NAME_PREFIX=$2
local EXPECT_SNI=$3
run retry_long assert_envoy_dynamic_cluster_exists_once $HOSTPORT $NAME_PREFIX $EXPECT_SNI
[ "$status" -eq 0 ]
}
function get_envoy_cluster_config {
local HOSTPORT=$1
local CLUSTER_NAME=$2
run retry_default curl -s -f $HOSTPORT/config_dump
[ "$status" -eq 0 ]
echo "$output" | jq --raw-output "
.configs[1].dynamic_active_clusters[]
| select(.cluster.name|startswith(\"${CLUSTER_NAME}\"))
| .cluster
"
}
function get_envoy_stats_flush_interval {
local HOSTPORT=$1
run retry_default curl -s -f $HOSTPORT/config_dump
[ "$status" -eq 0 ]
#echo "$output" > /workdir/s1_envoy_dump.json
echo "$output" | jq --raw-output '.configs[0].bootstrap.stats_flush_interval'
}
# snapshot_envoy_admin is meant to be used from a teardown scriptlet from the host.
function snapshot_envoy_admin {
local HOSTPORT=$1
local ENVOY_NAME=$2
local DC=${3:-primary}
local OUTDIR="${LOG_DIR}/envoy-snapshots/${DC}/${ENVOY_NAME}"
mkdir -p "${OUTDIR}"
docker_consul_exec "$DC" bash -c "curl -s http://${HOSTPORT}/config_dump" > "${OUTDIR}/config_dump.json"
docker_consul_exec "$DC" bash -c "curl -s http://${HOSTPORT}/clusters?format=json" > "${OUTDIR}/clusters.json"
docker_consul_exec "$DC" bash -c "curl -s http://${HOSTPORT}/stats" > "${OUTDIR}/stats.txt"
docker_consul_exec "$DC" bash -c "curl -s http://${HOSTPORT}/stats/prometheus" > "${OUTDIR}/stats_prometheus.txt"
}
function reset_envoy_metrics {
local HOSTPORT=$1
curl -m 5 -s -f -XPOST $HOSTPORT/reset_counters
return $?
}
function get_all_envoy_metrics {
local HOSTPORT=$1
curl -m 5 -s -f $HOSTPORT/stats
return $?
}
function get_envoy_metrics {
local HOSTPORT=$1
local METRICS=$2
get_all_envoy_metrics $HOSTPORT | grep "$METRICS"
}
function assert_upstream_has_endpoint_port {
local HOSTPORT=$1
local CLUSTER_NAME=$2
local PORT_VALUE=$3
run retry_long assert_upstream_has_endpoint_port_once $HOSTPORT $CLUSTER_NAME $PORT_VALUE
[ "$status" -eq 0 ]
}
function get_upstream_endpoint_in_status_count {
local HOSTPORT=$1
local CLUSTER_NAME=$2
local HEALTH_STATUS=$3
run curl -m 5 -s -f "http://${HOSTPORT}/clusters?format=json"
[ "$status" -eq 0 ]
echo "$output" | jq --raw-output "
.cluster_statuses[]
| select(.name|startswith(\"${CLUSTER_NAME}\"))
| [.host_statuses[].health_status.eds_health_status]
| [select(.[] == \"${HEALTH_STATUS}\")]
| length"
}
function assert_upstream_has_endpoints_in_status_once {
local HOSTPORT=$1
local CLUSTER_NAME=$2
local HEALTH_STATUS=$3
local EXPECT_COUNT=$4
GOT_COUNT=$(get_upstream_endpoint_in_status_count $HOSTPORT $CLUSTER_NAME $HEALTH_STATUS)
[ "$GOT_COUNT" -eq $EXPECT_COUNT ]
}
function assert_upstream_has_endpoints_in_status {
local HOSTPORT=$1
local CLUSTER_NAME=$2
local HEALTH_STATUS=$3
local EXPECT_COUNT=$4
run retry_long assert_upstream_has_endpoints_in_status_once $HOSTPORT $CLUSTER_NAME $HEALTH_STATUS $EXPECT_COUNT
[ "$status" -eq 0 ]
}
function assert_envoy_metric {
set -eEuo pipefail
local HOSTPORT=$1
local METRIC=$2
local EXPECT_COUNT=$3
METRICS=$(get_envoy_metrics $HOSTPORT "$METRIC")
if [ -z "${METRICS}" ]; then
echo "Metric not found" 1>&2
return 1
fi
GOT_COUNT=$(awk -F: '{print $2}' <<<"$METRICS" | head -n 1 | tr -d ' ')
if [ -z "$GOT_COUNT" ]; then
echo "Couldn't parse metric count" 1>&2
return 1
fi
if [ $EXPECT_COUNT -ne $GOT_COUNT ]; then
echo "$METRIC - expected count: $EXPECT_COUNT, actual count: $GOT_COUNT" 1>&2
return 1
fi
}
function assert_envoy_metric_at_least {
set -eEuo pipefail
local HOSTPORT=$1
local METRIC=$2
local EXPECT_COUNT=$3
METRICS=$(get_envoy_metrics $HOSTPORT "$METRIC")
if [ -z "${METRICS}" ]; then
echo "Metric not found" 1>&2
return 1
fi
GOT_COUNT=$(awk -F: '{print $2}' <<<"$METRICS" | head -n 1 | tr -d ' ')
if [ -z "$GOT_COUNT" ]; then
echo "Couldn't parse metric count" 1>&2
return 1
fi
if [ $EXPECT_COUNT -gt $GOT_COUNT ]; then
echo "$METRIC - expected >= count: $EXPECT_COUNT, actual count: $GOT_COUNT" 1>&2
return 1
fi
}
function assert_envoy_aggregate_metric_at_least {
set -eEuo pipefail
local HOSTPORT=$1
local METRIC=$2
local EXPECT_COUNT=$3
METRICS=$(get_envoy_metrics $HOSTPORT "$METRIC")
if [ -z "${METRICS}" ]; then
echo "Metric not found" 1>&2
return 1
fi
GOT_COUNT=$(awk '{ sum += $2 } END { print sum }' <<<"$METRICS")
if [ -z "$GOT_COUNT" ]; then
echo "Couldn't parse metric count" 1>&2
return 1
fi
if [ $EXPECT_COUNT -gt $GOT_COUNT ]; then
echo "$METRIC - expected >= count: $EXPECT_COUNT, actual count: $GOT_COUNT" 1>&2
return 1
fi
}
function get_healthy_service_count {
local SERVICE_NAME=$1
local DC=$2
local NS=$3
local AP=$4
local PEER_NAME=$5
run curl -m 5 -s -f ${HEADERS} "consul-${DC}-client:8500/v1/health/connect/${SERVICE_NAME}?passing&ns=${NS}&partition=${AP}&peer=${PEER_NAME}"
[ "$status" -eq 0 ]
echo "$output" | jq --raw-output '. | length'
}
function assert_alive_wan_member_count {
local DC=$1
local EXPECT_COUNT=$2
run retry_long assert_alive_wan_member_count_once $DC $EXPECT_COUNT
[ "$status" -eq 0 ]
}
function assert_alive_wan_member_count_once {
local DC=$1
local EXPECT_COUNT=$2
GOT_COUNT=$(get_alive_wan_member_count $DC)
[ "$GOT_COUNT" -eq "$EXPECT_COUNT" ]
}
function get_alive_wan_member_count {
local DC=$1
run retry_default curl -sL -f "consul-${DC}-server:8500/v1/agent/members?wan=1"
[ "$status" -eq 0 ]
# echo "$output" >&3
echo "$output" | jq '.[] | select(.Status == 1) | .Name' | wc -l
}
function assert_service_has_healthy_instances_once {
local SERVICE_NAME=$1
local EXPECT_COUNT=$2
local DC=${3:-primary}
local NS=${4:-}
local AP=${5:-}
local PEER_NAME=${6:-}
GOT_COUNT=$(get_healthy_service_count "$SERVICE_NAME" "$DC" "$NS" "$AP" "$PEER_NAME")
[ "$GOT_COUNT" -eq $EXPECT_COUNT ]
}
function assert_service_has_healthy_instances {
local SERVICE_NAME=$1
local EXPECT_COUNT=$2
local DC=${3:-primary}
local NS=${4:-}
local AP=${5:-}
local PEER_NAME=${6:-}
run retry_long assert_service_has_healthy_instances_once "$SERVICE_NAME" "$EXPECT_COUNT" "$DC" "$NS" "$AP" "$PEER_NAME"
[ "$status" -eq 0 ]
}
function check_intention {
local SOURCE=$1
local DESTINATION=$2
get_consul_hostname primary
curl -m 5 -s -f "${CONSUL_HOSTNAME}:8500/v1/connect/intentions/check?source=${SOURCE}&destination=${DESTINATION}" | jq ".Allowed"
}
function assert_intention_allowed {
local SOURCE=$1
local DESTINATION=$2
run check_intention "${SOURCE}" "${DESTINATION}"
[ "$status" -eq 0 ]
[ "$output" = "true" ]
}
function assert_intention_denied {
local SOURCE=$1
local DESTINATION=$2
run check_intention "${SOURCE}" "${DESTINATION}"
[ "$status" -eq 0 ]
[ "$output" = "false" ]
}
function docker_consul {
local DC=$1
shift 1
retry_default docker_exec envoy_consul-${DC}_1 "$@"
}
function docker_consul_for_proxy_bootstrap {
local DC=$1
shift 1
local CONTAINER_NAME="$SINGLE_CONTAINER_BASE_NAME"-"$DC"_1
docker.exe exec -i $CONTAINER_NAME bash.exe -c "$@"
}
function docker_exec {
if ! docker.exe exec -i "$@"; then
echo "Failed to execute: docker exec -i $@" 1>&2
return 1
fi
}
function docker_consul_exec {
local DC=$1
shift 1
docker_exec envoy_consul-${DC}_1 "$@"
}
function kill_envoy {
local BOOTSTRAP_NAME=$1
local DC=${2:-primary}
PORT=$( cat /c/workdir/$DC/envoy/${BOOTSTRAP_NAME}-bootstrap.json | jq .admin.address.socket_address.port_value )
PID=$( netstat -qo | grep "127.0.0.1:$PORT" | sed -r "s/.* //g" )
tskill $PID
}
function must_match_in_statsd_logs {
local DC=${2:-primary}
local FILE="/c/workdir/${DC}/statsd/statsd.log"
COUNT=$( grep -Ec $1 $FILE )
echo "COUNT of '$1' matches: $COUNT"
[ "$COUNT" -gt "0" ]
}
function must_match_in_prometheus_response {
run curl -m 5 -f -s $1/metrics
COUNT=$( echo "$output" | grep -Ec $2 )
echo "OUTPUT head -n 10"
echo "$output" | head -n 10
echo "COUNT of '$2' matches: $COUNT"
[ "$status" == 0 ]
[ "$COUNT" -gt "0" ]
}
function must_match_in_stats_proxy_response {
run curl -m 5 -f -s $1/$2
COUNT=$( echo "$output" | grep -Ec $3 )
echo "OUTPUT head -n 10"
echo "$output" | head -n 10
echo "COUNT of '$3' matches: $COUNT"
[ "$status" == 0 ]
[ "$COUNT" -gt "0" ]
}
# must_fail_tcp_connection checks that a request made through an upstream fails,
# probably due to authz being denied if all other tests passed already. Although
# we are using curl, this only works as expected for TCP upstreams as we are
# checking TCP-level errors. HTTP upstreams will return a valid 503 generated by
# Envoy rather than a connection-level error.
function must_fail_tcp_connection {
# Attempt to curl through upstream
run curl -m 5 --no-keepalive -s -v -f -d hello $1
echo "OUTPUT $output"
# Should fail during handshake and return "got nothing" error
[ "$status" == "52" ]
# Verbose output should enclude empty reply
echo "$output" | grep 'Empty reply from server'
}
function must_pass_tcp_connection {
run curl -m 5 --no-keepalive -s -f -d hello $1
echo "OUTPUT $output"
[ "$status" == "0" ]
[ "$output" = "hello" ]
}
# must_fail_http_connection see must_fail_tcp_connection but this expects Envoy
# to generate a 503 response since the upstreams have refused connection.
function must_fail_http_connection {
# Attempt to curl through upstream
run curl -m 5 --no-keepalive -s -i -d hello "$1"
echo "OUTPUT $output"
[ "$status" == "0" ]
local expect_response="${2:-403 Forbidden}"
# Should fail request with 503
echo "$output" | grep "${expect_response}"
}
# must_pass_http_request allows you to craft a specific http request to assert
# that envoy will NOT reject the request. Primarily of use for testing L7
# intentions.
function must_pass_http_request {
local METHOD=$1
local URL=$2
local DEBUG_HEADER_VALUE="${3:-""}"
local extra_args
if [[ -n "${DEBUG_HEADER_VALUE}" ]]; then
extra_args="-H x-test-debug:${DEBUG_HEADER_VALUE}"
fi
case "$METHOD" in
GET) ;;
DELETE)
extra_args="$extra_args -X${METHOD}"
;;
PUT | POST)
extra_args="$extra_args -d'{}' -X${METHOD}"
;;
*)
return 1
;;
esac
run curl -m 5 --no-keepalive -v -s -f $extra_args "$URL"
[ "$status" == 0 ]
}
# must_fail_http_request allows you to craft a specific http request to assert
# that envoy will reject the request. Primarily of use for testing L7
# intentions.
function must_fail_http_request {
local METHOD=$1
local URL=$2
local DEBUG_HEADER_VALUE="${3:-""}"
local extra_args
if [[ -n "${DEBUG_HEADER_VALUE}" ]]; then
extra_args="-H x-test-debug:${DEBUG_HEADER_VALUE}"
fi
case "$METHOD" in
HEAD)
extra_args="$extra_args -I"
;;
GET) ;;
DELETE)
extra_args="$extra_args -X${METHOD}"
;;
PUT | POST)
extra_args="$extra_args -d'{}' -X${METHOD}"
;;
*)
return 1
;;
esac
# Attempt to curl through upstream
run curl -m 5 --no-keepalive -s -i $extra_args "$URL"
echo "OUTPUT $output"
echo "$output" | grep "403 Forbidden"
}
function upsert_config_entry {
local DC="$1"
local BODY="$2"
echo "$BODY" | docker_consul "$DC" consul config write -
}
function gen_envoy_bootstrap {
SERVICE=$1
ADMIN_PORT=$2
DC=${3:-primary}
IS_GW=${4:-0}
EXTRA_ENVOY_BS_ARGS="${5-}"
ADMIN_HOST="0.0.0.0"
PROXY_ID="$SERVICE"
if ! is_set "$IS_GW"; then
PROXY_ID="$SERVICE-sidecar-proxy"
ADMIN_HOST="127.0.0.1"
fi
if output=$(docker_consul_for_proxy_bootstrap $DC "consul connect envoy -bootstrap \
-proxy-id $PROXY_ID \
-envoy-version "$ENVOY_VERSION" \
-http-addr envoy_consul-${DC}_1:8500 \
-grpc-addr envoy_consul-${DC}_1:8502 \
-admin-access-log-path="C:/envoy/envoy.log" \
-admin-bind $ADMIN_HOST:$ADMIN_PORT ${EXTRA_ENVOY_BS_ARGS} \
> /c/workdir/${DC}/envoy/${SERVICE}-bootstrap.json"); then
# All OK, write config to file
echo $output
#echo "$output" > /c/workdir/${DC}/envoy/$SERVICE-bootstrap.json
else
status=$?
# Command failed, instead of swallowing error (printed on stdout by docker
# it seems) by writing it to file, echo it
echo "$output"
#return $status
fi
}
function read_config_entry {
local KIND=$1
local NAME=$2
local DC=${3:-primary}
get_consul_hostname $DC
docker_consul_exec "$DC" bash -c "consul config read -kind $KIND -name $NAME -http-addr=\"$CONSUL_HOSTNAME:8500\""
}
function wait_for_namespace {
local NS="${1}"
local DC=${2:-primary}
get_consul_hostname $DC
retry_default docker_consul_exec "$DC" bash -c "curl -sLf http://${CONSUL_HOSTNAME}:8500/v1/namespace/${NS} >/dev/null"
}
function wait_for_config_entry {
retry_default read_config_entry "$@"
}
function assert_config_entry_status {
local TYPE="$1"
local STATUS="$2"
local REASON="$3"
local DC="$4"
local KIND="$5"
local NAME="$6"
local NS=${7:-}
local AP=${8:-}
local PEER=${9:-}
status=$(curl -s -f "consul-${DC}-client:8500/v1/config/${KIND}/${NAME}?passing&ns=${NS}&partition=${AP}&peer=${PEER}" | jq ".Status.Conditions[] | select(.Type == \"$TYPE\" and .Status == \"$STATUS\" and .Reason == \"$REASON\")")
[ -n "$status" ]
}
function delete_config_entry {
local KIND=$1
local NAME=$2
get_consul_hostname primary
retry_default curl -sL -XDELETE "http://${CONSUL_HOSTNAME}:8500/v1/config/${KIND}/${NAME}"
}
function register_services {
local DC=${1:-primary}
wait_for_leader "$DC"
docker_consul_exec ${DC} bash -c "consul services register workdir/${DC}/register/service_*.hcl"
}
# wait_for_leader waits until a leader is elected.
# Its first argument must be the datacenter name.
function wait_for_leader {
get_consul_hostname primary
retry_default docker_consul_exec "$1" bash -c "[[ $(curl --fail -sS http://${CONSUL_HOSTNAME}:8500/v1/status/leader) ]]"
}
function setup_upsert_l4_intention {
local SOURCE=$1
local DESTINATION=$2
local ACTION=$3
get_consul_hostname primary
retry_default docker_consul_exec primary bash -c "curl -sL -X PUT -d '{\"Action\": \"${ACTION}\"}' 'http://${CONSUL_HOSTNAME}:8500/v1/connect/intentions/exact?source=${SOURCE}&destination=${DESTINATION}'"
}
function upsert_l4_intention {
local SOURCE=$1
local DESTINATION=$2
local ACTION=$3
get_consul_hostname primary
retry_default curl -sL -XPUT "http://${CONSUL_HOSTNAME}:8500/v1/connect/intentions/exact?source=${SOURCE}&destination=${DESTINATION}" \
-d"{\"Action\": \"${ACTION}\"}" >/dev/null
}
function get_ca_root {
get_consul_hostname primary
curl -s -f "http://${CONSUL_HOSTNAME}:8500/v1/connect/ca/roots" | jq -r ".Roots[0].RootCert"
}
function wait_for_agent_service_register {
local SERVICE_ID=$1
local DC=${2:-primary}
get_consul_hostname $DC
retry_default docker_consul_exec "$DC" bash -c "curl -sLf 'http://${CONSUL_HOSTNAME}:8500/v1/agent/service/${SERVICE_ID}' >/dev/null"
}
function set_ttl_check_state {
local CHECK_ID=$1
local CHECK_STATE=$2
local DC=${3:-primary}
get_consul_hostname $DC
case "$CHECK_STATE" in
pass) ;;
warn) ;;
fail) ;;
*)
echo "invalid ttl check state '${CHECK_STATE}'" >&2
return 1
;;
esac
retry_default docker_consul_exec "$DC" bash -c "curl -sL -XPUT 'http://${CONSUL_HOSTNAME}:8500/v1/agent/check/warn/${CHECK_ID}' >/dev/null"
}
function get_upstream_fortio_name {
local HOST=$1
local PORT=$2
local PREFIX=$3
local DEBUG_HEADER_VALUE="${4:-""}"
local extra_args
if [[ -n "${DEBUG_HEADER_VALUE}" ]]; then
extra_args="-H x-test-debug:${DEBUG_HEADER_VALUE}"
fi
# split proto if https:// is at the front of the host since the --resolve
# string needs just a bare host.
local PROTO=""
local CA_FILE=""
if [ "${HOST:0:8}" = "https://" ]; then
HOST="${HOST:8}"
PROTO="https://"
# Fix in the CA_FILE parameter: for Windows environments, the root path starts with "/c"
extra_args="${extra_args} --cacert /c/workdir/test-sds-server/certs/ca-root.crt"
fi
# We use --resolve instead of setting a Host header since we need the right
# name to be sent for SNI in some cases too.
run retry_default curl --ssl-revoke-best-effort -v -s -f --resolve "${HOST}:${PORT}:127.0.0.1" $extra_args \
"${PROTO}${HOST}:${PORT}${PREFIX}/debug?env=dump"
# Useful Debugging but breaks the expectation that the value output is just
# the grep output when things don't fail
if [ "$status" != 0 ]; then
echo "GOT FORTIO OUTPUT: $output"
fi
[ "$status" == 0 ]
echo "$output" | grep -E "^FORTIO_NAME="
}
function get_upstream_endpoint_port {
local HOSTPORT=$1
local CLUSTER_NAME=$2
local PORT_VALUE=$3
run curl -s -f "http://${HOSTPORT}/clusters?format=json"
[ "$status" -eq 0 ]
echo "$output" | jq --raw-output "
.cluster_statuses[]
| select(.name|startswith(\"${CLUSTER_NAME}\"))
| [.host_statuses[].address.socket_address.port_value]
| [select(.[] == ${PORT_VALUE})]
| length"
}
function assert_upstream_has_endpoint_port_once {
local HOSTPORT=$1
local CLUSTER_NAME=$2
local PORT_VALUE=$3
GOT_COUNT=$(get_upstream_endpoint_port $HOSTPORT $CLUSTER_NAME $PORT_VALUE)
[ "$GOT_COUNT" -eq 1 ]
}
function assert_expected_fortio_name {
local EXPECT_NAME=$1
local HOST=${2:-"localhost"}
local PORT=${3:-5000}
local URL_PREFIX=${4:-""}
local DEBUG_HEADER_VALUE="${5:-""}"
run get_upstream_fortio_name ${HOST} ${PORT} "${URL_PREFIX}" "${DEBUG_HEADER_VALUE}"
echo "GOT: $output"
[ "$status" == 0 ]
[ "$output" == "FORTIO_NAME=${EXPECT_NAME}" ]
}
function assert_expected_fortio_name_pattern {
local EXPECT_NAME_PATTERN=$1
local HOST=${2:-"localhost"}
local PORT=${3:-5000}
local URL_PREFIX=${4:-""}
local DEBUG_HEADER_VALUE="${5:-""}"
GOT=$(get_upstream_fortio_name ${HOST} ${PORT} "${URL_PREFIX}" "${DEBUG_HEADER_VALUE}")
if [[ "$GOT" =~ $EXPECT_NAME_PATTERN ]]; then
:
else
echo "expected name pattern: $EXPECT_NAME_PATTERN, actual name: $GOT" 1>&2
return 1
fi
}
function get_upstream_fortio_host_header {
local HOST=$1
local PORT=$2
local PREFIX=$3
local DEBUG_HEADER_VALUE="${4:-""}"
local extra_args
if [[ -n "${DEBUG_HEADER_VALUE}" ]]; then
extra_args="-H x-test-debug:${DEBUG_HEADER_VALUE}"
fi
run retry_default curl -v -s -f -H"Host: ${HOST}" $extra_args \
"localhost:${PORT}${PREFIX}/debug"
[ "$status" == 0 ]
echo "$output" | grep -E "^Host: "
}
function assert_expected_fortio_host_header {
local EXPECT_HOST=$1
local HOST=${2:-"localhost"}
local PORT=${3:-5000}
local URL_PREFIX=${4:-""}
local DEBUG_HEADER_VALUE="${5:-""}"
GOT=$(get_upstream_fortio_host_header ${HOST} ${PORT} "${URL_PREFIX}" "${DEBUG_HEADER_VALUE}")
if [ "$GOT" != "Host: ${EXPECT_HOST}" ]; then
echo "expected Host header: $EXPECT_HOST, actual Host header: $GOT" 1>&2
return 1
fi
}
function create_peering {
local GENERATE_PEER=$1
local ESTABLISH_PEER=$2
run curl -m 5 -sL -XPOST "http://consul-${GENERATE_PEER}-client:8500/v1/peering/token" -d"{ \"PeerName\" : \"${GENERATE_PEER}-to-${ESTABLISH_PEER}\" }"
# echo "$output" >&3
[ "$status" == 0 ]
local token
token="$(echo "$output" | jq -r .PeeringToken)"
[ -n "$token" ]
run curl -m 5 -sLv -XPOST "http://consul-${ESTABLISH_PEER}-client:8500/v1/peering/establish" -d"{ \"PeerName\" : \"${ESTABLISH_PEER}-to-${GENERATE_PEER}\", \"PeeringToken\" : \"${token}\" }"
# echo "$output" >&3
[ "$status" == 0 ]
}
function assert_service_has_imported {
local DC=${1:-primary}
local SERVICE_NAME=$2
local PEER_NAME=$3
run curl -s -f "http://consul-${DC}-client:8500/v1/peering/${PEER_NAME}"
[ "$status" == 0 ]
echo "$output" | jq --raw-output '.StreamStatus.ImportedServices' | grep -e "${SERVICE_NAME}"
if [ $? -ne 0 ]; then
echo "Error finding service: ${SERVICE_NAME}"
return 1
fi
}
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
}
function get_url_header {
local URL=$1
local HEADER=$2
run curl -s -f -X GET -I "${URL}"
[ "$status" == 0 ]
RESP=$(echo "$output" | tr -d '\r')
RESP=$(echo "$RESP" | grep -E "^${HEADER}: ")
RESP=$(echo "$RESP" | sed "s/^${HEADER}: //g")
echo "$RESP"
}
function assert_url_header {
local URL=$1
local HEADER=$2
local VALUE=$3
run get_url_header "$URL" "$HEADER"
[ "$status" == 0 ]
[ "$VALUE" = "$output" ]
}
# assert_upstream_message asserts both the returned code
# and message from upstream service
function assert_upstream_message {
local HOSTPORT=$1
run curl -s -d hello localhost:$HOSTPORT
if [ "$status" -ne 0 ]; then
echo "Command failed"
return 1
fi
if (echo $output | grep 'hello'); then
return 0
fi
echo "expected message not found in $output"
return 1
}