diff --git a/agent/proxycfg/snapshot.go b/agent/proxycfg/snapshot.go index f341d53a04..6e909f17dd 100644 --- a/agent/proxycfg/snapshot.go +++ b/agent/proxycfg/snapshot.go @@ -705,6 +705,11 @@ func (c *configSnapshotAPIGateway) ToIngress(datacenter string) (configSnapshotI continue } + if !c.GatewayConfig.ListenerIsReady(name) { + // skip any listeners that might be in an invalid state + continue + } + ingressListener := structs.IngressListener{ Port: listener.Port, Protocol: string(listener.Protocol), diff --git a/agent/structs/config_entry_gateways.go b/agent/structs/config_entry_gateways.go index 449f56b337..83bbcfb159 100644 --- a/agent/structs/config_entry_gateways.go +++ b/agent/structs/config_entry_gateways.go @@ -713,6 +713,25 @@ type APIGatewayConfigEntry struct { RaftIndex } +func (e *APIGatewayConfigEntry) ListenerIsReady(name string) bool { + for _, condition := range e.Status.Conditions { + if !condition.Resource.IsSame(&ResourceReference{ + Kind: APIGateway, + SectionName: name, + Name: e.Name, + EnterpriseMeta: e.EnterpriseMeta, + }) { + continue + } + + if condition.Type == "Conflicted" && condition.Status == "True" { + return false + } + } + + return true +} + func (e *APIGatewayConfigEntry) GetKind() string { return APIGateway } diff --git a/test/integration/connect/envoy/case-api-gateway-tcp-conflicted/capture.sh b/test/integration/connect/envoy/case-api-gateway-tcp-conflicted/capture.sh new file mode 100644 index 0000000000..8ba0e0ddab --- /dev/null +++ b/test/integration/connect/envoy/case-api-gateway-tcp-conflicted/capture.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +snapshot_envoy_admin localhost:20000 api-gateway primary || true \ No newline at end of file diff --git a/test/integration/connect/envoy/case-api-gateway-tcp-conflicted/service_gateway.hcl b/test/integration/connect/envoy/case-api-gateway-tcp-conflicted/service_gateway.hcl new file mode 100644 index 0000000000..486c25c59e --- /dev/null +++ b/test/integration/connect/envoy/case-api-gateway-tcp-conflicted/service_gateway.hcl @@ -0,0 +1,4 @@ +services { + name = "api-gateway" + kind = "api-gateway" +} diff --git a/test/integration/connect/envoy/case-api-gateway-tcp-conflicted/setup.sh b/test/integration/connect/envoy/case-api-gateway-tcp-conflicted/setup.sh new file mode 100644 index 0000000000..2394ab2362 --- /dev/null +++ b/test/integration/connect/envoy/case-api-gateway-tcp-conflicted/setup.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +set -euo pipefail + +upsert_config_entry primary ' +kind = "api-gateway" +name = "api-gateway" +listeners = [ + { + port = 9999 + protocol = "tcp" + } +] +' + +upsert_config_entry primary ' +kind = "tcp-route" +name = "api-gateway-route-1" +services = [ + { + name = "s1" + } +] +parents = [ + { + name = "api-gateway" + } +] +' + +upsert_config_entry primary ' +kind = "tcp-route" +name = "api-gateway-route-2" +services = [ + { + name = "s2" + } +] +parents = [ + { + name = "api-gateway" + } +] +' + +register_services primary + +gen_envoy_bootstrap api-gateway 20000 primary true +gen_envoy_bootstrap s1 19000 +gen_envoy_bootstrap s2 19001 \ No newline at end of file diff --git a/test/integration/connect/envoy/case-api-gateway-tcp-conflicted/vars.sh b/test/integration/connect/envoy/case-api-gateway-tcp-conflicted/vars.sh new file mode 100644 index 0000000000..38a47d8527 --- /dev/null +++ b/test/integration/connect/envoy/case-api-gateway-tcp-conflicted/vars.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +export REQUIRED_SERVICES="$DEFAULT_REQUIRED_SERVICES api-gateway-primary" diff --git a/test/integration/connect/envoy/case-api-gateway-tcp-conflicted/verify.bats b/test/integration/connect/envoy/case-api-gateway-tcp-conflicted/verify.bats new file mode 100644 index 0000000000..387ea68934 --- /dev/null +++ b/test/integration/connect/envoy/case-api-gateway-tcp-conflicted/verify.bats @@ -0,0 +1,20 @@ +#!/usr/bin/env bats + +load helpers + +@test "api gateway proxy admin is up on :20000" { + retry_default curl -f -s localhost:20000/stats -o /dev/null +} + +@test "api gateway should have a conflicted status" { + assert_config_entry_status Accepted True Accepted primary api-gateway api-gateway + assert_config_entry_status Conflicted True RouteConflict primary api-gateway api-gateway +} + +@test "api gateway should have no healthy endpoints for s1" { + assert_upstream_missing 127.0.0.1:20000 s1 +} + +@test "api gateway should have no healthy endpoints for s2" { + assert_upstream_missing 127.0.0.1:20000 s2 +} \ No newline at end of file diff --git a/test/integration/connect/envoy/case-api-gateway-tcp-simple/verify.bats b/test/integration/connect/envoy/case-api-gateway-tcp-simple/verify.bats index 3b61378813..83ff3a6a58 100644 --- a/test/integration/connect/envoy/case-api-gateway-tcp-simple/verify.bats +++ b/test/integration/connect/envoy/case-api-gateway-tcp-simple/verify.bats @@ -6,6 +6,11 @@ load helpers retry_default curl -f -s localhost:20000/stats -o /dev/null } +@test "api gateway should have be accepted and not conflicted" { + assert_config_entry_status Accepted True Accepted primary api-gateway api-gateway + assert_config_entry_status Conflicted False NoConflict primary api-gateway api-gateway +} + @test "api gateway should have healthy endpoints for s1" { assert_upstream_has_endpoints_in_status 127.0.0.1:20000 s1 HEALTHY 1 } diff --git a/test/integration/connect/envoy/helpers.bash b/test/integration/connect/envoy/helpers.bash index 3612cdb5cd..6e9d13914a 100755 --- a/test/integration/connect/envoy/helpers.bash +++ b/test/integration/connect/envoy/helpers.bash @@ -318,6 +318,16 @@ function get_envoy_metrics { get_all_envoy_metrics $HOSTPORT | grep "$METRICS" } +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 get_upstream_endpoint_in_status_count { local HOSTPORT=$1 local CLUSTER_NAME=$2 @@ -343,6 +353,14 @@ function assert_upstream_has_endpoints_in_status_once { [ "$GOT_COUNT" -eq $EXPECT_COUNT ] } +function assert_upstream_missing { + local HOSTPORT=$1 + local CLUSTER_NAME=$2 + run retry_default get_upstream_endpoint $HOSTPORT $CLUSTER_NAME + echo "OUTPUT: $output $status" + [ "" == "$output" ] +} + function assert_upstream_has_endpoints_in_status { local HOSTPORT=$1 local CLUSTER_NAME=$2 @@ -761,6 +779,21 @@ function upsert_config_entry { echo "$BODY" | docker_consul "$DC" config write - } +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