From ca12ce926b76f0faa894b5bbd9e70a2ce28f59fc Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Wed, 31 May 2023 16:58:40 -0400 Subject: [PATCH] [API Gateway] Fix use of virtual resolvers in HTTPRoutes (#17055) * [API Gateway] Fix use of virtual resolvers in routes * Add changelog entry --- .changelog/17055.txt | 3 + agent/consul/discoverychain/gateway.go | 7 ++ agent/consul/discoverychain/gateway_test.go | 112 ++++++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 .changelog/17055.txt diff --git a/.changelog/17055.txt b/.changelog/17055.txt new file mode 100644 index 0000000000..9300c41121 --- /dev/null +++ b/.changelog/17055.txt @@ -0,0 +1,3 @@ +```release-note:bug +gateways: Fix an bug where targeting a virtual service defined by a service-resolver was broken for HTTPRoutes. +``` diff --git a/agent/consul/discoverychain/gateway.go b/agent/consul/discoverychain/gateway.go index 8c13bb59a8..e43e4b631f 100644 --- a/agent/consul/discoverychain/gateway.go +++ b/agent/consul/discoverychain/gateway.go @@ -249,6 +249,13 @@ func targetForResolverNode(nodeName string, chains []*structs.CompiledDiscoveryC splitterName := splitterPrefix + strings.TrimPrefix(nodeName, resolverPrefix) for _, c := range chains { + targetChainPrefix := resolverPrefix + c.ServiceName + "." + if strings.HasPrefix(nodeName, targetChainPrefix) && len(c.Nodes) == 1 { + // we have a virtual resolver that just maps to another resolver, return + // the given node name + return c.StartNode + } + for name, node := range c.Nodes { if node.IsSplitter() && strings.HasPrefix(splitterName, name) { return name diff --git a/agent/consul/discoverychain/gateway_test.go b/agent/consul/discoverychain/gateway_test.go index a98aaf0555..42bbed65eb 100644 --- a/agent/consul/discoverychain/gateway_test.go +++ b/agent/consul/discoverychain/gateway_test.go @@ -599,6 +599,118 @@ func TestGatewayChainSynthesizer_Synthesize(t *testing.T) { }, }}, }, + "HTTPRoute with virtual resolver": { + synthesizer: NewGatewayChainSynthesizer("dc1", "domain", "suffix", &structs.APIGatewayConfigEntry{ + Kind: structs.APIGateway, + Name: "gateway", + }), + httpRoutes: []*structs.HTTPRouteConfigEntry{ + { + Kind: structs.HTTPRoute, + Name: "http-route", + Rules: []structs.HTTPRouteRule{{ + Services: []structs.HTTPService{{ + Name: "foo", + }}, + }}, + }, + }, + chain: &structs.CompiledDiscoveryChain{ + ServiceName: "foo", + Namespace: "default", + Datacenter: "dc1", + StartNode: "resolver:foo-2.default.default.dc2", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "resolver:foo-2.default.default.dc2": { + Type: "resolver", + Name: "foo-2.default.default.dc2", + Resolver: &structs.DiscoveryResolver{ + Target: "foo-2.default.default.dc2", + Default: true, + ConnectTimeout: 5000000000, + }, + }, + }, + }, + extra: []*structs.CompiledDiscoveryChain{}, + expectedIngressServices: []structs.IngressService{{ + Name: "gateway-suffix-9b9265b", + Hosts: []string{"*"}, + }}, + expectedDiscoveryChains: []*structs.CompiledDiscoveryChain{{ + ServiceName: "gateway-suffix-9b9265b", + Partition: "default", + Namespace: "default", + Datacenter: "dc1", + Protocol: "http", + StartNode: "router:gateway-suffix-9b9265b.default.default", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "router:gateway-suffix-9b9265b.default.default": { + Type: "router", + Name: "gateway-suffix-9b9265b.default.default", + Routes: []*structs.DiscoveryRoute{{ + Definition: &structs.ServiceRoute{ + Match: &structs.ServiceRouteMatch{ + HTTP: &structs.ServiceRouteHTTPMatch{ + PathPrefix: "/", + }, + }, + Destination: &structs.ServiceRouteDestination{ + Service: "foo", + Partition: "default", + Namespace: "default", + RequestHeaders: &structs.HTTPHeaderModifiers{ + Add: make(map[string]string), + Set: make(map[string]string), + }, + }, + }, + NextNode: "resolver:foo-2.default.default.dc2", + }}, + }, + "resolver:foo.default.default.dc1": { + Type: "resolver", + Name: "foo.default.default.dc1", + Resolver: &structs.DiscoveryResolver{ + Target: "foo.default.default.dc1", + Default: true, + ConnectTimeout: 5000000000, + }, + }, + "resolver:foo-2.default.default.dc2": { + Type: "resolver", + Name: "foo-2.default.default.dc2", + Resolver: &structs.DiscoveryResolver{ + Target: "foo-2.default.default.dc2", + Default: true, + ConnectTimeout: 5000000000, + }, + }, + }, + Targets: map[string]*structs.DiscoveryTarget{ + "gateway-suffix-9b9265b.default.default.dc1": { + ID: "gateway-suffix-9b9265b.default.default.dc1", + Service: "gateway-suffix-9b9265b", + Datacenter: "dc1", + Partition: "default", + Namespace: "default", + ConnectTimeout: 5000000000, + SNI: "gateway-suffix-9b9265b.default.dc1.internal.domain", + Name: "gateway-suffix-9b9265b.default.dc1.internal.domain", + }, + "foo.default.default.dc1": { + ID: "foo.default.default.dc1", + Service: "foo", + Datacenter: "dc1", + Partition: "default", + Namespace: "default", + ConnectTimeout: 5000000000, + SNI: "foo.default.dc1.internal.domain", + Name: "foo.default.dc1.internal.domain", + }, + }, + }}, + }, } for name, tc := range cases {