fix: multiple grpc/http2 services for ingress listeners

This commit is contained in:
DanStough 2022-05-18 17:55:59 -04:00 committed by Dan Stough
parent 13e2c81451
commit 65ca7e0bfb
8 changed files with 252 additions and 3 deletions

3
.changelog/13127.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:bug
fix a bug that caused an error when creating `grpc` or `http2` ingress gateway listeners with multiple services
```

View File

@ -800,6 +800,109 @@ func TestConfigSnapshotIngress_HTTPMultipleServices(t testing.T) *ConfigSnapshot
}) })
} }
func TestConfigSnapshotIngress_GRPCMultipleServices(t testing.T) *ConfigSnapshot {
// We do not add baz/qux here so that we test the chain.IsDefault() case
entries := []structs.ConfigEntry{
&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal,
Config: map[string]interface{}{
"protocol": "http",
},
},
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "foo",
ConnectTimeout: 22 * time.Second,
},
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "bar",
ConnectTimeout: 22 * time.Second,
},
}
var (
foo = structs.NewServiceName("foo", nil)
fooUID = NewUpstreamIDFromServiceName(foo)
fooChain = discoverychain.TestCompileConfigEntries(t, "foo", "default", "default", "dc1", connect.TestClusterID+".consul", nil, entries...)
bar = structs.NewServiceName("bar", nil)
barUID = NewUpstreamIDFromServiceName(bar)
barChain = discoverychain.TestCompileConfigEntries(t, "bar", "default", "default", "dc1", connect.TestClusterID+".consul", nil, entries...)
)
require.False(t, fooChain.Default)
require.False(t, barChain.Default)
return TestConfigSnapshotIngressGateway(t, false, "http", "default", nil, func(entry *structs.IngressGatewayConfigEntry) {
entry.Listeners = []structs.IngressListener{
{
Port: 8080,
Protocol: "grpc",
Services: []structs.IngressService{
{
Name: "foo",
Hosts: []string{
"test1.example.com",
"test2.example.com",
"test2.example.com:8080",
},
},
{Name: "bar"},
},
},
}
}, []UpdateEvent{
{
CorrelationID: gatewayServicesWatchID,
Result: &structs.IndexedGatewayServices{
Services: []*structs.GatewayService{
{
Service: foo,
Port: 8080,
Protocol: "grpc",
Hosts: []string{
"test1.example.com",
"test2.example.com",
"test2.example.com:8080",
},
},
{
Service: bar,
Port: 8080,
Protocol: "grpc",
},
},
},
},
{
CorrelationID: "discovery-chain:" + fooUID.String(),
Result: &structs.DiscoveryChainResponse{
Chain: fooChain,
},
},
{
CorrelationID: "upstream-target:" + fooChain.ID() + ":" + fooUID.String(),
Result: &structs.IndexedCheckServiceNodes{
Nodes: TestUpstreamNodes(t, "foo"),
},
},
{
CorrelationID: "discovery-chain:" + barUID.String(),
Result: &structs.DiscoveryChainResponse{
Chain: barChain,
},
},
{
CorrelationID: "upstream-target:" + barChain.ID() + ":" + barUID.String(),
Result: &structs.IndexedCheckServiceNodes{
Nodes: TestUpstreamNodes(t, "bar"),
},
},
})
}
func TestConfigSnapshotIngress_MultipleListenersDuplicateService(t testing.T) *ConfigSnapshot { func TestConfigSnapshotIngress_MultipleListenersDuplicateService(t testing.T) *ConfigSnapshot {
var ( var (
foo = structs.NewServiceName("foo", nil) foo = structs.NewServiceName("foo", nil)

View File

@ -275,8 +275,8 @@ func (e *IngressGatewayConfigEntry) Validate() error {
} }
// Validate that http features aren't being used with tcp or another non-supported protocol. // Validate that http features aren't being used with tcp or another non-supported protocol.
if listener.Protocol != "http" && len(listener.Services) > 1 { if !IsProtocolHTTPLike(listener.Protocol) && len(listener.Services) > 1 {
return fmt.Errorf("Multiple services per listener are only supported for protocol = 'http' (listener on port %d)", return fmt.Errorf("Multiple services per listener are only supported for L7 protocols, 'http', 'grpc' and 'http2' (listener on port %d)",
listener.Port) listener.Port)
} }

View File

@ -135,6 +135,26 @@ func TestIngressGatewayConfigEntry(t *testing.T) {
validateErr: "Wildcard service name is only valid for protocol", validateErr: "Wildcard service name is only valid for protocol",
}, },
"http features: multiple services": { "http features: multiple services": {
entry: &IngressGatewayConfigEntry{
Kind: "ingress-gateway",
Name: "ingress-web",
Listeners: []IngressListener{
{
Port: 1111,
Protocol: "http",
Services: []IngressService{
{
Name: "db1",
},
{
Name: "db2",
},
},
},
},
},
},
"http features: multiple services on tcp listener": {
entry: &IngressGatewayConfigEntry{ entry: &IngressGatewayConfigEntry{
Kind: "ingress-gateway", Kind: "ingress-gateway",
Name: "ingress-web", Name: "ingress-web",
@ -153,7 +173,7 @@ func TestIngressGatewayConfigEntry(t *testing.T) {
}, },
}, },
}, },
validateErr: "Multiple services per listener are only supported for protocol", validateErr: "Multiple services per listener are only supported for L7",
}, },
// ========================== // ==========================
"tcp listener requires a defined service": { "tcp listener requires a defined service": {

View File

@ -680,6 +680,10 @@ func TestListenersFromSnapshot(t *testing.T) {
name: "ingress-http-multiple-services", name: "ingress-http-multiple-services",
create: proxycfg.TestConfigSnapshotIngress_HTTPMultipleServices, create: proxycfg.TestConfigSnapshotIngress_HTTPMultipleServices,
}, },
{
name: "ingress-grpc-multiple-services",
create: proxycfg.TestConfigSnapshotIngress_GRPCMultipleServices,
},
{ {
name: "terminating-gateway-no-api-cert", name: "terminating-gateway-no-api-cert",
create: func(t testinf.T) *proxycfg.ConfigSnapshot { create: func(t testinf.T) *proxycfg.ConfigSnapshot {

View File

@ -142,6 +142,10 @@ func TestRoutesFromSnapshot(t *testing.T) {
name: "ingress-http-multiple-services", name: "ingress-http-multiple-services",
create: proxycfg.TestConfigSnapshotIngress_HTTPMultipleServices, create: proxycfg.TestConfigSnapshotIngress_HTTPMultipleServices,
}, },
{
name: "ingress-grpc-multiple-services",
create: proxycfg.TestConfigSnapshotIngress_GRPCMultipleServices,
},
{ {
name: "ingress-with-chain-and-router-header-manip", name: "ingress-with-chain-and-router-header-manip",
create: func(t testinf.T) *proxycfg.ConfigSnapshot { create: func(t testinf.T) *proxycfg.ConfigSnapshot {

View File

@ -0,0 +1,65 @@
{
"versionInfo": "00000001",
"resources": [
{
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
"name": "grpc: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.grpc_stats",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.grpc_stats.v3.FilterConfig",
"statsForAllMethods": true
}
},
{
"name": "envoy.filters.http.grpc_http1_bridge",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.grpc_http1_bridge.v3.Config"
}
},
{
"name": "envoy.filters.http.router",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"
}
}
],
"tracing": {
"randomSampling": {}
},
"http2ProtocolOptions": {}
}
}
]
}
],
"trafficDirection": "OUTBOUND"
}
],
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
"nonce": "00000001"
}

View File

@ -0,0 +1,50 @@
{
"versionInfo": "00000001",
"resources": [
{
"@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
"name": "8080",
"virtualHosts": [
{
"name": "foo",
"domains": [
"test1.example.com",
"test2.example.com",
"test2.example.com:8080",
"test1.example.com:8080"
],
"routes": [
{
"match": {
"prefix": "/"
},
"route": {
"cluster": "foo.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
}
}
]
},
{
"name": "bar",
"domains": [
"bar.ingress.*",
"bar.ingress.*:8080"
],
"routes": [
{
"match": {
"prefix": "/"
},
"route": {
"cluster": "bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
}
}
]
}
],
"validateClusters": true
}
],
"typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
"nonce": "00000001"
}