1package state
2
3import (
4	"testing"
5	"time"
6
7	"github.com/hashicorp/consul/agent/structs"
8	"github.com/hashicorp/go-memdb"
9	"github.com/stretchr/testify/assert"
10	"github.com/stretchr/testify/require"
11)
12
13func TestStore_IntentionGet_none(t *testing.T) {
14	assert := assert.New(t)
15	s := testStateStore(t)
16
17	// Querying with no results returns nil.
18	ws := memdb.NewWatchSet()
19	idx, res, err := s.IntentionGet(ws, testUUID())
20	assert.Equal(uint64(1), idx)
21	assert.Nil(res)
22	assert.Nil(err)
23}
24
25func TestStore_IntentionSetGet_basic(t *testing.T) {
26	assert := assert.New(t)
27	s := testStateStore(t)
28
29	// Call Get to populate the watch set
30	ws := memdb.NewWatchSet()
31	_, _, err := s.IntentionGet(ws, testUUID())
32	assert.Nil(err)
33
34	// Build a valid intention
35	ixn := &structs.Intention{
36		ID:              testUUID(),
37		SourceNS:        "default",
38		SourceName:      "*",
39		DestinationNS:   "default",
40		DestinationName: "web",
41		Meta:            map[string]string{},
42	}
43
44	// Inserting a with empty ID is disallowed.
45	assert.NoError(s.IntentionSet(1, ixn))
46
47	// Make sure the index got updated.
48	assert.Equal(uint64(1), s.maxIndex(intentionsTableName))
49	assert.True(watchFired(ws), "watch fired")
50
51	// Read it back out and verify it.
52	expected := &structs.Intention{
53		ID:              ixn.ID,
54		SourceNS:        "default",
55		SourceName:      "*",
56		DestinationNS:   "default",
57		DestinationName: "web",
58		Meta:            map[string]string{},
59		RaftIndex: structs.RaftIndex{
60			CreateIndex: 1,
61			ModifyIndex: 1,
62		},
63	}
64	expected.UpdatePrecedence()
65
66	ws = memdb.NewWatchSet()
67	idx, actual, err := s.IntentionGet(ws, ixn.ID)
68	assert.NoError(err)
69	assert.Equal(expected.CreateIndex, idx)
70	assert.Equal(expected, actual)
71
72	// Change a value and test updating
73	ixn.SourceNS = "foo"
74	assert.NoError(s.IntentionSet(2, ixn))
75
76	// Change a value that isn't in the unique 4 tuple and check we don't
77	// incorrectly consider this a duplicate when updating.
78	ixn.Action = structs.IntentionActionDeny
79	assert.NoError(s.IntentionSet(2, ixn))
80
81	// Make sure the index got updated.
82	assert.Equal(uint64(2), s.maxIndex(intentionsTableName))
83	assert.True(watchFired(ws), "watch fired")
84
85	// Read it back and verify the data was updated
86	expected.SourceNS = ixn.SourceNS
87	expected.Action = structs.IntentionActionDeny
88	expected.ModifyIndex = 2
89	ws = memdb.NewWatchSet()
90	idx, actual, err = s.IntentionGet(ws, ixn.ID)
91	assert.NoError(err)
92	assert.Equal(expected.ModifyIndex, idx)
93	assert.Equal(expected, actual)
94
95	// Attempt to insert another intention with duplicate 4-tuple
96	ixn = &structs.Intention{
97		ID:              testUUID(),
98		SourceNS:        "default",
99		SourceName:      "*",
100		DestinationNS:   "default",
101		DestinationName: "web",
102		Meta:            map[string]string{},
103	}
104
105	// Duplicate 4-tuple should cause an error
106	ws = memdb.NewWatchSet()
107	assert.Error(s.IntentionSet(3, ixn))
108
109	// Make sure the index did NOT get updated.
110	assert.Equal(uint64(2), s.maxIndex(intentionsTableName))
111	assert.False(watchFired(ws), "watch not fired")
112}
113
114func TestStore_IntentionSet_emptyId(t *testing.T) {
115	assert := assert.New(t)
116	s := testStateStore(t)
117
118	ws := memdb.NewWatchSet()
119	_, _, err := s.IntentionGet(ws, testUUID())
120	assert.NoError(err)
121
122	// Inserting a with empty ID is disallowed.
123	err = s.IntentionSet(1, &structs.Intention{})
124	assert.Error(err)
125	assert.Contains(err.Error(), ErrMissingIntentionID.Error())
126
127	// Index is not updated if nothing is saved.
128	assert.Equal(s.maxIndex(intentionsTableName), uint64(0))
129	assert.False(watchFired(ws), "watch fired")
130}
131
132func TestStore_IntentionSet_updateCreatedAt(t *testing.T) {
133	assert := assert.New(t)
134	s := testStateStore(t)
135
136	// Build a valid intention
137	now := time.Now()
138	ixn := structs.Intention{
139		ID:        testUUID(),
140		CreatedAt: now,
141	}
142
143	// Insert
144	assert.NoError(s.IntentionSet(1, &ixn))
145
146	// Change a value and test updating
147	ixnUpdate := ixn
148	ixnUpdate.CreatedAt = now.Add(10 * time.Second)
149	assert.NoError(s.IntentionSet(2, &ixnUpdate))
150
151	// Read it back and verify
152	_, actual, err := s.IntentionGet(nil, ixn.ID)
153	assert.NoError(err)
154	assert.Equal(now, actual.CreatedAt)
155}
156
157func TestStore_IntentionSet_metaNil(t *testing.T) {
158	assert := assert.New(t)
159	s := testStateStore(t)
160
161	// Build a valid intention
162	ixn := structs.Intention{
163		ID: testUUID(),
164	}
165
166	// Insert
167	assert.NoError(s.IntentionSet(1, &ixn))
168
169	// Read it back and verify
170	_, actual, err := s.IntentionGet(nil, ixn.ID)
171	assert.NoError(err)
172	assert.NotNil(actual.Meta)
173}
174
175func TestStore_IntentionSet_metaSet(t *testing.T) {
176	assert := assert.New(t)
177	s := testStateStore(t)
178
179	// Build a valid intention
180	ixn := structs.Intention{
181		ID:   testUUID(),
182		Meta: map[string]string{"foo": "bar"},
183	}
184
185	// Insert
186	assert.NoError(s.IntentionSet(1, &ixn))
187
188	// Read it back and verify
189	_, actual, err := s.IntentionGet(nil, ixn.ID)
190	assert.NoError(err)
191	assert.Equal(ixn.Meta, actual.Meta)
192}
193
194func TestStore_IntentionDelete(t *testing.T) {
195	assert := assert.New(t)
196	s := testStateStore(t)
197
198	// Call Get to populate the watch set
199	ws := memdb.NewWatchSet()
200	_, _, err := s.IntentionGet(ws, testUUID())
201	assert.NoError(err)
202
203	// Create
204	ixn := &structs.Intention{ID: testUUID()}
205	assert.NoError(s.IntentionSet(1, ixn))
206
207	// Make sure the index got updated.
208	assert.Equal(s.maxIndex(intentionsTableName), uint64(1))
209	assert.True(watchFired(ws), "watch fired")
210
211	// Delete
212	assert.NoError(s.IntentionDelete(2, ixn.ID))
213
214	// Make sure the index got updated.
215	assert.Equal(s.maxIndex(intentionsTableName), uint64(2))
216	assert.True(watchFired(ws), "watch fired")
217
218	// Sanity check to make sure it's not there.
219	idx, actual, err := s.IntentionGet(nil, ixn.ID)
220	assert.NoError(err)
221	assert.Equal(idx, uint64(2))
222	assert.Nil(actual)
223}
224
225func TestStore_IntentionsList(t *testing.T) {
226	assert := assert.New(t)
227	s := testStateStore(t)
228
229	// Querying with no results returns nil.
230	ws := memdb.NewWatchSet()
231	idx, res, err := s.Intentions(ws)
232	assert.NoError(err)
233	assert.Nil(res)
234	assert.Equal(uint64(1), idx)
235
236	// Create some intentions
237	ixns := structs.Intentions{
238		&structs.Intention{
239			ID:   testUUID(),
240			Meta: map[string]string{},
241		},
242		&structs.Intention{
243			ID:   testUUID(),
244			Meta: map[string]string{},
245		},
246	}
247
248	// Force deterministic sort order
249	ixns[0].ID = "a" + ixns[0].ID[1:]
250	ixns[1].ID = "b" + ixns[1].ID[1:]
251
252	// Create
253	for i, ixn := range ixns {
254		assert.NoError(s.IntentionSet(uint64(1+i), ixn))
255	}
256	assert.True(watchFired(ws), "watch fired")
257
258	// Read it back and verify.
259	expected := structs.Intentions{
260		&structs.Intention{
261			ID:   ixns[0].ID,
262			Meta: map[string]string{},
263			RaftIndex: structs.RaftIndex{
264				CreateIndex: 1,
265				ModifyIndex: 1,
266			},
267		},
268		&structs.Intention{
269			ID:   ixns[1].ID,
270			Meta: map[string]string{},
271			RaftIndex: structs.RaftIndex{
272				CreateIndex: 2,
273				ModifyIndex: 2,
274			},
275		},
276	}
277	for i := range expected {
278		expected[i].UpdatePrecedence() // to match what is returned...
279	}
280	idx, actual, err := s.Intentions(nil)
281	assert.NoError(err)
282	assert.Equal(idx, uint64(2))
283	assert.Equal(expected, actual)
284}
285
286// Test the matrix of match logic.
287//
288// Note that this doesn't need to test the intention sort logic exhaustively
289// since this is tested in their sort implementation in the structs.
290func TestStore_IntentionMatch_table(t *testing.T) {
291	type testCase struct {
292		Name     string
293		Insert   [][]string   // List of intentions to insert
294		Query    [][]string   // List of intentions to match
295		Expected [][][]string // List of matches, where each match is a list of intentions
296	}
297
298	cases := []testCase{
299		{
300			"single exact namespace/name",
301			[][]string{
302				{"foo", "*"},
303				{"foo", "bar"},
304				{"foo", "baz"}, // shouldn't match
305				{"bar", "bar"}, // shouldn't match
306				{"bar", "*"},   // shouldn't match
307				{"*", "*"},
308			},
309			[][]string{
310				{"foo", "bar"},
311			},
312			[][][]string{
313				{
314					{"foo", "bar"},
315					{"foo", "*"},
316					{"*", "*"},
317				},
318			},
319		},
320
321		{
322			"multiple exact namespace/name",
323			[][]string{
324				{"foo", "*"},
325				{"foo", "bar"},
326				{"foo", "baz"}, // shouldn't match
327				{"bar", "bar"},
328				{"bar", "*"},
329			},
330			[][]string{
331				{"foo", "bar"},
332				{"bar", "bar"},
333			},
334			[][][]string{
335				{
336					{"foo", "bar"},
337					{"foo", "*"},
338				},
339				{
340					{"bar", "bar"},
341					{"bar", "*"},
342				},
343			},
344		},
345
346		{
347			"single exact namespace/name with duplicate destinations",
348			[][]string{
349				// 4-tuple specifies src and destination to test duplicate destinations
350				// with different sources. We flip them around to test in both
351				// directions. The first pair are the ones searched on in both cases so
352				// the duplicates need to be there.
353				{"foo", "bar", "foo", "*"},
354				{"foo", "bar", "bar", "*"},
355				{"*", "*", "*", "*"},
356			},
357			[][]string{
358				{"foo", "bar"},
359			},
360			[][][]string{
361				{
362					// Note the first two have the same precedence so we rely on arbitrary
363					// lexicographical tie-break behaviour.
364					{"foo", "bar", "bar", "*"},
365					{"foo", "bar", "foo", "*"},
366					{"*", "*", "*", "*"},
367				},
368			},
369		},
370	}
371
372	// testRunner implements the test for a single case, but can be
373	// parameterized to run for both source and destination so we can
374	// test both cases.
375	testRunner := func(t *testing.T, tc testCase, typ structs.IntentionMatchType) {
376		// Insert the set
377		assert := assert.New(t)
378		s := testStateStore(t)
379		var idx uint64 = 1
380		for _, v := range tc.Insert {
381			ixn := &structs.Intention{ID: testUUID()}
382			switch typ {
383			case structs.IntentionMatchDestination:
384				ixn.DestinationNS = v[0]
385				ixn.DestinationName = v[1]
386				if len(v) == 4 {
387					ixn.SourceNS = v[2]
388					ixn.SourceName = v[3]
389				}
390			case structs.IntentionMatchSource:
391				ixn.SourceNS = v[0]
392				ixn.SourceName = v[1]
393				if len(v) == 4 {
394					ixn.DestinationNS = v[2]
395					ixn.DestinationName = v[3]
396				}
397			}
398
399			assert.NoError(s.IntentionSet(idx, ixn))
400
401			idx++
402		}
403
404		// Build the arguments
405		args := &structs.IntentionQueryMatch{Type: typ}
406		for _, q := range tc.Query {
407			args.Entries = append(args.Entries, structs.IntentionMatchEntry{
408				Namespace: q[0],
409				Name:      q[1],
410			})
411		}
412
413		// Match
414		_, matches, err := s.IntentionMatch(nil, args)
415		assert.NoError(err)
416
417		// Should have equal lengths
418		require.Len(t, matches, len(tc.Expected))
419
420		// Verify matches
421		for i, expected := range tc.Expected {
422			var actual [][]string
423			for _, ixn := range matches[i] {
424				switch typ {
425				case structs.IntentionMatchDestination:
426					if len(expected) > 1 && len(expected[0]) == 4 {
427						actual = append(actual, []string{
428							ixn.DestinationNS,
429							ixn.DestinationName,
430							ixn.SourceNS,
431							ixn.SourceName,
432						})
433					} else {
434						actual = append(actual, []string{ixn.DestinationNS, ixn.DestinationName})
435					}
436				case structs.IntentionMatchSource:
437					if len(expected) > 1 && len(expected[0]) == 4 {
438						actual = append(actual, []string{
439							ixn.SourceNS,
440							ixn.SourceName,
441							ixn.DestinationNS,
442							ixn.DestinationName,
443						})
444					} else {
445						actual = append(actual, []string{ixn.SourceNS, ixn.SourceName})
446					}
447				}
448			}
449
450			assert.Equal(expected, actual)
451		}
452	}
453
454	for _, tc := range cases {
455		t.Run(tc.Name+" (destination)", func(t *testing.T) {
456			testRunner(t, tc, structs.IntentionMatchDestination)
457		})
458
459		t.Run(tc.Name+" (source)", func(t *testing.T) {
460			testRunner(t, tc, structs.IntentionMatchSource)
461		})
462	}
463}
464
465func TestStore_Intention_Snapshot_Restore(t *testing.T) {
466	assert := assert.New(t)
467	s := testStateStore(t)
468
469	// Create some intentions.
470	ixns := structs.Intentions{
471		&structs.Intention{
472			DestinationName: "foo",
473		},
474		&structs.Intention{
475			DestinationName: "bar",
476		},
477		&structs.Intention{
478			DestinationName: "baz",
479		},
480	}
481
482	// Force the sort order of the UUIDs before we create them so the
483	// order is deterministic.
484	id := testUUID()
485	ixns[0].ID = "a" + id[1:]
486	ixns[1].ID = "b" + id[1:]
487	ixns[2].ID = "c" + id[1:]
488
489	// Now create
490	for i, ixn := range ixns {
491		assert.NoError(s.IntentionSet(uint64(4+i), ixn))
492	}
493
494	// Snapshot the queries.
495	snap := s.Snapshot()
496	defer snap.Close()
497
498	// Alter the real state store.
499	assert.NoError(s.IntentionDelete(7, ixns[0].ID))
500
501	// Verify the snapshot.
502	assert.Equal(snap.LastIndex(), uint64(6))
503
504	// Expect them sorted in insertion order
505	expected := structs.Intentions{
506		&structs.Intention{
507			ID:              ixns[0].ID,
508			DestinationName: "foo",
509			Meta:            map[string]string{},
510			RaftIndex: structs.RaftIndex{
511				CreateIndex: 4,
512				ModifyIndex: 4,
513			},
514		},
515		&structs.Intention{
516			ID:              ixns[1].ID,
517			DestinationName: "bar",
518			Meta:            map[string]string{},
519			RaftIndex: structs.RaftIndex{
520				CreateIndex: 5,
521				ModifyIndex: 5,
522			},
523		},
524		&structs.Intention{
525			ID:              ixns[2].ID,
526			DestinationName: "baz",
527			Meta:            map[string]string{},
528			RaftIndex: structs.RaftIndex{
529				CreateIndex: 6,
530				ModifyIndex: 6,
531			},
532		},
533	}
534	for i := range expected {
535		expected[i].UpdatePrecedence() // to match what is returned...
536	}
537	dump, err := snap.Intentions()
538	assert.NoError(err)
539	assert.Equal(expected, dump)
540
541	// Restore the values into a new state store.
542	func() {
543		s := testStateStore(t)
544		restore := s.Restore()
545		for _, ixn := range dump {
546			assert.NoError(restore.Intention(ixn))
547		}
548		restore.Commit()
549
550		// Read the restored values back out and verify that they match. Note that
551		// Intentions are returned precedence sorted unlike the snapshot so we need
552		// to rearrange the expected slice some.
553		expected[0], expected[1], expected[2] = expected[1], expected[2], expected[0]
554		idx, actual, err := s.Intentions(nil)
555		assert.NoError(err)
556		assert.Equal(idx, uint64(6))
557		assert.Equal(expected, actual)
558	}()
559}
560