1package state
2
3import (
4	"reflect"
5	"strings"
6	"testing"
7
8	"github.com/hashicorp/consul/agent/structs"
9	"github.com/hashicorp/go-memdb"
10)
11
12func TestStateStore_PreparedQuery_isUUID(t *testing.T) {
13	cases := map[string]bool{
14		"":                                      false,
15		"nope":                                  false,
16		"f004177f-2c28-83b7-4229-eacc25fe55d1":  true,
17		"F004177F-2C28-83B7-4229-EACC25FE55D1":  true,
18		"x004177f-2c28-83b7-4229-eacc25fe55d1":  false, // Bad hex
19		"f004177f-xc28-83b7-4229-eacc25fe55d1":  false, // Bad hex
20		"f004177f-2c28-x3b7-4229-eacc25fe55d1":  false, // Bad hex
21		"f004177f-2c28-83b7-x229-eacc25fe55d1":  false, // Bad hex
22		"f004177f-2c28-83b7-4229-xacc25fe55d1":  false, // Bad hex
23		" f004177f-2c28-83b7-4229-eacc25fe55d1": false, // Leading whitespace
24		"f004177f-2c28-83b7-4229-eacc25fe55d1 ": false, // Trailing whitespace
25	}
26	for i := 0; i < 100; i++ {
27		cases[testUUID()] = true
28	}
29
30	for str, expected := range cases {
31		if actual := isUUID(str); actual != expected {
32			t.Fatalf("bad: '%s'", str)
33		}
34	}
35}
36
37func TestStateStore_PreparedQuerySet_PreparedQueryGet(t *testing.T) {
38	s := testStateStore(t)
39
40	// Querying with no results returns nil.
41	ws := memdb.NewWatchSet()
42	idx, res, err := s.PreparedQueryGet(ws, testUUID())
43	if idx != 0 || res != nil || err != nil {
44		t.Fatalf("expected (0, nil, nil), got: (%d, %#v, %#v)", idx, res, err)
45	}
46
47	// Inserting a query with empty ID is disallowed.
48	if err := s.PreparedQuerySet(1, &structs.PreparedQuery{}); err == nil {
49		t.Fatalf("expected %#v, got: %#v", ErrMissingQueryID, err)
50	}
51
52	// Index is not updated if nothing is saved.
53	if idx := s.maxIndex("prepared-queries"); idx != 0 {
54		t.Fatalf("bad index: %d", idx)
55	}
56	if watchFired(ws) {
57		t.Fatalf("bad")
58	}
59
60	// Build a legit-looking query with the most basic options.
61	query := &structs.PreparedQuery{
62		ID:      testUUID(),
63		Session: "nope",
64		Service: structs.ServiceQuery{
65			Service: "redis",
66		},
67	}
68
69	// The set will still fail because the session is bogus.
70	err = s.PreparedQuerySet(1, query)
71	if err == nil || !strings.Contains(err.Error(), "invalid session") {
72		t.Fatalf("bad: %v", err)
73	}
74
75	// Index is not updated if nothing is saved.
76	if idx := s.maxIndex("prepared-queries"); idx != 0 {
77		t.Fatalf("bad index: %d", idx)
78	}
79	if watchFired(ws) {
80		t.Fatalf("bad")
81	}
82
83	// Now register the service and remove the bogus session.
84	testRegisterNode(t, s, 1, "foo")
85	testRegisterService(t, s, 2, "foo", "redis")
86	query.Session = ""
87
88	// This should go through.
89	if err := s.PreparedQuerySet(3, query); err != nil {
90		t.Fatalf("err: %s", err)
91	}
92
93	// Make sure the index got updated.
94	if idx := s.maxIndex("prepared-queries"); idx != 3 {
95		t.Fatalf("bad index: %d", idx)
96	}
97	if !watchFired(ws) {
98		t.Fatalf("bad")
99	}
100
101	// Read it back out and verify it.
102	expected := &structs.PreparedQuery{
103		ID: query.ID,
104		Service: structs.ServiceQuery{
105			Service: "redis",
106		},
107		RaftIndex: structs.RaftIndex{
108			CreateIndex: 3,
109			ModifyIndex: 3,
110		},
111	}
112	ws = memdb.NewWatchSet()
113	idx, actual, err := s.PreparedQueryGet(ws, query.ID)
114	if err != nil {
115		t.Fatalf("err: %s", err)
116	}
117	if idx != 3 {
118		t.Fatalf("bad index: %d", idx)
119	}
120	if !reflect.DeepEqual(actual, expected) {
121		t.Fatalf("bad: %v", actual)
122	}
123
124	// Give it a name and set it again.
125	query.Name = "test-query"
126	if err := s.PreparedQuerySet(4, query); err != nil {
127		t.Fatalf("err: %s", err)
128	}
129
130	// Make sure the index got updated.
131	if idx := s.maxIndex("prepared-queries"); idx != 4 {
132		t.Fatalf("bad index: %d", idx)
133	}
134	if !watchFired(ws) {
135		t.Fatalf("bad")
136	}
137
138	// Read it back and verify the data was updated as well as the index.
139	expected.Name = "test-query"
140	expected.ModifyIndex = 4
141	ws = memdb.NewWatchSet()
142	idx, actual, err = s.PreparedQueryGet(ws, query.ID)
143	if err != nil {
144		t.Fatalf("err: %s", err)
145	}
146	if idx != 4 {
147		t.Fatalf("bad index: %d", idx)
148	}
149	if !reflect.DeepEqual(actual, expected) {
150		t.Fatalf("bad: %v", actual)
151	}
152
153	// Try to tie it to a bogus session.
154	query.Session = testUUID()
155	err = s.PreparedQuerySet(5, query)
156	if err == nil || !strings.Contains(err.Error(), "invalid session") {
157		t.Fatalf("bad: %v", err)
158	}
159
160	// Index is not updated if nothing is saved.
161	if idx := s.maxIndex("prepared-queries"); idx != 4 {
162		t.Fatalf("bad index: %d", idx)
163	}
164	if watchFired(ws) {
165		t.Fatalf("bad")
166	}
167
168	// Now make a session and try again.
169	session := &structs.Session{
170		ID:   query.Session,
171		Node: "foo",
172	}
173	if err := s.SessionCreate(5, session); err != nil {
174		t.Fatalf("err: %s", err)
175	}
176	if err := s.PreparedQuerySet(6, query); err != nil {
177		t.Fatalf("err: %s", err)
178	}
179
180	// Make sure the index got updated.
181	if idx := s.maxIndex("prepared-queries"); idx != 6 {
182		t.Fatalf("bad index: %d", idx)
183	}
184	if !watchFired(ws) {
185		t.Fatalf("bad")
186	}
187
188	// Read it back and verify the data was updated as well as the index.
189	expected.Session = query.Session
190	expected.ModifyIndex = 6
191	ws = memdb.NewWatchSet()
192	idx, actual, err = s.PreparedQueryGet(ws, query.ID)
193	if err != nil {
194		t.Fatalf("err: %s", err)
195	}
196	if idx != 6 {
197		t.Fatalf("bad index: %d", idx)
198	}
199	if !reflect.DeepEqual(actual, expected) {
200		t.Fatalf("bad: %v", actual)
201	}
202
203	// Try to register a query with the same name and make sure it fails.
204	{
205		evil := &structs.PreparedQuery{
206			ID:   testUUID(),
207			Name: query.Name,
208			Service: structs.ServiceQuery{
209				Service: "redis",
210			},
211		}
212		err := s.PreparedQuerySet(7, evil)
213		if err == nil || !strings.Contains(err.Error(), "aliases an existing query name") {
214			t.Fatalf("bad: %v", err)
215		}
216
217		// Sanity check to make sure it's not there.
218		idx, actual, err := s.PreparedQueryGet(nil, evil.ID)
219		if err != nil {
220			t.Fatalf("err: %s", err)
221		}
222		if idx != 6 {
223			t.Fatalf("bad index: %d", idx)
224		}
225		if actual != nil {
226			t.Fatalf("bad: %v", actual)
227		}
228	}
229
230	// Try to abuse the system by trying to register a query whose name
231	// aliases a real query ID.
232	{
233		evil := &structs.PreparedQuery{
234			ID:   testUUID(),
235			Name: query.ID,
236			Service: structs.ServiceQuery{
237				Service: "redis",
238			},
239		}
240		err := s.PreparedQuerySet(8, evil)
241		if err == nil || !strings.Contains(err.Error(), "aliases an existing query ID") {
242			t.Fatalf("bad: %v", err)
243		}
244
245		// Sanity check to make sure it's not there.
246		idx, actual, err := s.PreparedQueryGet(nil, evil.ID)
247		if err != nil {
248			t.Fatalf("err: %s", err)
249		}
250		if idx != 6 {
251			t.Fatalf("bad index: %d", idx)
252		}
253		if actual != nil {
254			t.Fatalf("bad: %v", actual)
255		}
256	}
257
258	// Try to register a template that squats on the existing query's name.
259	{
260		evil := &structs.PreparedQuery{
261			ID:   testUUID(),
262			Name: query.Name,
263			Template: structs.QueryTemplateOptions{
264				Type: structs.QueryTemplateTypeNamePrefixMatch,
265			},
266			Service: structs.ServiceQuery{
267				Service: "redis",
268			},
269		}
270		err := s.PreparedQuerySet(8, evil)
271		if err == nil || !strings.Contains(err.Error(), "aliases an existing query name") {
272			t.Fatalf("bad: %v", err)
273		}
274
275		// Sanity check to make sure it's not there.
276		idx, actual, err := s.PreparedQueryGet(nil, evil.ID)
277		if err != nil {
278			t.Fatalf("err: %s", err)
279		}
280		if idx != 6 {
281			t.Fatalf("bad index: %d", idx)
282		}
283		if actual != nil {
284			t.Fatalf("bad: %v", actual)
285		}
286	}
287
288	// Index is not updated if nothing is saved.
289	if idx := s.maxIndex("prepared-queries"); idx != 6 {
290		t.Fatalf("bad index: %d", idx)
291	}
292	if watchFired(ws) {
293		t.Fatalf("bad")
294	}
295
296	// Turn the query into a template with an empty name.
297	query.Name = ""
298	query.Template = structs.QueryTemplateOptions{
299		Type: structs.QueryTemplateTypeNamePrefixMatch,
300	}
301	if err := s.PreparedQuerySet(9, query); err != nil {
302		t.Fatalf("err: %s", err)
303	}
304
305	// Make sure the index got updated.
306	if idx := s.maxIndex("prepared-queries"); idx != 9 {
307		t.Fatalf("bad index: %d", idx)
308	}
309	if !watchFired(ws) {
310		t.Fatalf("bad")
311	}
312
313	// Read it back and verify the data was updated as well as the index.
314	expected.Name = ""
315	expected.Template = structs.QueryTemplateOptions{
316		Type: structs.QueryTemplateTypeNamePrefixMatch,
317	}
318	expected.ModifyIndex = 9
319	ws = memdb.NewWatchSet()
320	idx, actual, err = s.PreparedQueryGet(ws, query.ID)
321	if err != nil {
322		t.Fatalf("err: %s", err)
323	}
324	if idx != 9 {
325		t.Fatalf("bad index: %d", idx)
326	}
327	if !reflect.DeepEqual(actual, expected) {
328		t.Fatalf("bad: %v", actual)
329	}
330
331	// Try to register a template that squats on the empty prefix.
332	{
333		evil := &structs.PreparedQuery{
334			ID:   testUUID(),
335			Name: "",
336			Template: structs.QueryTemplateOptions{
337				Type: structs.QueryTemplateTypeNamePrefixMatch,
338			},
339			Service: structs.ServiceQuery{
340				Service: "redis",
341			},
342		}
343		err := s.PreparedQuerySet(10, evil)
344		if err == nil || !strings.Contains(err.Error(), "query template with an empty name already exists") {
345			t.Fatalf("bad: %v", err)
346		}
347
348		// Sanity check to make sure it's not there.
349		idx, actual, err := s.PreparedQueryGet(nil, evil.ID)
350		if err != nil {
351			t.Fatalf("err: %s", err)
352		}
353		if idx != 9 {
354			t.Fatalf("bad index: %d", idx)
355		}
356		if actual != nil {
357			t.Fatalf("bad: %v", actual)
358		}
359	}
360
361	// Give the query template a name.
362	query.Name = "prefix"
363	if err := s.PreparedQuerySet(11, query); err != nil {
364		t.Fatalf("err: %s", err)
365	}
366
367	// Make sure the index got updated.
368	if idx := s.maxIndex("prepared-queries"); idx != 11 {
369		t.Fatalf("bad index: %d", idx)
370	}
371	if !watchFired(ws) {
372		t.Fatalf("bad")
373	}
374
375	// Read it back and verify the data was updated as well as the index.
376	expected.Name = "prefix"
377	expected.ModifyIndex = 11
378	ws = memdb.NewWatchSet()
379	idx, actual, err = s.PreparedQueryGet(ws, query.ID)
380	if err != nil {
381		t.Fatalf("err: %s", err)
382	}
383	if idx != 11 {
384		t.Fatalf("bad index: %d", idx)
385	}
386	if !reflect.DeepEqual(actual, expected) {
387		t.Fatalf("bad: %v", actual)
388	}
389
390	// Try to register a template that squats on the prefix.
391	{
392		evil := &structs.PreparedQuery{
393			ID:   testUUID(),
394			Name: "prefix",
395			Template: structs.QueryTemplateOptions{
396				Type: structs.QueryTemplateTypeNamePrefixMatch,
397			},
398			Service: structs.ServiceQuery{
399				Service: "redis",
400			},
401		}
402		err := s.PreparedQuerySet(12, evil)
403		if err == nil || !strings.Contains(err.Error(), "aliases an existing query name") {
404			t.Fatalf("bad: %v", err)
405		}
406
407		// Sanity check to make sure it's not there.
408		idx, actual, err := s.PreparedQueryGet(nil, evil.ID)
409		if err != nil {
410			t.Fatalf("err: %s", err)
411		}
412		if idx != 11 {
413			t.Fatalf("bad index: %d", idx)
414		}
415		if actual != nil {
416			t.Fatalf("bad: %v", actual)
417		}
418	}
419
420	// Try to register a template that doesn't compile.
421	{
422		evil := &structs.PreparedQuery{
423			ID:   testUUID(),
424			Name: "legit-prefix",
425			Template: structs.QueryTemplateOptions{
426				Type: structs.QueryTemplateTypeNamePrefixMatch,
427			},
428			Service: structs.ServiceQuery{
429				Service: "${nope",
430			},
431		}
432		err := s.PreparedQuerySet(13, evil)
433		if err == nil || !strings.Contains(err.Error(), "failed compiling template") {
434			t.Fatalf("bad: %v", err)
435		}
436
437		// Sanity check to make sure it's not there.
438		idx, actual, err := s.PreparedQueryGet(nil, evil.ID)
439		if err != nil {
440			t.Fatalf("err: %s", err)
441		}
442		if idx != 11 {
443			t.Fatalf("bad index: %d", idx)
444		}
445		if actual != nil {
446			t.Fatalf("bad: %v", actual)
447		}
448	}
449
450	if watchFired(ws) {
451		t.Fatalf("bad")
452	}
453}
454
455func TestStateStore_PreparedQueryDelete(t *testing.T) {
456	s := testStateStore(t)
457
458	// Set up our test environment.
459	testRegisterNode(t, s, 1, "foo")
460	testRegisterService(t, s, 2, "foo", "redis")
461
462	// Create a new query.
463	query := &structs.PreparedQuery{
464		ID: testUUID(),
465		Service: structs.ServiceQuery{
466			Service: "redis",
467		},
468	}
469
470	// Deleting a query that doesn't exist should be a no-op.
471	if err := s.PreparedQueryDelete(3, query.ID); err != nil {
472		t.Fatalf("err: %s", err)
473	}
474
475	// Index is not updated if nothing is saved.
476	if idx := s.maxIndex("prepared-queries"); idx != 0 {
477		t.Fatalf("bad index: %d", idx)
478	}
479
480	// Now add the query to the data store.
481	if err := s.PreparedQuerySet(3, query); err != nil {
482		t.Fatalf("err: %s", err)
483	}
484
485	// Make sure the index got updated.
486	if idx := s.maxIndex("prepared-queries"); idx != 3 {
487		t.Fatalf("bad index: %d", idx)
488	}
489
490	// Read it back out and verify it.
491	expected := &structs.PreparedQuery{
492		ID: query.ID,
493		Service: structs.ServiceQuery{
494			Service: "redis",
495		},
496		RaftIndex: structs.RaftIndex{
497			CreateIndex: 3,
498			ModifyIndex: 3,
499		},
500	}
501	ws := memdb.NewWatchSet()
502	idx, actual, err := s.PreparedQueryGet(ws, query.ID)
503	if err != nil {
504		t.Fatalf("err: %s", err)
505	}
506	if idx != 3 {
507		t.Fatalf("bad index: %d", idx)
508	}
509	if !reflect.DeepEqual(actual, expected) {
510		t.Fatalf("bad: %v", actual)
511	}
512
513	// Now delete it.
514	if err := s.PreparedQueryDelete(4, query.ID); err != nil {
515		t.Fatalf("err: %s", err)
516	}
517
518	// Make sure the index got updated.
519	if idx := s.maxIndex("prepared-queries"); idx != 4 {
520		t.Fatalf("bad index: %d", idx)
521	}
522	if !watchFired(ws) {
523		t.Fatalf("bad")
524	}
525
526	// Sanity check to make sure it's not there.
527	idx, actual, err = s.PreparedQueryGet(nil, query.ID)
528	if err != nil {
529		t.Fatalf("err: %s", err)
530	}
531	if idx != 4 {
532		t.Fatalf("bad index: %d", idx)
533	}
534	if actual != nil {
535		t.Fatalf("bad: %v", actual)
536	}
537}
538
539func TestStateStore_PreparedQueryResolve(t *testing.T) {
540	s := testStateStore(t)
541
542	// Set up our test environment.
543	testRegisterNode(t, s, 1, "foo")
544	testRegisterService(t, s, 2, "foo", "redis")
545
546	// Create a new query.
547	query := &structs.PreparedQuery{
548		ID:   testUUID(),
549		Name: "my-test-query",
550		Service: structs.ServiceQuery{
551			Service: "redis",
552		},
553	}
554
555	// Try to lookup a query that's not there using something that looks
556	// like a real ID.
557	idx, actual, err := s.PreparedQueryResolve(query.ID, structs.QuerySource{})
558	if err != nil {
559		t.Fatalf("err: %s", err)
560	}
561	if idx != 0 {
562		t.Fatalf("bad index: %d", idx)
563	}
564	if actual != nil {
565		t.Fatalf("bad: %v", actual)
566	}
567
568	// Try to lookup a query that's not there using something that looks
569	// like a name
570	idx, actual, err = s.PreparedQueryResolve(query.Name, structs.QuerySource{})
571	if err != nil {
572		t.Fatalf("err: %s", err)
573	}
574	if idx != 0 {
575		t.Fatalf("bad index: %d", idx)
576	}
577	if actual != nil {
578		t.Fatalf("bad: %v", actual)
579	}
580
581	// Now actually insert the query.
582	if err := s.PreparedQuerySet(3, query); err != nil {
583		t.Fatalf("err: %s", err)
584	}
585
586	// Make sure the index got updated.
587	if idx := s.maxIndex("prepared-queries"); idx != 3 {
588		t.Fatalf("bad index: %d", idx)
589	}
590
591	// Read it back out using the ID and verify it.
592	expected := &structs.PreparedQuery{
593		ID:   query.ID,
594		Name: "my-test-query",
595		Service: structs.ServiceQuery{
596			Service: "redis",
597		},
598		RaftIndex: structs.RaftIndex{
599			CreateIndex: 3,
600			ModifyIndex: 3,
601		},
602	}
603	idx, actual, err = s.PreparedQueryResolve(query.ID, structs.QuerySource{})
604	if err != nil {
605		t.Fatalf("err: %s", err)
606	}
607	if idx != 3 {
608		t.Fatalf("bad index: %d", idx)
609	}
610	if !reflect.DeepEqual(actual, expected) {
611		t.Fatalf("bad: %v", actual)
612	}
613
614	// Read it back using the name and verify it again.
615	idx, actual, err = s.PreparedQueryResolve(query.Name, structs.QuerySource{})
616	if err != nil {
617		t.Fatalf("err: %s", err)
618	}
619	if idx != 3 {
620		t.Fatalf("bad index: %d", idx)
621	}
622	if !reflect.DeepEqual(actual, expected) {
623		t.Fatalf("bad: %v", actual)
624	}
625
626	// Make sure an empty lookup is well-behaved if there are actual queries
627	// in the state store.
628	idx, actual, err = s.PreparedQueryResolve("", structs.QuerySource{})
629	if err != ErrMissingQueryID {
630		t.Fatalf("bad: %v ", err)
631	}
632	if idx != 0 {
633		t.Fatalf("bad index: %d", idx)
634	}
635	if actual != nil {
636		t.Fatalf("bad: %v", actual)
637	}
638
639	// Create two prepared query templates, one a longer prefix of the
640	// other.
641	tmpl1 := &structs.PreparedQuery{
642		ID:   testUUID(),
643		Name: "prod-",
644		Template: structs.QueryTemplateOptions{
645			Type: structs.QueryTemplateTypeNamePrefixMatch,
646		},
647		Service: structs.ServiceQuery{
648			Service: "${name.suffix}",
649		},
650	}
651	if err := s.PreparedQuerySet(4, tmpl1); err != nil {
652		t.Fatalf("err: %s", err)
653	}
654	tmpl2 := &structs.PreparedQuery{
655		ID:   testUUID(),
656		Name: "prod-redis",
657		Template: structs.QueryTemplateOptions{
658			Type:   structs.QueryTemplateTypeNamePrefixMatch,
659			Regexp: "^prod-(.*)$",
660		},
661		Service: structs.ServiceQuery{
662			Service: "${match(1)}-master",
663		},
664	}
665	if err := s.PreparedQuerySet(5, tmpl2); err != nil {
666		t.Fatalf("err: %s", err)
667	}
668
669	// Resolve the less-specific prefix.
670	expected = &structs.PreparedQuery{
671		ID:   tmpl1.ID,
672		Name: "prod-",
673		Template: structs.QueryTemplateOptions{
674			Type: structs.QueryTemplateTypeNamePrefixMatch,
675		},
676		Service: structs.ServiceQuery{
677			Service: "mongodb",
678		},
679		RaftIndex: structs.RaftIndex{
680			CreateIndex: 4,
681			ModifyIndex: 4,
682		},
683	}
684	idx, actual, err = s.PreparedQueryResolve("prod-mongodb", structs.QuerySource{})
685	if err != nil {
686		t.Fatalf("err: %s", err)
687	}
688	if idx != 5 {
689		t.Fatalf("bad index: %d", idx)
690	}
691	if !reflect.DeepEqual(actual, expected) {
692		t.Fatalf("bad: %v", actual)
693	}
694
695	// Now resolve the more specific prefix.
696	expected = &structs.PreparedQuery{
697		ID:   tmpl2.ID,
698		Name: "prod-redis",
699		Template: structs.QueryTemplateOptions{
700			Type:   structs.QueryTemplateTypeNamePrefixMatch,
701			Regexp: "^prod-(.*)$",
702		},
703		Service: structs.ServiceQuery{
704			Service: "redis-foobar-master",
705		},
706		RaftIndex: structs.RaftIndex{
707			CreateIndex: 5,
708			ModifyIndex: 5,
709		},
710	}
711	idx, actual, err = s.PreparedQueryResolve("prod-redis-foobar", structs.QuerySource{})
712	if err != nil {
713		t.Fatalf("err: %s", err)
714	}
715	if idx != 5 {
716		t.Fatalf("bad index: %d", idx)
717	}
718	if !reflect.DeepEqual(actual, expected) {
719		t.Fatalf("bad: %v", actual)
720	}
721
722	// Resolve an exact-match prefix. The output of this one doesn't match a
723	// sensical service name, but it still renders.
724	expected = &structs.PreparedQuery{
725		ID:   tmpl1.ID,
726		Name: "prod-",
727		Template: structs.QueryTemplateOptions{
728			Type: structs.QueryTemplateTypeNamePrefixMatch,
729		},
730		Service: structs.ServiceQuery{
731			Service: "",
732		},
733		RaftIndex: structs.RaftIndex{
734			CreateIndex: 4,
735			ModifyIndex: 4,
736		},
737	}
738	idx, actual, err = s.PreparedQueryResolve("prod-", structs.QuerySource{})
739	if err != nil {
740		t.Fatalf("err: %s", err)
741	}
742	if idx != 5 {
743		t.Fatalf("bad index: %d", idx)
744	}
745	if !reflect.DeepEqual(actual, expected) {
746		t.Fatalf("bad: %v", actual)
747	}
748
749	// Make sure you can't run a prepared query template by ID, since that
750	// makes no sense.
751	_, _, err = s.PreparedQueryResolve(tmpl1.ID, structs.QuerySource{})
752	if err == nil || !strings.Contains(err.Error(), "prepared query templates can only be resolved up by name") {
753		t.Fatalf("bad: %v", err)
754	}
755}
756
757func TestStateStore_PreparedQueryList(t *testing.T) {
758	s := testStateStore(t)
759
760	// Make sure nothing is returned for an empty query
761	ws := memdb.NewWatchSet()
762	idx, actual, err := s.PreparedQueryList(ws)
763	if err != nil {
764		t.Fatalf("err: %s", err)
765	}
766	if idx != 0 {
767		t.Fatalf("bad index: %d", idx)
768	}
769	if len(actual) != 0 {
770		t.Fatalf("bad: %v", actual)
771	}
772
773	// Set up our test environment.
774	testRegisterNode(t, s, 1, "foo")
775	testRegisterService(t, s, 2, "foo", "redis")
776	testRegisterService(t, s, 3, "foo", "mongodb")
777
778	// Create some queries.
779	queries := structs.PreparedQueries{
780		&structs.PreparedQuery{
781			ID:   testUUID(),
782			Name: "alice",
783			Service: structs.ServiceQuery{
784				Service: "redis",
785			},
786		},
787		&structs.PreparedQuery{
788			ID:   testUUID(),
789			Name: "bob",
790			Service: structs.ServiceQuery{
791				Service: "mongodb",
792			},
793		},
794	}
795
796	// Force the sort order of the UUIDs before we create them so the
797	// order is deterministic.
798	queries[0].ID = "a" + queries[0].ID[1:]
799	queries[1].ID = "b" + queries[1].ID[1:]
800
801	// Now create the queries.
802	for i, query := range queries {
803		if err := s.PreparedQuerySet(uint64(4+i), query); err != nil {
804			t.Fatalf("err: %s", err)
805		}
806	}
807	if !watchFired(ws) {
808		t.Fatalf("bad")
809	}
810
811	// Read it back and verify.
812	expected := structs.PreparedQueries{
813		&structs.PreparedQuery{
814			ID:   queries[0].ID,
815			Name: "alice",
816			Service: structs.ServiceQuery{
817				Service: "redis",
818			},
819			RaftIndex: structs.RaftIndex{
820				CreateIndex: 4,
821				ModifyIndex: 4,
822			},
823		},
824		&structs.PreparedQuery{
825			ID:   queries[1].ID,
826			Name: "bob",
827			Service: structs.ServiceQuery{
828				Service: "mongodb",
829			},
830			RaftIndex: structs.RaftIndex{
831				CreateIndex: 5,
832				ModifyIndex: 5,
833			},
834		},
835	}
836	idx, actual, err = s.PreparedQueryList(nil)
837	if err != nil {
838		t.Fatalf("err: %s", err)
839	}
840	if idx != 5 {
841		t.Fatalf("bad index: %d", idx)
842	}
843	if !reflect.DeepEqual(actual, expected) {
844		t.Fatalf("bad: %v", actual)
845	}
846}
847
848func TestStateStore_PreparedQuery_Snapshot_Restore(t *testing.T) {
849	s := testStateStore(t)
850
851	// Set up our test environment.
852	testRegisterNode(t, s, 1, "foo")
853	testRegisterService(t, s, 2, "foo", "redis")
854	testRegisterService(t, s, 3, "foo", "mongodb")
855
856	// Create some queries.
857	queries := structs.PreparedQueries{
858		&structs.PreparedQuery{
859			ID:   testUUID(),
860			Name: "alice",
861			Service: structs.ServiceQuery{
862				Service: "redis",
863			},
864		},
865		&structs.PreparedQuery{
866			ID:   testUUID(),
867			Name: "bob-",
868			Template: structs.QueryTemplateOptions{
869				Type: structs.QueryTemplateTypeNamePrefixMatch,
870			},
871			Service: structs.ServiceQuery{
872				Service: "${name.suffix}",
873			},
874		},
875	}
876
877	// Force the sort order of the UUIDs before we create them so the
878	// order is deterministic.
879	queries[0].ID = "a" + queries[0].ID[1:]
880	queries[1].ID = "b" + queries[1].ID[1:]
881
882	// Now create the queries.
883	for i, query := range queries {
884		if err := s.PreparedQuerySet(uint64(4+i), query); err != nil {
885			t.Fatalf("err: %s", err)
886		}
887	}
888
889	// Snapshot the queries.
890	snap := s.Snapshot()
891	defer snap.Close()
892
893	// Alter the real state store.
894	if err := s.PreparedQueryDelete(6, queries[0].ID); err != nil {
895		t.Fatalf("err: %s", err)
896	}
897
898	// Verify the snapshot.
899	if idx := snap.LastIndex(); idx != 5 {
900		t.Fatalf("bad index: %d", idx)
901	}
902	expected := structs.PreparedQueries{
903		&structs.PreparedQuery{
904			ID:   queries[0].ID,
905			Name: "alice",
906			Service: structs.ServiceQuery{
907				Service: "redis",
908			},
909			RaftIndex: structs.RaftIndex{
910				CreateIndex: 4,
911				ModifyIndex: 4,
912			},
913		},
914		&structs.PreparedQuery{
915			ID:   queries[1].ID,
916			Name: "bob-",
917			Template: structs.QueryTemplateOptions{
918				Type: structs.QueryTemplateTypeNamePrefixMatch,
919			},
920			Service: structs.ServiceQuery{
921				Service: "${name.suffix}",
922			},
923			RaftIndex: structs.RaftIndex{
924				CreateIndex: 5,
925				ModifyIndex: 5,
926			},
927		},
928	}
929	dump, err := snap.PreparedQueries()
930	if err != nil {
931		t.Fatalf("err: %s", err)
932	}
933	if !reflect.DeepEqual(dump, expected) {
934		t.Fatalf("bad: %v", dump)
935	}
936
937	// Restore the values into a new state store.
938	func() {
939		s := testStateStore(t)
940		restore := s.Restore()
941		for _, query := range dump {
942			if err := restore.PreparedQuery(query); err != nil {
943				t.Fatalf("err: %s", err)
944			}
945		}
946		restore.Commit()
947
948		// Read the restored queries back out and verify that they
949		// match.
950		idx, actual, err := s.PreparedQueryList(nil)
951		if err != nil {
952			t.Fatalf("err: %s", err)
953		}
954		if idx != 5 {
955			t.Fatalf("bad index: %d", idx)
956		}
957		if !reflect.DeepEqual(actual, expected) {
958			t.Fatalf("bad: %v", actual)
959		}
960
961		// Make sure the second query, which is a template, was compiled
962		// and can be resolved.
963		_, query, err := s.PreparedQueryResolve("bob-backwards-is-bob", structs.QuerySource{})
964		if err != nil {
965			t.Fatalf("err: %s", err)
966		}
967		if query == nil {
968			t.Fatalf("should have resolved the query")
969		}
970		if query.Service.Service != "backwards-is-bob" {
971			t.Fatalf("bad: %s", query.Service.Service)
972		}
973	}()
974}
975