github.phpd.cn/hashicorp/consul@v1.4.5/agent/consul/state/prepared_query_test.go (about)

     1  package state
     2  
     3  import (
     4  	"reflect"
     5  	"strings"
     6  	"testing"
     7  
     8  	"github.com/hashicorp/consul/agent/structs"
     9  	"github.com/hashicorp/go-memdb"
    10  )
    11  
    12  func 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  
    37  func 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(), "failed session lookup") {
    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  
   455  func 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  
   539  func 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  
   757  func 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  
   848  func 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  }