1package discoverychain
2
3import (
4	"testing"
5	"time"
6
7	"github.com/hashicorp/consul/agent/connect"
8	"github.com/hashicorp/consul/agent/structs"
9	"github.com/stretchr/testify/require"
10)
11
12type compileTestCase struct {
13	entries *structs.DiscoveryChainConfigEntries
14	setup   func(req *CompileRequest)
15	expect  *structs.CompiledDiscoveryChain
16	// expectIsDefault tests behavior of CompiledDiscoveryChain.IsDefault()
17	expectIsDefault bool
18	expectCustom    bool
19	expectErr       string
20	expectGraphErr  bool
21}
22
23func TestCompile(t *testing.T) {
24	t.Parallel()
25
26	cases := map[string]compileTestCase{
27		"router with defaults":                             testcase_JustRouterWithDefaults(),
28		"router with defaults and resolver":                testcase_RouterWithDefaults_NoSplit_WithResolver(),
29		"router with defaults and noop split":              testcase_RouterWithDefaults_WithNoopSplit_DefaultResolver(),
30		"router with defaults and noop split and resolver": testcase_RouterWithDefaults_WithNoopSplit_WithResolver(),
31		"router with no destination":                       testcase_JustRouterWithNoDestination(),
32		"route bypasses splitter":                          testcase_RouteBypassesSplit(),
33		"noop split":                                       testcase_NoopSplit_DefaultResolver(),
34		"noop split with protocol from proxy defaults":     testcase_NoopSplit_DefaultResolver_ProtocolFromProxyDefaults(),
35		"noop split with resolver":                         testcase_NoopSplit_WithResolver(),
36		"subset split":                                     testcase_SubsetSplit(),
37		"service split":                                    testcase_ServiceSplit(),
38		"split bypasses next splitter":                     testcase_SplitBypassesSplit(),
39		"service redirect":                                 testcase_ServiceRedirect(),
40		"service and subset redirect":                      testcase_ServiceAndSubsetRedirect(),
41		"datacenter redirect":                              testcase_DatacenterRedirect(),
42		"datacenter redirect with mesh gateways":           testcase_DatacenterRedirect_WithMeshGateways(),
43		"service failover":                                 testcase_ServiceFailover(),
44		"service failover through redirect":                testcase_ServiceFailoverThroughRedirect(),
45		"circular resolver failover":                       testcase_Resolver_CircularFailover(),
46		"service and subset failover":                      testcase_ServiceAndSubsetFailover(),
47		"datacenter failover":                              testcase_DatacenterFailover(),
48		"datacenter failover with mesh gateways":           testcase_DatacenterFailover_WithMeshGateways(),
49		"noop split to resolver with default subset":       testcase_NoopSplit_WithDefaultSubset(),
50		"resolver with default subset":                     testcase_Resolve_WithDefaultSubset(),
51		"default resolver with external sni":               testcase_DefaultResolver_ExternalSNI(),
52		"resolver with no entries and inferring defaults":  testcase_DefaultResolver(),
53		"default resolver with proxy defaults":             testcase_DefaultResolver_WithProxyDefaults(),
54		"loadbalancer splitter and resolver":               testcase_LBSplitterAndResolver(),
55		"loadbalancer resolver":                            testcase_LBResolver(),
56		"service redirect to service with default resolver is not a default chain": testcase_RedirectToDefaultResolverIsNotDefaultChain(),
57
58		"all the bells and whistles": testcase_AllBellsAndWhistles(),
59		"multi dc canary":            testcase_MultiDatacenterCanary(),
60
61		// various errors
62		"splitter requires valid protocol":        testcase_SplitterRequiresValidProtocol(),
63		"router requires valid protocol":          testcase_RouterRequiresValidProtocol(),
64		"split to unsplittable protocol":          testcase_SplitToUnsplittableProtocol(),
65		"route to unroutable protocol":            testcase_RouteToUnroutableProtocol(),
66		"failover crosses protocols":              testcase_FailoverCrossesProtocols(),
67		"redirect crosses protocols":              testcase_RedirectCrossesProtocols(),
68		"redirect to missing subset":              testcase_RedirectToMissingSubset(),
69		"resolver with failover and external sni": testcase_Resolver_ExternalSNI_FailoverNotAllowed(),
70		"resolver with subsets and external sni":  testcase_Resolver_ExternalSNI_SubsetsNotAllowed(),
71		"resolver with redirect and external sni": testcase_Resolver_ExternalSNI_RedirectNotAllowed(),
72
73		// overrides
74		"resolver with protocol from override":         testcase_ResolverProtocolOverride(),
75		"resolver with protocol from override ignored": testcase_ResolverProtocolOverrideIgnored(),
76		"router ignored due to protocol override":      testcase_RouterIgnored_ResolverProtocolOverride(),
77
78		// circular references
79		"circular resolver redirect": testcase_Resolver_CircularRedirect(),
80		"circular split":             testcase_CircularSplit(),
81	}
82
83	for name, tc := range cases {
84		tc := tc
85		t.Run(name, func(t *testing.T) {
86			t.Parallel()
87
88			// sanity check entries are normalized and valid
89			for _, entry := range tc.entries.Routers {
90				require.NoError(t, entry.Normalize())
91				require.NoError(t, entry.Validate())
92			}
93			for _, entry := range tc.entries.Splitters {
94				require.NoError(t, entry.Normalize())
95				require.NoError(t, entry.Validate())
96			}
97			for _, entry := range tc.entries.Resolvers {
98				require.NoError(t, entry.Normalize())
99				require.NoError(t, entry.Validate())
100			}
101
102			req := CompileRequest{
103				ServiceName:           "main",
104				EvaluateInNamespace:   "default",
105				EvaluateInDatacenter:  "dc1",
106				EvaluateInTrustDomain: "trustdomain.consul",
107				UseInDatacenter:       "dc1",
108				Entries:               tc.entries,
109			}
110			if tc.setup != nil {
111				tc.setup(&req)
112			}
113
114			res, err := Compile(req)
115			if tc.expectErr != "" {
116				require.Error(t, err)
117				require.Contains(t, err.Error(), tc.expectErr)
118				_, ok := err.(*structs.ConfigEntryGraphError)
119				if tc.expectGraphErr {
120					require.True(t, ok, "%T is not a *ConfigEntryGraphError", err)
121				} else {
122					require.False(t, ok, "did not expect a *ConfigEntryGraphError here: %v", err)
123				}
124			} else {
125				require.NoError(t, err)
126
127				// Avoid requiring unnecessary test boilerplate and inject these
128				// ourselves.
129				tc.expect.ServiceName = "main"
130				tc.expect.Namespace = "default"
131				tc.expect.Datacenter = "dc1"
132
133				if tc.expectCustom {
134					require.NotEmpty(t, res.CustomizationHash)
135					res.CustomizationHash = ""
136				} else {
137					require.Empty(t, res.CustomizationHash)
138				}
139
140				require.Equal(t, tc.expect, res)
141				require.Equal(t, tc.expectIsDefault, res.IsDefault())
142			}
143		})
144	}
145}
146
147func testcase_JustRouterWithDefaults() compileTestCase {
148	entries := newEntries()
149	setServiceProtocol(entries, "main", "http")
150
151	entries.AddRouters(
152		&structs.ServiceRouterConfigEntry{
153			Kind: "service-router",
154			Name: "main",
155		},
156	)
157
158	expect := &structs.CompiledDiscoveryChain{
159		Protocol:  "http",
160		StartNode: "router:main.default",
161		Nodes: map[string]*structs.DiscoveryGraphNode{
162			"router:main.default": {
163				Type: structs.DiscoveryGraphNodeTypeRouter,
164				Name: "main.default",
165				Routes: []*structs.DiscoveryRoute{
166					{
167						Definition: newDefaultServiceRoute("main", "default"),
168						NextNode:   "resolver:main.default.dc1",
169					},
170				},
171			},
172			"resolver:main.default.dc1": {
173				Type: structs.DiscoveryGraphNodeTypeResolver,
174				Name: "main.default.dc1",
175				Resolver: &structs.DiscoveryResolver{
176					Default:        true,
177					ConnectTimeout: 5 * time.Second,
178					Target:         "main.default.dc1",
179				},
180			},
181		},
182		Targets: map[string]*structs.DiscoveryTarget{
183			"main.default.dc1": newTarget("main", "", "default", "dc1", nil),
184		},
185	}
186
187	return compileTestCase{entries: entries, expect: expect}
188}
189
190func testcase_JustRouterWithNoDestination() compileTestCase {
191	entries := newEntries()
192	setServiceProtocol(entries, "main", "http")
193
194	entries.AddRouters(
195		&structs.ServiceRouterConfigEntry{
196			Kind: "service-router",
197			Name: "main",
198			Routes: []structs.ServiceRoute{
199				{
200					Match: &structs.ServiceRouteMatch{
201						HTTP: &structs.ServiceRouteHTTPMatch{
202							PathPrefix: "/",
203						},
204					},
205				},
206			},
207		},
208	)
209
210	expect := &structs.CompiledDiscoveryChain{
211		Protocol:  "http",
212		StartNode: "router:main.default",
213		Nodes: map[string]*structs.DiscoveryGraphNode{
214			"router:main.default": {
215				Type: structs.DiscoveryGraphNodeTypeRouter,
216				Name: "main.default",
217				Routes: []*structs.DiscoveryRoute{
218					{
219						Definition: &structs.ServiceRoute{
220							Match: &structs.ServiceRouteMatch{
221								HTTP: &structs.ServiceRouteHTTPMatch{
222									PathPrefix: "/",
223								},
224							},
225						},
226						NextNode: "resolver:main.default.dc1",
227					},
228					{
229						Definition: newDefaultServiceRoute("main", "default"),
230						NextNode:   "resolver:main.default.dc1",
231					},
232				},
233			},
234			"resolver:main.default.dc1": {
235				Type: structs.DiscoveryGraphNodeTypeResolver,
236				Name: "main.default.dc1",
237				Resolver: &structs.DiscoveryResolver{
238					Default:        true,
239					ConnectTimeout: 5 * time.Second,
240					Target:         "main.default.dc1",
241				},
242			},
243		},
244		Targets: map[string]*structs.DiscoveryTarget{
245			"main.default.dc1": newTarget("main", "", "default", "dc1", nil),
246		},
247	}
248
249	return compileTestCase{entries: entries, expect: expect}
250}
251
252func testcase_RouterWithDefaults_NoSplit_WithResolver() compileTestCase {
253	entries := newEntries()
254	setServiceProtocol(entries, "main", "http")
255
256	entries.AddRouters(
257		&structs.ServiceRouterConfigEntry{
258			Kind: "service-router",
259			Name: "main",
260		},
261	)
262	entries.AddResolvers(
263		&structs.ServiceResolverConfigEntry{
264			Kind:           "service-resolver",
265			Name:           "main",
266			ConnectTimeout: 33 * time.Second,
267		},
268	)
269
270	expect := &structs.CompiledDiscoveryChain{
271		Protocol:  "http",
272		StartNode: "router:main.default",
273		Nodes: map[string]*structs.DiscoveryGraphNode{
274			"router:main.default": {
275				Type: structs.DiscoveryGraphNodeTypeRouter,
276				Name: "main.default",
277				Routes: []*structs.DiscoveryRoute{
278					{
279						Definition: newDefaultServiceRoute("main", "default"),
280						NextNode:   "resolver:main.default.dc1",
281					},
282				},
283			},
284			"resolver:main.default.dc1": {
285				Type: structs.DiscoveryGraphNodeTypeResolver,
286				Name: "main.default.dc1",
287				Resolver: &structs.DiscoveryResolver{
288					ConnectTimeout: 33 * time.Second,
289					Target:         "main.default.dc1",
290				},
291			},
292		},
293		Targets: map[string]*structs.DiscoveryTarget{
294			"main.default.dc1": newTarget("main", "", "default", "dc1", nil),
295		},
296	}
297
298	return compileTestCase{entries: entries, expect: expect}
299}
300
301func testcase_RouterWithDefaults_WithNoopSplit_DefaultResolver() compileTestCase {
302	entries := newEntries()
303	setServiceProtocol(entries, "main", "http")
304
305	entries.AddRouters(
306		&structs.ServiceRouterConfigEntry{
307			Kind: "service-router",
308			Name: "main",
309		},
310	)
311	entries.AddSplitters(
312		&structs.ServiceSplitterConfigEntry{
313			Kind: "service-splitter",
314			Name: "main",
315			Splits: []structs.ServiceSplit{
316				{Weight: 100},
317			},
318		},
319	)
320
321	expect := &structs.CompiledDiscoveryChain{
322		Protocol:  "http",
323		StartNode: "router:main.default",
324		Nodes: map[string]*structs.DiscoveryGraphNode{
325			"router:main.default": {
326				Type: structs.DiscoveryGraphNodeTypeRouter,
327				Name: "main.default",
328				Routes: []*structs.DiscoveryRoute{
329					{
330						Definition: newDefaultServiceRoute("main", "default"),
331						NextNode:   "splitter:main.default",
332					},
333				},
334			},
335			"splitter:main.default": {
336				Type: structs.DiscoveryGraphNodeTypeSplitter,
337				Name: "main.default",
338				Splits: []*structs.DiscoverySplit{
339					{
340						Weight:   100,
341						NextNode: "resolver:main.default.dc1",
342					},
343				},
344			},
345			"resolver:main.default.dc1": {
346				Type: structs.DiscoveryGraphNodeTypeResolver,
347				Name: "main.default.dc1",
348				Resolver: &structs.DiscoveryResolver{
349					Default:        true,
350					ConnectTimeout: 5 * time.Second,
351					Target:         "main.default.dc1",
352				},
353			},
354		},
355		Targets: map[string]*structs.DiscoveryTarget{
356			"main.default.dc1": newTarget("main", "", "default", "dc1", nil),
357		},
358	}
359
360	return compileTestCase{entries: entries, expect: expect}
361}
362
363func testcase_NoopSplit_DefaultResolver_ProtocolFromProxyDefaults() compileTestCase {
364	entries := newEntries()
365	setGlobalProxyProtocol(entries, "http")
366
367	entries.AddRouters(
368		&structs.ServiceRouterConfigEntry{
369			Kind: "service-router",
370			Name: "main",
371		},
372	)
373	entries.AddSplitters(
374		&structs.ServiceSplitterConfigEntry{
375			Kind: "service-splitter",
376			Name: "main",
377			Splits: []structs.ServiceSplit{
378				{Weight: 100},
379			},
380		},
381	)
382
383	expect := &structs.CompiledDiscoveryChain{
384		Protocol:  "http",
385		StartNode: "router:main.default",
386		Nodes: map[string]*structs.DiscoveryGraphNode{
387			"router:main.default": {
388				Type: structs.DiscoveryGraphNodeTypeRouter,
389				Name: "main.default",
390				Routes: []*structs.DiscoveryRoute{
391					{
392						Definition: newDefaultServiceRoute("main", "default"),
393						NextNode:   "splitter:main.default",
394					},
395				},
396			},
397			"splitter:main.default": {
398				Type: structs.DiscoveryGraphNodeTypeSplitter,
399				Name: "main.default",
400				Splits: []*structs.DiscoverySplit{
401					{
402						Weight:   100,
403						NextNode: "resolver:main.default.dc1",
404					},
405				},
406			},
407			"resolver:main.default.dc1": {
408				Type: structs.DiscoveryGraphNodeTypeResolver,
409				Name: "main.default.dc1",
410				Resolver: &structs.DiscoveryResolver{
411					Default:        true,
412					ConnectTimeout: 5 * time.Second,
413					Target:         "main.default.dc1",
414				},
415			},
416		},
417		Targets: map[string]*structs.DiscoveryTarget{
418			"main.default.dc1": newTarget("main", "", "default", "dc1", nil),
419		},
420	}
421
422	return compileTestCase{entries: entries, expect: expect}
423}
424
425func testcase_RouterWithDefaults_WithNoopSplit_WithResolver() compileTestCase {
426	entries := newEntries()
427	setServiceProtocol(entries, "main", "http")
428
429	entries.AddRouters(
430		&structs.ServiceRouterConfigEntry{
431			Kind: "service-router",
432			Name: "main",
433		},
434	)
435	entries.AddSplitters(
436		&structs.ServiceSplitterConfigEntry{
437			Kind: "service-splitter",
438			Name: "main",
439			Splits: []structs.ServiceSplit{
440				{Weight: 100},
441			},
442		},
443	)
444	entries.AddResolvers(
445		&structs.ServiceResolverConfigEntry{
446			Kind:           "service-resolver",
447			Name:           "main",
448			ConnectTimeout: 33 * time.Second,
449		},
450	)
451
452	expect := &structs.CompiledDiscoveryChain{
453		Protocol:  "http",
454		StartNode: "router:main.default",
455		Nodes: map[string]*structs.DiscoveryGraphNode{
456			"router:main.default": {
457				Type: structs.DiscoveryGraphNodeTypeRouter,
458				Name: "main.default",
459				Routes: []*structs.DiscoveryRoute{
460					{
461						Definition: newDefaultServiceRoute("main", "default"),
462						NextNode:   "splitter:main.default",
463					},
464				},
465			},
466			"splitter:main.default": {
467				Type: structs.DiscoveryGraphNodeTypeSplitter,
468				Name: "main.default",
469				Splits: []*structs.DiscoverySplit{
470					{
471						Weight:   100,
472						NextNode: "resolver:main.default.dc1",
473					},
474				},
475			},
476			"resolver:main.default.dc1": {
477				Type: structs.DiscoveryGraphNodeTypeResolver,
478				Name: "main.default.dc1",
479				Resolver: &structs.DiscoveryResolver{
480					ConnectTimeout: 33 * time.Second,
481					Target:         "main.default.dc1",
482				},
483			},
484		},
485		Targets: map[string]*structs.DiscoveryTarget{
486			"main.default.dc1": newTarget("main", "", "default", "dc1", nil),
487		},
488	}
489
490	return compileTestCase{entries: entries, expect: expect}
491}
492
493func testcase_RouteBypassesSplit() compileTestCase {
494	entries := newEntries()
495	setServiceProtocol(entries, "main", "http")
496	setServiceProtocol(entries, "other", "http")
497
498	entries.AddRouters(
499		&structs.ServiceRouterConfigEntry{
500			Kind: "service-router",
501			Name: "main",
502			Routes: []structs.ServiceRoute{
503				// route direct subset reference (bypass split)
504				newSimpleRoute("other", func(r *structs.ServiceRoute) {
505					r.Destination.ServiceSubset = "bypass"
506				}),
507			},
508		},
509	)
510	entries.AddSplitters(
511		&structs.ServiceSplitterConfigEntry{
512			Kind: "service-splitter",
513			Name: "other",
514			Splits: []structs.ServiceSplit{
515				{Weight: 100, Service: "ignored"},
516			},
517		},
518	)
519	entries.AddResolvers(
520		&structs.ServiceResolverConfigEntry{
521			Kind: "service-resolver",
522			Name: "other",
523			Subsets: map[string]structs.ServiceResolverSubset{
524				"bypass": {
525					Filter: "Service.Meta.version == bypass",
526				},
527			},
528		},
529	)
530
531	router := entries.GetRouter(structs.NewServiceID("main", nil))
532
533	expect := &structs.CompiledDiscoveryChain{
534		Protocol:  "http",
535		StartNode: "router:main.default",
536		Nodes: map[string]*structs.DiscoveryGraphNode{
537			"router:main.default": {
538				Type: structs.DiscoveryGraphNodeTypeRouter,
539				Name: "main.default",
540				Routes: []*structs.DiscoveryRoute{
541					{
542						Definition: &router.Routes[0],
543						NextNode:   "resolver:bypass.other.default.dc1",
544					},
545					{
546						Definition: newDefaultServiceRoute("main", "default"),
547						NextNode:   "resolver:main.default.dc1",
548					},
549				},
550			},
551			"resolver:main.default.dc1": {
552				Type: structs.DiscoveryGraphNodeTypeResolver,
553				Name: "main.default.dc1",
554				Resolver: &structs.DiscoveryResolver{
555					Default:        true,
556					ConnectTimeout: 5 * time.Second,
557					Target:         "main.default.dc1",
558				},
559			},
560			"resolver:bypass.other.default.dc1": {
561				Type: structs.DiscoveryGraphNodeTypeResolver,
562				Name: "bypass.other.default.dc1",
563				Resolver: &structs.DiscoveryResolver{
564					ConnectTimeout: 5 * time.Second,
565					Target:         "bypass.other.default.dc1",
566				},
567			},
568		},
569		Targets: map[string]*structs.DiscoveryTarget{
570			"main.default.dc1": newTarget("main", "", "default", "dc1", nil),
571			"bypass.other.default.dc1": newTarget("other", "bypass", "default", "dc1", func(t *structs.DiscoveryTarget) {
572				t.Subset = structs.ServiceResolverSubset{
573					Filter: "Service.Meta.version == bypass",
574				}
575			}),
576		},
577	}
578
579	return compileTestCase{entries: entries, expect: expect}
580}
581
582func testcase_NoopSplit_DefaultResolver() compileTestCase {
583	entries := newEntries()
584	setServiceProtocol(entries, "main", "http")
585
586	entries.AddSplitters(
587		&structs.ServiceSplitterConfigEntry{
588			Kind: "service-splitter",
589			Name: "main",
590			Splits: []structs.ServiceSplit{
591				{Weight: 100},
592			},
593		},
594	)
595
596	expect := &structs.CompiledDiscoveryChain{
597		Protocol:  "http",
598		StartNode: "splitter:main.default",
599		Nodes: map[string]*structs.DiscoveryGraphNode{
600			"splitter:main.default": {
601				Type: structs.DiscoveryGraphNodeTypeSplitter,
602				Name: "main.default",
603				Splits: []*structs.DiscoverySplit{
604					{
605						Weight:   100,
606						NextNode: "resolver:main.default.dc1",
607					},
608				},
609			},
610			"resolver:main.default.dc1": {
611				Type: structs.DiscoveryGraphNodeTypeResolver,
612				Name: "main.default.dc1",
613				Resolver: &structs.DiscoveryResolver{
614					Default:        true,
615					ConnectTimeout: 5 * time.Second,
616					Target:         "main.default.dc1",
617				},
618			},
619		},
620		Targets: map[string]*structs.DiscoveryTarget{
621			"main.default.dc1": newTarget("main", "", "default", "dc1", nil),
622		},
623	}
624
625	return compileTestCase{entries: entries, expect: expect}
626}
627
628func testcase_NoopSplit_WithResolver() compileTestCase {
629	entries := newEntries()
630	setServiceProtocol(entries, "main", "http")
631
632	entries.AddSplitters(
633		&structs.ServiceSplitterConfigEntry{
634			Kind: "service-splitter",
635			Name: "main",
636			Splits: []structs.ServiceSplit{
637				{Weight: 100},
638			},
639		},
640	)
641	entries.AddResolvers(
642		&structs.ServiceResolverConfigEntry{
643			Kind:           "service-resolver",
644			Name:           "main",
645			ConnectTimeout: 33 * time.Second,
646		},
647	)
648
649	expect := &structs.CompiledDiscoveryChain{
650		Protocol:  "http",
651		StartNode: "splitter:main.default",
652		Nodes: map[string]*structs.DiscoveryGraphNode{
653			"splitter:main.default": {
654				Type: structs.DiscoveryGraphNodeTypeSplitter,
655				Name: "main.default",
656				Splits: []*structs.DiscoverySplit{
657					{
658						Weight:   100,
659						NextNode: "resolver:main.default.dc1",
660					},
661				},
662			},
663			"resolver:main.default.dc1": {
664				Type: structs.DiscoveryGraphNodeTypeResolver,
665				Name: "main.default.dc1",
666				Resolver: &structs.DiscoveryResolver{
667					ConnectTimeout: 33 * time.Second,
668					Target:         "main.default.dc1",
669				},
670			},
671		},
672		Targets: map[string]*structs.DiscoveryTarget{
673			"main.default.dc1": newTarget("main", "", "default", "dc1", nil),
674		},
675	}
676
677	return compileTestCase{entries: entries, expect: expect}
678}
679
680func testcase_SubsetSplit() compileTestCase {
681	entries := newEntries()
682	setServiceProtocol(entries, "main", "http")
683
684	entries.AddSplitters(
685		&structs.ServiceSplitterConfigEntry{
686			Kind: "service-splitter",
687			Name: "main",
688			Splits: []structs.ServiceSplit{
689				{Weight: 60, ServiceSubset: "v2"},
690				{Weight: 40, ServiceSubset: "v1"},
691			},
692		},
693	)
694	entries.AddResolvers(
695		&structs.ServiceResolverConfigEntry{
696			Kind: "service-resolver",
697			Name: "main",
698			Subsets: map[string]structs.ServiceResolverSubset{
699				"v1": {
700					Filter: "Service.Meta.version == 1",
701				},
702				"v2": {
703					Filter: "Service.Meta.version == 2",
704				},
705			},
706		},
707	)
708
709	expect := &structs.CompiledDiscoveryChain{
710		Protocol:  "http",
711		StartNode: "splitter:main.default",
712		Nodes: map[string]*structs.DiscoveryGraphNode{
713			"splitter:main.default": {
714				Type: structs.DiscoveryGraphNodeTypeSplitter,
715				Name: "main.default",
716				Splits: []*structs.DiscoverySplit{
717					{
718						Weight:   60,
719						NextNode: "resolver:v2.main.default.dc1",
720					},
721					{
722						Weight:   40,
723						NextNode: "resolver:v1.main.default.dc1",
724					},
725				},
726			},
727			"resolver:v2.main.default.dc1": {
728				Type: structs.DiscoveryGraphNodeTypeResolver,
729				Name: "v2.main.default.dc1",
730				Resolver: &structs.DiscoveryResolver{
731					ConnectTimeout: 5 * time.Second,
732					Target:         "v2.main.default.dc1",
733				},
734			},
735			"resolver:v1.main.default.dc1": {
736				Type: structs.DiscoveryGraphNodeTypeResolver,
737				Name: "v1.main.default.dc1",
738				Resolver: &structs.DiscoveryResolver{
739					ConnectTimeout: 5 * time.Second,
740					Target:         "v1.main.default.dc1",
741				},
742			},
743		},
744		Targets: map[string]*structs.DiscoveryTarget{
745			"v2.main.default.dc1": newTarget("main", "v2", "default", "dc1", func(t *structs.DiscoveryTarget) {
746				t.Subset = structs.ServiceResolverSubset{
747					Filter: "Service.Meta.version == 2",
748				}
749			}),
750			"v1.main.default.dc1": newTarget("main", "v1", "default", "dc1", func(t *structs.DiscoveryTarget) {
751				t.Subset = structs.ServiceResolverSubset{
752					Filter: "Service.Meta.version == 1",
753				}
754			}),
755		},
756	}
757
758	return compileTestCase{entries: entries, expect: expect}
759}
760
761func testcase_ServiceSplit() compileTestCase {
762	entries := newEntries()
763	setServiceProtocol(entries, "main", "http")
764	setServiceProtocol(entries, "foo", "http")
765	setServiceProtocol(entries, "bar", "http")
766
767	entries.AddSplitters(
768		&structs.ServiceSplitterConfigEntry{
769			Kind: "service-splitter",
770			Name: "main",
771			Splits: []structs.ServiceSplit{
772				{Weight: 60, Service: "foo"},
773				{Weight: 40, Service: "bar"},
774			},
775		},
776	)
777
778	expect := &structs.CompiledDiscoveryChain{
779		Protocol:  "http",
780		StartNode: "splitter:main.default",
781		Nodes: map[string]*structs.DiscoveryGraphNode{
782			"splitter:main.default": {
783				Type: structs.DiscoveryGraphNodeTypeSplitter,
784				Name: "main.default",
785				Splits: []*structs.DiscoverySplit{
786					{
787						Weight:   60,
788						NextNode: "resolver:foo.default.dc1",
789					},
790					{
791						Weight:   40,
792						NextNode: "resolver:bar.default.dc1",
793					},
794				},
795			},
796			"resolver:foo.default.dc1": {
797				Type: structs.DiscoveryGraphNodeTypeResolver,
798				Name: "foo.default.dc1",
799				Resolver: &structs.DiscoveryResolver{
800					Default:        true,
801					ConnectTimeout: 5 * time.Second,
802					Target:         "foo.default.dc1",
803				},
804			},
805			"resolver:bar.default.dc1": {
806				Type: structs.DiscoveryGraphNodeTypeResolver,
807				Name: "bar.default.dc1",
808				Resolver: &structs.DiscoveryResolver{
809					Default:        true,
810					ConnectTimeout: 5 * time.Second,
811					Target:         "bar.default.dc1",
812				},
813			},
814		},
815		Targets: map[string]*structs.DiscoveryTarget{
816			"foo.default.dc1": newTarget("foo", "", "default", "dc1", nil),
817			"bar.default.dc1": newTarget("bar", "", "default", "dc1", nil),
818		},
819	}
820
821	return compileTestCase{entries: entries, expect: expect}
822}
823
824func testcase_SplitBypassesSplit() compileTestCase {
825	entries := newEntries()
826	setServiceProtocol(entries, "main", "http")
827	setServiceProtocol(entries, "next", "http")
828
829	entries.AddSplitters(
830		&structs.ServiceSplitterConfigEntry{
831			Kind: "service-splitter",
832			Name: "main",
833			Splits: []structs.ServiceSplit{
834				{
835					Weight:        100,
836					Service:       "next",
837					ServiceSubset: "bypassed",
838				},
839			},
840		},
841		&structs.ServiceSplitterConfigEntry{
842			Kind: "service-splitter",
843			Name: "next",
844			Splits: []structs.ServiceSplit{
845				{
846					Weight:        100,
847					ServiceSubset: "not-bypassed",
848				},
849			},
850		},
851	)
852	entries.AddResolvers(
853		&structs.ServiceResolverConfigEntry{
854			Kind: "service-resolver",
855			Name: "next",
856			Subsets: map[string]structs.ServiceResolverSubset{
857				"bypassed": {
858					Filter: "Service.Meta.version == bypass",
859				},
860				"not-bypassed": {
861					Filter: "Service.Meta.version != bypass",
862				},
863			},
864		},
865	)
866
867	expect := &structs.CompiledDiscoveryChain{
868		Protocol:  "http",
869		StartNode: "splitter:main.default",
870		Nodes: map[string]*structs.DiscoveryGraphNode{
871			"splitter:main.default": {
872				Type: structs.DiscoveryGraphNodeTypeSplitter,
873				Name: "main.default",
874				Splits: []*structs.DiscoverySplit{
875					{
876						Weight:   100,
877						NextNode: "resolver:bypassed.next.default.dc1",
878					},
879				},
880			},
881			"resolver:bypassed.next.default.dc1": {
882				Type: structs.DiscoveryGraphNodeTypeResolver,
883				Name: "bypassed.next.default.dc1",
884				Resolver: &structs.DiscoveryResolver{
885					ConnectTimeout: 5 * time.Second,
886					Target:         "bypassed.next.default.dc1",
887				},
888			},
889		},
890		Targets: map[string]*structs.DiscoveryTarget{
891			"bypassed.next.default.dc1": newTarget("next", "bypassed", "default", "dc1", func(t *structs.DiscoveryTarget) {
892				t.Subset = structs.ServiceResolverSubset{
893					Filter: "Service.Meta.version == bypass",
894				}
895			}),
896		},
897	}
898
899	return compileTestCase{entries: entries, expect: expect}
900}
901
902func testcase_ServiceRedirect() compileTestCase {
903	entries := newEntries()
904	entries.AddResolvers(
905		&structs.ServiceResolverConfigEntry{
906			Kind: "service-resolver",
907			Name: "main",
908			Redirect: &structs.ServiceResolverRedirect{
909				Service: "other",
910			},
911		},
912	)
913
914	expect := &structs.CompiledDiscoveryChain{
915		Protocol:  "tcp",
916		StartNode: "resolver:other.default.dc1",
917		Nodes: map[string]*structs.DiscoveryGraphNode{
918			"resolver:other.default.dc1": {
919				Type: structs.DiscoveryGraphNodeTypeResolver,
920				Name: "other.default.dc1",
921				Resolver: &structs.DiscoveryResolver{
922					Default:        true,
923					ConnectTimeout: 5 * time.Second,
924					Target:         "other.default.dc1",
925				},
926			},
927		},
928		Targets: map[string]*structs.DiscoveryTarget{
929			"other.default.dc1": newTarget("other", "", "default", "dc1", nil),
930		},
931	}
932
933	return compileTestCase{entries: entries, expect: expect}
934}
935
936func testcase_ServiceAndSubsetRedirect() compileTestCase {
937	entries := newEntries()
938	entries.AddResolvers(
939		&structs.ServiceResolverConfigEntry{
940			Kind: "service-resolver",
941			Name: "main",
942			Redirect: &structs.ServiceResolverRedirect{
943				Service:       "other",
944				ServiceSubset: "v2",
945			},
946		},
947		&structs.ServiceResolverConfigEntry{
948			Kind: "service-resolver",
949			Name: "other",
950			Subsets: map[string]structs.ServiceResolverSubset{
951				"v1": {
952					Filter: "Service.Meta.version == 1",
953				},
954				"v2": {
955					Filter: "Service.Meta.version == 2",
956				},
957			},
958		},
959	)
960
961	expect := &structs.CompiledDiscoveryChain{
962		Protocol:  "tcp",
963		StartNode: "resolver:v2.other.default.dc1",
964		Nodes: map[string]*structs.DiscoveryGraphNode{
965			"resolver:v2.other.default.dc1": {
966				Type: structs.DiscoveryGraphNodeTypeResolver,
967				Name: "v2.other.default.dc1",
968				Resolver: &structs.DiscoveryResolver{
969					ConnectTimeout: 5 * time.Second,
970					Target:         "v2.other.default.dc1",
971				},
972			},
973		},
974		Targets: map[string]*structs.DiscoveryTarget{
975			"v2.other.default.dc1": newTarget("other", "v2", "default", "dc1", func(t *structs.DiscoveryTarget) {
976				t.Subset = structs.ServiceResolverSubset{
977					Filter: "Service.Meta.version == 2",
978				}
979			}),
980		},
981	}
982	return compileTestCase{entries: entries, expect: expect}
983}
984
985func testcase_DatacenterRedirect() compileTestCase {
986	entries := newEntries()
987	entries.AddResolvers(
988		&structs.ServiceResolverConfigEntry{
989			Kind: "service-resolver",
990			Name: "main",
991			Redirect: &structs.ServiceResolverRedirect{
992				Datacenter: "dc9",
993			},
994		},
995	)
996
997	expect := &structs.CompiledDiscoveryChain{
998		Protocol:  "tcp",
999		StartNode: "resolver:main.default.dc9",
1000		Nodes: map[string]*structs.DiscoveryGraphNode{
1001			"resolver:main.default.dc9": {
1002				Type: structs.DiscoveryGraphNodeTypeResolver,
1003				Name: "main.default.dc9",
1004				Resolver: &structs.DiscoveryResolver{
1005					ConnectTimeout: 5 * time.Second,
1006					Target:         "main.default.dc9",
1007				},
1008			},
1009		},
1010		Targets: map[string]*structs.DiscoveryTarget{
1011			"main.default.dc9": newTarget("main", "", "default", "dc9", nil),
1012		},
1013	}
1014	return compileTestCase{entries: entries, expect: expect}
1015}
1016
1017func testcase_DatacenterRedirect_WithMeshGateways() compileTestCase {
1018	entries := newEntries()
1019	entries.GlobalProxy = &structs.ProxyConfigEntry{
1020		Kind: structs.ProxyDefaults,
1021		Name: structs.ProxyConfigGlobal,
1022		MeshGateway: structs.MeshGatewayConfig{
1023			Mode: structs.MeshGatewayModeRemote,
1024		},
1025	}
1026	entries.AddResolvers(
1027		&structs.ServiceResolverConfigEntry{
1028			Kind: "service-resolver",
1029			Name: "main",
1030			Redirect: &structs.ServiceResolverRedirect{
1031				Datacenter: "dc9",
1032			},
1033		},
1034	)
1035
1036	expect := &structs.CompiledDiscoveryChain{
1037		Protocol:  "tcp",
1038		StartNode: "resolver:main.default.dc9",
1039		Nodes: map[string]*structs.DiscoveryGraphNode{
1040			"resolver:main.default.dc9": {
1041				Type: structs.DiscoveryGraphNodeTypeResolver,
1042				Name: "main.default.dc9",
1043				Resolver: &structs.DiscoveryResolver{
1044					ConnectTimeout: 5 * time.Second,
1045					Target:         "main.default.dc9",
1046				},
1047			},
1048		},
1049		Targets: map[string]*structs.DiscoveryTarget{
1050			"main.default.dc9": newTarget("main", "", "default", "dc9", func(t *structs.DiscoveryTarget) {
1051				t.MeshGateway = structs.MeshGatewayConfig{
1052					Mode: structs.MeshGatewayModeRemote,
1053				}
1054			}),
1055		},
1056	}
1057	return compileTestCase{entries: entries, expect: expect}
1058}
1059
1060func testcase_ServiceFailover() compileTestCase {
1061	entries := newEntries()
1062	entries.AddResolvers(
1063		&structs.ServiceResolverConfigEntry{
1064			Kind: "service-resolver",
1065			Name: "main",
1066			Failover: map[string]structs.ServiceResolverFailover{
1067				"*": {Service: "backup"},
1068			},
1069		},
1070	)
1071
1072	expect := &structs.CompiledDiscoveryChain{
1073		Protocol:  "tcp",
1074		StartNode: "resolver:main.default.dc1",
1075		Nodes: map[string]*structs.DiscoveryGraphNode{
1076			"resolver:main.default.dc1": {
1077				Type: structs.DiscoveryGraphNodeTypeResolver,
1078				Name: "main.default.dc1",
1079				Resolver: &structs.DiscoveryResolver{
1080					ConnectTimeout: 5 * time.Second,
1081					Target:         "main.default.dc1",
1082					Failover: &structs.DiscoveryFailover{
1083						Targets: []string{"backup.default.dc1"},
1084					},
1085				},
1086			},
1087		},
1088		Targets: map[string]*structs.DiscoveryTarget{
1089			"main.default.dc1":   newTarget("main", "", "default", "dc1", nil),
1090			"backup.default.dc1": newTarget("backup", "", "default", "dc1", nil),
1091		},
1092	}
1093	return compileTestCase{entries: entries, expect: expect}
1094}
1095
1096func testcase_ServiceFailoverThroughRedirect() compileTestCase {
1097	entries := newEntries()
1098	entries.AddResolvers(
1099		&structs.ServiceResolverConfigEntry{
1100			Kind: "service-resolver",
1101			Name: "backup",
1102			Redirect: &structs.ServiceResolverRedirect{
1103				Service: "actual",
1104			},
1105		},
1106		&structs.ServiceResolverConfigEntry{
1107			Kind: "service-resolver",
1108			Name: "main",
1109			Failover: map[string]structs.ServiceResolverFailover{
1110				"*": {Service: "backup"},
1111			},
1112		},
1113	)
1114
1115	expect := &structs.CompiledDiscoveryChain{
1116		Protocol:  "tcp",
1117		StartNode: "resolver:main.default.dc1",
1118		Nodes: map[string]*structs.DiscoveryGraphNode{
1119			"resolver:main.default.dc1": {
1120				Type: structs.DiscoveryGraphNodeTypeResolver,
1121				Name: "main.default.dc1",
1122				Resolver: &structs.DiscoveryResolver{
1123					ConnectTimeout: 5 * time.Second,
1124					Target:         "main.default.dc1",
1125					Failover: &structs.DiscoveryFailover{
1126						Targets: []string{"actual.default.dc1"},
1127					},
1128				},
1129			},
1130		},
1131		Targets: map[string]*structs.DiscoveryTarget{
1132			"main.default.dc1":   newTarget("main", "", "default", "dc1", nil),
1133			"actual.default.dc1": newTarget("actual", "", "default", "dc1", nil),
1134		},
1135	}
1136	return compileTestCase{entries: entries, expect: expect}
1137}
1138
1139func testcase_Resolver_CircularFailover() compileTestCase {
1140	entries := newEntries()
1141	entries.AddResolvers(
1142		&structs.ServiceResolverConfigEntry{
1143			Kind: "service-resolver",
1144			Name: "backup",
1145			Failover: map[string]structs.ServiceResolverFailover{
1146				"*": {Service: "main"},
1147			},
1148		},
1149		&structs.ServiceResolverConfigEntry{
1150			Kind: "service-resolver",
1151			Name: "main",
1152			Failover: map[string]structs.ServiceResolverFailover{
1153				"*": {Service: "backup"},
1154			},
1155		},
1156	)
1157
1158	expect := &structs.CompiledDiscoveryChain{
1159		Protocol:  "tcp",
1160		StartNode: "resolver:main.default.dc1",
1161		Nodes: map[string]*structs.DiscoveryGraphNode{
1162			"resolver:main.default.dc1": {
1163				Type: structs.DiscoveryGraphNodeTypeResolver,
1164				Name: "main.default.dc1",
1165				Resolver: &structs.DiscoveryResolver{
1166					ConnectTimeout: 5 * time.Second,
1167					Target:         "main.default.dc1",
1168					Failover: &structs.DiscoveryFailover{
1169						Targets: []string{"backup.default.dc1"},
1170					},
1171				},
1172			},
1173		},
1174		Targets: map[string]*structs.DiscoveryTarget{
1175			"main.default.dc1":   newTarget("main", "", "default", "dc1", nil),
1176			"backup.default.dc1": newTarget("backup", "", "default", "dc1", nil),
1177		},
1178	}
1179	return compileTestCase{entries: entries, expect: expect}
1180}
1181
1182func testcase_ServiceAndSubsetFailover() compileTestCase {
1183	entries := newEntries()
1184	entries.AddResolvers(
1185		&structs.ServiceResolverConfigEntry{
1186			Kind: "service-resolver",
1187			Name: "main",
1188			Subsets: map[string]structs.ServiceResolverSubset{
1189				"backup": {
1190					Filter: "Service.Meta.version == backup",
1191				},
1192			},
1193			Failover: map[string]structs.ServiceResolverFailover{
1194				"*": {ServiceSubset: "backup"},
1195			},
1196		},
1197	)
1198
1199	expect := &structs.CompiledDiscoveryChain{
1200		Protocol:  "tcp",
1201		StartNode: "resolver:main.default.dc1",
1202		Nodes: map[string]*structs.DiscoveryGraphNode{
1203			"resolver:main.default.dc1": {
1204				Type: structs.DiscoveryGraphNodeTypeResolver,
1205				Name: "main.default.dc1",
1206				Resolver: &structs.DiscoveryResolver{
1207					ConnectTimeout: 5 * time.Second,
1208					Target:         "main.default.dc1",
1209					Failover: &structs.DiscoveryFailover{
1210						Targets: []string{"backup.main.default.dc1"},
1211					},
1212				},
1213			},
1214		},
1215		Targets: map[string]*structs.DiscoveryTarget{
1216			"main.default.dc1": newTarget("main", "", "default", "dc1", nil),
1217			"backup.main.default.dc1": newTarget("main", "backup", "default", "dc1", func(t *structs.DiscoveryTarget) {
1218				t.Subset = structs.ServiceResolverSubset{
1219					Filter: "Service.Meta.version == backup",
1220				}
1221			}),
1222		},
1223	}
1224	return compileTestCase{entries: entries, expect: expect}
1225}
1226
1227func testcase_DatacenterFailover() compileTestCase {
1228	entries := newEntries()
1229	entries.AddResolvers(
1230		&structs.ServiceResolverConfigEntry{
1231			Kind: "service-resolver",
1232			Name: "main",
1233			Failover: map[string]structs.ServiceResolverFailover{
1234				"*": {Datacenters: []string{"dc2", "dc4"}},
1235			},
1236		},
1237	)
1238
1239	expect := &structs.CompiledDiscoveryChain{
1240		Protocol:  "tcp",
1241		StartNode: "resolver:main.default.dc1",
1242		Nodes: map[string]*structs.DiscoveryGraphNode{
1243			"resolver:main.default.dc1": {
1244				Type: structs.DiscoveryGraphNodeTypeResolver,
1245				Name: "main.default.dc1",
1246				Resolver: &structs.DiscoveryResolver{
1247					ConnectTimeout: 5 * time.Second,
1248					Target:         "main.default.dc1",
1249					Failover: &structs.DiscoveryFailover{
1250						Targets: []string{"main.default.dc2", "main.default.dc4"},
1251					},
1252				},
1253			},
1254		},
1255		Targets: map[string]*structs.DiscoveryTarget{
1256			"main.default.dc1": newTarget("main", "", "default", "dc1", nil),
1257			"main.default.dc2": newTarget("main", "", "default", "dc2", nil),
1258			"main.default.dc4": newTarget("main", "", "default", "dc4", nil),
1259		},
1260	}
1261	return compileTestCase{entries: entries, expect: expect}
1262}
1263
1264func testcase_DatacenterFailover_WithMeshGateways() compileTestCase {
1265	entries := newEntries()
1266	entries.GlobalProxy = &structs.ProxyConfigEntry{
1267		Kind: structs.ProxyDefaults,
1268		Name: structs.ProxyConfigGlobal,
1269		MeshGateway: structs.MeshGatewayConfig{
1270			Mode: structs.MeshGatewayModeRemote,
1271		},
1272	}
1273	entries.AddResolvers(
1274		&structs.ServiceResolverConfigEntry{
1275			Kind: "service-resolver",
1276			Name: "main",
1277			Failover: map[string]structs.ServiceResolverFailover{
1278				"*": {Datacenters: []string{"dc2", "dc4"}},
1279			},
1280		},
1281	)
1282
1283	expect := &structs.CompiledDiscoveryChain{
1284		Protocol:  "tcp",
1285		StartNode: "resolver:main.default.dc1",
1286		Nodes: map[string]*structs.DiscoveryGraphNode{
1287			"resolver:main.default.dc1": {
1288				Type: structs.DiscoveryGraphNodeTypeResolver,
1289				Name: "main.default.dc1",
1290				Resolver: &structs.DiscoveryResolver{
1291					ConnectTimeout: 5 * time.Second,
1292					Target:         "main.default.dc1",
1293					Failover: &structs.DiscoveryFailover{
1294						Targets: []string{
1295							"main.default.dc2",
1296							"main.default.dc4",
1297						},
1298					},
1299				},
1300			},
1301		},
1302		Targets: map[string]*structs.DiscoveryTarget{
1303			"main.default.dc1": newTarget("main", "", "default", "dc1", nil),
1304			"main.default.dc2": newTarget("main", "", "default", "dc2", func(t *structs.DiscoveryTarget) {
1305				t.MeshGateway = structs.MeshGatewayConfig{
1306					Mode: structs.MeshGatewayModeRemote,
1307				}
1308			}),
1309			"main.default.dc4": newTarget("main", "", "default", "dc4", func(t *structs.DiscoveryTarget) {
1310				t.MeshGateway = structs.MeshGatewayConfig{
1311					Mode: structs.MeshGatewayModeRemote,
1312				}
1313			}),
1314		},
1315	}
1316	return compileTestCase{entries: entries, expect: expect}
1317}
1318
1319func testcase_NoopSplit_WithDefaultSubset() compileTestCase {
1320	entries := newEntries()
1321	setServiceProtocol(entries, "main", "http")
1322
1323	entries.AddSplitters(
1324		&structs.ServiceSplitterConfigEntry{
1325			Kind: "service-splitter",
1326			Name: "main",
1327			Splits: []structs.ServiceSplit{
1328				{Weight: 100},
1329			},
1330		},
1331	)
1332	entries.AddResolvers(
1333		&structs.ServiceResolverConfigEntry{
1334			Kind:          "service-resolver",
1335			Name:          "main",
1336			DefaultSubset: "v2",
1337			Subsets: map[string]structs.ServiceResolverSubset{
1338				"v1": {Filter: "Service.Meta.version == 1"},
1339				"v2": {Filter: "Service.Meta.version == 2"},
1340			},
1341		},
1342	)
1343
1344	expect := &structs.CompiledDiscoveryChain{
1345		Protocol:  "http",
1346		StartNode: "splitter:main.default",
1347		Nodes: map[string]*structs.DiscoveryGraphNode{
1348			"splitter:main.default": {
1349				Type: structs.DiscoveryGraphNodeTypeSplitter,
1350				Name: "main.default",
1351				Splits: []*structs.DiscoverySplit{
1352					{
1353						Weight:   100,
1354						NextNode: "resolver:v2.main.default.dc1",
1355					},
1356				},
1357			},
1358			"resolver:v2.main.default.dc1": {
1359				Type: structs.DiscoveryGraphNodeTypeResolver,
1360				Name: "v2.main.default.dc1",
1361				Resolver: &structs.DiscoveryResolver{
1362					ConnectTimeout: 5 * time.Second,
1363					Target:         "v2.main.default.dc1",
1364				},
1365			},
1366		},
1367		Targets: map[string]*structs.DiscoveryTarget{
1368			"v2.main.default.dc1": newTarget("main", "v2", "default", "dc1", func(t *structs.DiscoveryTarget) {
1369				t.Subset = structs.ServiceResolverSubset{
1370					Filter: "Service.Meta.version == 2",
1371				}
1372			}),
1373		},
1374	}
1375	return compileTestCase{entries: entries, expect: expect}
1376}
1377
1378func testcase_DefaultResolver() compileTestCase {
1379	entries := newEntries()
1380
1381	expect := &structs.CompiledDiscoveryChain{
1382		Protocol:  "tcp",
1383		StartNode: "resolver:main.default.dc1",
1384		Nodes: map[string]*structs.DiscoveryGraphNode{
1385			"resolver:main.default.dc1": {
1386				Type: structs.DiscoveryGraphNodeTypeResolver,
1387				Name: "main.default.dc1",
1388				Resolver: &structs.DiscoveryResolver{
1389					Default:        true,
1390					ConnectTimeout: 5 * time.Second,
1391					Target:         "main.default.dc1",
1392				},
1393			},
1394		},
1395		Targets: map[string]*structs.DiscoveryTarget{
1396			// TODO-TARGET
1397			"main.default.dc1": newTarget("main", "", "default", "dc1", nil),
1398		},
1399	}
1400	return compileTestCase{entries: entries, expect: expect, expectIsDefault: true}
1401}
1402
1403func testcase_DefaultResolver_WithProxyDefaults() compileTestCase {
1404	entries := newEntries()
1405	entries.GlobalProxy = &structs.ProxyConfigEntry{
1406		Kind: structs.ProxyDefaults,
1407		Name: structs.ProxyConfigGlobal,
1408		Config: map[string]interface{}{
1409			"protocol": "grpc",
1410		},
1411		MeshGateway: structs.MeshGatewayConfig{
1412			Mode: structs.MeshGatewayModeRemote,
1413		},
1414	}
1415
1416	expect := &structs.CompiledDiscoveryChain{
1417		Protocol:  "grpc",
1418		StartNode: "resolver:main.default.dc1",
1419		Nodes: map[string]*structs.DiscoveryGraphNode{
1420			"resolver:main.default.dc1": {
1421				Type: structs.DiscoveryGraphNodeTypeResolver,
1422				Name: "main.default.dc1",
1423				Resolver: &structs.DiscoveryResolver{
1424					Default:        true,
1425					ConnectTimeout: 5 * time.Second,
1426					Target:         "main.default.dc1",
1427				},
1428			},
1429		},
1430		Targets: map[string]*structs.DiscoveryTarget{
1431			"main.default.dc1": newTarget("main", "", "default", "dc1", nil),
1432		},
1433	}
1434	return compileTestCase{entries: entries, expect: expect, expectIsDefault: true}
1435}
1436
1437func testcase_RedirectToDefaultResolverIsNotDefaultChain() compileTestCase {
1438	entries := newEntries()
1439	entries.AddResolvers(
1440		&structs.ServiceResolverConfigEntry{
1441			Kind: structs.ServiceResolver,
1442			Name: "main",
1443			Redirect: &structs.ServiceResolverRedirect{
1444				Service: "other",
1445			},
1446		},
1447	)
1448
1449	expect := &structs.CompiledDiscoveryChain{
1450		Protocol:  "tcp",
1451		StartNode: "resolver:other.default.dc1",
1452		Nodes: map[string]*structs.DiscoveryGraphNode{
1453			"resolver:other.default.dc1": {
1454				Type: structs.DiscoveryGraphNodeTypeResolver,
1455				Name: "other.default.dc1",
1456				Resolver: &structs.DiscoveryResolver{
1457					Default:        true,
1458					ConnectTimeout: 5 * time.Second,
1459					Target:         "other.default.dc1",
1460				},
1461			},
1462		},
1463		Targets: map[string]*structs.DiscoveryTarget{
1464			"other.default.dc1": newTarget("other", "", "default", "dc1", nil),
1465		},
1466	}
1467
1468	return compileTestCase{entries: entries, expect: expect, expectIsDefault: false /*being explicit here because this is the whole point of this test*/}
1469}
1470
1471func testcase_Resolve_WithDefaultSubset() compileTestCase {
1472	entries := newEntries()
1473	entries.AddResolvers(
1474		&structs.ServiceResolverConfigEntry{
1475			Kind:          "service-resolver",
1476			Name:          "main",
1477			DefaultSubset: "v2",
1478			Subsets: map[string]structs.ServiceResolverSubset{
1479				"v1": {Filter: "Service.Meta.version == 1"},
1480				"v2": {Filter: "Service.Meta.version == 2"},
1481			},
1482		},
1483	)
1484
1485	expect := &structs.CompiledDiscoveryChain{
1486		Protocol:  "tcp",
1487		StartNode: "resolver:v2.main.default.dc1",
1488		Nodes: map[string]*structs.DiscoveryGraphNode{
1489			"resolver:v2.main.default.dc1": {
1490				Type: structs.DiscoveryGraphNodeTypeResolver,
1491				Name: "v2.main.default.dc1",
1492				Resolver: &structs.DiscoveryResolver{
1493					ConnectTimeout: 5 * time.Second,
1494					Target:         "v2.main.default.dc1",
1495				},
1496			},
1497		},
1498		Targets: map[string]*structs.DiscoveryTarget{
1499			"v2.main.default.dc1": newTarget("main", "v2", "default", "dc1", func(t *structs.DiscoveryTarget) {
1500				t.Subset = structs.ServiceResolverSubset{
1501					Filter: "Service.Meta.version == 2",
1502				}
1503			}),
1504		},
1505	}
1506	return compileTestCase{entries: entries, expect: expect}
1507}
1508
1509func testcase_DefaultResolver_ExternalSNI() compileTestCase {
1510	entries := newEntries()
1511	entries.AddServices(&structs.ServiceConfigEntry{
1512		Kind:        structs.ServiceDefaults,
1513		Name:        "main",
1514		ExternalSNI: "main.some.other.service.mesh",
1515	})
1516
1517	expect := &structs.CompiledDiscoveryChain{
1518		Protocol:  "tcp",
1519		StartNode: "resolver:main.default.dc1",
1520		Nodes: map[string]*structs.DiscoveryGraphNode{
1521			"resolver:main.default.dc1": {
1522				Type: structs.DiscoveryGraphNodeTypeResolver,
1523				Name: "main.default.dc1",
1524				Resolver: &structs.DiscoveryResolver{
1525					Default:        true,
1526					ConnectTimeout: 5 * time.Second,
1527					Target:         "main.default.dc1",
1528				},
1529			},
1530		},
1531		Targets: map[string]*structs.DiscoveryTarget{
1532			"main.default.dc1": newTarget("main", "", "default", "dc1", func(t *structs.DiscoveryTarget) {
1533				t.SNI = "main.some.other.service.mesh"
1534				t.External = true
1535			}),
1536		},
1537	}
1538	return compileTestCase{entries: entries, expect: expect, expectIsDefault: true}
1539}
1540
1541func testcase_Resolver_ExternalSNI_FailoverNotAllowed() compileTestCase {
1542	entries := newEntries()
1543	entries.AddServices(&structs.ServiceConfigEntry{
1544		Kind:        structs.ServiceDefaults,
1545		Name:        "main",
1546		ExternalSNI: "main.some.other.service.mesh",
1547	})
1548	entries.AddResolvers(&structs.ServiceResolverConfigEntry{
1549		Kind:           "service-resolver",
1550		Name:           "main",
1551		ConnectTimeout: 33 * time.Second,
1552		Failover: map[string]structs.ServiceResolverFailover{
1553			"*": {Service: "backup"},
1554		},
1555	})
1556
1557	return compileTestCase{
1558		entries:        entries,
1559		expectErr:      `service "main" has an external SNI set; cannot define failover for external services`,
1560		expectGraphErr: true,
1561	}
1562}
1563
1564func testcase_Resolver_ExternalSNI_SubsetsNotAllowed() compileTestCase {
1565	entries := newEntries()
1566	entries.AddServices(&structs.ServiceConfigEntry{
1567		Kind:        structs.ServiceDefaults,
1568		Name:        "main",
1569		ExternalSNI: "main.some.other.service.mesh",
1570	})
1571	entries.AddResolvers(&structs.ServiceResolverConfigEntry{
1572		Kind:           "service-resolver",
1573		Name:           "main",
1574		ConnectTimeout: 33 * time.Second,
1575		Subsets: map[string]structs.ServiceResolverSubset{
1576			"canary": {
1577				Filter: "Service.Meta.version == canary",
1578			},
1579		},
1580	})
1581
1582	return compileTestCase{
1583		entries:        entries,
1584		expectErr:      `service "main" has an external SNI set; cannot define subsets for external services`,
1585		expectGraphErr: true,
1586	}
1587}
1588
1589func testcase_Resolver_ExternalSNI_RedirectNotAllowed() compileTestCase {
1590	entries := newEntries()
1591	entries.AddServices(&structs.ServiceConfigEntry{
1592		Kind:        structs.ServiceDefaults,
1593		Name:        "main",
1594		ExternalSNI: "main.some.other.service.mesh",
1595	})
1596	entries.AddResolvers(&structs.ServiceResolverConfigEntry{
1597		Kind:           "service-resolver",
1598		Name:           "main",
1599		ConnectTimeout: 33 * time.Second,
1600		Redirect: &structs.ServiceResolverRedirect{
1601			Datacenter: "dc2",
1602		},
1603	})
1604
1605	return compileTestCase{
1606		entries:        entries,
1607		expectErr:      `service "main" has an external SNI set; cannot define redirects for external services`,
1608		expectGraphErr: true,
1609	}
1610}
1611
1612func testcase_MultiDatacenterCanary() compileTestCase {
1613	entries := newEntries()
1614	setServiceProtocol(entries, "main", "http")
1615	setServiceProtocol(entries, "main-dc2", "http")
1616	setServiceProtocol(entries, "main-dc3", "http")
1617
1618	entries.AddSplitters(
1619		&structs.ServiceSplitterConfigEntry{
1620			Kind: "service-splitter",
1621			Name: "main",
1622			Splits: []structs.ServiceSplit{
1623				{Weight: 60, Service: "main-dc2"},
1624				{Weight: 40, Service: "main-dc3"},
1625			},
1626		},
1627	)
1628	entries.AddResolvers(
1629		&structs.ServiceResolverConfigEntry{
1630			Kind: "service-resolver",
1631			Name: "main-dc2",
1632			Redirect: &structs.ServiceResolverRedirect{
1633				Service:    "main",
1634				Datacenter: "dc2",
1635			},
1636		},
1637		&structs.ServiceResolverConfigEntry{
1638			Kind: "service-resolver",
1639			Name: "main-dc3",
1640			Redirect: &structs.ServiceResolverRedirect{
1641				Service:    "main",
1642				Datacenter: "dc3",
1643			},
1644		},
1645		&structs.ServiceResolverConfigEntry{
1646			Kind:           "service-resolver",
1647			Name:           "main",
1648			ConnectTimeout: 33 * time.Second,
1649		},
1650	)
1651
1652	expect := &structs.CompiledDiscoveryChain{
1653		Protocol:  "http",
1654		StartNode: "splitter:main.default",
1655		Nodes: map[string]*structs.DiscoveryGraphNode{
1656			"splitter:main.default": {
1657				Type: structs.DiscoveryGraphNodeTypeSplitter,
1658				Name: "main.default",
1659				Splits: []*structs.DiscoverySplit{
1660					{
1661						Weight:   60,
1662						NextNode: "resolver:main.default.dc2",
1663					},
1664					{
1665						Weight:   40,
1666						NextNode: "resolver:main.default.dc3",
1667					},
1668				},
1669			},
1670			"resolver:main.default.dc2": {
1671				Type: structs.DiscoveryGraphNodeTypeResolver,
1672				Name: "main.default.dc2",
1673				Resolver: &structs.DiscoveryResolver{
1674					ConnectTimeout: 33 * time.Second,
1675					Target:         "main.default.dc2",
1676				},
1677			},
1678			"resolver:main.default.dc3": {
1679				Type: structs.DiscoveryGraphNodeTypeResolver,
1680				Name: "main.default.dc3",
1681				Resolver: &structs.DiscoveryResolver{
1682					ConnectTimeout: 33 * time.Second,
1683					Target:         "main.default.dc3",
1684				},
1685			},
1686		},
1687		Targets: map[string]*structs.DiscoveryTarget{
1688			"main.default.dc2": newTarget("main", "", "default", "dc2", nil),
1689			"main.default.dc3": newTarget("main", "", "default", "dc3", nil),
1690		},
1691	}
1692	return compileTestCase{entries: entries, expect: expect}
1693}
1694
1695func testcase_AllBellsAndWhistles() compileTestCase {
1696	entries := newEntries()
1697	setServiceProtocol(entries, "main", "http")
1698	setServiceProtocol(entries, "svc-redirect", "http")
1699	setServiceProtocol(entries, "svc-redirect-again", "http")
1700	setServiceProtocol(entries, "svc-split", "http")
1701	setServiceProtocol(entries, "svc-split-again", "http")
1702	setServiceProtocol(entries, "svc-split-one-more-time", "http")
1703	setServiceProtocol(entries, "redirected", "http")
1704
1705	entries.AddRouters(
1706		&structs.ServiceRouterConfigEntry{
1707			Kind: "service-router",
1708			Name: "main",
1709			Routes: []structs.ServiceRoute{
1710				newSimpleRoute("svc-redirect"), // double redirected to a default subset
1711				newSimpleRoute("svc-split"),    // one split is split further
1712			},
1713		},
1714	)
1715	entries.AddSplitters(
1716		&structs.ServiceSplitterConfigEntry{
1717			Kind: "service-splitter",
1718			Name: "svc-split",
1719			Splits: []structs.ServiceSplit{
1720				{Weight: 60, Service: "svc-redirect"},    // double redirected to a default subset
1721				{Weight: 40, Service: "svc-split-again"}, // split again
1722			},
1723		},
1724		&structs.ServiceSplitterConfigEntry{
1725			Kind: "service-splitter",
1726			Name: "svc-split-again",
1727			Splits: []structs.ServiceSplit{
1728				{Weight: 75, Service: "main", ServiceSubset: "v1"},
1729				{Weight: 25, Service: "svc-split-one-more-time"},
1730			},
1731		},
1732		&structs.ServiceSplitterConfigEntry{
1733			Kind: "service-splitter",
1734			Name: "svc-split-one-more-time",
1735			Splits: []structs.ServiceSplit{
1736				{Weight: 80, Service: "main", ServiceSubset: "v2"},
1737				{Weight: 20, Service: "main", ServiceSubset: "v3"},
1738			},
1739		},
1740	)
1741
1742	entries.AddResolvers(
1743		&structs.ServiceResolverConfigEntry{
1744			Kind: "service-resolver",
1745			Name: "svc-redirect",
1746			Redirect: &structs.ServiceResolverRedirect{
1747				Service: "svc-redirect-again",
1748			},
1749		},
1750		&structs.ServiceResolverConfigEntry{
1751			Kind: "service-resolver",
1752			Name: "svc-redirect-again",
1753			Redirect: &structs.ServiceResolverRedirect{
1754				Service: "redirected",
1755			},
1756		},
1757		&structs.ServiceResolverConfigEntry{
1758			Kind:          "service-resolver",
1759			Name:          "redirected",
1760			DefaultSubset: "prod",
1761			Subsets: map[string]structs.ServiceResolverSubset{
1762				"prod": {Filter: "ServiceMeta.env == prod"},
1763				"qa":   {Filter: "ServiceMeta.env == qa"},
1764			},
1765			LoadBalancer: &structs.LoadBalancer{
1766				Policy: "ring_hash",
1767				RingHashConfig: &structs.RingHashConfig{
1768					MaximumRingSize: 100,
1769				},
1770				HashPolicies: []structs.HashPolicy{
1771					{
1772						SourceIP: true,
1773					},
1774				},
1775			},
1776		},
1777		&structs.ServiceResolverConfigEntry{
1778			Kind:          "service-resolver",
1779			Name:          "main",
1780			DefaultSubset: "default-subset",
1781			Subsets: map[string]structs.ServiceResolverSubset{
1782				"v1":             {Filter: "Service.Meta.version == 1"},
1783				"v2":             {Filter: "Service.Meta.version == 2"},
1784				"v3":             {Filter: "Service.Meta.version == 3"},
1785				"default-subset": {OnlyPassing: true},
1786			},
1787		},
1788	)
1789
1790	var (
1791		router = entries.GetRouter(structs.NewServiceID("main", nil))
1792	)
1793
1794	expect := &structs.CompiledDiscoveryChain{
1795		Protocol:  "http",
1796		StartNode: "router:main.default",
1797		Nodes: map[string]*structs.DiscoveryGraphNode{
1798			"router:main.default": {
1799				Type: structs.DiscoveryGraphNodeTypeRouter,
1800				Name: "main.default",
1801				Routes: []*structs.DiscoveryRoute{
1802					{
1803						Definition: &router.Routes[0],
1804						NextNode:   "resolver:prod.redirected.default.dc1",
1805					},
1806					{
1807						Definition: &router.Routes[1],
1808						NextNode:   "splitter:svc-split.default",
1809					},
1810					{
1811						Definition: newDefaultServiceRoute("main", "default"),
1812						NextNode:   "resolver:default-subset.main.default.dc1",
1813					},
1814				},
1815			},
1816			"splitter:svc-split.default": {
1817				Type: structs.DiscoveryGraphNodeTypeSplitter,
1818				Name: "svc-split.default",
1819				Splits: []*structs.DiscoverySplit{
1820					{
1821						Weight:   60,
1822						NextNode: "resolver:prod.redirected.default.dc1",
1823					},
1824					{
1825						Weight:   30,
1826						NextNode: "resolver:v1.main.default.dc1",
1827					},
1828					{
1829						Weight:   8,
1830						NextNode: "resolver:v2.main.default.dc1",
1831					},
1832					{
1833						Weight:   2,
1834						NextNode: "resolver:v3.main.default.dc1",
1835					},
1836				},
1837				LoadBalancer: &structs.LoadBalancer{
1838					Policy: "ring_hash",
1839					RingHashConfig: &structs.RingHashConfig{
1840						MaximumRingSize: 100,
1841					},
1842					HashPolicies: []structs.HashPolicy{
1843						{
1844							SourceIP: true,
1845						},
1846					},
1847				},
1848			},
1849			"resolver:prod.redirected.default.dc1": {
1850				Type: structs.DiscoveryGraphNodeTypeResolver,
1851				Name: "prod.redirected.default.dc1",
1852				Resolver: &structs.DiscoveryResolver{
1853					ConnectTimeout: 5 * time.Second,
1854					Target:         "prod.redirected.default.dc1",
1855				},
1856				LoadBalancer: &structs.LoadBalancer{
1857					Policy: "ring_hash",
1858					RingHashConfig: &structs.RingHashConfig{
1859						MaximumRingSize: 100,
1860					},
1861					HashPolicies: []structs.HashPolicy{
1862						{
1863							SourceIP: true,
1864						},
1865					},
1866				},
1867			},
1868			"resolver:v1.main.default.dc1": {
1869				Type: structs.DiscoveryGraphNodeTypeResolver,
1870				Name: "v1.main.default.dc1",
1871				Resolver: &structs.DiscoveryResolver{
1872					ConnectTimeout: 5 * time.Second,
1873					Target:         "v1.main.default.dc1",
1874				},
1875			},
1876			"resolver:v2.main.default.dc1": {
1877				Type: structs.DiscoveryGraphNodeTypeResolver,
1878				Name: "v2.main.default.dc1",
1879				Resolver: &structs.DiscoveryResolver{
1880					ConnectTimeout: 5 * time.Second,
1881					Target:         "v2.main.default.dc1",
1882				},
1883			},
1884			"resolver:v3.main.default.dc1": {
1885				Type: structs.DiscoveryGraphNodeTypeResolver,
1886				Name: "v3.main.default.dc1",
1887				Resolver: &structs.DiscoveryResolver{
1888					ConnectTimeout: 5 * time.Second,
1889					Target:         "v3.main.default.dc1",
1890				},
1891			},
1892			"resolver:default-subset.main.default.dc1": {
1893				Type: structs.DiscoveryGraphNodeTypeResolver,
1894				Name: "default-subset.main.default.dc1",
1895				Resolver: &structs.DiscoveryResolver{
1896					ConnectTimeout: 5 * time.Second,
1897					Target:         "default-subset.main.default.dc1",
1898				},
1899			},
1900		},
1901		Targets: map[string]*structs.DiscoveryTarget{
1902			"prod.redirected.default.dc1": newTarget("redirected", "prod", "default", "dc1", func(t *structs.DiscoveryTarget) {
1903				t.Subset = structs.ServiceResolverSubset{
1904					Filter: "ServiceMeta.env == prod",
1905				}
1906			}),
1907			"v1.main.default.dc1": newTarget("main", "v1", "default", "dc1", func(t *structs.DiscoveryTarget) {
1908				t.Subset = structs.ServiceResolverSubset{
1909					Filter: "Service.Meta.version == 1",
1910				}
1911			}),
1912			"v2.main.default.dc1": newTarget("main", "v2", "default", "dc1", func(t *structs.DiscoveryTarget) {
1913				t.Subset = structs.ServiceResolverSubset{
1914					Filter: "Service.Meta.version == 2",
1915				}
1916			}),
1917			"v3.main.default.dc1": newTarget("main", "v3", "default", "dc1", func(t *structs.DiscoveryTarget) {
1918				t.Subset = structs.ServiceResolverSubset{
1919					Filter: "Service.Meta.version == 3",
1920				}
1921			}),
1922			"default-subset.main.default.dc1": newTarget("main", "default-subset", "default", "dc1", func(t *structs.DiscoveryTarget) {
1923				t.Subset = structs.ServiceResolverSubset{OnlyPassing: true}
1924			}),
1925		},
1926	}
1927	return compileTestCase{entries: entries, expect: expect}
1928}
1929
1930func testcase_SplitterRequiresValidProtocol() compileTestCase {
1931	entries := newEntries()
1932	setServiceProtocol(entries, "main", "tcp")
1933
1934	entries.AddSplitters(
1935		&structs.ServiceSplitterConfigEntry{
1936			Kind: structs.ServiceSplitter,
1937			Name: "main",
1938			Splits: []structs.ServiceSplit{
1939				{Weight: 90, Namespace: "v1"},
1940				{Weight: 10, Namespace: "v2"},
1941			},
1942		},
1943	)
1944
1945	return compileTestCase{
1946		entries:        entries,
1947		expectErr:      "does not permit advanced routing or splitting behavior",
1948		expectGraphErr: true,
1949	}
1950}
1951
1952func testcase_RouterRequiresValidProtocol() compileTestCase {
1953	entries := newEntries()
1954	setServiceProtocol(entries, "main", "tcp")
1955
1956	entries.AddRouters(
1957		&structs.ServiceRouterConfigEntry{
1958			Kind: structs.ServiceRouter,
1959			Name: "main",
1960			Routes: []structs.ServiceRoute{
1961				{
1962					Match: &structs.ServiceRouteMatch{
1963						HTTP: &structs.ServiceRouteHTTPMatch{
1964							PathExact: "/other",
1965						},
1966					},
1967					Destination: &structs.ServiceRouteDestination{
1968						Namespace: "other",
1969					},
1970				},
1971			},
1972		},
1973	)
1974	return compileTestCase{
1975		entries:        entries,
1976		expectErr:      "does not permit advanced routing or splitting behavior",
1977		expectGraphErr: true,
1978	}
1979}
1980
1981func testcase_SplitToUnsplittableProtocol() compileTestCase {
1982	entries := newEntries()
1983	setServiceProtocol(entries, "main", "tcp")
1984	setServiceProtocol(entries, "other", "tcp")
1985
1986	entries.AddSplitters(
1987		&structs.ServiceSplitterConfigEntry{
1988			Kind: structs.ServiceSplitter,
1989			Name: "main",
1990			Splits: []structs.ServiceSplit{
1991				{Weight: 90},
1992				{Weight: 10, Service: "other"},
1993			},
1994		},
1995	)
1996	return compileTestCase{
1997		entries:        entries,
1998		expectErr:      "does not permit advanced routing or splitting behavior",
1999		expectGraphErr: true,
2000	}
2001}
2002
2003func testcase_RouteToUnroutableProtocol() compileTestCase {
2004	entries := newEntries()
2005	setServiceProtocol(entries, "main", "tcp")
2006	setServiceProtocol(entries, "other", "tcp")
2007
2008	entries.AddRouters(
2009		&structs.ServiceRouterConfigEntry{
2010			Kind: structs.ServiceRouter,
2011			Name: "main",
2012			Routes: []structs.ServiceRoute{
2013				{
2014					Match: &structs.ServiceRouteMatch{
2015						HTTP: &structs.ServiceRouteHTTPMatch{
2016							PathExact: "/other",
2017						},
2018					},
2019					Destination: &structs.ServiceRouteDestination{
2020						Service: "other",
2021					},
2022				},
2023			},
2024		},
2025	)
2026
2027	return compileTestCase{
2028		entries:        entries,
2029		expectErr:      "does not permit advanced routing or splitting behavior",
2030		expectGraphErr: true,
2031	}
2032}
2033
2034func testcase_FailoverCrossesProtocols() compileTestCase {
2035	entries := newEntries()
2036	setServiceProtocol(entries, "main", "grpc")
2037	setServiceProtocol(entries, "other", "tcp")
2038
2039	entries.AddResolvers(
2040		&structs.ServiceResolverConfigEntry{
2041			Kind: structs.ServiceResolver,
2042			Name: "main",
2043			Failover: map[string]structs.ServiceResolverFailover{
2044				"*": {
2045					Service: "other",
2046				},
2047			},
2048		},
2049	)
2050
2051	return compileTestCase{
2052		entries:        entries,
2053		expectErr:      "uses inconsistent protocols",
2054		expectGraphErr: true,
2055	}
2056}
2057
2058func testcase_RedirectCrossesProtocols() compileTestCase {
2059	entries := newEntries()
2060	setServiceProtocol(entries, "main", "grpc")
2061	setServiceProtocol(entries, "other", "tcp")
2062
2063	entries.AddResolvers(
2064		&structs.ServiceResolverConfigEntry{
2065			Kind: structs.ServiceResolver,
2066			Name: "main",
2067			Redirect: &structs.ServiceResolverRedirect{
2068				Service: "other",
2069			},
2070		},
2071	)
2072	return compileTestCase{
2073		entries:        entries,
2074		expectErr:      "uses inconsistent protocols",
2075		expectGraphErr: true,
2076	}
2077}
2078
2079func testcase_RedirectToMissingSubset() compileTestCase {
2080	entries := newEntries()
2081
2082	entries.AddResolvers(
2083		&structs.ServiceResolverConfigEntry{
2084			Kind:           structs.ServiceResolver,
2085			Name:           "other",
2086			ConnectTimeout: 33 * time.Second,
2087		},
2088		&structs.ServiceResolverConfigEntry{
2089			Kind: structs.ServiceResolver,
2090			Name: "main",
2091			Redirect: &structs.ServiceResolverRedirect{
2092				Service:       "other",
2093				ServiceSubset: "v1",
2094			},
2095		},
2096	)
2097
2098	return compileTestCase{
2099		entries:        entries,
2100		expectErr:      `does not have a subset named "v1"`,
2101		expectGraphErr: true,
2102	}
2103}
2104
2105func testcase_ResolverProtocolOverride() compileTestCase {
2106	entries := newEntries()
2107	setServiceProtocol(entries, "main", "grpc")
2108
2109	expect := &structs.CompiledDiscoveryChain{
2110		Protocol:  "http2",
2111		StartNode: "resolver:main.default.dc1",
2112		Nodes: map[string]*structs.DiscoveryGraphNode{
2113			"resolver:main.default.dc1": {
2114				Type: structs.DiscoveryGraphNodeTypeResolver,
2115				Name: "main.default.dc1",
2116				Resolver: &structs.DiscoveryResolver{
2117					Default:        true,
2118					ConnectTimeout: 5 * time.Second,
2119					Target:         "main.default.dc1",
2120				},
2121			},
2122		},
2123		Targets: map[string]*structs.DiscoveryTarget{
2124			// TODO-TARGET
2125			"main.default.dc1": newTarget("main", "", "default", "dc1", nil),
2126		},
2127	}
2128	return compileTestCase{entries: entries, expect: expect, expectIsDefault: true,
2129		expectCustom: true,
2130		setup: func(req *CompileRequest) {
2131			req.OverrideProtocol = "http2"
2132		},
2133	}
2134}
2135
2136func testcase_ResolverProtocolOverrideIgnored() compileTestCase {
2137	// This shows that if you try to override the protocol to its current value
2138	// the override is completely ignored.
2139	entries := newEntries()
2140	setServiceProtocol(entries, "main", "http2")
2141
2142	expect := &structs.CompiledDiscoveryChain{
2143		Protocol:  "http2",
2144		StartNode: "resolver:main.default.dc1",
2145		Nodes: map[string]*structs.DiscoveryGraphNode{
2146			"resolver:main.default.dc1": {
2147				Type: structs.DiscoveryGraphNodeTypeResolver,
2148				Name: "main.default.dc1",
2149				Resolver: &structs.DiscoveryResolver{
2150					Default:        true,
2151					ConnectTimeout: 5 * time.Second,
2152					Target:         "main.default.dc1",
2153				},
2154			},
2155		},
2156		Targets: map[string]*structs.DiscoveryTarget{
2157			// TODO-TARGET
2158			"main.default.dc1": newTarget("main", "", "default", "dc1", nil),
2159		},
2160	}
2161	return compileTestCase{entries: entries, expect: expect, expectIsDefault: true,
2162		setup: func(req *CompileRequest) {
2163			req.OverrideProtocol = "http2"
2164		},
2165	}
2166}
2167
2168func testcase_RouterIgnored_ResolverProtocolOverride() compileTestCase {
2169	entries := newEntries()
2170	setServiceProtocol(entries, "main", "grpc")
2171
2172	entries.AddRouters(
2173		&structs.ServiceRouterConfigEntry{
2174			Kind: "service-router",
2175			Name: "main",
2176		},
2177	)
2178
2179	expect := &structs.CompiledDiscoveryChain{
2180		Protocol:  "tcp",
2181		StartNode: "resolver:main.default.dc1",
2182		Nodes: map[string]*structs.DiscoveryGraphNode{
2183			"resolver:main.default.dc1": {
2184				Type: structs.DiscoveryGraphNodeTypeResolver,
2185				Name: "main.default.dc1",
2186				Resolver: &structs.DiscoveryResolver{
2187					Default:        true,
2188					ConnectTimeout: 5 * time.Second,
2189					Target:         "main.default.dc1",
2190				},
2191			},
2192		},
2193		Targets: map[string]*structs.DiscoveryTarget{
2194			// TODO-TARGET
2195			"main.default.dc1": newTarget("main", "", "default", "dc1", nil),
2196		},
2197	}
2198	return compileTestCase{entries: entries, expect: expect, expectIsDefault: true,
2199		expectCustom: true,
2200		setup: func(req *CompileRequest) {
2201			req.OverrideProtocol = "tcp"
2202		},
2203	}
2204}
2205
2206func testcase_Resolver_CircularRedirect() compileTestCase {
2207	entries := newEntries()
2208	entries.AddResolvers(
2209		&structs.ServiceResolverConfigEntry{
2210			Kind: "service-resolver",
2211			Name: "main",
2212			Redirect: &structs.ServiceResolverRedirect{
2213				Service: "other",
2214			},
2215		},
2216		&structs.ServiceResolverConfigEntry{
2217			Kind: "service-resolver",
2218			Name: "other",
2219			Redirect: &structs.ServiceResolverRedirect{
2220				Service: "main",
2221			},
2222		},
2223	)
2224
2225	return compileTestCase{entries: entries,
2226		expectErr:      "detected circular resolver redirect",
2227		expectGraphErr: true,
2228	}
2229}
2230
2231func testcase_CircularSplit() compileTestCase {
2232	entries := newEntries()
2233	setGlobalProxyProtocol(entries, "http")
2234	entries.AddSplitters(
2235		&structs.ServiceSplitterConfigEntry{
2236			Kind: "service-splitter",
2237			Name: "main",
2238			Splits: []structs.ServiceSplit{
2239				{Weight: 100, Service: "other"},
2240			},
2241		},
2242		&structs.ServiceSplitterConfigEntry{
2243			Kind: "service-splitter",
2244			Name: "other",
2245			Splits: []structs.ServiceSplit{
2246				{Weight: 100, Service: "main"},
2247			},
2248		},
2249	)
2250
2251	return compileTestCase{entries: entries,
2252		expectErr:      "detected circular reference",
2253		expectGraphErr: true,
2254	}
2255}
2256
2257func testcase_LBSplitterAndResolver() compileTestCase {
2258	entries := newEntries()
2259	setServiceProtocol(entries, "foo", "http")
2260	setServiceProtocol(entries, "bar", "http")
2261	setServiceProtocol(entries, "baz", "http")
2262
2263	entries.AddSplitters(
2264		&structs.ServiceSplitterConfigEntry{
2265			Kind: "service-splitter",
2266			Name: "main",
2267			Splits: []structs.ServiceSplit{
2268				{Weight: 60, Service: "foo"},
2269				{Weight: 20, Service: "bar"},
2270				{Weight: 20, Service: "baz"},
2271			},
2272		},
2273	)
2274
2275	entries.AddResolvers(
2276		&structs.ServiceResolverConfigEntry{
2277			Kind: "service-resolver",
2278			Name: "foo",
2279			LoadBalancer: &structs.LoadBalancer{
2280				Policy: "least_request",
2281				LeastRequestConfig: &structs.LeastRequestConfig{
2282					ChoiceCount: 3,
2283				},
2284			},
2285		},
2286		&structs.ServiceResolverConfigEntry{
2287			Kind: "service-resolver",
2288			Name: "bar",
2289			LoadBalancer: &structs.LoadBalancer{
2290				Policy: "ring_hash",
2291				RingHashConfig: &structs.RingHashConfig{
2292					MaximumRingSize: 101,
2293				},
2294				HashPolicies: []structs.HashPolicy{
2295					{
2296						SourceIP: true,
2297					},
2298				},
2299			},
2300		},
2301		&structs.ServiceResolverConfigEntry{
2302			Kind: "service-resolver",
2303			Name: "baz",
2304			LoadBalancer: &structs.LoadBalancer{
2305				Policy: "maglev",
2306				HashPolicies: []structs.HashPolicy{
2307					{
2308						Field:      "cookie",
2309						FieldValue: "chocolate-chip",
2310						CookieConfig: &structs.CookieConfig{
2311							TTL:  2 * time.Minute,
2312							Path: "/bowl",
2313						},
2314						Terminal: true,
2315					},
2316				},
2317			},
2318		},
2319	)
2320
2321	expect := &structs.CompiledDiscoveryChain{
2322		Protocol:  "http",
2323		StartNode: "splitter:main.default",
2324		Nodes: map[string]*structs.DiscoveryGraphNode{
2325			"splitter:main.default": {
2326				Type: structs.DiscoveryGraphNodeTypeSplitter,
2327				Name: "main.default",
2328				Splits: []*structs.DiscoverySplit{
2329					{
2330						Weight:   60,
2331						NextNode: "resolver:foo.default.dc1",
2332					},
2333					{
2334						Weight:   20,
2335						NextNode: "resolver:bar.default.dc1",
2336					},
2337					{
2338						Weight:   20,
2339						NextNode: "resolver:baz.default.dc1",
2340					},
2341				},
2342				// The LB config from bar is attached because splitters only care about hash-based policies,
2343				// and it's the config from bar not baz because we pick the first one we encounter in the Splits.
2344				LoadBalancer: &structs.LoadBalancer{
2345					Policy: "ring_hash",
2346					RingHashConfig: &structs.RingHashConfig{
2347						MaximumRingSize: 101,
2348					},
2349					HashPolicies: []structs.HashPolicy{
2350						{
2351							SourceIP: true,
2352						},
2353					},
2354				},
2355			},
2356			// Each service's LB config is passed down from the service-resolver to the resolver node
2357			"resolver:foo.default.dc1": {
2358				Type: structs.DiscoveryGraphNodeTypeResolver,
2359				Name: "foo.default.dc1",
2360				Resolver: &structs.DiscoveryResolver{
2361					Default:        false,
2362					ConnectTimeout: 5 * time.Second,
2363					Target:         "foo.default.dc1",
2364				},
2365				LoadBalancer: &structs.LoadBalancer{
2366					Policy: "least_request",
2367					LeastRequestConfig: &structs.LeastRequestConfig{
2368						ChoiceCount: 3,
2369					},
2370				},
2371			},
2372			"resolver:bar.default.dc1": {
2373				Type: structs.DiscoveryGraphNodeTypeResolver,
2374				Name: "bar.default.dc1",
2375				Resolver: &structs.DiscoveryResolver{
2376					Default:        false,
2377					ConnectTimeout: 5 * time.Second,
2378					Target:         "bar.default.dc1",
2379				},
2380				LoadBalancer: &structs.LoadBalancer{
2381					Policy: "ring_hash",
2382					RingHashConfig: &structs.RingHashConfig{
2383						MaximumRingSize: 101,
2384					},
2385					HashPolicies: []structs.HashPolicy{
2386						{
2387							SourceIP: true,
2388						},
2389					},
2390				},
2391			},
2392			"resolver:baz.default.dc1": {
2393				Type: structs.DiscoveryGraphNodeTypeResolver,
2394				Name: "baz.default.dc1",
2395				Resolver: &structs.DiscoveryResolver{
2396					Default:        false,
2397					ConnectTimeout: 5 * time.Second,
2398					Target:         "baz.default.dc1",
2399				},
2400				LoadBalancer: &structs.LoadBalancer{
2401					Policy: "maglev",
2402					HashPolicies: []structs.HashPolicy{
2403						{
2404							Field:      "cookie",
2405							FieldValue: "chocolate-chip",
2406							CookieConfig: &structs.CookieConfig{
2407								TTL:  2 * time.Minute,
2408								Path: "/bowl",
2409							},
2410							Terminal: true,
2411						},
2412					},
2413				},
2414			},
2415		},
2416		Targets: map[string]*structs.DiscoveryTarget{
2417			"foo.default.dc1": newTarget("foo", "", "default", "dc1", nil),
2418			"bar.default.dc1": newTarget("bar", "", "default", "dc1", nil),
2419			"baz.default.dc1": newTarget("baz", "", "default", "dc1", nil),
2420		},
2421	}
2422
2423	return compileTestCase{entries: entries, expect: expect}
2424}
2425
2426// ensure chain with LB cfg in resolver isn't a default chain (!IsDefault)
2427func testcase_LBResolver() compileTestCase {
2428	entries := newEntries()
2429	setServiceProtocol(entries, "main", "http")
2430
2431	entries.AddResolvers(
2432		&structs.ServiceResolverConfigEntry{
2433			Kind: "service-resolver",
2434			Name: "main",
2435			LoadBalancer: &structs.LoadBalancer{
2436				Policy: "ring_hash",
2437				RingHashConfig: &structs.RingHashConfig{
2438					MaximumRingSize: 101,
2439				},
2440				HashPolicies: []structs.HashPolicy{
2441					{
2442						SourceIP: true,
2443					},
2444				},
2445			},
2446		},
2447	)
2448
2449	expect := &structs.CompiledDiscoveryChain{
2450		Protocol:  "http",
2451		StartNode: "resolver:main.default.dc1",
2452		Nodes: map[string]*structs.DiscoveryGraphNode{
2453			"resolver:main.default.dc1": {
2454				Type: structs.DiscoveryGraphNodeTypeResolver,
2455				Name: "main.default.dc1",
2456				Resolver: &structs.DiscoveryResolver{
2457					Default:        false,
2458					ConnectTimeout: 5 * time.Second,
2459					Target:         "main.default.dc1",
2460				},
2461				LoadBalancer: &structs.LoadBalancer{
2462					Policy: "ring_hash",
2463					RingHashConfig: &structs.RingHashConfig{
2464						MaximumRingSize: 101,
2465					},
2466					HashPolicies: []structs.HashPolicy{
2467						{
2468							SourceIP: true,
2469						},
2470					},
2471				},
2472			},
2473		},
2474		Targets: map[string]*structs.DiscoveryTarget{
2475			"main.default.dc1": newTarget("main", "", "default", "dc1", nil),
2476		},
2477	}
2478
2479	return compileTestCase{entries: entries, expect: expect}
2480}
2481
2482func newSimpleRoute(name string, muts ...func(*structs.ServiceRoute)) structs.ServiceRoute {
2483	r := structs.ServiceRoute{
2484		Match: &structs.ServiceRouteMatch{
2485			HTTP: &structs.ServiceRouteHTTPMatch{PathPrefix: "/" + name},
2486		},
2487		Destination: &structs.ServiceRouteDestination{Service: name},
2488	}
2489
2490	for _, mut := range muts {
2491		mut(&r)
2492	}
2493
2494	return r
2495}
2496
2497func setGlobalProxyProtocol(entries *structs.DiscoveryChainConfigEntries, protocol string) {
2498	entries.GlobalProxy = &structs.ProxyConfigEntry{
2499		Kind: structs.ProxyDefaults,
2500		Name: structs.ProxyConfigGlobal,
2501		Config: map[string]interface{}{
2502			"protocol": protocol,
2503		},
2504	}
2505}
2506
2507func setServiceProtocol(entries *structs.DiscoveryChainConfigEntries, name, protocol string) {
2508	entries.AddServices(&structs.ServiceConfigEntry{
2509		Kind:     structs.ServiceDefaults,
2510		Name:     name,
2511		Protocol: protocol,
2512	})
2513}
2514
2515func newEntries() *structs.DiscoveryChainConfigEntries {
2516	return &structs.DiscoveryChainConfigEntries{
2517		Routers:   make(map[structs.ServiceID]*structs.ServiceRouterConfigEntry),
2518		Splitters: make(map[structs.ServiceID]*structs.ServiceSplitterConfigEntry),
2519		Resolvers: make(map[structs.ServiceID]*structs.ServiceResolverConfigEntry),
2520	}
2521}
2522
2523func newTarget(service, serviceSubset, namespace, datacenter string, modFn func(t *structs.DiscoveryTarget)) *structs.DiscoveryTarget {
2524	t := structs.NewDiscoveryTarget(service, serviceSubset, namespace, datacenter)
2525	t.SNI = connect.TargetSNI(t, "trustdomain.consul")
2526	t.Name = t.SNI
2527	if modFn != nil {
2528		modFn(t)
2529	}
2530	return t
2531}
2532