API Gateway Envoy Golden Listener Tests (#16221)
* Simple API Gateway e2e test for tcp routes * Drop DNSSans since we don't front the Gateway with a leaf cert * WIP listener tests for api-gateway * Return early if no routes * Add back in leaf cert to testing * Fix merge conflicts * Re-add kind to setup * Fix iteration over listener upstreams * New tcp listener test * Add tests for API Gateway with TCP and HTTP routes * Move zero-route check back * Drop generateIngressDNSSANs * Check for chains not routes --------- Co-authored-by: Andrew Stucki <andrew.stucki@hashicorp.com>
This commit is contained in:
parent
fd99cee9ac
commit
c66f9ebf39
|
@ -0,0 +1,145 @@
|
|||
package proxycfg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hashicorp/consul/agent/connect"
|
||||
"github.com/hashicorp/consul/agent/consul/discoverychain"
|
||||
"github.com/mitchellh/go-testing-interface"
|
||||
|
||||
"github.com/hashicorp/consul/agent/structs"
|
||||
)
|
||||
|
||||
func TestConfigSnapshotAPIGateway(
|
||||
t testing.T,
|
||||
variation string,
|
||||
nsFn func(ns *structs.NodeService),
|
||||
configFn func(entry *structs.APIGatewayConfigEntry, boundEntry *structs.BoundAPIGatewayConfigEntry),
|
||||
routes []structs.BoundRoute,
|
||||
extraUpdates []UpdateEvent,
|
||||
additionalEntries ...structs.ConfigEntry,
|
||||
) *ConfigSnapshot {
|
||||
roots, placeholderLeaf := TestCerts(t)
|
||||
|
||||
entry := &structs.APIGatewayConfigEntry{
|
||||
Kind: structs.APIGateway,
|
||||
Name: "api-gateway",
|
||||
}
|
||||
boundEntry := &structs.BoundAPIGatewayConfigEntry{
|
||||
Kind: structs.BoundAPIGateway,
|
||||
Name: "api-gateway",
|
||||
}
|
||||
|
||||
if configFn != nil {
|
||||
configFn(entry, boundEntry)
|
||||
}
|
||||
|
||||
baseEvents := []UpdateEvent{
|
||||
{
|
||||
CorrelationID: rootsWatchID,
|
||||
Result: roots,
|
||||
},
|
||||
{
|
||||
CorrelationID: leafWatchID,
|
||||
Result: placeholderLeaf,
|
||||
},
|
||||
{
|
||||
CorrelationID: gatewayConfigWatchID,
|
||||
Result: &structs.ConfigEntryResponse{
|
||||
Entry: entry,
|
||||
},
|
||||
},
|
||||
{
|
||||
CorrelationID: gatewayConfigWatchID,
|
||||
Result: &structs.ConfigEntryResponse{
|
||||
Entry: boundEntry,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, route := range routes {
|
||||
// Add the watch event for the route.
|
||||
watch := UpdateEvent{
|
||||
CorrelationID: routeConfigWatchID,
|
||||
Result: &structs.ConfigEntryResponse{
|
||||
Entry: route,
|
||||
},
|
||||
}
|
||||
baseEvents = append(baseEvents, watch)
|
||||
|
||||
// Add the watch event for the discovery chain.
|
||||
entries := []structs.ConfigEntry{
|
||||
&structs.ProxyConfigEntry{
|
||||
Kind: structs.ProxyDefaults,
|
||||
Name: structs.ProxyConfigGlobal,
|
||||
Config: map[string]interface{}{
|
||||
"protocol": route.GetProtocol(),
|
||||
},
|
||||
},
|
||||
&structs.ServiceResolverConfigEntry{
|
||||
Kind: structs.ServiceResolver,
|
||||
Name: "api-gateway",
|
||||
},
|
||||
}
|
||||
|
||||
// Add a discovery chain watch event for each service.
|
||||
for _, serviceName := range route.GetServiceNames() {
|
||||
discoChain := UpdateEvent{
|
||||
CorrelationID: fmt.Sprintf("discovery-chain:%s", UpstreamIDString("", "", serviceName.Name, &serviceName.EnterpriseMeta, "")),
|
||||
Result: &structs.DiscoveryChainResponse{
|
||||
Chain: discoverychain.TestCompileConfigEntries(t, serviceName.Name, "default", "default", "dc1", connect.TestClusterID+".consul", nil, entries...),
|
||||
},
|
||||
}
|
||||
baseEvents = append(baseEvents, discoChain)
|
||||
}
|
||||
}
|
||||
|
||||
upstreams := structs.TestUpstreams(t)
|
||||
|
||||
baseEvents = testSpliceEvents(baseEvents, setupTestVariationConfigEntriesAndSnapshot(
|
||||
t, variation, upstreams, additionalEntries...,
|
||||
))
|
||||
|
||||
return testConfigSnapshotFixture(t, &structs.NodeService{
|
||||
Kind: structs.ServiceKindAPIGateway,
|
||||
Service: "api-gateway",
|
||||
Address: "1.2.3.4",
|
||||
Meta: nil,
|
||||
TaggedAddresses: nil,
|
||||
}, nsFn, nil, testSpliceEvents(baseEvents, extraUpdates))
|
||||
}
|
||||
|
||||
// TestConfigSnapshotAPIGateway_NilConfigEntry is used to test when
|
||||
// the update event for the config entry returns nil
|
||||
// since this always happens on the first watch if it doesn't exist.
|
||||
func TestConfigSnapshotAPIGateway_NilConfigEntry(
|
||||
t testing.T,
|
||||
) *ConfigSnapshot {
|
||||
roots, _ := TestCerts(t)
|
||||
|
||||
baseEvents := []UpdateEvent{
|
||||
{
|
||||
CorrelationID: rootsWatchID,
|
||||
Result: roots,
|
||||
},
|
||||
{
|
||||
CorrelationID: gatewayConfigWatchID,
|
||||
Result: &structs.ConfigEntryResponse{
|
||||
Entry: nil, // The first watch on a config entry will return nil if the config entry doesn't exist.
|
||||
},
|
||||
},
|
||||
{
|
||||
CorrelationID: gatewayConfigWatchID,
|
||||
Result: &structs.ConfigEntryResponse{
|
||||
Entry: nil, // The first watch on a config entry will return nil if the config entry doesn't exist.
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return testConfigSnapshotFixture(t, &structs.NodeService{
|
||||
Kind: structs.ServiceKindAPIGateway,
|
||||
Service: "api-gateway",
|
||||
Address: "1.2.3.4",
|
||||
Meta: nil,
|
||||
TaggedAddresses: nil,
|
||||
}, nil, nil, testSpliceEvents(baseEvents, nil))
|
||||
}
|
|
@ -527,6 +527,211 @@ func TestListenersFromSnapshot(t *testing.T) {
|
|||
}, nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "api-gateway",
|
||||
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
|
||||
return proxycfg.TestConfigSnapshotAPIGateway(t, "default", nil, nil, nil, nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "api-gateway-nil-config-entry",
|
||||
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
|
||||
return proxycfg.TestConfigSnapshotAPIGateway_NilConfigEntry(t)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "api-gateway-tcp-listener",
|
||||
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
|
||||
return proxycfg.TestConfigSnapshotAPIGateway(t, "default", nil, func(entry *structs.APIGatewayConfigEntry, bound *structs.BoundAPIGatewayConfigEntry) {
|
||||
entry.Listeners = []structs.APIGatewayListener{
|
||||
{
|
||||
Name: "listener",
|
||||
Protocol: structs.ListenerProtocolTCP,
|
||||
Port: 8080,
|
||||
},
|
||||
}
|
||||
bound.Listeners = []structs.BoundAPIGatewayListener{
|
||||
{
|
||||
Name: "listener",
|
||||
},
|
||||
}
|
||||
}, nil, nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "api-gateway-tcp-listener-with-tcp-route",
|
||||
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
|
||||
return proxycfg.TestConfigSnapshotAPIGateway(t, "default", nil, func(entry *structs.APIGatewayConfigEntry, bound *structs.BoundAPIGatewayConfigEntry) {
|
||||
entry.Listeners = []structs.APIGatewayListener{
|
||||
{
|
||||
Name: "listener",
|
||||
Protocol: structs.ListenerProtocolTCP,
|
||||
Port: 8080,
|
||||
},
|
||||
}
|
||||
bound.Listeners = []structs.BoundAPIGatewayListener{
|
||||
{
|
||||
Name: "listener",
|
||||
Routes: []structs.ResourceReference{
|
||||
{
|
||||
Name: "tcp-route",
|
||||
Kind: structs.TCPRoute,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
}, []structs.BoundRoute{
|
||||
&structs.TCPRouteConfigEntry{
|
||||
Name: "tcp-route",
|
||||
Kind: structs.TCPRoute,
|
||||
Parents: []structs.ResourceReference{
|
||||
{
|
||||
Kind: structs.APIGateway,
|
||||
Name: "api-gateway",
|
||||
},
|
||||
},
|
||||
Services: []structs.TCPService{
|
||||
{Name: "tcp-service"},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "api-gateway-http-listener",
|
||||
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
|
||||
return proxycfg.TestConfigSnapshotAPIGateway(t, "default", nil, func(entry *structs.APIGatewayConfigEntry, bound *structs.BoundAPIGatewayConfigEntry) {
|
||||
entry.Listeners = []structs.APIGatewayListener{
|
||||
{
|
||||
Name: "listener",
|
||||
Protocol: structs.ListenerProtocolHTTP,
|
||||
Port: 8080,
|
||||
},
|
||||
}
|
||||
bound.Listeners = []structs.BoundAPIGatewayListener{
|
||||
{
|
||||
Name: "listener",
|
||||
Routes: []structs.ResourceReference{},
|
||||
},
|
||||
}
|
||||
}, nil, nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "api-gateway-http-listener-with-http-route",
|
||||
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
|
||||
return proxycfg.TestConfigSnapshotAPIGateway(t, "default", nil, func(entry *structs.APIGatewayConfigEntry, bound *structs.BoundAPIGatewayConfigEntry) {
|
||||
entry.Listeners = []structs.APIGatewayListener{
|
||||
{
|
||||
Name: "listener",
|
||||
Protocol: structs.ListenerProtocolHTTP,
|
||||
Port: 8080,
|
||||
},
|
||||
}
|
||||
bound.Listeners = []structs.BoundAPIGatewayListener{
|
||||
{
|
||||
Name: "listener",
|
||||
Routes: []structs.ResourceReference{
|
||||
{
|
||||
Name: "http-route",
|
||||
Kind: structs.HTTPRoute,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}, []structs.BoundRoute{
|
||||
&structs.HTTPRouteConfigEntry{
|
||||
Name: "http-route",
|
||||
Kind: structs.HTTPRoute,
|
||||
Parents: []structs.ResourceReference{
|
||||
{
|
||||
Kind: structs.APIGateway,
|
||||
Name: "api-gateway",
|
||||
},
|
||||
},
|
||||
Rules: []structs.HTTPRouteRule{
|
||||
{
|
||||
Services: []structs.HTTPService{
|
||||
{Name: "http-service"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "api-gateway-tcp-listener-with-tcp-and-http-route",
|
||||
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
|
||||
return proxycfg.TestConfigSnapshotAPIGateway(t, "default", nil, func(entry *structs.APIGatewayConfigEntry, bound *structs.BoundAPIGatewayConfigEntry) {
|
||||
entry.Listeners = []structs.APIGatewayListener{
|
||||
{
|
||||
Name: "listener-tcp",
|
||||
Protocol: structs.ListenerProtocolTCP,
|
||||
Port: 8080,
|
||||
},
|
||||
{
|
||||
Name: "listener-http",
|
||||
Protocol: structs.ListenerProtocolHTTP,
|
||||
Port: 8081,
|
||||
},
|
||||
}
|
||||
bound.Listeners = []structs.BoundAPIGatewayListener{
|
||||
{
|
||||
Name: "listener-tcp",
|
||||
Routes: []structs.ResourceReference{
|
||||
{
|
||||
Name: "tcp-route",
|
||||
Kind: structs.TCPRoute,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "listener-http",
|
||||
Routes: []structs.ResourceReference{
|
||||
{
|
||||
Name: "http-route",
|
||||
Kind: structs.HTTPRoute,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
}, []structs.BoundRoute{
|
||||
&structs.TCPRouteConfigEntry{
|
||||
Name: "tcp-route",
|
||||
Kind: structs.TCPRoute,
|
||||
Parents: []structs.ResourceReference{
|
||||
{
|
||||
Kind: structs.APIGateway,
|
||||
Name: "api-gateway",
|
||||
},
|
||||
},
|
||||
Services: []structs.TCPService{
|
||||
{Name: "tcp-service"},
|
||||
},
|
||||
},
|
||||
&structs.HTTPRouteConfigEntry{
|
||||
Name: "http-route",
|
||||
Kind: structs.HTTPRoute,
|
||||
Parents: []structs.ResourceReference{
|
||||
{
|
||||
Kind: structs.APIGateway,
|
||||
Name: "api-gateway",
|
||||
},
|
||||
},
|
||||
Rules: []structs.HTTPRouteRule{
|
||||
{
|
||||
Services: []structs.HTTPService{
|
||||
{Name: "http-service"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ingress-gateway",
|
||||
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
|
||||
|
|
49
agent/xds/testdata/listeners/api-gateway-http-listener-with-http-route.latest.golden
vendored
Normal file
49
agent/xds/testdata/listeners/api-gateway-http-listener-with-http-route.latest.golden
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"versionInfo": "00000001",
|
||||
"resources": [
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"name": "http:1.2.3.4:8080",
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "1.2.3.4",
|
||||
"portValue": 8080
|
||||
}
|
||||
},
|
||||
"filterChains": [
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"name": "envoy.filters.network.http_connection_manager",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
|
||||
"statPrefix": "ingress_upstream_8080",
|
||||
"rds": {
|
||||
"configSource": {
|
||||
"ads": {},
|
||||
"resourceApiVersion": "V3"
|
||||
},
|
||||
"routeConfigName": "8080"
|
||||
},
|
||||
"httpFilters": [
|
||||
{
|
||||
"name": "envoy.filters.http.router",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tracing": {
|
||||
"randomSampling": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"trafficDirection": "OUTBOUND"
|
||||
}
|
||||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"nonce": "00000001"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"versionInfo": "00000001",
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"nonce": "00000001"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"versionInfo": "00000001",
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"nonce": "00000001"
|
||||
}
|
74
agent/xds/testdata/listeners/api-gateway-tcp-listener-with-tcp-and-http-route.latest.golden
vendored
Normal file
74
agent/xds/testdata/listeners/api-gateway-tcp-listener-with-tcp-and-http-route.latest.golden
vendored
Normal file
|
@ -0,0 +1,74 @@
|
|||
{
|
||||
"versionInfo": "00000001",
|
||||
"resources": [
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"name": "http:1.2.3.4:8081",
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "1.2.3.4",
|
||||
"portValue": 8081
|
||||
}
|
||||
},
|
||||
"filterChains": [
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"name": "envoy.filters.network.http_connection_manager",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
|
||||
"statPrefix": "ingress_upstream_8081",
|
||||
"rds": {
|
||||
"configSource": {
|
||||
"ads": {},
|
||||
"resourceApiVersion": "V3"
|
||||
},
|
||||
"routeConfigName": "8081"
|
||||
},
|
||||
"httpFilters": [
|
||||
{
|
||||
"name": "envoy.filters.http.router",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tracing": {
|
||||
"randomSampling": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"trafficDirection": "OUTBOUND"
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"name": "tcp-service:1.2.3.4:8080",
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "1.2.3.4",
|
||||
"portValue": 8080
|
||||
}
|
||||
},
|
||||
"filterChains": [
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"name": "envoy.filters.network.tcp_proxy",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy",
|
||||
"statPrefix": "upstream.tcp-service.default.default.dc1",
|
||||
"cluster": "tcp-service.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"trafficDirection": "OUTBOUND"
|
||||
}
|
||||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"nonce": "00000001"
|
||||
}
|
32
agent/xds/testdata/listeners/api-gateway-tcp-listener-with-tcp-route.latest.golden
vendored
Normal file
32
agent/xds/testdata/listeners/api-gateway-tcp-listener-with-tcp-route.latest.golden
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"versionInfo": "00000001",
|
||||
"resources": [
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"name": "tcp-service:1.2.3.4:8080",
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "1.2.3.4",
|
||||
"portValue": 8080
|
||||
}
|
||||
},
|
||||
"filterChains": [
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"name": "envoy.filters.network.tcp_proxy",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy",
|
||||
"statPrefix": "upstream.tcp-service.default.default.dc1",
|
||||
"cluster": "tcp-service.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"trafficDirection": "OUTBOUND"
|
||||
}
|
||||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"nonce": "00000001"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"versionInfo": "00000001",
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"nonce": "00000001"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"versionInfo": "00000001",
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"nonce": "00000001"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"versionInfo": "00000001",
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"nonce": "00000001"
|
||||
}
|
|
@ -38,6 +38,7 @@ services = [
|
|||
]
|
||||
parents = [
|
||||
{
|
||||
kind = "api-gateway"
|
||||
name = "api-gateway"
|
||||
}
|
||||
]
|
||||
|
@ -47,4 +48,4 @@ register_services primary
|
|||
|
||||
gen_envoy_bootstrap api-gateway 20000 primary true
|
||||
gen_envoy_bootstrap s1 19000
|
||||
gen_envoy_bootstrap s2 19001
|
||||
gen_envoy_bootstrap s2 19001
|
||||
|
|
|
@ -47,6 +47,7 @@ parents = [
|
|||
{
|
||||
name = "api-gateway"
|
||||
sectionName = "listener-two"
|
||||
kind = "api-gateway"
|
||||
}
|
||||
]
|
||||
'
|
||||
|
@ -73,4 +74,4 @@ register_services primary
|
|||
|
||||
gen_envoy_bootstrap api-gateway 20000 primary true
|
||||
gen_envoy_bootstrap s1 19000
|
||||
gen_envoy_bootstrap s2 19001
|
||||
gen_envoy_bootstrap s2 19001
|
||||
|
|
|
@ -29,4 +29,4 @@ load helpers
|
|||
|
||||
@test "api gateway should get an intentions error connecting to s2 via configured port" {
|
||||
run retry_default must_fail_tcp_connection localhost:9998
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue