1package state
2
3import (
4	"testing"
5	"time"
6
7	memdb "github.com/hashicorp/go-memdb"
8	"github.com/stretchr/testify/require"
9
10	"github.com/hashicorp/consul/agent/structs"
11	"github.com/hashicorp/consul/sdk/testutil"
12)
13
14func TestStore_ConfigEntry(t *testing.T) {
15	require := require.New(t)
16	s := testConfigStateStore(t)
17
18	expected := &structs.ProxyConfigEntry{
19		Kind: structs.ProxyDefaults,
20		Name: "global",
21		Config: map[string]interface{}{
22			"DestinationServiceName": "foo",
23		},
24	}
25
26	// Create
27	require.NoError(s.EnsureConfigEntry(0, expected))
28
29	idx, config, err := s.ConfigEntry(nil, structs.ProxyDefaults, "global", nil)
30	require.NoError(err)
31	require.Equal(uint64(0), idx)
32	require.Equal(expected, config)
33
34	// Update
35	updated := &structs.ProxyConfigEntry{
36		Kind: structs.ProxyDefaults,
37		Name: "global",
38		Config: map[string]interface{}{
39			"DestinationServiceName": "bar",
40		},
41	}
42	require.NoError(s.EnsureConfigEntry(1, updated))
43
44	idx, config, err = s.ConfigEntry(nil, structs.ProxyDefaults, "global", nil)
45	require.NoError(err)
46	require.Equal(uint64(1), idx)
47	require.Equal(updated, config)
48
49	// Delete
50	require.NoError(s.DeleteConfigEntry(2, structs.ProxyDefaults, "global", nil))
51
52	idx, config, err = s.ConfigEntry(nil, structs.ProxyDefaults, "global", nil)
53	require.NoError(err)
54	require.Equal(uint64(2), idx)
55	require.Nil(config)
56
57	// Set up a watch.
58	serviceConf := &structs.ServiceConfigEntry{
59		Kind: structs.ServiceDefaults,
60		Name: "foo",
61	}
62	require.NoError(s.EnsureConfigEntry(3, serviceConf))
63
64	ws := memdb.NewWatchSet()
65	_, _, err = s.ConfigEntry(ws, structs.ServiceDefaults, "foo", nil)
66	require.NoError(err)
67
68	// Make an unrelated modification and make sure the watch doesn't fire.
69	require.NoError(s.EnsureConfigEntry(4, updated))
70	require.False(watchFired(ws))
71
72	// Update the watched config and make sure it fires.
73	serviceConf.Protocol = "http"
74	require.NoError(s.EnsureConfigEntry(5, serviceConf))
75	require.True(watchFired(ws))
76}
77
78func TestStore_ConfigEntryCAS(t *testing.T) {
79	require := require.New(t)
80	s := testConfigStateStore(t)
81
82	expected := &structs.ProxyConfigEntry{
83		Kind: structs.ProxyDefaults,
84		Name: "global",
85		Config: map[string]interface{}{
86			"DestinationServiceName": "foo",
87		},
88	}
89
90	// Create
91	require.NoError(s.EnsureConfigEntry(1, expected))
92
93	idx, config, err := s.ConfigEntry(nil, structs.ProxyDefaults, "global", nil)
94	require.NoError(err)
95	require.Equal(uint64(1), idx)
96	require.Equal(expected, config)
97
98	// Update with invalid index
99	updated := &structs.ProxyConfigEntry{
100		Kind: structs.ProxyDefaults,
101		Name: "global",
102		Config: map[string]interface{}{
103			"DestinationServiceName": "bar",
104		},
105	}
106	ok, err := s.EnsureConfigEntryCAS(2, 99, updated)
107	require.False(ok)
108	require.NoError(err)
109
110	// Entry should not be changed
111	idx, config, err = s.ConfigEntry(nil, structs.ProxyDefaults, "global", nil)
112	require.NoError(err)
113	require.Equal(uint64(1), idx)
114	require.Equal(expected, config)
115
116	// Update with a valid index
117	ok, err = s.EnsureConfigEntryCAS(2, 1, updated)
118	require.True(ok)
119	require.NoError(err)
120
121	// Entry should be updated
122	idx, config, err = s.ConfigEntry(nil, structs.ProxyDefaults, "global", nil)
123	require.NoError(err)
124	require.Equal(uint64(2), idx)
125	require.Equal(updated, config)
126}
127
128func TestStore_ConfigEntry_UpdateOver(t *testing.T) {
129	// This test uses ServiceIntentions because they are the only
130	// kind that implements UpdateOver() at this time.
131
132	s := testConfigStateStore(t)
133
134	var (
135		idA = testUUID()
136		idB = testUUID()
137
138		loc   = time.FixedZone("UTC-8", -8*60*60)
139		timeA = time.Date(1955, 11, 5, 6, 15, 0, 0, loc)
140		timeB = time.Date(1985, 10, 26, 1, 35, 0, 0, loc)
141	)
142	require.NotEqual(t, idA, idB)
143
144	initial := &structs.ServiceIntentionsConfigEntry{
145		Kind: structs.ServiceIntentions,
146		Name: "api",
147		Sources: []*structs.SourceIntention{
148			{
149				LegacyID:         idA,
150				Name:             "web",
151				Action:           structs.IntentionActionAllow,
152				LegacyCreateTime: &timeA,
153				LegacyUpdateTime: &timeA,
154			},
155		},
156	}
157
158	// Create
159	nextIndex := uint64(1)
160	require.NoError(t, s.EnsureConfigEntry(nextIndex, initial.Clone()))
161
162	idx, raw, err := s.ConfigEntry(nil, structs.ServiceIntentions, "api", nil)
163	require.NoError(t, err)
164	require.Equal(t, nextIndex, idx)
165
166	got, ok := raw.(*structs.ServiceIntentionsConfigEntry)
167	require.True(t, ok)
168	initial.RaftIndex = got.RaftIndex
169	require.Equal(t, initial, got)
170
171	t.Run("update and fail change legacyID", func(t *testing.T) {
172		// Update
173		updated := &structs.ServiceIntentionsConfigEntry{
174			Kind: structs.ServiceIntentions,
175			Name: "api",
176			Sources: []*structs.SourceIntention{
177				{
178					LegacyID:         idB,
179					Name:             "web",
180					Action:           structs.IntentionActionDeny,
181					LegacyCreateTime: &timeB,
182					LegacyUpdateTime: &timeB,
183				},
184			},
185		}
186
187		nextIndex++
188		err := s.EnsureConfigEntry(nextIndex, updated.Clone())
189		testutil.RequireErrorContains(t, err, "cannot set this field to a different value")
190	})
191
192	t.Run("update and do not update create time", func(t *testing.T) {
193		// Update
194		updated := &structs.ServiceIntentionsConfigEntry{
195			Kind: structs.ServiceIntentions,
196			Name: "api",
197			Sources: []*structs.SourceIntention{
198				{
199					LegacyID:         idA,
200					Name:             "web",
201					Action:           structs.IntentionActionDeny,
202					LegacyCreateTime: &timeB,
203					LegacyUpdateTime: &timeB,
204				},
205			},
206		}
207
208		nextIndex++
209		require.NoError(t, s.EnsureConfigEntry(nextIndex, updated.Clone()))
210
211		// check
212		idx, raw, err = s.ConfigEntry(nil, structs.ServiceIntentions, "api", nil)
213		require.NoError(t, err)
214		require.Equal(t, nextIndex, idx)
215
216		got, ok = raw.(*structs.ServiceIntentionsConfigEntry)
217		require.True(t, ok)
218		updated.RaftIndex = got.RaftIndex
219		updated.Sources[0].LegacyCreateTime = &timeA // UpdateOver will not replace this
220		require.Equal(t, updated, got)
221	})
222}
223
224func TestStore_ConfigEntries(t *testing.T) {
225	require := require.New(t)
226	s := testConfigStateStore(t)
227
228	// Create some config entries.
229	entry1 := &structs.ProxyConfigEntry{
230		Kind: structs.ProxyDefaults,
231		Name: "test1",
232	}
233	entry2 := &structs.ServiceConfigEntry{
234		Kind: structs.ServiceDefaults,
235		Name: "test2",
236	}
237	entry3 := &structs.ServiceConfigEntry{
238		Kind: structs.ServiceDefaults,
239		Name: "test3",
240	}
241
242	require.NoError(s.EnsureConfigEntry(0, entry1))
243	require.NoError(s.EnsureConfigEntry(1, entry2))
244	require.NoError(s.EnsureConfigEntry(2, entry3))
245
246	// Get all entries
247	idx, entries, err := s.ConfigEntries(nil, nil)
248	require.NoError(err)
249	require.Equal(uint64(2), idx)
250	require.Equal([]structs.ConfigEntry{entry1, entry2, entry3}, entries)
251
252	// Get all proxy entries
253	idx, entries, err = s.ConfigEntriesByKind(nil, structs.ProxyDefaults, nil)
254	require.NoError(err)
255	require.Equal(uint64(2), idx)
256	require.Equal([]structs.ConfigEntry{entry1}, entries)
257
258	// Get all service entries
259	ws := memdb.NewWatchSet()
260	idx, entries, err = s.ConfigEntriesByKind(ws, structs.ServiceDefaults, nil)
261	require.NoError(err)
262	require.Equal(uint64(2), idx)
263	require.Equal([]structs.ConfigEntry{entry2, entry3}, entries)
264
265	// Watch should not have fired
266	require.False(watchFired(ws))
267
268	// Now make an update and make sure the watch fires.
269	require.NoError(s.EnsureConfigEntry(3, &structs.ServiceConfigEntry{
270		Kind:     structs.ServiceDefaults,
271		Name:     "test2",
272		Protocol: "tcp",
273	}))
274	require.True(watchFired(ws))
275}
276
277func TestStore_ConfigEntry_GraphValidation(t *testing.T) {
278	type tcase struct {
279		entries        []structs.ConfigEntry
280		op             func(t *testing.T, s *Store) error
281		expectErr      string
282		expectGraphErr bool
283	}
284	cases := map[string]tcase{
285		"splitter fails without default protocol": {
286			entries: []structs.ConfigEntry{},
287			op: func(t *testing.T, s *Store) error {
288				entry := &structs.ServiceSplitterConfigEntry{
289					Kind: structs.ServiceSplitter,
290					Name: "main",
291					Splits: []structs.ServiceSplit{
292						{Weight: 100},
293					},
294				}
295				return s.EnsureConfigEntry(0, entry)
296			},
297			expectErr:      "does not permit advanced routing or splitting behavior",
298			expectGraphErr: true,
299		},
300		"splitter fails with tcp protocol": {
301			entries: []structs.ConfigEntry{
302				&structs.ServiceConfigEntry{
303					Kind:     structs.ServiceDefaults,
304					Name:     "main",
305					Protocol: "tcp",
306				},
307			},
308			op: func(t *testing.T, s *Store) error {
309				entry := &structs.ServiceSplitterConfigEntry{
310					Kind: structs.ServiceSplitter,
311					Name: "main",
312					Splits: []structs.ServiceSplit{
313						{Weight: 100},
314					},
315				}
316				return s.EnsureConfigEntry(0, entry)
317			},
318			expectErr:      "does not permit advanced routing or splitting behavior",
319			expectGraphErr: true,
320		},
321		"splitter works with http protocol": {
322			entries: []structs.ConfigEntry{
323				&structs.ProxyConfigEntry{
324					Kind: structs.ProxyDefaults,
325					Name: structs.ProxyConfigGlobal,
326					Config: map[string]interface{}{
327						"protocol": "tcp", // loses
328					},
329				},
330				&structs.ServiceConfigEntry{
331					Kind:           structs.ServiceDefaults,
332					Name:           "main",
333					Protocol:       "http",
334					EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
335				},
336				&structs.ServiceResolverConfigEntry{
337					Kind: structs.ServiceResolver,
338					Name: "main",
339					Subsets: map[string]structs.ServiceResolverSubset{
340						"v1": {
341							Filter: "Service.Meta.version == v1",
342						},
343						"v2": {
344							Filter: "Service.Meta.version == v2",
345						},
346					},
347				},
348			},
349			op: func(t *testing.T, s *Store) error {
350				entry := &structs.ServiceSplitterConfigEntry{
351					Kind: structs.ServiceSplitter,
352					Name: "main",
353					Splits: []structs.ServiceSplit{
354						{Weight: 90, ServiceSubset: "v1"},
355						{Weight: 10, ServiceSubset: "v2"},
356					},
357					EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
358				}
359				return s.EnsureConfigEntry(0, entry)
360			},
361		},
362		"splitter works with http protocol (from proxy-defaults)": {
363			entries: []structs.ConfigEntry{
364				&structs.ProxyConfigEntry{
365					Kind: structs.ProxyDefaults,
366					Name: structs.ProxyConfigGlobal,
367					Config: map[string]interface{}{
368						"protocol": "http",
369					},
370				},
371				&structs.ServiceResolverConfigEntry{
372					Kind: structs.ServiceResolver,
373					Name: "main",
374					Subsets: map[string]structs.ServiceResolverSubset{
375						"v1": {
376							Filter: "Service.Meta.version == v1",
377						},
378						"v2": {
379							Filter: "Service.Meta.version == v2",
380						},
381					},
382				},
383			},
384			op: func(t *testing.T, s *Store) error {
385				entry := &structs.ServiceSplitterConfigEntry{
386					Kind: structs.ServiceSplitter,
387					Name: "main",
388					Splits: []structs.ServiceSplit{
389						{Weight: 90, ServiceSubset: "v1"},
390						{Weight: 10, ServiceSubset: "v2"},
391					},
392				}
393				return s.EnsureConfigEntry(0, entry)
394			},
395		},
396		"router fails with tcp protocol": {
397			entries: []structs.ConfigEntry{
398				&structs.ServiceConfigEntry{
399					Kind:     structs.ServiceDefaults,
400					Name:     "main",
401					Protocol: "tcp",
402				},
403				&structs.ServiceResolverConfigEntry{
404					Kind: structs.ServiceResolver,
405					Name: "main",
406					Subsets: map[string]structs.ServiceResolverSubset{
407						"other": {
408							Filter: "Service.Meta.version == other",
409						},
410					},
411				},
412			},
413			op: func(t *testing.T, s *Store) error {
414				entry := &structs.ServiceRouterConfigEntry{
415					Kind: structs.ServiceRouter,
416					Name: "main",
417					Routes: []structs.ServiceRoute{
418						{
419							Match: &structs.ServiceRouteMatch{
420								HTTP: &structs.ServiceRouteHTTPMatch{
421									PathExact: "/other",
422								},
423							},
424							Destination: &structs.ServiceRouteDestination{
425								ServiceSubset: "other",
426							},
427						},
428					},
429				}
430				return s.EnsureConfigEntry(0, entry)
431			},
432			expectErr:      "does not permit advanced routing or splitting behavior",
433			expectGraphErr: true,
434		},
435		"router fails without default protocol": {
436			entries: []structs.ConfigEntry{
437				&structs.ServiceResolverConfigEntry{
438					Kind: structs.ServiceResolver,
439					Name: "main",
440					Subsets: map[string]structs.ServiceResolverSubset{
441						"other": {
442							Filter: "Service.Meta.version == other",
443						},
444					},
445				},
446			},
447			op: func(t *testing.T, s *Store) error {
448				entry := &structs.ServiceRouterConfigEntry{
449					Kind: structs.ServiceRouter,
450					Name: "main",
451					Routes: []structs.ServiceRoute{
452						{
453							Match: &structs.ServiceRouteMatch{
454								HTTP: &structs.ServiceRouteHTTPMatch{
455									PathExact: "/other",
456								},
457							},
458							Destination: &structs.ServiceRouteDestination{
459								ServiceSubset: "other",
460							},
461						},
462					},
463				}
464				return s.EnsureConfigEntry(0, entry)
465			},
466			expectErr:      "does not permit advanced routing or splitting behavior",
467			expectGraphErr: true,
468		},
469		/////////////////////////////////////////////////
470		"cannot remove default protocol after splitter created": {
471			entries: []structs.ConfigEntry{
472				&structs.ServiceConfigEntry{
473					Kind:     structs.ServiceDefaults,
474					Name:     "main",
475					Protocol: "http",
476				},
477				&structs.ServiceResolverConfigEntry{
478					Kind: structs.ServiceResolver,
479					Name: "main",
480					Subsets: map[string]structs.ServiceResolverSubset{
481						"v1": {
482							Filter: "Service.Meta.version == v1",
483						},
484						"v2": {
485							Filter: "Service.Meta.version == v2",
486						},
487					},
488				},
489				&structs.ServiceSplitterConfigEntry{
490					Kind: structs.ServiceSplitter,
491					Name: "main",
492					Splits: []structs.ServiceSplit{
493						{Weight: 90, ServiceSubset: "v1"},
494						{Weight: 10, ServiceSubset: "v2"},
495					},
496				},
497			},
498			op: func(t *testing.T, s *Store) error {
499				return s.DeleteConfigEntry(0, structs.ServiceDefaults, "main", nil)
500			},
501			expectErr:      "does not permit advanced routing or splitting behavior",
502			expectGraphErr: true,
503		},
504		"cannot remove global default protocol after splitter created": {
505			entries: []structs.ConfigEntry{
506				&structs.ProxyConfigEntry{
507					Kind: structs.ProxyDefaults,
508					Name: structs.ProxyConfigGlobal,
509					Config: map[string]interface{}{
510						"protocol": "http",
511					},
512				},
513				&structs.ServiceResolverConfigEntry{
514					Kind: structs.ServiceResolver,
515					Name: "main",
516					Subsets: map[string]structs.ServiceResolverSubset{
517						"v1": {
518							Filter: "Service.Meta.version == v1",
519						},
520						"v2": {
521							Filter: "Service.Meta.version == v2",
522						},
523					},
524				},
525				&structs.ServiceSplitterConfigEntry{
526					Kind: structs.ServiceSplitter,
527					Name: "main",
528					Splits: []structs.ServiceSplit{
529						{Weight: 90, ServiceSubset: "v1"},
530						{Weight: 10, ServiceSubset: "v2"},
531					},
532				},
533			},
534			op: func(t *testing.T, s *Store) error {
535				return s.DeleteConfigEntry(0, structs.ProxyDefaults, structs.ProxyConfigGlobal, nil)
536			},
537			expectErr:      "does not permit advanced routing or splitting behavior",
538			expectGraphErr: true,
539		},
540		"can remove global default protocol after splitter created if service default overrides it": {
541			entries: []structs.ConfigEntry{
542				&structs.ProxyConfigEntry{
543					Kind: structs.ProxyDefaults,
544					Name: structs.ProxyConfigGlobal,
545					Config: map[string]interface{}{
546						"protocol": "http",
547					},
548				},
549				&structs.ServiceConfigEntry{
550					Kind:     structs.ServiceDefaults,
551					Name:     "main",
552					Protocol: "http",
553				},
554				&structs.ServiceResolverConfigEntry{
555					Kind: structs.ServiceResolver,
556					Name: "main",
557					Subsets: map[string]structs.ServiceResolverSubset{
558						"v1": {
559							Filter: "Service.Meta.version == v1",
560						},
561						"v2": {
562							Filter: "Service.Meta.version == v2",
563						},
564					},
565				},
566				&structs.ServiceSplitterConfigEntry{
567					Kind: structs.ServiceSplitter,
568					Name: "main",
569					Splits: []structs.ServiceSplit{
570						{Weight: 90, ServiceSubset: "v1"},
571						{Weight: 10, ServiceSubset: "v2"},
572					},
573				},
574			},
575			op: func(t *testing.T, s *Store) error {
576				return s.DeleteConfigEntry(0, structs.ProxyDefaults, structs.ProxyConfigGlobal, nil)
577			},
578		},
579		"cannot change to tcp protocol after splitter created": {
580			entries: []structs.ConfigEntry{
581				&structs.ServiceConfigEntry{
582					Kind:     structs.ServiceDefaults,
583					Name:     "main",
584					Protocol: "http",
585				},
586				&structs.ServiceResolverConfigEntry{
587					Kind: structs.ServiceResolver,
588					Name: "main",
589					Subsets: map[string]structs.ServiceResolverSubset{
590						"v1": {
591							Filter: "Service.Meta.version == v1",
592						},
593						"v2": {
594							Filter: "Service.Meta.version == v2",
595						},
596					},
597				},
598				&structs.ServiceSplitterConfigEntry{
599					Kind: structs.ServiceSplitter,
600					Name: "main",
601					Splits: []structs.ServiceSplit{
602						{Weight: 90, ServiceSubset: "v1"},
603						{Weight: 10, ServiceSubset: "v2"},
604					},
605				},
606			},
607			op: func(t *testing.T, s *Store) error {
608				entry := &structs.ServiceConfigEntry{
609					Kind:     structs.ServiceDefaults,
610					Name:     "main",
611					Protocol: "tcp",
612				}
613				return s.EnsureConfigEntry(0, entry)
614			},
615			expectErr:      "does not permit advanced routing or splitting behavior",
616			expectGraphErr: true,
617		},
618		"cannot remove default protocol after router created": {
619			entries: []structs.ConfigEntry{
620				&structs.ServiceConfigEntry{
621					Kind:     structs.ServiceDefaults,
622					Name:     "main",
623					Protocol: "http",
624				},
625				&structs.ServiceResolverConfigEntry{
626					Kind: structs.ServiceResolver,
627					Name: "main",
628					Subsets: map[string]structs.ServiceResolverSubset{
629						"other": {
630							Filter: "Service.Meta.version == other",
631						},
632					},
633				},
634				&structs.ServiceRouterConfigEntry{
635					Kind: structs.ServiceRouter,
636					Name: "main",
637					Routes: []structs.ServiceRoute{
638						{
639							Match: &structs.ServiceRouteMatch{
640								HTTP: &structs.ServiceRouteHTTPMatch{
641									PathExact: "/other",
642								},
643							},
644							Destination: &structs.ServiceRouteDestination{
645								ServiceSubset: "other",
646							},
647						},
648					},
649				},
650			},
651			op: func(t *testing.T, s *Store) error {
652				return s.DeleteConfigEntry(0, structs.ServiceDefaults, "main", nil)
653			},
654			expectErr:      "does not permit advanced routing or splitting behavior",
655			expectGraphErr: true,
656		},
657		"cannot change to tcp protocol after router created": {
658			entries: []structs.ConfigEntry{
659				&structs.ServiceConfigEntry{
660					Kind:     structs.ServiceDefaults,
661					Name:     "main",
662					Protocol: "http",
663				},
664				&structs.ServiceResolverConfigEntry{
665					Kind: structs.ServiceResolver,
666					Name: "main",
667					Subsets: map[string]structs.ServiceResolverSubset{
668						"other": {
669							Filter: "Service.Meta.version == other",
670						},
671					},
672				},
673				&structs.ServiceRouterConfigEntry{
674					Kind: structs.ServiceRouter,
675					Name: "main",
676					Routes: []structs.ServiceRoute{
677						{
678							Match: &structs.ServiceRouteMatch{
679								HTTP: &structs.ServiceRouteHTTPMatch{
680									PathExact: "/other",
681								},
682							},
683							Destination: &structs.ServiceRouteDestination{
684								ServiceSubset: "other",
685							},
686						},
687					},
688				},
689			},
690			op: func(t *testing.T, s *Store) error {
691				entry := &structs.ServiceConfigEntry{
692					Kind:     structs.ServiceDefaults,
693					Name:     "main",
694					Protocol: "tcp",
695				}
696				return s.EnsureConfigEntry(0, entry)
697			},
698			expectErr:      "does not permit advanced routing or splitting behavior",
699			expectGraphErr: true,
700		},
701		/////////////////////////////////////////////////
702		"cannot split to a service using tcp": {
703			entries: []structs.ConfigEntry{
704				&structs.ServiceConfigEntry{
705					Kind:     structs.ServiceDefaults,
706					Name:     "main",
707					Protocol: "http",
708				},
709				&structs.ServiceConfigEntry{
710					Kind:     structs.ServiceDefaults,
711					Name:     "other",
712					Protocol: "tcp",
713				},
714			},
715			op: func(t *testing.T, s *Store) error {
716				entry := &structs.ServiceSplitterConfigEntry{
717					Kind: structs.ServiceSplitter,
718					Name: "main",
719					Splits: []structs.ServiceSplit{
720						{Weight: 90},
721						{Weight: 10, Service: "other"},
722					},
723				}
724				return s.EnsureConfigEntry(0, entry)
725			},
726			expectErr:      "uses inconsistent protocols",
727			expectGraphErr: true,
728		},
729		"cannot route to a service using tcp": {
730			entries: []structs.ConfigEntry{
731				&structs.ServiceConfigEntry{
732					Kind:     structs.ServiceDefaults,
733					Name:     "main",
734					Protocol: "http",
735				},
736				&structs.ServiceConfigEntry{
737					Kind:     structs.ServiceDefaults,
738					Name:     "other",
739					Protocol: "tcp",
740				},
741			},
742			op: func(t *testing.T, s *Store) error {
743				entry := &structs.ServiceRouterConfigEntry{
744					Kind: structs.ServiceRouter,
745					Name: "main",
746					Routes: []structs.ServiceRoute{
747						{
748							Match: &structs.ServiceRouteMatch{
749								HTTP: &structs.ServiceRouteHTTPMatch{
750									PathExact: "/other",
751								},
752							},
753							Destination: &structs.ServiceRouteDestination{
754								Service: "other",
755							},
756						},
757					},
758				}
759				return s.EnsureConfigEntry(0, entry)
760			},
761			expectErr:      "uses inconsistent protocols",
762			expectGraphErr: true,
763		},
764		/////////////////////////////////////////////////
765		"cannot failover to a service using a different protocol": {
766			entries: []structs.ConfigEntry{
767				&structs.ServiceConfigEntry{
768					Kind:     structs.ServiceDefaults,
769					Name:     "main",
770					Protocol: "grpc",
771				},
772				&structs.ServiceConfigEntry{
773					Kind:     structs.ServiceDefaults,
774					Name:     "other",
775					Protocol: "tcp",
776				},
777				&structs.ServiceResolverConfigEntry{
778					Kind:           structs.ServiceResolver,
779					Name:           "main",
780					ConnectTimeout: 33 * time.Second,
781				},
782			},
783			op: func(t *testing.T, s *Store) error {
784				entry := &structs.ServiceResolverConfigEntry{
785					Kind: structs.ServiceResolver,
786					Name: "main",
787					Failover: map[string]structs.ServiceResolverFailover{
788						"*": {
789							Service: "other",
790						},
791					},
792				}
793				return s.EnsureConfigEntry(0, entry)
794			},
795			expectErr:      "uses inconsistent protocols",
796			expectGraphErr: true,
797		},
798		"cannot redirect to a service using a different protocol": {
799			entries: []structs.ConfigEntry{
800				&structs.ServiceConfigEntry{
801					Kind:     structs.ServiceDefaults,
802					Name:     "main",
803					Protocol: "grpc",
804				},
805				&structs.ServiceConfigEntry{
806					Kind:     structs.ServiceDefaults,
807					Name:     "other",
808					Protocol: "tcp",
809				},
810				&structs.ServiceResolverConfigEntry{
811					Kind:           structs.ServiceResolver,
812					Name:           "main",
813					ConnectTimeout: 33 * time.Second,
814				},
815			},
816			op: func(t *testing.T, s *Store) error {
817				entry := &structs.ServiceResolverConfigEntry{
818					Kind: structs.ServiceResolver,
819					Name: "main",
820					Redirect: &structs.ServiceResolverRedirect{
821						Service: "other",
822					},
823				}
824				return s.EnsureConfigEntry(0, entry)
825			},
826			expectErr:      "uses inconsistent protocols",
827			expectGraphErr: true,
828		},
829		/////////////////////////////////////////////////
830		"redirect to a subset that does exist is fine": {
831			entries: []structs.ConfigEntry{
832				&structs.ServiceResolverConfigEntry{
833					Kind:           structs.ServiceResolver,
834					Name:           "other",
835					ConnectTimeout: 33 * time.Second,
836					Subsets: map[string]structs.ServiceResolverSubset{
837						"v1": {
838							Filter: "Service.Meta.version == v1",
839						},
840					},
841				},
842			},
843			op: func(t *testing.T, s *Store) error {
844				entry := &structs.ServiceResolverConfigEntry{
845					Kind: structs.ServiceResolver,
846					Name: "main",
847					Redirect: &structs.ServiceResolverRedirect{
848						Service:       "other",
849						ServiceSubset: "v1",
850					},
851				}
852				return s.EnsureConfigEntry(0, entry)
853			},
854		},
855		"cannot redirect to a subset that does not exist": {
856			entries: []structs.ConfigEntry{
857				&structs.ServiceResolverConfigEntry{
858					Kind:           structs.ServiceResolver,
859					Name:           "other",
860					ConnectTimeout: 33 * time.Second,
861				},
862			},
863			op: func(t *testing.T, s *Store) error {
864				entry := &structs.ServiceResolverConfigEntry{
865					Kind: structs.ServiceResolver,
866					Name: "main",
867					Redirect: &structs.ServiceResolverRedirect{
868						Service:       "other",
869						ServiceSubset: "v1",
870					},
871				}
872				return s.EnsureConfigEntry(0, entry)
873			},
874			expectErr:      `does not have a subset named "v1"`,
875			expectGraphErr: true,
876		},
877		/////////////////////////////////////////////////
878		"cannot introduce circular resolver redirect": {
879			entries: []structs.ConfigEntry{
880				&structs.ServiceResolverConfigEntry{
881					Kind: structs.ServiceResolver,
882					Name: "other",
883					Redirect: &structs.ServiceResolverRedirect{
884						Service: "main",
885					},
886				},
887			},
888			op: func(t *testing.T, s *Store) error {
889				entry := &structs.ServiceResolverConfigEntry{
890					Kind: structs.ServiceResolver,
891					Name: "main",
892					Redirect: &structs.ServiceResolverRedirect{
893						Service: "other",
894					},
895				}
896				return s.EnsureConfigEntry(0, entry)
897			},
898			expectErr:      `detected circular resolver redirect`,
899			expectGraphErr: true,
900		},
901		"cannot introduce circular split": {
902			entries: []structs.ConfigEntry{
903				&structs.ProxyConfigEntry{
904					Kind: structs.ProxyDefaults,
905					Name: structs.ProxyConfigGlobal,
906					Config: map[string]interface{}{
907						"protocol": "http",
908					},
909				},
910				&structs.ServiceSplitterConfigEntry{
911					Kind: "service-splitter",
912					Name: "other",
913					Splits: []structs.ServiceSplit{
914						{Weight: 100, Service: "main"},
915					},
916				},
917			},
918			op: func(t *testing.T, s *Store) error {
919				entry := &structs.ServiceSplitterConfigEntry{
920					Kind: "service-splitter",
921					Name: "main",
922					Splits: []structs.ServiceSplit{
923						{Weight: 100, Service: "other"},
924					},
925				}
926				return s.EnsureConfigEntry(0, entry)
927			},
928			expectErr:      `detected circular reference`,
929			expectGraphErr: true,
930		},
931	}
932
933	for name, tc := range cases {
934		name := name
935		tc := tc
936
937		t.Run(name, func(t *testing.T) {
938			s := testConfigStateStore(t)
939			for _, entry := range tc.entries {
940				require.NoError(t, entry.Normalize())
941				require.NoError(t, s.EnsureConfigEntry(0, entry))
942			}
943
944			err := tc.op(t, s)
945			if tc.expectErr != "" {
946				require.Error(t, err)
947				require.Contains(t, err.Error(), tc.expectErr)
948				_, ok := err.(*structs.ConfigEntryGraphError)
949				if tc.expectGraphErr {
950					require.True(t, ok, "%T is not a *ConfigEntryGraphError", err)
951				} else {
952					require.False(t, ok, "did not expect a *ConfigEntryGraphError here: %v", err)
953				}
954			} else {
955				require.NoError(t, err)
956			}
957		})
958	}
959}
960
961func TestStore_ReadDiscoveryChainConfigEntries_Overrides(t *testing.T) {
962	for _, tc := range []struct {
963		name           string
964		entries        []structs.ConfigEntry
965		expectBefore   []ConfigEntryKindName
966		overrides      map[ConfigEntryKindName]structs.ConfigEntry
967		expectAfter    []ConfigEntryKindName
968		expectAfterErr string
969		checkAfter     func(t *testing.T, entrySet *structs.DiscoveryChainConfigEntries)
970	}{
971		{
972			name: "mask service-defaults",
973			entries: []structs.ConfigEntry{
974				&structs.ServiceConfigEntry{
975					Kind:     structs.ServiceDefaults,
976					Name:     "main",
977					Protocol: "tcp",
978				},
979			},
980			expectBefore: []ConfigEntryKindName{
981				NewConfigEntryKindName(structs.ServiceDefaults, "main", nil),
982			},
983			overrides: map[ConfigEntryKindName]structs.ConfigEntry{
984				NewConfigEntryKindName(structs.ServiceDefaults, "main", nil): nil,
985			},
986			expectAfter: []ConfigEntryKindName{
987				// nothing
988			},
989		},
990		{
991			name: "edit service-defaults",
992			entries: []structs.ConfigEntry{
993				&structs.ServiceConfigEntry{
994					Kind:     structs.ServiceDefaults,
995					Name:     "main",
996					Protocol: "tcp",
997				},
998			},
999			expectBefore: []ConfigEntryKindName{
1000				NewConfigEntryKindName(structs.ServiceDefaults, "main", nil),
1001			},
1002			overrides: map[ConfigEntryKindName]structs.ConfigEntry{
1003				NewConfigEntryKindName(structs.ServiceDefaults, "main", nil): &structs.ServiceConfigEntry{
1004					Kind:     structs.ServiceDefaults,
1005					Name:     "main",
1006					Protocol: "grpc",
1007				},
1008			},
1009			expectAfter: []ConfigEntryKindName{
1010				NewConfigEntryKindName(structs.ServiceDefaults, "main", nil),
1011			},
1012			checkAfter: func(t *testing.T, entrySet *structs.DiscoveryChainConfigEntries) {
1013				defaults := entrySet.GetService(structs.NewServiceID("main", nil))
1014				require.NotNil(t, defaults)
1015				require.Equal(t, "grpc", defaults.Protocol)
1016			},
1017		},
1018
1019		{
1020			name: "mask service-router",
1021			entries: []structs.ConfigEntry{
1022				&structs.ServiceConfigEntry{
1023					Kind:     structs.ServiceDefaults,
1024					Name:     "main",
1025					Protocol: "http",
1026				},
1027				&structs.ServiceRouterConfigEntry{
1028					Kind: structs.ServiceRouter,
1029					Name: "main",
1030				},
1031			},
1032			expectBefore: []ConfigEntryKindName{
1033				NewConfigEntryKindName(structs.ServiceDefaults, "main", nil),
1034				NewConfigEntryKindName(structs.ServiceRouter, "main", nil),
1035			},
1036			overrides: map[ConfigEntryKindName]structs.ConfigEntry{
1037				NewConfigEntryKindName(structs.ServiceRouter, "main", nil): nil,
1038			},
1039			expectAfter: []ConfigEntryKindName{
1040				NewConfigEntryKindName(structs.ServiceDefaults, "main", nil),
1041			},
1042		},
1043		{
1044			name: "edit service-router",
1045			entries: []structs.ConfigEntry{
1046				&structs.ServiceConfigEntry{
1047					Kind:     structs.ServiceDefaults,
1048					Name:     "main",
1049					Protocol: "http",
1050				},
1051				&structs.ServiceResolverConfigEntry{
1052					Kind: structs.ServiceResolver,
1053					Name: "main",
1054					Subsets: map[string]structs.ServiceResolverSubset{
1055						"v1": {Filter: "Service.Meta.version == v1"},
1056						"v2": {Filter: "Service.Meta.version == v2"},
1057						"v3": {Filter: "Service.Meta.version == v3"},
1058					},
1059				},
1060				&structs.ServiceRouterConfigEntry{
1061					Kind: structs.ServiceRouter,
1062					Name: "main",
1063					Routes: []structs.ServiceRoute{
1064						{
1065							Match: &structs.ServiceRouteMatch{
1066								HTTP: &structs.ServiceRouteHTTPMatch{
1067									PathExact: "/admin",
1068								},
1069							},
1070							Destination: &structs.ServiceRouteDestination{
1071								ServiceSubset: "v2",
1072							},
1073						},
1074					},
1075				},
1076			},
1077			expectBefore: []ConfigEntryKindName{
1078				NewConfigEntryKindName(structs.ServiceDefaults, "main", nil),
1079				NewConfigEntryKindName(structs.ServiceResolver, "main", nil),
1080				NewConfigEntryKindName(structs.ServiceRouter, "main", nil),
1081			},
1082			overrides: map[ConfigEntryKindName]structs.ConfigEntry{
1083				NewConfigEntryKindName(structs.ServiceRouter, "main", nil): &structs.ServiceRouterConfigEntry{
1084					Kind: structs.ServiceRouter,
1085					Name: "main",
1086					Routes: []structs.ServiceRoute{
1087						{
1088							Match: &structs.ServiceRouteMatch{
1089								HTTP: &structs.ServiceRouteHTTPMatch{
1090									PathExact: "/admin",
1091								},
1092							},
1093							Destination: &structs.ServiceRouteDestination{
1094								ServiceSubset: "v3",
1095							},
1096						},
1097					},
1098				},
1099			},
1100			expectAfter: []ConfigEntryKindName{
1101				NewConfigEntryKindName(structs.ServiceDefaults, "main", nil),
1102				NewConfigEntryKindName(structs.ServiceResolver, "main", nil),
1103				NewConfigEntryKindName(structs.ServiceRouter, "main", nil),
1104			},
1105			checkAfter: func(t *testing.T, entrySet *structs.DiscoveryChainConfigEntries) {
1106				router := entrySet.GetRouter(structs.NewServiceID("main", nil))
1107				require.NotNil(t, router)
1108				require.Len(t, router.Routes, 1)
1109
1110				expect := structs.ServiceRoute{
1111					Match: &structs.ServiceRouteMatch{
1112						HTTP: &structs.ServiceRouteHTTPMatch{
1113							PathExact: "/admin",
1114						},
1115					},
1116					Destination: &structs.ServiceRouteDestination{
1117						ServiceSubset: "v3",
1118					},
1119				}
1120				require.Equal(t, expect, router.Routes[0])
1121			},
1122		},
1123
1124		{
1125			name: "mask service-splitter",
1126			entries: []structs.ConfigEntry{
1127				&structs.ServiceConfigEntry{
1128					Kind:     structs.ServiceDefaults,
1129					Name:     "main",
1130					Protocol: "http",
1131				},
1132				&structs.ServiceSplitterConfigEntry{
1133					Kind: structs.ServiceSplitter,
1134					Name: "main",
1135					Splits: []structs.ServiceSplit{
1136						{Weight: 100},
1137					},
1138				},
1139			},
1140			expectBefore: []ConfigEntryKindName{
1141				NewConfigEntryKindName(structs.ServiceDefaults, "main", nil),
1142				NewConfigEntryKindName(structs.ServiceSplitter, "main", nil),
1143			},
1144			overrides: map[ConfigEntryKindName]structs.ConfigEntry{
1145				NewConfigEntryKindName(structs.ServiceSplitter, "main", nil): nil,
1146			},
1147			expectAfter: []ConfigEntryKindName{
1148				NewConfigEntryKindName(structs.ServiceDefaults, "main", nil),
1149			},
1150		},
1151		{
1152			name: "edit service-splitter",
1153			entries: []structs.ConfigEntry{
1154				&structs.ServiceConfigEntry{
1155					Kind:     structs.ServiceDefaults,
1156					Name:     "main",
1157					Protocol: "http",
1158				},
1159				&structs.ServiceSplitterConfigEntry{
1160					Kind: structs.ServiceSplitter,
1161					Name: "main",
1162					Splits: []structs.ServiceSplit{
1163						{Weight: 100},
1164					},
1165				},
1166			},
1167			expectBefore: []ConfigEntryKindName{
1168				NewConfigEntryKindName(structs.ServiceDefaults, "main", nil),
1169				NewConfigEntryKindName(structs.ServiceSplitter, "main", nil),
1170			},
1171			overrides: map[ConfigEntryKindName]structs.ConfigEntry{
1172				NewConfigEntryKindName(structs.ServiceSplitter, "main", nil): &structs.ServiceSplitterConfigEntry{
1173					Kind: structs.ServiceSplitter,
1174					Name: "main",
1175					Splits: []structs.ServiceSplit{
1176						{Weight: 85, ServiceSubset: "v1"},
1177						{Weight: 15, ServiceSubset: "v2"},
1178					},
1179				},
1180			},
1181			expectAfter: []ConfigEntryKindName{
1182				NewConfigEntryKindName(structs.ServiceDefaults, "main", nil),
1183				NewConfigEntryKindName(structs.ServiceSplitter, "main", nil),
1184			},
1185			checkAfter: func(t *testing.T, entrySet *structs.DiscoveryChainConfigEntries) {
1186				splitter := entrySet.GetSplitter(structs.NewServiceID("main", nil))
1187				require.NotNil(t, splitter)
1188				require.Len(t, splitter.Splits, 2)
1189
1190				expect := []structs.ServiceSplit{
1191					{Weight: 85, ServiceSubset: "v1"},
1192					{Weight: 15, ServiceSubset: "v2"},
1193				}
1194				require.Equal(t, expect, splitter.Splits)
1195			},
1196		},
1197
1198		{
1199			name: "mask service-resolver",
1200			entries: []structs.ConfigEntry{
1201				&structs.ServiceResolverConfigEntry{
1202					Kind: structs.ServiceResolver,
1203					Name: "main",
1204				},
1205			},
1206			expectBefore: []ConfigEntryKindName{
1207				NewConfigEntryKindName(structs.ServiceResolver, "main", nil),
1208			},
1209			overrides: map[ConfigEntryKindName]structs.ConfigEntry{
1210				NewConfigEntryKindName(structs.ServiceResolver, "main", nil): nil,
1211			},
1212			expectAfter: []ConfigEntryKindName{
1213				// nothing
1214			},
1215		},
1216		{
1217			name: "edit service-resolver",
1218			entries: []structs.ConfigEntry{
1219				&structs.ServiceResolverConfigEntry{
1220					Kind: structs.ServiceResolver,
1221					Name: "main",
1222				},
1223			},
1224			expectBefore: []ConfigEntryKindName{
1225				NewConfigEntryKindName(structs.ServiceResolver, "main", nil),
1226			},
1227			overrides: map[ConfigEntryKindName]structs.ConfigEntry{
1228				NewConfigEntryKindName(structs.ServiceResolver, "main", nil): &structs.ServiceResolverConfigEntry{
1229					Kind:           structs.ServiceResolver,
1230					Name:           "main",
1231					ConnectTimeout: 33 * time.Second,
1232				},
1233			},
1234			expectAfter: []ConfigEntryKindName{
1235				NewConfigEntryKindName(structs.ServiceResolver, "main", nil),
1236			},
1237			checkAfter: func(t *testing.T, entrySet *structs.DiscoveryChainConfigEntries) {
1238				resolver := entrySet.GetResolver(structs.NewServiceID("main", nil))
1239				require.NotNil(t, resolver)
1240				require.Equal(t, 33*time.Second, resolver.ConnectTimeout)
1241			},
1242		},
1243	} {
1244		tc := tc
1245
1246		t.Run(tc.name, func(t *testing.T) {
1247			s := testConfigStateStore(t)
1248			for _, entry := range tc.entries {
1249				require.NoError(t, s.EnsureConfigEntry(0, entry))
1250			}
1251
1252			t.Run("without override", func(t *testing.T) {
1253				_, entrySet, err := s.readDiscoveryChainConfigEntries(nil, "main", nil, nil)
1254				require.NoError(t, err)
1255				got := entrySetToKindNames(entrySet)
1256				require.ElementsMatch(t, tc.expectBefore, got)
1257			})
1258
1259			t.Run("with override", func(t *testing.T) {
1260				_, entrySet, err := s.readDiscoveryChainConfigEntries(nil, "main", tc.overrides, nil)
1261
1262				if tc.expectAfterErr != "" {
1263					require.Error(t, err)
1264					require.Contains(t, err.Error(), tc.expectAfterErr)
1265				} else {
1266					require.NoError(t, err)
1267					got := entrySetToKindNames(entrySet)
1268					require.ElementsMatch(t, tc.expectAfter, got)
1269
1270					if tc.checkAfter != nil {
1271						tc.checkAfter(t, entrySet)
1272					}
1273				}
1274			})
1275		})
1276	}
1277}
1278
1279func entrySetToKindNames(entrySet *structs.DiscoveryChainConfigEntries) []ConfigEntryKindName {
1280	var out []ConfigEntryKindName
1281	for _, entry := range entrySet.Routers {
1282		out = append(out, NewConfigEntryKindName(
1283			entry.Kind,
1284			entry.Name,
1285			&entry.EnterpriseMeta,
1286		))
1287	}
1288	for _, entry := range entrySet.Splitters {
1289		out = append(out, NewConfigEntryKindName(
1290			entry.Kind,
1291			entry.Name,
1292			&entry.EnterpriseMeta,
1293		))
1294	}
1295	for _, entry := range entrySet.Resolvers {
1296		out = append(out, NewConfigEntryKindName(
1297			entry.Kind,
1298			entry.Name,
1299			&entry.EnterpriseMeta,
1300		))
1301	}
1302	for _, entry := range entrySet.Services {
1303		out = append(out, NewConfigEntryKindName(
1304			entry.Kind,
1305			entry.Name,
1306			&entry.EnterpriseMeta,
1307		))
1308	}
1309	return out
1310}
1311
1312func TestStore_ReadDiscoveryChainConfigEntries_SubsetSplit(t *testing.T) {
1313	s := testConfigStateStore(t)
1314
1315	entries := []structs.ConfigEntry{
1316		&structs.ServiceConfigEntry{
1317			Kind:     structs.ServiceDefaults,
1318			Name:     "main",
1319			Protocol: "http",
1320		},
1321		&structs.ServiceResolverConfigEntry{
1322			Kind: structs.ServiceResolver,
1323			Name: "main",
1324			Subsets: map[string]structs.ServiceResolverSubset{
1325				"v1": {
1326					Filter: "Service.Meta.version == v1",
1327				},
1328				"v2": {
1329					Filter: "Service.Meta.version == v2",
1330				},
1331			},
1332		},
1333		&structs.ServiceSplitterConfigEntry{
1334			Kind: structs.ServiceSplitter,
1335			Name: "main",
1336			Splits: []structs.ServiceSplit{
1337				{Weight: 90, ServiceSubset: "v1"},
1338				{Weight: 10, ServiceSubset: "v2"},
1339			},
1340		},
1341	}
1342
1343	for _, entry := range entries {
1344		require.NoError(t, s.EnsureConfigEntry(0, entry))
1345	}
1346
1347	_, entrySet, err := s.readDiscoveryChainConfigEntries(nil, "main", nil, nil)
1348	require.NoError(t, err)
1349
1350	require.Len(t, entrySet.Routers, 0)
1351	require.Len(t, entrySet.Splitters, 1)
1352	require.Len(t, entrySet.Resolvers, 1)
1353	require.Len(t, entrySet.Services, 1)
1354}
1355
1356// TODO(rb): add ServiceIntentions tests
1357
1358func TestStore_ValidateGatewayNamesCannotBeShared(t *testing.T) {
1359	s := testConfigStateStore(t)
1360
1361	ingress := &structs.IngressGatewayConfigEntry{
1362		Kind: structs.IngressGateway,
1363		Name: "gateway",
1364	}
1365	require.NoError(t, s.EnsureConfigEntry(0, ingress))
1366
1367	terminating := &structs.TerminatingGatewayConfigEntry{
1368		Kind: structs.TerminatingGateway,
1369		Name: "gateway",
1370	}
1371	// Cannot have 2 gateways with same service name
1372	require.Error(t, s.EnsureConfigEntry(1, terminating))
1373
1374	ingress = &structs.IngressGatewayConfigEntry{
1375		Kind: structs.IngressGateway,
1376		Name: "gateway",
1377		Listeners: []structs.IngressListener{
1378			{Port: 8080},
1379		},
1380	}
1381	require.NoError(t, s.EnsureConfigEntry(2, ingress))
1382	require.NoError(t, s.DeleteConfigEntry(3, structs.IngressGateway, "gateway", nil))
1383
1384	// Adding the terminating gateway with same name should now work
1385	require.NoError(t, s.EnsureConfigEntry(4, terminating))
1386
1387	// Cannot have 2 gateways with same service name
1388	require.Error(t, s.EnsureConfigEntry(5, ingress))
1389}
1390
1391func TestStore_ValidateIngressGatewayErrorOnMismatchedProtocols(t *testing.T) {
1392	newIngress := func(protocol, name string) *structs.IngressGatewayConfigEntry {
1393		return &structs.IngressGatewayConfigEntry{
1394			Kind: structs.IngressGateway,
1395			Name: "gateway",
1396			Listeners: []structs.IngressListener{
1397				{
1398					Port:     8080,
1399					Protocol: protocol,
1400					Services: []structs.IngressService{
1401						{Name: name},
1402					},
1403				},
1404			},
1405		}
1406	}
1407
1408	t.Run("http ingress fails with http upstream later changed to tcp", func(t *testing.T) {
1409		s := testConfigStateStore(t)
1410
1411		// First set the target service as http
1412		expected := &structs.ServiceConfigEntry{
1413			Kind:     structs.ServiceDefaults,
1414			Name:     "web",
1415			Protocol: "http",
1416		}
1417		require.NoError(t, s.EnsureConfigEntry(0, expected))
1418
1419		// Next configure http ingress to route to the http service
1420		require.NoError(t, s.EnsureConfigEntry(1, newIngress("http", "web")))
1421
1422		t.Run("via modification", func(t *testing.T) {
1423			// Now redefine the target service as tcp
1424			expected = &structs.ServiceConfigEntry{
1425				Kind:     structs.ServiceDefaults,
1426				Name:     "web",
1427				Protocol: "tcp",
1428			}
1429
1430			err := s.EnsureConfigEntry(2, expected)
1431			require.Error(t, err)
1432			require.Contains(t, err.Error(), `has protocol "tcp"`)
1433		})
1434		t.Run("via deletion", func(t *testing.T) {
1435			// This will fall back to the default tcp.
1436			err := s.DeleteConfigEntry(2, structs.ServiceDefaults, "web", nil)
1437			require.Error(t, err)
1438			require.Contains(t, err.Error(), `has protocol "tcp"`)
1439		})
1440	})
1441
1442	t.Run("tcp ingress ok with tcp upstream (defaulted) later changed to http", func(t *testing.T) {
1443		s := testConfigStateStore(t)
1444
1445		// First configure tcp ingress to route to a defaulted tcp service
1446		require.NoError(t, s.EnsureConfigEntry(0, newIngress("tcp", "web")))
1447
1448		// Now redefine the target service as http
1449		expected := &structs.ServiceConfigEntry{
1450			Kind:     structs.ServiceDefaults,
1451			Name:     "web",
1452			Protocol: "http",
1453		}
1454		require.NoError(t, s.EnsureConfigEntry(1, expected))
1455	})
1456
1457	t.Run("tcp ingress fails with tcp upstream (defaulted) later changed to http", func(t *testing.T) {
1458		s := testConfigStateStore(t)
1459
1460		// First configure tcp ingress to route to a defaulted tcp service
1461		require.NoError(t, s.EnsureConfigEntry(0, newIngress("tcp", "web")))
1462
1463		// Now redefine the target service as http
1464		expected := &structs.ServiceConfigEntry{
1465			Kind:     structs.ServiceDefaults,
1466			Name:     "web",
1467			Protocol: "http",
1468		}
1469		require.NoError(t, s.EnsureConfigEntry(1, expected))
1470
1471		t.Run("and a router defined", func(t *testing.T) {
1472			// This part should fail.
1473			expected2 := &structs.ServiceRouterConfigEntry{
1474				Kind: structs.ServiceRouter,
1475				Name: "web",
1476			}
1477			err := s.EnsureConfigEntry(2, expected2)
1478			require.Error(t, err)
1479			require.Contains(t, err.Error(), `has protocol "http"`)
1480		})
1481
1482		t.Run("and a splitter defined", func(t *testing.T) {
1483			// This part should fail.
1484			expected2 := &structs.ServiceSplitterConfigEntry{
1485				Kind: structs.ServiceSplitter,
1486				Name: "web",
1487				Splits: []structs.ServiceSplit{
1488					{Weight: 100},
1489				},
1490			}
1491			err := s.EnsureConfigEntry(2, expected2)
1492			require.Error(t, err)
1493			require.Contains(t, err.Error(), `has protocol "http"`)
1494		})
1495	})
1496
1497	t.Run("http ingress fails with tcp upstream (defaulted)", func(t *testing.T) {
1498		s := testConfigStateStore(t)
1499		err := s.EnsureConfigEntry(0, newIngress("http", "web"))
1500		require.Error(t, err)
1501		require.Contains(t, err.Error(), `has protocol "tcp"`)
1502	})
1503
1504	t.Run("http ingress fails with http2 upstream (via proxy-defaults)", func(t *testing.T) {
1505		s := testConfigStateStore(t)
1506		expected := &structs.ProxyConfigEntry{
1507			Kind: structs.ProxyDefaults,
1508			Name: "global",
1509			Config: map[string]interface{}{
1510				"protocol": "http2",
1511			},
1512		}
1513		require.NoError(t, s.EnsureConfigEntry(0, expected))
1514
1515		err := s.EnsureConfigEntry(1, newIngress("http", "web"))
1516		require.Error(t, err)
1517		require.Contains(t, err.Error(), `has protocol "http2"`)
1518	})
1519
1520	t.Run("http ingress fails with grpc upstream (via service-defaults)", func(t *testing.T) {
1521		s := testConfigStateStore(t)
1522		expected := &structs.ServiceConfigEntry{
1523			Kind:     structs.ServiceDefaults,
1524			Name:     "web",
1525			Protocol: "grpc",
1526		}
1527		require.NoError(t, s.EnsureConfigEntry(1, expected))
1528		err := s.EnsureConfigEntry(2, newIngress("http", "web"))
1529		require.Error(t, err)
1530		require.Contains(t, err.Error(), `has protocol "grpc"`)
1531	})
1532
1533	t.Run("http ingress ok with http upstream (via service-defaults)", func(t *testing.T) {
1534		s := testConfigStateStore(t)
1535		expected := &structs.ServiceConfigEntry{
1536			Kind:     structs.ServiceDefaults,
1537			Name:     "web",
1538			Protocol: "http",
1539		}
1540		require.NoError(t, s.EnsureConfigEntry(2, expected))
1541		require.NoError(t, s.EnsureConfigEntry(3, newIngress("http", "web")))
1542	})
1543
1544	t.Run("http ingress ignores wildcard specifier", func(t *testing.T) {
1545		s := testConfigStateStore(t)
1546		require.NoError(t, s.EnsureConfigEntry(4, newIngress("http", "*")))
1547	})
1548
1549	t.Run("deleting ingress config entry ok", func(t *testing.T) {
1550		s := testConfigStateStore(t)
1551		require.NoError(t, s.EnsureConfigEntry(1, newIngress("tcp", "web")))
1552		require.NoError(t, s.DeleteConfigEntry(5, structs.IngressGateway, "gateway", nil))
1553	})
1554}
1555
1556func TestSourcesForTarget(t *testing.T) {
1557	defaultMeta := *structs.DefaultEnterpriseMeta()
1558
1559	type expect struct {
1560		idx   uint64
1561		names []structs.ServiceName
1562	}
1563	tt := []struct {
1564		name    string
1565		entries []structs.ConfigEntry
1566		expect  expect
1567	}{
1568		{
1569			name:    "no relevant config entries",
1570			entries: []structs.ConfigEntry{},
1571			expect: expect{
1572				idx: 1,
1573				names: []structs.ServiceName{
1574					{Name: "sink", EnterpriseMeta: defaultMeta},
1575				},
1576			},
1577		},
1578		{
1579			name: "from route match",
1580			entries: []structs.ConfigEntry{
1581				&structs.ProxyConfigEntry{
1582					Kind: structs.ProxyDefaults,
1583					Name: structs.ProxyConfigGlobal,
1584					Config: map[string]interface{}{
1585						"protocol": "http",
1586					},
1587				},
1588				&structs.ServiceRouterConfigEntry{
1589					Kind: structs.ServiceRouter,
1590					Name: "web",
1591					Routes: []structs.ServiceRoute{
1592						{
1593							Match: &structs.ServiceRouteMatch{
1594								HTTP: &structs.ServiceRouteHTTPMatch{
1595									PathExact: "/sink",
1596								},
1597							},
1598							Destination: &structs.ServiceRouteDestination{
1599								Service: "sink",
1600							},
1601						},
1602					},
1603				},
1604			},
1605			expect: expect{
1606				idx: 2,
1607				names: []structs.ServiceName{
1608					{Name: "web", EnterpriseMeta: defaultMeta},
1609					{Name: "sink", EnterpriseMeta: defaultMeta},
1610				},
1611			},
1612		},
1613		{
1614			name: "from redirect",
1615			entries: []structs.ConfigEntry{
1616				&structs.ProxyConfigEntry{
1617					Kind: structs.ProxyDefaults,
1618					Name: structs.ProxyConfigGlobal,
1619					Config: map[string]interface{}{
1620						"protocol": "http",
1621					},
1622				},
1623				&structs.ServiceResolverConfigEntry{
1624					Kind: structs.ServiceResolver,
1625					Name: "web",
1626					Redirect: &structs.ServiceResolverRedirect{
1627						Service: "sink",
1628					},
1629				},
1630			},
1631			expect: expect{
1632				idx: 2,
1633				names: []structs.ServiceName{
1634					{Name: "web", EnterpriseMeta: defaultMeta},
1635					{Name: "sink", EnterpriseMeta: defaultMeta},
1636				},
1637			},
1638		},
1639		{
1640			name: "from failover",
1641			entries: []structs.ConfigEntry{
1642				&structs.ProxyConfigEntry{
1643					Kind: structs.ProxyDefaults,
1644					Name: structs.ProxyConfigGlobal,
1645					Config: map[string]interface{}{
1646						"protocol": "http",
1647					},
1648				},
1649				&structs.ServiceResolverConfigEntry{
1650					Kind: structs.ServiceResolver,
1651					Name: "web",
1652					Failover: map[string]structs.ServiceResolverFailover{
1653						"*": {
1654							Service:     "sink",
1655							Datacenters: []string{"dc2", "dc3"},
1656						},
1657					},
1658				},
1659			},
1660			expect: expect{
1661				idx: 2,
1662				names: []structs.ServiceName{
1663					{Name: "web", EnterpriseMeta: defaultMeta},
1664					{Name: "sink", EnterpriseMeta: defaultMeta},
1665				},
1666			},
1667		},
1668		{
1669			name: "from splitter",
1670			entries: []structs.ConfigEntry{
1671				&structs.ProxyConfigEntry{
1672					Kind: structs.ProxyDefaults,
1673					Name: structs.ProxyConfigGlobal,
1674					Config: map[string]interface{}{
1675						"protocol": "http",
1676					},
1677				},
1678				&structs.ServiceSplitterConfigEntry{
1679					Kind: structs.ServiceSplitter,
1680					Name: "web",
1681					Splits: []structs.ServiceSplit{
1682						{Weight: 90, Service: "web"},
1683						{Weight: 10, Service: "sink"},
1684					},
1685				},
1686			},
1687			expect: expect{
1688				idx: 2,
1689				names: []structs.ServiceName{
1690					{Name: "web", EnterpriseMeta: defaultMeta},
1691					{Name: "sink", EnterpriseMeta: defaultMeta},
1692				},
1693			},
1694		},
1695		{
1696			name: "chained route redirect",
1697			entries: []structs.ConfigEntry{
1698				&structs.ProxyConfigEntry{
1699					Kind: structs.ProxyDefaults,
1700					Name: structs.ProxyConfigGlobal,
1701					Config: map[string]interface{}{
1702						"protocol": "http",
1703					},
1704				},
1705				&structs.ServiceRouterConfigEntry{
1706					Kind: structs.ServiceRouter,
1707					Name: "source",
1708					Routes: []structs.ServiceRoute{
1709						{
1710							Match: &structs.ServiceRouteMatch{
1711								HTTP: &structs.ServiceRouteHTTPMatch{
1712									PathExact: "/route",
1713								},
1714							},
1715							Destination: &structs.ServiceRouteDestination{
1716								Service: "routed",
1717							},
1718						},
1719					},
1720				},
1721				&structs.ServiceResolverConfigEntry{
1722					Kind: structs.ServiceResolver,
1723					Name: "routed",
1724					Redirect: &structs.ServiceResolverRedirect{
1725						Service: "sink",
1726					},
1727				},
1728			},
1729			expect: expect{
1730				idx: 3,
1731				names: []structs.ServiceName{
1732					{Name: "source", EnterpriseMeta: defaultMeta},
1733					{Name: "routed", EnterpriseMeta: defaultMeta},
1734					{Name: "sink", EnterpriseMeta: defaultMeta},
1735				},
1736			},
1737		},
1738		{
1739			name: "kitchen sink with multiple services referencing sink directly",
1740			entries: []structs.ConfigEntry{
1741				&structs.ProxyConfigEntry{
1742					Kind: structs.ProxyDefaults,
1743					Name: structs.ProxyConfigGlobal,
1744					Config: map[string]interface{}{
1745						"protocol": "http",
1746					},
1747				},
1748				&structs.ServiceRouterConfigEntry{
1749					Kind: structs.ServiceRouter,
1750					Name: "routed",
1751					Routes: []structs.ServiceRoute{
1752						{
1753							Match: &structs.ServiceRouteMatch{
1754								HTTP: &structs.ServiceRouteHTTPMatch{
1755									PathExact: "/sink",
1756								},
1757							},
1758							Destination: &structs.ServiceRouteDestination{
1759								Service: "sink",
1760							},
1761						},
1762					},
1763				},
1764				&structs.ServiceResolverConfigEntry{
1765					Kind: structs.ServiceResolver,
1766					Name: "redirected",
1767					Redirect: &structs.ServiceResolverRedirect{
1768						Service: "sink",
1769					},
1770				},
1771				&structs.ServiceResolverConfigEntry{
1772					Kind: structs.ServiceResolver,
1773					Name: "failed-over",
1774					Failover: map[string]structs.ServiceResolverFailover{
1775						"*": {
1776							Service:     "sink",
1777							Datacenters: []string{"dc2", "dc3"},
1778						},
1779					},
1780				},
1781				&structs.ServiceSplitterConfigEntry{
1782					Kind: structs.ServiceSplitter,
1783					Name: "split",
1784					Splits: []structs.ServiceSplit{
1785						{Weight: 90, Service: "no-op"},
1786						{Weight: 10, Service: "sink"},
1787					},
1788				},
1789				&structs.ServiceSplitterConfigEntry{
1790					Kind: structs.ServiceSplitter,
1791					Name: "unrelated",
1792					Splits: []structs.ServiceSplit{
1793						{Weight: 90, Service: "zip"},
1794						{Weight: 10, Service: "zop"},
1795					},
1796				},
1797			},
1798			expect: expect{
1799				idx: 6,
1800				names: []structs.ServiceName{
1801					{Name: "split", EnterpriseMeta: defaultMeta},
1802					{Name: "failed-over", EnterpriseMeta: defaultMeta},
1803					{Name: "redirected", EnterpriseMeta: defaultMeta},
1804					{Name: "routed", EnterpriseMeta: defaultMeta},
1805					{Name: "sink", EnterpriseMeta: defaultMeta},
1806				},
1807			},
1808		},
1809	}
1810
1811	for _, tc := range tt {
1812		t.Run(tc.name, func(t *testing.T) {
1813			s := testStateStore(t)
1814			ws := memdb.NewWatchSet()
1815
1816			ca := &structs.CAConfiguration{
1817				Provider: "consul",
1818			}
1819			err := s.CASetConfig(0, ca)
1820			require.NoError(t, err)
1821
1822			var i uint64 = 1
1823			for _, entry := range tc.entries {
1824				require.NoError(t, entry.Normalize())
1825				require.NoError(t, s.EnsureConfigEntry(i, entry))
1826				i++
1827			}
1828
1829			tx := s.db.ReadTxn()
1830			defer tx.Abort()
1831
1832			sn := structs.NewServiceName("sink", structs.DefaultEnterpriseMeta())
1833			idx, names, err := s.discoveryChainSourcesTxn(tx, ws, "dc1", sn)
1834			require.NoError(t, err)
1835
1836			require.Equal(t, tc.expect.idx, idx)
1837			require.ElementsMatch(t, tc.expect.names, names)
1838		})
1839	}
1840}
1841
1842func TestTargetsForSource(t *testing.T) {
1843	defaultMeta := *structs.DefaultEnterpriseMeta()
1844
1845	type expect struct {
1846		idx uint64
1847		ids []structs.ServiceName
1848	}
1849	tt := []struct {
1850		name    string
1851		entries []structs.ConfigEntry
1852		expect  expect
1853	}{
1854		{
1855			name: "from route match",
1856			entries: []structs.ConfigEntry{
1857				&structs.ProxyConfigEntry{
1858					Kind: structs.ProxyDefaults,
1859					Name: structs.ProxyConfigGlobal,
1860					Config: map[string]interface{}{
1861						"protocol": "http",
1862					},
1863				},
1864				&structs.ServiceRouterConfigEntry{
1865					Kind: structs.ServiceRouter,
1866					Name: "web",
1867					Routes: []structs.ServiceRoute{
1868						{
1869							Match: &structs.ServiceRouteMatch{
1870								HTTP: &structs.ServiceRouteHTTPMatch{
1871									PathExact: "/sink",
1872								},
1873							},
1874							Destination: &structs.ServiceRouteDestination{
1875								Service: "sink",
1876							},
1877						},
1878					},
1879				},
1880			},
1881			expect: expect{
1882				idx: 2,
1883				ids: []structs.ServiceName{
1884					{Name: "web", EnterpriseMeta: defaultMeta},
1885					{Name: "sink", EnterpriseMeta: defaultMeta},
1886				},
1887			},
1888		},
1889		{
1890			name: "from redirect",
1891			entries: []structs.ConfigEntry{
1892				&structs.ProxyConfigEntry{
1893					Kind: structs.ProxyDefaults,
1894					Name: structs.ProxyConfigGlobal,
1895					Config: map[string]interface{}{
1896						"protocol": "http",
1897					},
1898				},
1899				&structs.ServiceResolverConfigEntry{
1900					Kind: structs.ServiceResolver,
1901					Name: "web",
1902					Redirect: &structs.ServiceResolverRedirect{
1903						Service: "sink",
1904					},
1905				},
1906			},
1907			expect: expect{
1908				idx: 2,
1909				ids: []structs.ServiceName{
1910					{Name: "sink", EnterpriseMeta: defaultMeta},
1911				},
1912			},
1913		},
1914		{
1915			name: "from failover",
1916			entries: []structs.ConfigEntry{
1917				&structs.ProxyConfigEntry{
1918					Kind: structs.ProxyDefaults,
1919					Name: structs.ProxyConfigGlobal,
1920					Config: map[string]interface{}{
1921						"protocol": "http",
1922					},
1923				},
1924				&structs.ServiceResolverConfigEntry{
1925					Kind: structs.ServiceResolver,
1926					Name: "web",
1927					Failover: map[string]structs.ServiceResolverFailover{
1928						"*": {
1929							Service:     "remote-web",
1930							Datacenters: []string{"dc2", "dc3"},
1931						},
1932					},
1933				},
1934			},
1935			expect: expect{
1936				idx: 2,
1937				ids: []structs.ServiceName{
1938					{Name: "web", EnterpriseMeta: defaultMeta},
1939				},
1940			},
1941		},
1942		{
1943			name: "from splitter",
1944			entries: []structs.ConfigEntry{
1945				&structs.ProxyConfigEntry{
1946					Kind: structs.ProxyDefaults,
1947					Name: structs.ProxyConfigGlobal,
1948					Config: map[string]interface{}{
1949						"protocol": "http",
1950					},
1951				},
1952				&structs.ServiceSplitterConfigEntry{
1953					Kind: structs.ServiceSplitter,
1954					Name: "web",
1955					Splits: []structs.ServiceSplit{
1956						{Weight: 90, Service: "web"},
1957						{Weight: 10, Service: "sink"},
1958					},
1959				},
1960			},
1961			expect: expect{
1962				idx: 2,
1963				ids: []structs.ServiceName{
1964					{Name: "web", EnterpriseMeta: defaultMeta},
1965					{Name: "sink", EnterpriseMeta: defaultMeta},
1966				},
1967			},
1968		},
1969		{
1970			name: "chained route redirect",
1971			entries: []structs.ConfigEntry{
1972				&structs.ProxyConfigEntry{
1973					Kind: structs.ProxyDefaults,
1974					Name: structs.ProxyConfigGlobal,
1975					Config: map[string]interface{}{
1976						"protocol": "http",
1977					},
1978				},
1979				&structs.ServiceRouterConfigEntry{
1980					Kind: structs.ServiceRouter,
1981					Name: "web",
1982					Routes: []structs.ServiceRoute{
1983						{
1984							Match: &structs.ServiceRouteMatch{
1985								HTTP: &structs.ServiceRouteHTTPMatch{
1986									PathExact: "/route",
1987								},
1988							},
1989							Destination: &structs.ServiceRouteDestination{
1990								Service: "routed",
1991							},
1992						},
1993					},
1994				},
1995				&structs.ServiceResolverConfigEntry{
1996					Kind: structs.ServiceResolver,
1997					Name: "routed",
1998					Redirect: &structs.ServiceResolverRedirect{
1999						Service: "sink",
2000					},
2001				},
2002			},
2003			expect: expect{
2004				idx: 3,
2005				ids: []structs.ServiceName{
2006					{Name: "web", EnterpriseMeta: defaultMeta},
2007					{Name: "sink", EnterpriseMeta: defaultMeta},
2008				},
2009			},
2010		},
2011	}
2012
2013	for _, tc := range tt {
2014		t.Run(tc.name, func(t *testing.T) {
2015			s := testStateStore(t)
2016			ws := memdb.NewWatchSet()
2017
2018			ca := &structs.CAConfiguration{
2019				Provider: "consul",
2020			}
2021			err := s.CASetConfig(0, ca)
2022			require.NoError(t, err)
2023
2024			var i uint64 = 1
2025			for _, entry := range tc.entries {
2026				require.NoError(t, entry.Normalize())
2027				require.NoError(t, s.EnsureConfigEntry(i, entry))
2028				i++
2029			}
2030
2031			tx := s.db.ReadTxn()
2032			defer tx.Abort()
2033
2034			idx, ids, err := s.discoveryChainTargetsTxn(tx, ws, "dc1", "web", nil)
2035			require.NoError(t, err)
2036
2037			require.Equal(t, tc.expect.idx, idx)
2038			require.ElementsMatch(t, tc.expect.ids, ids)
2039		})
2040	}
2041}
2042
2043func TestStore_ValidateServiceIntentionsErrorOnIncompatibleProtocols(t *testing.T) {
2044	l7perms := []*structs.IntentionPermission{
2045		{
2046			Action: structs.IntentionActionAllow,
2047			HTTP: &structs.IntentionHTTPPermission{
2048				PathPrefix: "/v2/",
2049			},
2050		},
2051	}
2052
2053	serviceDefaults := func(service, protocol string) *structs.ServiceConfigEntry {
2054		return &structs.ServiceConfigEntry{
2055			Kind:     structs.ServiceDefaults,
2056			Name:     service,
2057			Protocol: protocol,
2058		}
2059	}
2060
2061	proxyDefaults := func(protocol string) *structs.ProxyConfigEntry {
2062		return &structs.ProxyConfigEntry{
2063			Kind: structs.ProxyDefaults,
2064			Name: structs.ProxyConfigGlobal,
2065			Config: map[string]interface{}{
2066				"protocol": protocol,
2067			},
2068		}
2069	}
2070
2071	type operation struct {
2072		entry    structs.ConfigEntry
2073		deletion bool
2074	}
2075
2076	type testcase struct {
2077		ops           []operation
2078		expectLastErr string
2079	}
2080
2081	cases := map[string]testcase{
2082		"L4 intention cannot upgrade to L7 when tcp": {
2083			ops: []operation{
2084				{ // set the target service as tcp
2085					entry: serviceDefaults("api", "tcp"),
2086				},
2087				{ // create an L4 intention
2088					entry: &structs.ServiceIntentionsConfigEntry{
2089						Kind: structs.ServiceIntentions,
2090						Name: "api",
2091						Sources: []*structs.SourceIntention{
2092							{Name: "web", Action: structs.IntentionActionAllow},
2093						},
2094					},
2095				},
2096				{ // Should fail if converted to L7
2097					entry: &structs.ServiceIntentionsConfigEntry{
2098						Kind: structs.ServiceIntentions,
2099						Name: "api",
2100						Sources: []*structs.SourceIntention{
2101							{Name: "web", Permissions: l7perms},
2102						},
2103					},
2104				},
2105			},
2106			expectLastErr: `has protocol "tcp"`,
2107		},
2108		"L4 intention can upgrade to L7 when made http via service-defaults": {
2109			ops: []operation{
2110				{ // set the target service as tcp
2111					entry: serviceDefaults("api", "tcp"),
2112				},
2113				{ // create an L4 intention
2114					entry: &structs.ServiceIntentionsConfigEntry{
2115						Kind: structs.ServiceIntentions,
2116						Name: "api",
2117						Sources: []*structs.SourceIntention{
2118							{Name: "web", Action: structs.IntentionActionAllow},
2119						},
2120					},
2121				},
2122				{ // set the target service as http
2123					entry: serviceDefaults("api", "http"),
2124				},
2125				{ // Should succeed if converted to L7
2126					entry: &structs.ServiceIntentionsConfigEntry{
2127						Kind: structs.ServiceIntentions,
2128						Name: "api",
2129						Sources: []*structs.SourceIntention{
2130							{Name: "web", Permissions: l7perms},
2131						},
2132					},
2133				},
2134			},
2135		},
2136		"L4 intention can upgrade to L7 when made http via proxy-defaults": {
2137			ops: []operation{
2138				{ // set the target service as tcp
2139					entry: proxyDefaults("tcp"),
2140				},
2141				{ // create an L4 intention
2142					entry: &structs.ServiceIntentionsConfigEntry{
2143						Kind: structs.ServiceIntentions,
2144						Name: "api",
2145						Sources: []*structs.SourceIntention{
2146							{Name: "web", Action: structs.IntentionActionAllow},
2147						},
2148					},
2149				},
2150				{ // set the target service as http
2151					entry: proxyDefaults("http"),
2152				},
2153				{ // Should succeed if converted to L7
2154					entry: &structs.ServiceIntentionsConfigEntry{
2155						Kind: structs.ServiceIntentions,
2156						Name: "api",
2157						Sources: []*structs.SourceIntention{
2158							{Name: "web", Permissions: l7perms},
2159						},
2160					},
2161				},
2162			},
2163		},
2164		"L7 intention cannot have protocol downgraded to tcp via modification via service-defaults": {
2165			ops: []operation{
2166				{ // set the target service as http
2167					entry: serviceDefaults("api", "http"),
2168				},
2169				{ // create an L7 intention
2170					entry: &structs.ServiceIntentionsConfigEntry{
2171						Kind: structs.ServiceIntentions,
2172						Name: "api",
2173						Sources: []*structs.SourceIntention{
2174							{Name: "web", Permissions: l7perms},
2175						},
2176					},
2177				},
2178				{ // setting the target service as tcp should fail
2179					entry: serviceDefaults("api", "tcp"),
2180				},
2181			},
2182			expectLastErr: `has protocol "tcp"`,
2183		},
2184		"L7 intention cannot have protocol downgraded to tcp via modification via proxy-defaults": {
2185			ops: []operation{
2186				{ // set the target service as http
2187					entry: proxyDefaults("http"),
2188				},
2189				{ // create an L7 intention
2190					entry: &structs.ServiceIntentionsConfigEntry{
2191						Kind: structs.ServiceIntentions,
2192						Name: "api",
2193						Sources: []*structs.SourceIntention{
2194							{Name: "web", Permissions: l7perms},
2195						},
2196					},
2197				},
2198				{ // setting the target service as tcp should fail
2199					entry: proxyDefaults("tcp"),
2200				},
2201			},
2202			expectLastErr: `has protocol "tcp"`,
2203		},
2204		"L7 intention cannot have protocol downgraded to tcp via deletion of service-defaults": {
2205			ops: []operation{
2206				{ // set the target service as http
2207					entry: serviceDefaults("api", "http"),
2208				},
2209				{ // create an L7 intention
2210					entry: &structs.ServiceIntentionsConfigEntry{
2211						Kind: structs.ServiceIntentions,
2212						Name: "api",
2213						Sources: []*structs.SourceIntention{
2214							{Name: "web", Permissions: l7perms},
2215						},
2216					},
2217				},
2218				{ // setting the target service as tcp should fail
2219					entry:    serviceDefaults("api", "tcp"),
2220					deletion: true,
2221				},
2222			},
2223			expectLastErr: `has protocol "tcp"`,
2224		},
2225		"L7 intention cannot have protocol downgraded to tcp via deletion of proxy-defaults": {
2226			ops: []operation{
2227				{ // set the target service as http
2228					entry: proxyDefaults("http"),
2229				},
2230				{ // create an L7 intention
2231					entry: &structs.ServiceIntentionsConfigEntry{
2232						Kind: structs.ServiceIntentions,
2233						Name: "api",
2234						Sources: []*structs.SourceIntention{
2235							{Name: "web", Permissions: l7perms},
2236						},
2237					},
2238				},
2239				{ // setting the target service as tcp should fail
2240					entry:    proxyDefaults("tcp"),
2241					deletion: true,
2242				},
2243			},
2244			expectLastErr: `has protocol "tcp"`,
2245		},
2246	}
2247
2248	for name, tc := range cases {
2249		tc := tc
2250		t.Run(name, func(t *testing.T) {
2251			s := testStateStore(t)
2252
2253			var nextIndex = uint64(1)
2254
2255			for i, op := range tc.ops {
2256				isLast := (i == len(tc.ops)-1)
2257
2258				var err error
2259				if op.deletion {
2260					err = s.DeleteConfigEntry(nextIndex, op.entry.GetKind(), op.entry.GetName(), nil)
2261				} else {
2262					err = s.EnsureConfigEntry(nextIndex, op.entry)
2263				}
2264
2265				if isLast && tc.expectLastErr != "" {
2266					testutil.RequireErrorContains(t, err, `has protocol "tcp"`)
2267				} else {
2268					require.NoError(t, err)
2269				}
2270			}
2271		})
2272	}
2273}
2274