1package discoverychain
2
3import (
4	"fmt"
5	"strings"
6	"time"
7
8	"github.com/mitchellh/hashstructure"
9	"github.com/mitchellh/mapstructure"
10
11	"github.com/hashicorp/consul/agent/connect"
12	"github.com/hashicorp/consul/agent/structs"
13)
14
15type CompileRequest struct {
16	ServiceName           string
17	EvaluateInNamespace   string
18	EvaluateInDatacenter  string
19	EvaluateInTrustDomain string
20	UseInDatacenter       string // where the results will be used from
21
22	// OverrideMeshGateway allows for the setting to be overridden for any
23	// resolver in the compiled chain.
24	OverrideMeshGateway structs.MeshGatewayConfig
25
26	// OverrideProtocol allows for the final protocol for the chain to be
27	// altered.
28	//
29	// - If the chain ordinarily would be TCP and an L7 protocol is passed here
30	// the chain will not include Routers or Splitters.
31	//
32	// - If the chain ordinarily would be L7 and TCP is passed here the chain
33	// will not include Routers or Splitters.
34	OverrideProtocol string
35
36	// OverrideConnectTimeout allows for the ConnectTimeout setting to be
37	// overridden for any resolver in the compiled chain.
38	OverrideConnectTimeout time.Duration
39
40	Entries *structs.DiscoveryChainConfigEntries
41}
42
43// Compile assembles a discovery chain in the form of a graph of nodes using
44// raw config entries and local context.
45//
46// "Node" referenced in this file refers to a node in a graph and not to the
47// Consul construct called a "Node".
48//
49// Omitting router and splitter entries for services not using an L7 protocol
50// (like HTTP) happens during initial fetching, but for sanity purposes a quick
51// reinforcement of that happens here, too.
52//
53// May return a *structs.ConfigEntryGraphError, but that is only expected when
54// being used to validate modifications to the config entry graph. It should
55// not be expected when compiling existing entries at runtime that are already
56// valid.
57func Compile(req CompileRequest) (*structs.CompiledDiscoveryChain, error) {
58	var (
59		serviceName           = req.ServiceName
60		evaluateInNamespace   = req.EvaluateInNamespace
61		evaluateInDatacenter  = req.EvaluateInDatacenter
62		evaluateInTrustDomain = req.EvaluateInTrustDomain
63		useInDatacenter       = req.UseInDatacenter
64		entries               = req.Entries
65	)
66	if serviceName == "" {
67		return nil, fmt.Errorf("serviceName is required")
68	}
69	if evaluateInNamespace == "" {
70		return nil, fmt.Errorf("evaluateInNamespace is required")
71	}
72	if evaluateInDatacenter == "" {
73		return nil, fmt.Errorf("evaluateInDatacenter is required")
74	}
75	if evaluateInTrustDomain == "" {
76		return nil, fmt.Errorf("evaluateInTrustDomain is required")
77	}
78	if useInDatacenter == "" {
79		return nil, fmt.Errorf("useInDatacenter is required")
80	}
81	if entries == nil {
82		return nil, fmt.Errorf("entries is required")
83	}
84
85	c := &compiler{
86		serviceName:            serviceName,
87		evaluateInNamespace:    evaluateInNamespace,
88		evaluateInDatacenter:   evaluateInDatacenter,
89		evaluateInTrustDomain:  evaluateInTrustDomain,
90		useInDatacenter:        useInDatacenter,
91		overrideMeshGateway:    req.OverrideMeshGateway,
92		overrideProtocol:       req.OverrideProtocol,
93		overrideConnectTimeout: req.OverrideConnectTimeout,
94		entries:                entries,
95
96		resolvers:     make(map[structs.ServiceID]*structs.ServiceResolverConfigEntry),
97		splitterNodes: make(map[string]*structs.DiscoveryGraphNode),
98		resolveNodes:  make(map[string]*structs.DiscoveryGraphNode),
99
100		nodes:           make(map[string]*structs.DiscoveryGraphNode),
101		loadedTargets:   make(map[string]*structs.DiscoveryTarget),
102		retainedTargets: make(map[string]struct{}),
103	}
104
105	if req.OverrideProtocol != "" {
106		c.disableAdvancedRoutingFeatures = !enableAdvancedRoutingForProtocol(req.OverrideProtocol)
107	}
108
109	// Clone this resolver map to avoid mutating the input map during compilation.
110	if len(entries.Resolvers) > 0 {
111		for k, v := range entries.Resolvers {
112			c.resolvers[k] = v
113		}
114	}
115
116	return c.compile()
117}
118
119// compiler is a single-use struct for handling intermediate state necessary
120// for assembling a discovery chain from raw config entries.
121type compiler struct {
122	serviceName            string
123	evaluateInNamespace    string
124	evaluateInDatacenter   string
125	evaluateInTrustDomain  string
126	useInDatacenter        string
127	overrideMeshGateway    structs.MeshGatewayConfig
128	overrideProtocol       string
129	overrideConnectTimeout time.Duration
130
131	// config entries that are being compiled (will be mutated during compilation)
132	//
133	// This is an INPUT field.
134	entries *structs.DiscoveryChainConfigEntries
135
136	// resolvers is initially seeded by copying the provided entries.Resolvers
137	// map and default resolvers are added as they are needed.
138	resolvers map[structs.ServiceID]*structs.ServiceResolverConfigEntry
139
140	// cached nodes
141	splitterNodes map[string]*structs.DiscoveryGraphNode
142	resolveNodes  map[string]*structs.DiscoveryGraphNode
143
144	// usesAdvancedRoutingFeatures is set to true if config entries for routing
145	// or splitting appear in the compiled chain
146	usesAdvancedRoutingFeatures bool
147
148	// disableAdvancedRoutingFeatures is set to true if overrideProtocol is set to tcp
149	disableAdvancedRoutingFeatures bool
150
151	// customizedBy indicates which override values customized how the
152	// compilation behaved.
153	//
154	// This is an OUTPUT field.
155	customizedBy customizationMarkers
156
157	// protocol is the common protocol used for all referenced services. These
158	// cannot be mixed.
159	//
160	// This is an OUTPUT field.
161	protocol string
162
163	// startNode is computed inside of assembleChain()
164	//
165	// This is an OUTPUT field.
166	startNode string
167
168	// nodes is computed inside of compile()
169	//
170	// This is an OUTPUT field.
171	nodes map[string]*structs.DiscoveryGraphNode
172
173	// This is an OUTPUT field.
174	loadedTargets   map[string]*structs.DiscoveryTarget
175	retainedTargets map[string]struct{}
176}
177
178type customizationMarkers struct {
179	MeshGateway    bool
180	Protocol       bool
181	ConnectTimeout bool
182}
183
184// serviceIDString deviates from the standard formatting you would get with
185// the String() method on the type itself. It is this way to be more
186// consistent with other string ids within the discovery chain.
187func serviceIDString(sid structs.ServiceID) string {
188	return fmt.Sprintf("%s.%s", sid.ID, sid.NamespaceOrDefault())
189}
190
191func (m *customizationMarkers) IsZero() bool {
192	return !m.MeshGateway && !m.Protocol && !m.ConnectTimeout
193}
194
195// recordNode stores the node internally in the compiled chain.
196func (c *compiler) recordNode(node *structs.DiscoveryGraphNode) {
197	// Some types have their own type-specific lookups, so record those, too.
198	switch node.Type {
199	case structs.DiscoveryGraphNodeTypeRouter:
200		// no special storage
201	case structs.DiscoveryGraphNodeTypeSplitter:
202		c.splitterNodes[node.Name] = node
203	case structs.DiscoveryGraphNodeTypeResolver:
204		c.resolveNodes[node.Resolver.Target] = node
205	default:
206		panic("unknown node type '" + node.Type + "'")
207	}
208
209	c.nodes[node.MapKey()] = node
210}
211
212func (c *compiler) recordServiceProtocol(sid structs.ServiceID) error {
213	if serviceDefault := c.entries.GetService(sid); serviceDefault != nil {
214		return c.recordProtocol(sid, serviceDefault.Protocol)
215	}
216	if c.entries.GlobalProxy != nil {
217		var cfg proxyConfig
218		// Ignore errors and fallback on defaults if it does happen.
219		_ = mapstructure.WeakDecode(c.entries.GlobalProxy.Config, &cfg)
220		if cfg.Protocol != "" {
221			return c.recordProtocol(sid, cfg.Protocol)
222		}
223	}
224	return c.recordProtocol(sid, "")
225}
226
227// proxyConfig is a snippet from agent/xds/config.go:ProxyConfig
228type proxyConfig struct {
229	Protocol string `mapstructure:"protocol"`
230}
231
232func (c *compiler) recordProtocol(fromService structs.ServiceID, protocol string) error {
233	if protocol == "" {
234		protocol = "tcp"
235	} else {
236		protocol = strings.ToLower(protocol)
237	}
238
239	if c.protocol == "" {
240		c.protocol = protocol
241	} else if c.protocol != protocol {
242		return &structs.ConfigEntryGraphError{
243			Message: fmt.Sprintf(
244				"discovery chain %q uses inconsistent protocols; service %q has %q which is not %q",
245				c.serviceName, fromService.String(), protocol, c.protocol,
246			),
247		}
248	}
249
250	return nil
251}
252
253func (c *compiler) compile() (*structs.CompiledDiscoveryChain, error) {
254	if err := c.assembleChain(); err != nil {
255		return nil, err
256	}
257
258	// We don't need these intermediates anymore.
259	c.splitterNodes = nil
260	c.resolveNodes = nil
261
262	if c.startNode == "" {
263		panic("impossible to return no results")
264	}
265
266	if err := c.detectCircularReferences(); err != nil {
267		return nil, err
268	}
269
270	c.flattenAdjacentSplitterNodes()
271
272	if err := c.removeUnusedNodes(); err != nil {
273		return nil, err
274	}
275
276	for targetID := range c.loadedTargets {
277		if _, ok := c.retainedTargets[targetID]; !ok {
278			delete(c.loadedTargets, targetID)
279		}
280	}
281
282	if !enableAdvancedRoutingForProtocol(c.protocol) && c.usesAdvancedRoutingFeatures {
283		return nil, &structs.ConfigEntryGraphError{
284			Message: fmt.Sprintf(
285				"discovery chain %q uses a protocol %q that does not permit advanced routing or splitting behavior",
286				c.serviceName, c.protocol,
287			),
288		}
289	}
290
291	if c.overrideProtocol != "" {
292		if c.overrideProtocol != c.protocol {
293			c.protocol = c.overrideProtocol
294			c.customizedBy.Protocol = true
295		}
296	}
297
298	var customizationHash string
299	if !c.customizedBy.IsZero() {
300		var customization struct {
301			OverrideMeshGateway    structs.MeshGatewayConfig
302			OverrideProtocol       string
303			OverrideConnectTimeout time.Duration
304		}
305
306		if c.customizedBy.MeshGateway {
307			customization.OverrideMeshGateway = c.overrideMeshGateway
308		}
309		if c.customizedBy.Protocol {
310			customization.OverrideProtocol = c.overrideProtocol
311		}
312		if c.customizedBy.ConnectTimeout {
313			customization.OverrideConnectTimeout = c.overrideConnectTimeout
314		}
315		v, err := hashstructure.Hash(customization, nil)
316		if err != nil {
317			return nil, fmt.Errorf("cannot create customization hash key: %v", err)
318		}
319		customizationHash = fmt.Sprintf("%x", v)[0:8]
320	}
321
322	return &structs.CompiledDiscoveryChain{
323		ServiceName:       c.serviceName,
324		Namespace:         c.evaluateInNamespace,
325		Datacenter:        c.evaluateInDatacenter,
326		CustomizationHash: customizationHash,
327		Protocol:          c.protocol,
328		StartNode:         c.startNode,
329		Nodes:             c.nodes,
330		Targets:           c.loadedTargets,
331	}, nil
332}
333
334func (c *compiler) detectCircularReferences() error {
335	var (
336		todo       stringStack
337		visited    = make(map[string]struct{})
338		visitChain stringStack
339	)
340
341	todo.Push(c.startNode)
342	for {
343		current, ok := todo.Pop()
344		if !ok {
345			break
346		}
347		if current == "_popvisit" {
348			if v, ok := visitChain.Pop(); ok {
349				delete(visited, v)
350			}
351			continue
352		}
353		visitChain.Push(current)
354
355		if _, ok := visited[current]; ok {
356			return &structs.ConfigEntryGraphError{
357				Message: fmt.Sprintf(
358					"detected circular reference: [%s]",
359					strings.Join(visitChain.Items(), " -> "),
360				),
361			}
362		}
363		visited[current] = struct{}{}
364
365		todo.Push("_popvisit")
366
367		node := c.nodes[current]
368
369		switch node.Type {
370		case structs.DiscoveryGraphNodeTypeRouter:
371			for _, route := range node.Routes {
372				todo.Push(route.NextNode)
373			}
374		case structs.DiscoveryGraphNodeTypeSplitter:
375			for _, split := range node.Splits {
376				todo.Push(split.NextNode)
377			}
378		case structs.DiscoveryGraphNodeTypeResolver:
379			// Circular redirects are detected elsewhere and failover isn't
380			// recursive so there's nothing more to do here.
381		default:
382			return fmt.Errorf("unexpected graph node type: %s", node.Type)
383		}
384	}
385
386	return nil
387}
388
389func (c *compiler) flattenAdjacentSplitterNodes() {
390	for {
391		anyChanged := false
392		for _, node := range c.nodes {
393			if node.Type != structs.DiscoveryGraphNodeTypeSplitter {
394				continue
395			}
396
397			fixedSplits := make([]*structs.DiscoverySplit, 0, len(node.Splits))
398			changed := false
399			for _, split := range node.Splits {
400				nextNode := c.nodes[split.NextNode]
401				if nextNode.Type != structs.DiscoveryGraphNodeTypeSplitter {
402					fixedSplits = append(fixedSplits, split)
403					continue
404				}
405
406				changed = true
407
408				for _, innerSplit := range nextNode.Splits {
409					effectiveWeight := split.Weight * innerSplit.Weight / 100
410
411					newDiscoverySplit := &structs.DiscoverySplit{
412						Weight:   structs.NormalizeServiceSplitWeight(effectiveWeight),
413						NextNode: innerSplit.NextNode,
414					}
415
416					fixedSplits = append(fixedSplits, newDiscoverySplit)
417				}
418			}
419
420			if changed {
421				node.Splits = fixedSplits
422				anyChanged = true
423			}
424		}
425
426		if !anyChanged {
427			return
428		}
429	}
430}
431
432// removeUnusedNodes walks the chain from the start and prunes any nodes that
433// are no longer referenced. This can happen as a result of operations like
434// flattenAdjacentSplitterNodes().
435func (c *compiler) removeUnusedNodes() error {
436	var (
437		visited = make(map[string]struct{})
438		todo    = make(map[string]struct{})
439	)
440
441	todo[c.startNode] = struct{}{}
442
443	getNext := func() string {
444		if len(todo) == 0 {
445			return ""
446		}
447		for k := range todo {
448			delete(todo, k)
449			return k
450		}
451		return ""
452	}
453
454	for {
455		next := getNext()
456		if next == "" {
457			break
458		}
459		if _, ok := visited[next]; ok {
460			continue
461		}
462		visited[next] = struct{}{}
463
464		node := c.nodes[next]
465		if node == nil {
466			return fmt.Errorf("compilation references non-retained node %q", next)
467		}
468
469		switch node.Type {
470		case structs.DiscoveryGraphNodeTypeRouter:
471			for _, route := range node.Routes {
472				todo[route.NextNode] = struct{}{}
473			}
474		case structs.DiscoveryGraphNodeTypeSplitter:
475			for _, split := range node.Splits {
476				todo[split.NextNode] = struct{}{}
477			}
478		case structs.DiscoveryGraphNodeTypeResolver:
479			// nothing special
480		default:
481			return fmt.Errorf("unknown node type %q", node.Type)
482		}
483	}
484
485	if len(visited) == len(c.nodes) {
486		return nil
487	}
488
489	for name := range c.nodes {
490		if _, ok := visited[name]; !ok {
491			delete(c.nodes, name)
492		}
493	}
494
495	return nil
496}
497
498// assembleChain will do the initial assembly of a chain of DiscoveryGraphNode
499// entries from the provided config entries.
500func (c *compiler) assembleChain() error {
501	if c.startNode != "" || len(c.nodes) > 0 {
502		return fmt.Errorf("assembleChain should only be called once")
503	}
504
505	sid := structs.NewServiceID(c.serviceName, c.GetEnterpriseMeta())
506
507	// Check for short circuit path.
508	if len(c.resolvers) == 0 && c.entries.IsChainEmpty() {
509		// Materialize defaults and cache.
510		c.resolvers[sid] = newDefaultServiceResolver(sid)
511	}
512
513	// The only router we consult is the one for the service name at the top of
514	// the chain.
515	router := c.entries.GetRouter(sid)
516	if router != nil && c.disableAdvancedRoutingFeatures {
517		router = nil
518		c.customizedBy.Protocol = true
519	}
520
521	if router == nil {
522		// If no router is configured, move on down the line to the next hop of
523		// the chain.
524		node, err := c.getSplitterOrResolverNode(c.newTarget(c.serviceName, "", "", ""))
525		if err != nil {
526			return err
527		}
528
529		c.startNode = node.MapKey()
530		return nil
531	}
532
533	routerID := structs.NewServiceID(router.Name, router.GetEnterpriseMeta())
534
535	routeNode := &structs.DiscoveryGraphNode{
536		Type:   structs.DiscoveryGraphNodeTypeRouter,
537		Name:   serviceIDString(routerID),
538		Routes: make([]*structs.DiscoveryRoute, 0, len(router.Routes)+1),
539	}
540	c.usesAdvancedRoutingFeatures = true
541	if err := c.recordServiceProtocol(routerID); err != nil {
542		return err
543	}
544
545	for i := range router.Routes {
546		// We don't use range variables here because we'll take the address of
547		// this route and store that in a DiscoveryGraphNode and the range
548		// variables share memory addresses between iterations which is exactly
549		// wrong for us here.
550		route := router.Routes[i]
551
552		compiledRoute := &structs.DiscoveryRoute{Definition: &route}
553		routeNode.Routes = append(routeNode.Routes, compiledRoute)
554
555		dest := route.Destination
556		if dest == nil {
557			dest = &structs.ServiceRouteDestination{
558				Service:   c.serviceName,
559				Namespace: router.NamespaceOrDefault(),
560			}
561		}
562		svc := defaultIfEmpty(dest.Service, c.serviceName)
563		destNamespace := defaultIfEmpty(dest.Namespace, router.NamespaceOrDefault())
564
565		// Check to see if the destination is eligible for splitting.
566		var (
567			node *structs.DiscoveryGraphNode
568			err  error
569		)
570		if dest.ServiceSubset == "" {
571			node, err = c.getSplitterOrResolverNode(
572				c.newTarget(svc, "", destNamespace, ""),
573			)
574		} else {
575			node, err = c.getResolverNode(
576				c.newTarget(svc, dest.ServiceSubset, destNamespace, ""),
577				false,
578			)
579		}
580		if err != nil {
581			return err
582		}
583		compiledRoute.NextNode = node.MapKey()
584	}
585
586	// If we have a router, we'll add a catch-all route at the end to send
587	// unmatched traffic to the next hop in the chain.
588	defaultDestinationNode, err := c.getSplitterOrResolverNode(c.newTarget(router.Name, "", router.NamespaceOrDefault(), ""))
589	if err != nil {
590		return err
591	}
592
593	defaultRoute := &structs.DiscoveryRoute{
594		Definition: newDefaultServiceRoute(router.Name, router.NamespaceOrDefault()),
595		NextNode:   defaultDestinationNode.MapKey(),
596	}
597	routeNode.Routes = append(routeNode.Routes, defaultRoute)
598
599	c.startNode = routeNode.MapKey()
600	c.recordNode(routeNode)
601
602	return nil
603}
604
605func newDefaultServiceRoute(serviceName string, namespace string) *structs.ServiceRoute {
606	return &structs.ServiceRoute{
607		Match: &structs.ServiceRouteMatch{
608			HTTP: &structs.ServiceRouteHTTPMatch{
609				PathPrefix: "/",
610			},
611		},
612		Destination: &structs.ServiceRouteDestination{
613			Service:   serviceName,
614			Namespace: namespace,
615		},
616	}
617}
618
619func (c *compiler) newTarget(service, serviceSubset, namespace, datacenter string) *structs.DiscoveryTarget {
620	if service == "" {
621		panic("newTarget called with empty service which makes no sense")
622	}
623
624	t := structs.NewDiscoveryTarget(
625		service,
626		serviceSubset,
627		defaultIfEmpty(namespace, c.evaluateInNamespace),
628		defaultIfEmpty(datacenter, c.evaluateInDatacenter),
629	)
630
631	// Set default connect SNI. This will be overridden later if the service
632	// has an explicit SNI value configured in service-defaults.
633	t.SNI = connect.TargetSNI(t, c.evaluateInTrustDomain)
634
635	// Use the same representation for the name. This will NOT be overridden
636	// later.
637	t.Name = t.SNI
638
639	prev, ok := c.loadedTargets[t.ID]
640	if ok {
641		return prev
642	}
643	c.loadedTargets[t.ID] = t
644	return t
645}
646
647func (c *compiler) rewriteTarget(t *structs.DiscoveryTarget, service, serviceSubset, namespace, datacenter string) *structs.DiscoveryTarget {
648	var (
649		service2       = t.Service
650		serviceSubset2 = t.ServiceSubset
651		namespace2     = t.Namespace
652		datacenter2    = t.Datacenter
653	)
654
655	if service != "" && service != service2 {
656		service2 = service
657		// Reset the chosen subset if we reference a service other than our own.
658		serviceSubset2 = ""
659	}
660	if serviceSubset != "" {
661		serviceSubset2 = serviceSubset
662	}
663	if namespace != "" {
664		namespace2 = namespace
665	}
666	if datacenter != "" {
667		datacenter2 = datacenter
668	}
669
670	return c.newTarget(service2, serviceSubset2, namespace2, datacenter2)
671}
672
673func (c *compiler) getSplitterOrResolverNode(target *structs.DiscoveryTarget) (*structs.DiscoveryGraphNode, error) {
674	nextNode, err := c.getSplitterNode(target.ServiceID())
675	if err != nil {
676		return nil, err
677	} else if nextNode != nil {
678		return nextNode, nil
679	}
680	return c.getResolverNode(target, false)
681}
682
683func (c *compiler) getSplitterNode(sid structs.ServiceID) (*structs.DiscoveryGraphNode, error) {
684	name := serviceIDString(sid)
685	// Do we already have the node?
686	if prev, ok := c.splitterNodes[name]; ok {
687		return prev, nil
688	}
689
690	// Fetch the config entry.
691	splitter := c.entries.GetSplitter(sid)
692	if splitter != nil && c.disableAdvancedRoutingFeatures {
693		splitter = nil
694		c.customizedBy.Protocol = true
695	}
696	if splitter == nil {
697		return nil, nil
698	}
699
700	// Build node.
701	splitNode := &structs.DiscoveryGraphNode{
702		Type:   structs.DiscoveryGraphNodeTypeSplitter,
703		Name:   name,
704		Splits: make([]*structs.DiscoverySplit, 0, len(splitter.Splits)),
705	}
706
707	// If we record this exists before recursing down it will short-circuit
708	// sanely if there is some sort of graph loop below.
709	c.recordNode(splitNode)
710
711	var hasLB bool
712	for _, split := range splitter.Splits {
713		compiledSplit := &structs.DiscoverySplit{
714			Weight: split.Weight,
715		}
716		splitNode.Splits = append(splitNode.Splits, compiledSplit)
717
718		svc := defaultIfEmpty(split.Service, sid.ID)
719		splitID := structs.ServiceID{
720			ID:             svc,
721			EnterpriseMeta: *split.GetEnterpriseMeta(&sid.EnterpriseMeta),
722		}
723
724		// Check to see if the split is eligible for additional splitting.
725		if !splitID.Matches(sid) && split.ServiceSubset == "" {
726			nextNode, err := c.getSplitterNode(splitID)
727			if err != nil {
728				return nil, err
729			} else if nextNode != nil {
730				compiledSplit.NextNode = nextNode.MapKey()
731				continue
732			}
733			// fall through to group-resolver
734		}
735
736		node, err := c.getResolverNode(
737			c.newTarget(splitID.ID, split.ServiceSubset, splitID.NamespaceOrDefault(), ""),
738			false,
739		)
740		if err != nil {
741			return nil, err
742		}
743		compiledSplit.NextNode = node.MapKey()
744
745		// There exists the possibility that a splitter may split between two distinct service names
746		// with distinct hash-based load balancer configs specified in their service resolvers.
747		// We cannot apply multiple hash policies to a splitter node's route action.
748		// Therefore, we attach the first hash-based load balancer config we encounter.
749		if !hasLB {
750			if lb := node.LoadBalancer; lb != nil && lb.IsHashBased() {
751				splitNode.LoadBalancer = node.LoadBalancer
752				hasLB = true
753			}
754		}
755	}
756
757	c.usesAdvancedRoutingFeatures = true
758	return splitNode, nil
759}
760
761// getResolverNode handles most of the code to handle redirection/rewriting
762// capabilities from a resolver config entry. It recurses into itself to
763// _generate_ targets used for failover out of convenience.
764func (c *compiler) getResolverNode(target *structs.DiscoveryTarget, recursedForFailover bool) (*structs.DiscoveryGraphNode, error) {
765	var (
766		// State to help detect redirect cycles and print helpful error
767		// messages.
768		redirectHistory = make(map[string]struct{})
769		redirectOrder   []string
770	)
771
772RESOLVE_AGAIN:
773	// Do we already have the node?
774	if prev, ok := c.resolveNodes[target.ID]; ok {
775		return prev, nil
776	}
777
778	targetID := target.ServiceID()
779
780	if err := c.recordServiceProtocol(targetID); err != nil {
781		return nil, err
782	}
783
784	// Fetch the config entry.
785	resolver, ok := c.resolvers[targetID]
786	if !ok {
787		// Materialize defaults and cache.
788		resolver = newDefaultServiceResolver(targetID)
789		c.resolvers[targetID] = resolver
790	}
791
792	if _, ok := redirectHistory[target.ID]; ok {
793		redirectOrder = append(redirectOrder, target.ID)
794
795		return nil, &structs.ConfigEntryGraphError{
796			Message: fmt.Sprintf(
797				"detected circular resolver redirect: [%s]",
798				strings.Join(redirectOrder, " -> "),
799			),
800		}
801	}
802	redirectHistory[target.ID] = struct{}{}
803	redirectOrder = append(redirectOrder, target.ID)
804
805	// Handle redirects right up front.
806	//
807	// TODO(rb): What about a redirected subset reference? (web/v2, but web redirects to alt/"")
808	if resolver.Redirect != nil {
809		redirect := resolver.Redirect
810
811		redirectedTarget := c.rewriteTarget(
812			target,
813			redirect.Service,
814			redirect.ServiceSubset,
815			redirect.Namespace,
816			redirect.Datacenter,
817		)
818		if redirectedTarget.ID != target.ID {
819			target = redirectedTarget
820			goto RESOLVE_AGAIN
821		}
822	}
823
824	// Handle default subset.
825	if target.ServiceSubset == "" && resolver.DefaultSubset != "" {
826		target = c.rewriteTarget(
827			target,
828			"",
829			resolver.DefaultSubset,
830			"",
831			"",
832		)
833		goto RESOLVE_AGAIN
834	}
835
836	if target.ServiceSubset != "" && !resolver.SubsetExists(target.ServiceSubset) {
837		return nil, &structs.ConfigEntryGraphError{
838			Message: fmt.Sprintf(
839				"service %q does not have a subset named %q",
840				target.Service,
841				target.ServiceSubset,
842			),
843		}
844	}
845
846	connectTimeout := resolver.ConnectTimeout
847	if connectTimeout < 1 {
848		connectTimeout = 5 * time.Second
849	}
850
851	if c.overrideConnectTimeout > 0 {
852		if connectTimeout != c.overrideConnectTimeout {
853			connectTimeout = c.overrideConnectTimeout
854			c.customizedBy.ConnectTimeout = true
855		}
856	}
857
858	// Build node.
859	node := &structs.DiscoveryGraphNode{
860		Type: structs.DiscoveryGraphNodeTypeResolver,
861		Name: target.ID,
862		Resolver: &structs.DiscoveryResolver{
863			Default:        resolver.IsDefault(),
864			Target:         target.ID,
865			ConnectTimeout: connectTimeout,
866		},
867		LoadBalancer: resolver.LoadBalancer,
868	}
869
870	target.Subset = resolver.Subsets[target.ServiceSubset]
871
872	if serviceDefault := c.entries.GetService(targetID); serviceDefault != nil && serviceDefault.ExternalSNI != "" {
873		// Override the default SNI value.
874		target.SNI = serviceDefault.ExternalSNI
875		target.External = true
876	}
877
878	// If using external SNI the service is fundamentally external.
879	if target.External {
880		if resolver.Redirect != nil {
881			return nil, &structs.ConfigEntryGraphError{
882				Message: fmt.Sprintf(
883					"service %q has an external SNI set; cannot define redirects for external services",
884					target.Service,
885				),
886			}
887		}
888		if len(resolver.Subsets) > 0 {
889			return nil, &structs.ConfigEntryGraphError{
890				Message: fmt.Sprintf(
891					"service %q has an external SNI set; cannot define subsets for external services",
892					target.Service,
893				),
894			}
895		}
896		if len(resolver.Failover) > 0 {
897			return nil, &structs.ConfigEntryGraphError{
898				Message: fmt.Sprintf(
899					"service %q has an external SNI set; cannot define failover for external services",
900					target.Service,
901				),
902			}
903		}
904	}
905
906	// TODO (mesh-gateway)- maybe allow using a gateway within a datacenter at some point
907	if target.Datacenter == c.useInDatacenter {
908		target.MeshGateway.Mode = structs.MeshGatewayModeDefault
909
910	} else if target.External {
911		// Bypass mesh gateways if it is an external service.
912		target.MeshGateway.Mode = structs.MeshGatewayModeDefault
913
914	} else {
915		// Default mesh gateway settings
916		if serviceDefault := c.entries.GetService(targetID); serviceDefault != nil {
917			target.MeshGateway = serviceDefault.MeshGateway
918		}
919
920		if c.entries.GlobalProxy != nil && target.MeshGateway.Mode == structs.MeshGatewayModeDefault {
921			target.MeshGateway.Mode = c.entries.GlobalProxy.MeshGateway.Mode
922		}
923
924		if c.overrideMeshGateway.Mode != structs.MeshGatewayModeDefault {
925			if target.MeshGateway.Mode != c.overrideMeshGateway.Mode {
926				target.MeshGateway.Mode = c.overrideMeshGateway.Mode
927				c.customizedBy.MeshGateway = true
928			}
929		}
930	}
931
932	// Retain this target in the final results.
933	c.retainedTargets[target.ID] = struct{}{}
934
935	if recursedForFailover {
936		// If we recursed here from ourselves in a failover context, just emit
937		// this node without caching it or even processing failover again.
938		// This is a little weird but it keeps the redirect/default-subset
939		// logic in one place.
940		return node, nil
941	}
942
943	// If we record this exists before recursing down it will short-circuit
944	// sanely if there is some sort of graph loop below.
945	c.recordNode(node)
946
947	if len(resolver.Failover) > 0 {
948		f := resolver.Failover
949
950		// Determine which failover section applies.
951		failover, ok := f[target.ServiceSubset]
952		if !ok {
953			failover, ok = f["*"]
954		}
955
956		if ok {
957			// Determine which failover definitions apply.
958			var failoverTargets []*structs.DiscoveryTarget
959			if len(failover.Datacenters) > 0 {
960				for _, dc := range failover.Datacenters {
961					// Rewrite the target as per the failover policy.
962					failoverTarget := c.rewriteTarget(
963						target,
964						failover.Service,
965						failover.ServiceSubset,
966						failover.Namespace,
967						dc,
968					)
969					if failoverTarget.ID != target.ID { // don't failover to yourself
970						failoverTargets = append(failoverTargets, failoverTarget)
971					}
972				}
973			} else {
974				// Rewrite the target as per the failover policy.
975				failoverTarget := c.rewriteTarget(
976					target,
977					failover.Service,
978					failover.ServiceSubset,
979					failover.Namespace,
980					"",
981				)
982				if failoverTarget.ID != target.ID { // don't failover to yourself
983					failoverTargets = append(failoverTargets, failoverTarget)
984				}
985			}
986
987			// If we filtered everything out then no point in having a failover.
988			if len(failoverTargets) > 0 {
989				df := &structs.DiscoveryFailover{}
990				node.Resolver.Failover = df
991
992				// Take care of doing any redirects or configuration loading
993				// related to targets by cheating a bit and recursing into
994				// ourselves.
995				for _, target := range failoverTargets {
996					failoverResolveNode, err := c.getResolverNode(target, true)
997					if err != nil {
998						return nil, err
999					}
1000					failoverTarget := failoverResolveNode.Resolver.Target
1001					df.Targets = append(df.Targets, failoverTarget)
1002				}
1003			}
1004		}
1005	}
1006
1007	return node, nil
1008}
1009
1010func newDefaultServiceResolver(sid structs.ServiceID) *structs.ServiceResolverConfigEntry {
1011	return &structs.ServiceResolverConfigEntry{
1012		Kind:           structs.ServiceResolver,
1013		Name:           sid.ID,
1014		EnterpriseMeta: sid.EnterpriseMeta,
1015	}
1016}
1017
1018func defaultIfEmpty(val, defaultVal string) string {
1019	if val != "" {
1020		return val
1021	}
1022	return defaultVal
1023}
1024
1025func enableAdvancedRoutingForProtocol(protocol string) bool {
1026	return structs.IsProtocolHTTPLike(protocol)
1027}
1028