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

     1  package state
     2  
     3  import (
     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  
    13  func 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  
    25  func 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  
   114  func 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  
   132  func 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  
   157  func 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  
   175  func 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  
   194  func 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  
   225  func 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.
   290  func 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 behavior.
   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  
   465  func 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  }