Skip to content

Commit 26057f2

Browse files
Merge 4ae45d4 into backport/sujay/apigw/upstream-subset-router/largely-fit-python
2 parents 386923c + 4ae45d4 commit 26057f2

3 files changed

Lines changed: 502 additions & 22 deletions

File tree

agent/consul/discoverychain/gateway.go

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"strconv"
1111
"strings"
1212

13+
"github.com/hashicorp/consul/acl"
1314
"github.com/hashicorp/consul/agent/configentry"
1415
"github.com/hashicorp/consul/agent/structs"
1516
)
@@ -24,6 +25,7 @@ type GatewayChainSynthesizer struct {
2425
hostname string
2526
matchesByHostname map[string][]hostnameMatch
2627
tcpRoutes []structs.TCPRouteConfigEntry
28+
serviceRouters map[structs.ServiceName][]*structs.ServiceRoute
2729
}
2830

2931
type hostnameMatch struct {
@@ -113,7 +115,9 @@ func (l *GatewayChainSynthesizer) Synthesize(chains ...*structs.CompiledDiscover
113115
return nil, nil, fmt.Errorf("must provide at least one compiled discovery chain")
114116
}
115117

118+
l.serviceRouters = serviceRouterRulesFromChains(chains)
116119
services, set := l.synthesizeEntries()
120+
resolverEntries := resolverEntriesFromChains(chains)
117121

118122
if len(set) == 0 {
119123
// we can't actually compile a discovery chain, i.e. we're using a TCPRoute-based listener, instead, just return the ingresses
@@ -125,6 +129,9 @@ func (l *GatewayChainSynthesizer) Synthesize(chains ...*structs.CompiledDiscover
125129
for i, service := range services {
126130

127131
entries := set[i]
132+
if len(resolverEntries) > 0 {
133+
entries.AddResolvers(resolverEntries...)
134+
}
128135

129136
compiled, err := Compile(CompileRequest{
130137
ServiceName: service.Name,
@@ -192,6 +199,59 @@ func (l *GatewayChainSynthesizer) Synthesize(chains ...*structs.CompiledDiscover
192199
return services, compiledChains, nil
193200
}
194201

202+
// resolverEntriesFromChains builds minimal service-resolver entries that include
203+
// subset definitions from the real compiled chains. This allows gateway
204+
// synthesis to compile routes that reference service subsets.
205+
func resolverEntriesFromChains(chains []*structs.CompiledDiscoveryChain) []*structs.ServiceResolverConfigEntry {
206+
out := []*structs.ServiceResolverConfigEntry{}
207+
seen := map[structs.ServiceID]*structs.ServiceResolverConfigEntry{}
208+
209+
for _, chain := range chains {
210+
if chain == nil {
211+
continue
212+
}
213+
entMeta := acl.NewEnterpriseMetaWithPartition(chain.Partition, chain.Namespace)
214+
sid := structs.NewServiceID(chain.ServiceName, &entMeta)
215+
216+
entry, ok := seen[sid]
217+
if !ok {
218+
entry = &structs.ServiceResolverConfigEntry{
219+
Kind: structs.ServiceResolver,
220+
Name: chain.ServiceName,
221+
EnterpriseMeta: sid.EnterpriseMeta,
222+
Subsets: make(map[string]structs.ServiceResolverSubset),
223+
}
224+
seen[sid] = entry
225+
}
226+
227+
for _, target := range chain.Targets {
228+
if target == nil {
229+
continue
230+
}
231+
if target.Service != chain.ServiceName {
232+
continue
233+
}
234+
if target.Namespace != chain.Namespace || target.Partition != chain.Partition {
235+
continue
236+
}
237+
if target.ServiceSubset == "" {
238+
continue
239+
}
240+
if _, ok := entry.Subsets[target.ServiceSubset]; !ok {
241+
entry.Subsets[target.ServiceSubset] = target.Subset
242+
}
243+
}
244+
}
245+
246+
for _, entry := range seen {
247+
if len(entry.Subsets) == 0 {
248+
continue
249+
}
250+
out = append(out, entry)
251+
}
252+
return out
253+
}
254+
195255
// consolidateHTTPRoutes combines all rules into the shortest possible list of routes
196256
// with one route per hostname containing all rules for that hostname.
197257
func (l *GatewayChainSynthesizer) consolidateHTTPRoutes() []structs.HTTPRouteConfigEntry {
@@ -290,7 +350,7 @@ func (l *GatewayChainSynthesizer) synthesizeEntries() ([]structs.IngressService,
290350
entries := []*configentry.DiscoveryChainSet{}
291351

292352
for _, route := range l.consolidateHTTPRoutes() {
293-
ingress, router, splitters, defaults := synthesizeHTTPRouteDiscoveryChain(route)
353+
ingress, router, splitters, defaults := synthesizeHTTPRouteDiscoveryChain(route, l.serviceRouters)
294354

295355
services = append(services, ingress)
296356

@@ -307,3 +367,39 @@ func (l *GatewayChainSynthesizer) synthesizeEntries() ([]structs.IngressService,
307367

308368
return services, entries
309369
}
370+
371+
func serviceRouterRulesFromChains(chains []*structs.CompiledDiscoveryChain) map[structs.ServiceName][]*structs.ServiceRoute {
372+
out := make(map[structs.ServiceName][]*structs.ServiceRoute)
373+
for _, chain := range chains {
374+
if chain == nil {
375+
continue
376+
}
377+
startNode := chain.Nodes[chain.StartNode]
378+
if startNode == nil || !startNode.IsRouter() {
379+
continue
380+
}
381+
routes := make([]*structs.ServiceRoute, 0, len(startNode.Routes))
382+
for _, route := range startNode.Routes {
383+
if route == nil || route.Definition == nil {
384+
continue
385+
}
386+
def := route.Definition.DeepCopy()
387+
// If the route destination doesn't include a subset, but the next
388+
// resolver target does, propagate it. This ensures default-subset
389+
// behavior is preserved when composing gateway routes.
390+
if def.Destination != nil && def.Destination.ServiceSubset == "" {
391+
if node := chain.Nodes[route.NextNode]; node != nil && node.IsResolver() && node.Resolver != nil {
392+
if target := chain.Targets[node.Resolver.Target]; target != nil && target.ServiceSubset != "" {
393+
def.Destination.ServiceSubset = target.ServiceSubset
394+
}
395+
}
396+
}
397+
routes = append(routes, def)
398+
}
399+
if len(routes) == 0 {
400+
continue
401+
}
402+
out[chain.CompoundServiceName()] = routes
403+
}
404+
return out
405+
}

0 commit comments

Comments
 (0)