@@ -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
2931type 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.
197257func (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