github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/nomad/state/state_store_variables_test.go (about)

     1  package state
     2  
     3  import (
     4  	"encoding/json"
     5  	"sort"
     6  	"strings"
     7  	"testing"
     8  
     9  	memdb "github.com/hashicorp/go-memdb"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	"github.com/hashicorp/nomad/ci"
    13  	"github.com/hashicorp/nomad/helper/uuid"
    14  	"github.com/hashicorp/nomad/nomad/mock"
    15  	"github.com/hashicorp/nomad/nomad/structs"
    16  )
    17  
    18  func TestStateStore_GetVariable(t *testing.T) {
    19  	ci.Parallel(t)
    20  	testState := testStateStore(t)
    21  	ws := memdb.NewWatchSet()
    22  	sve, err := testState.GetVariable(ws, "default", "not/a/path")
    23  	require.NoError(t, err)
    24  	require.Nil(t, sve)
    25  }
    26  
    27  func TestStateStore_UpsertVariables(t *testing.T) {
    28  	ci.Parallel(t)
    29  	testState := testStateStore(t)
    30  	ws := memdb.NewWatchSet()
    31  
    32  	svs := []*structs.VariableEncrypted{
    33  		mock.VariableEncrypted(),
    34  		mock.VariableEncrypted(),
    35  	}
    36  	svs[0].Path = "aaaaa"
    37  	svs[1].Path = "bbbbb"
    38  
    39  	insertIndex := uint64(20)
    40  
    41  	var expectedQuotaSize int
    42  	for _, v := range svs {
    43  		expectedQuotaSize += len(v.Data)
    44  	}
    45  
    46  	// Ensure new variables are inserted as expected with their
    47  	// correct indexes, along with an update to the index table.
    48  	t.Run("1 create new variables", func(t *testing.T) {
    49  		// Perform the initial upsert of variables.
    50  		for _, sv := range svs {
    51  			insertIndex++
    52  			resp := testState.VarSet(insertIndex, &structs.VarApplyStateRequest{
    53  				Op:  structs.VarOpSet,
    54  				Var: sv,
    55  			})
    56  			require.NoError(t, resp.Error)
    57  		}
    58  
    59  		// Check that the index for the table was modified as expected.
    60  		initialIndex, err := testState.Index(TableVariables)
    61  		require.NoError(t, err)
    62  		require.Equal(t, insertIndex, initialIndex)
    63  
    64  		// List all the variables in the table
    65  		iter, err := testState.Variables(ws)
    66  		require.NoError(t, err)
    67  
    68  		got := []*structs.VariableEncrypted{}
    69  		for raw := iter.Next(); raw != nil; raw = iter.Next() {
    70  			sv := raw.(*structs.VariableEncrypted)
    71  			var svCopy structs.VariableEncrypted
    72  			svCopy = sv.Copy()
    73  			got = append(got, &svCopy)
    74  		}
    75  		require.Len(t, got, 2, "incorrect number of variables found")
    76  
    77  		// Ensure the create and modify indexes are populated correctly.
    78  		require.Equal(t, uint64(21), got[0].CreateIndex, "%s: incorrect create index", got[0].Path)
    79  		require.Equal(t, uint64(21), got[0].ModifyIndex, "%s: incorrect modify index", got[0].Path)
    80  		require.Equal(t, uint64(22), got[1].CreateIndex, "%s: incorrect create index", got[1].Path)
    81  		require.Equal(t, uint64(22), got[1].ModifyIndex, "%s: incorrect modify index", got[1].Path)
    82  
    83  		quotaUsed, err := testState.VariablesQuotaByNamespace(ws, structs.DefaultNamespace)
    84  		require.NoError(t, err)
    85  		require.Equal(t, int64(expectedQuotaSize), quotaUsed.Size)
    86  
    87  		// update the mocks so the test element has the correct create/modify
    88  		// indexes and times now that we have validated them
    89  		svs = got
    90  	})
    91  
    92  	t.Run("1a fetch variable", func(t *testing.T) {
    93  		sve, err := testState.GetVariable(ws, svs[0].Namespace, svs[0].Path)
    94  		require.NoError(t, err)
    95  		require.NotNil(t, sve)
    96  	})
    97  
    98  	// Upsert the exact same variables without any modification. In this
    99  	// case, the index table should not be updated, indicating no write actually
   100  	// happened due to equality checking.
   101  	t.Run("2 upsert same", func(t *testing.T) {
   102  		reInsertIndex := uint64(30)
   103  
   104  		for _, sv := range svs {
   105  			svReq := &structs.VarApplyStateRequest{
   106  				Op:  structs.VarOpSet,
   107  				Var: sv,
   108  			}
   109  			reInsertIndex++
   110  			resp := testState.VarSet(reInsertIndex, svReq)
   111  			require.NoError(t, resp.Error)
   112  		}
   113  
   114  		reInsertActualIndex, err := testState.Index(TableVariables)
   115  		require.NoError(t, err)
   116  		require.Equal(t, insertIndex, reInsertActualIndex, "index should not have changed")
   117  
   118  		quotaUsed, err := testState.VariablesQuotaByNamespace(ws, structs.DefaultNamespace)
   119  		require.NoError(t, err)
   120  		require.Equal(t, int64(expectedQuotaSize), quotaUsed.Size)
   121  	})
   122  
   123  	// Modify a single one of the previously inserted variables and
   124  	// performs an upsert. This ensures the index table is modified correctly
   125  	// and that each variable is updated, or not, as expected.
   126  	t.Run("3 modify one", func(t *testing.T) {
   127  		sv1Update := svs[0].Copy()
   128  		sv1Update.KeyID = "sv1-update"
   129  
   130  		buf := make([]byte, 1+len(sv1Update.Data))
   131  		copy(buf, sv1Update.Data)
   132  		buf[len(buf)-1] = 'x'
   133  		sv1Update.Data = buf
   134  
   135  		update1Index := uint64(40)
   136  
   137  		resp := testState.VarSet(update1Index, &structs.VarApplyStateRequest{
   138  			Op:  structs.VarOpSet,
   139  			Var: &sv1Update,
   140  		})
   141  		require.NoError(t, resp.Error)
   142  
   143  		// Check that the index for the table was modified as expected.
   144  		updateActualIndex, err := testState.Index(TableVariables)
   145  		require.NoError(t, err)
   146  		require.Equal(t, update1Index, updateActualIndex, "index should have changed")
   147  
   148  		// Get the variables from the table.
   149  		iter, err := testState.Variables(ws)
   150  		require.NoError(t, err)
   151  
   152  		got := []*structs.VariableEncrypted{}
   153  
   154  		// Iterate all the stored variables and assert indexes have been updated as expected
   155  		for raw := iter.Next(); raw != nil; raw = iter.Next() {
   156  			sv := raw.(*structs.VariableEncrypted)
   157  			var svCopy structs.VariableEncrypted
   158  			svCopy = sv.Copy()
   159  			got = append(got, &svCopy)
   160  		}
   161  		require.Len(t, got, 2)
   162  		require.Equal(t, update1Index, got[0].ModifyIndex)
   163  		require.Equal(t, insertIndex, got[1].ModifyIndex)
   164  
   165  		// update the mocks so the test element has the correct create/modify
   166  		// indexes and times now that we have validated them
   167  		svs = got
   168  
   169  		quotaUsed, err := testState.VariablesQuotaByNamespace(ws, structs.DefaultNamespace)
   170  		require.NoError(t, err)
   171  		require.Equal(t, int64(expectedQuotaSize+1), quotaUsed.Size)
   172  	})
   173  
   174  	// Modify the second variable but send an upsert request that
   175  	// includes this and the already modified variable.
   176  	t.Run("4 upsert other", func(t *testing.T) {
   177  		update2Index := uint64(50)
   178  		sv2 := svs[1].Copy()
   179  		sv2.KeyID = "sv2-update"
   180  		sv2.ModifyIndex = update2Index
   181  
   182  		resp := testState.VarSet(update2Index, &structs.VarApplyStateRequest{
   183  			Op:  structs.VarOpSet,
   184  			Var: &sv2,
   185  		})
   186  		require.NoError(t, resp.Error)
   187  
   188  		// Check that the index for the table was modified as expected.
   189  		update2ActualIndex, err := testState.Index(TableVariables)
   190  		require.NoError(t, err)
   191  		require.Equal(t, update2Index, update2ActualIndex, "index should have changed")
   192  
   193  		// Get the variables from the table.
   194  		iter, err := testState.Variables(ws)
   195  		require.NoError(t, err)
   196  
   197  		got := []structs.VariableEncrypted{}
   198  
   199  		// Iterate all the stored variables and assert indexes have been updated as expected
   200  		for raw := iter.Next(); raw != nil; raw = iter.Next() {
   201  			sv := raw.(*structs.VariableEncrypted)
   202  			got = append(got, sv.Copy())
   203  		}
   204  		require.Len(t, got, 2)
   205  		require.Equal(t, svs[0].ModifyIndex, got[0].ModifyIndex)
   206  		require.Equal(t, update2Index, got[1].ModifyIndex)
   207  
   208  		require.True(t, svs[0].Equal(got[0]))
   209  		require.True(t, sv2.Equal(got[1]))
   210  
   211  		quotaUsed, err := testState.VariablesQuotaByNamespace(ws, structs.DefaultNamespace)
   212  		require.NoError(t, err)
   213  		require.Equal(t, int64(expectedQuotaSize+1), quotaUsed.Size)
   214  
   215  	})
   216  }
   217  
   218  func TestStateStore_DeleteVariable(t *testing.T) {
   219  	ci.Parallel(t)
   220  	testState := testStateStore(t)
   221  
   222  	// Generate some test variables that we will use and modify throughout.
   223  	svs := []*structs.VariableEncrypted{
   224  		mock.VariableEncrypted(),
   225  		mock.VariableEncrypted(),
   226  	}
   227  	svs[0].Path = "aaaaa"
   228  	svs[1].Path = "bbbbb"
   229  
   230  	initialIndex := uint64(10)
   231  
   232  	t.Run("1 delete a variable that does not exist", func(t *testing.T) {
   233  
   234  		resp := testState.VarDelete(initialIndex, &structs.VarApplyStateRequest{
   235  			Op:  structs.VarOpDelete,
   236  			Var: svs[0],
   237  		})
   238  		require.NoError(t, resp.Error, "deleting non-existing var is not an error")
   239  
   240  		actualInitialIndex, err := testState.Index(TableVariables)
   241  		require.NoError(t, err)
   242  		require.Equal(t, uint64(0), actualInitialIndex, "index should not have changed")
   243  
   244  		quotaUsed, err := testState.VariablesQuotaByNamespace(nil, structs.DefaultNamespace)
   245  		require.NoError(t, err)
   246  		require.Nil(t, quotaUsed)
   247  	})
   248  
   249  	// Upsert two variables, deletes one, then ensure the
   250  	// remaining is left as expected.
   251  	t.Run("2 upsert variable and delete", func(t *testing.T) {
   252  
   253  		ns := mock.Namespace()
   254  		ns.Name = svs[0].Namespace
   255  		require.NoError(t, testState.UpsertNamespaces(initialIndex, []*structs.Namespace{ns}))
   256  
   257  		for _, sv := range svs {
   258  			svReq := &structs.VarApplyStateRequest{
   259  				Op:  structs.VarOpSet,
   260  				Var: sv,
   261  			}
   262  			initialIndex++
   263  			resp := testState.VarSet(initialIndex, svReq)
   264  			require.NoError(t, resp.Error)
   265  		}
   266  
   267  		// Perform the delete.
   268  		delete1Index := uint64(20)
   269  
   270  		resp := testState.VarDelete(delete1Index, &structs.VarApplyStateRequest{
   271  			Op:  structs.VarOpDelete,
   272  			Var: svs[0],
   273  		})
   274  		require.NoError(t, resp.Error)
   275  
   276  		// Check that the index for the table was modified as expected.
   277  		actualDelete1Index, err := testState.Index(TableVariables)
   278  		require.NoError(t, err)
   279  		require.Equal(t, delete1Index, actualDelete1Index, "index should have changed")
   280  
   281  		ws := memdb.NewWatchSet()
   282  
   283  		// Get the variables from the table.
   284  		iter, err := testState.Variables(ws)
   285  		require.NoError(t, err)
   286  
   287  		var delete1Count int
   288  		var expectedQuotaSize int
   289  
   290  		// Iterate all the stored variables and assert we have the expected
   291  		// number.
   292  		for raw := iter.Next(); raw != nil; raw = iter.Next() {
   293  			delete1Count++
   294  			v := raw.(*structs.VariableEncrypted)
   295  			expectedQuotaSize += len(v.Data)
   296  		}
   297  		require.Equal(t, 1, delete1Count, "unexpected number of variables in table")
   298  		quotaUsed, err := testState.VariablesQuotaByNamespace(ws, structs.DefaultNamespace)
   299  		require.NoError(t, err)
   300  		require.Equal(t, int64(expectedQuotaSize), quotaUsed.Size)
   301  	})
   302  
   303  	t.Run("3 delete remaining variable", func(t *testing.T) {
   304  		delete2Index := uint64(30)
   305  
   306  		resp := testState.VarDelete(delete2Index, &structs.VarApplyStateRequest{
   307  			Op:  structs.VarOpDelete,
   308  			Var: svs[1],
   309  		})
   310  		require.NoError(t, resp.Error)
   311  
   312  		// Check that the index for the table was modified as expected.
   313  		actualDelete2Index, err := testState.Index(TableVariables)
   314  		require.NoError(t, err)
   315  		require.Equal(t, delete2Index, actualDelete2Index, "index should have changed")
   316  
   317  		// Get the variables from the table.
   318  		ws := memdb.NewWatchSet()
   319  		iter, err := testState.Variables(ws)
   320  		require.NoError(t, err)
   321  
   322  		var delete2Count int
   323  
   324  		// Ensure the table is empty.
   325  		for raw := iter.Next(); raw != nil; raw = iter.Next() {
   326  			delete2Count++
   327  		}
   328  		require.Equal(t, 0, delete2Count, "unexpected number of variables in table")
   329  
   330  		quotaUsed, err := testState.VariablesQuotaByNamespace(ws, structs.DefaultNamespace)
   331  		require.NoError(t, err)
   332  		require.Equal(t, int64(0), quotaUsed.Size)
   333  	})
   334  }
   335  
   336  func TestStateStore_GetVariables(t *testing.T) {
   337  	ci.Parallel(t)
   338  	testState := testStateStore(t)
   339  
   340  	ns := mock.Namespace()
   341  	ns.Name = "~*magical*~"
   342  	initialIndex := uint64(10)
   343  	require.NoError(t, testState.UpsertNamespaces(initialIndex, []*structs.Namespace{ns}))
   344  
   345  	// Generate some test variables in different namespaces and upsert them.
   346  	svs := []*structs.VariableEncrypted{
   347  		mock.VariableEncrypted(),
   348  		mock.VariableEncrypted(),
   349  	}
   350  	svs[0].Path = "aaaaa"
   351  	svs[0].Namespace = "~*magical*~"
   352  	svs[1].Path = "bbbbb"
   353  
   354  	for _, sv := range svs {
   355  		svReq := &structs.VarApplyStateRequest{
   356  			Op:  structs.VarOpSet,
   357  			Var: sv,
   358  		}
   359  		initialIndex++
   360  		resp := testState.VarSet(initialIndex, svReq)
   361  		require.NoError(t, resp.Error)
   362  	}
   363  
   364  	// Look up variables using the namespace of the first mock variable.
   365  	ws := memdb.NewWatchSet()
   366  	iter, err := testState.GetVariablesByNamespace(ws, svs[0].Namespace)
   367  	require.NoError(t, err)
   368  
   369  	var count1 int
   370  
   371  	for raw := iter.Next(); raw != nil; raw = iter.Next() {
   372  		sv := raw.(*structs.VariableEncrypted)
   373  		require.Equal(t, svs[0].Namespace, sv.Namespace)
   374  		require.Equal(t, uint64(11), sv.CreateIndex, "%s incorrect create index", sv.Path)
   375  		require.Equal(t, uint64(11), sv.ModifyIndex, "%s incorrect modify index", sv.Path)
   376  		count1++
   377  	}
   378  
   379  	require.Equal(t, 1, count1)
   380  
   381  	// Look up variables using the namespace of the second mock variable.
   382  	iter, err = testState.GetVariablesByNamespace(ws, svs[1].Namespace)
   383  	require.NoError(t, err)
   384  
   385  	var count2 int
   386  
   387  	for raw := iter.Next(); raw != nil; raw = iter.Next() {
   388  		count2++
   389  		sv := raw.(*structs.VariableEncrypted)
   390  		require.Equal(t, initialIndex, sv.CreateIndex, "%s incorrect create index", sv.Path)
   391  		require.Equal(t, initialIndex, sv.ModifyIndex, "%s incorrect modify index", sv.Path)
   392  		require.Equal(t, svs[1].Namespace, sv.Namespace)
   393  	}
   394  	require.Equal(t, 1, count2)
   395  
   396  	// Look up variables using a namespace that shouldn't contain any
   397  	// variables.
   398  	iter, err = testState.GetVariablesByNamespace(ws, "pony-club")
   399  	require.NoError(t, err)
   400  
   401  	var count3 int
   402  
   403  	for raw := iter.Next(); raw != nil; raw = iter.Next() {
   404  		count3++
   405  	}
   406  	require.Equal(t, 0, count3)
   407  }
   408  
   409  func TestStateStore_ListVariablesByNamespaceAndPrefix(t *testing.T) {
   410  	ci.Parallel(t)
   411  	testState := testStateStore(t)
   412  
   413  	// Generate some test variables and upsert them.
   414  	svs := []*structs.VariableEncrypted{}
   415  	for i := 0; i < 6; i++ {
   416  		sv := mock.VariableEncrypted()
   417  		svs = append(svs, sv)
   418  	}
   419  
   420  	svs[0].Path = "a/b"
   421  	svs[1].Path = "a/b/c"
   422  	svs[2].Path = "unrelated/b/c"
   423  	svs[3].Namespace = "other"
   424  	svs[3].Path = "a/b/c"
   425  	svs[4].Namespace = "other"
   426  	svs[4].Path = "a/q/z"
   427  	svs[5].Namespace = "other"
   428  	svs[5].Path = "a/z/z"
   429  
   430  	ns := mock.Namespace()
   431  	ns.Name = "other"
   432  	initialIndex := uint64(10)
   433  	require.NoError(t, testState.UpsertNamespaces(initialIndex, []*structs.Namespace{ns}))
   434  
   435  	for _, sv := range svs {
   436  		svReq := &structs.VarApplyStateRequest{
   437  			Op:  structs.VarOpSet,
   438  			Var: sv,
   439  		}
   440  		initialIndex++
   441  		resp := testState.VarSet(initialIndex, svReq)
   442  		require.NoError(t, resp.Error)
   443  	}
   444  
   445  	t.Run("ByNamespace", func(t *testing.T) {
   446  		testCases := []struct {
   447  			desc          string
   448  			namespace     string
   449  			expectedCount int
   450  		}{
   451  			{
   452  				desc:          "default",
   453  				namespace:     "default",
   454  				expectedCount: 2,
   455  			},
   456  			{
   457  				desc:          "other",
   458  				namespace:     "other",
   459  				expectedCount: 3,
   460  			},
   461  			{
   462  				desc:          "nonexistent",
   463  				namespace:     "BAD",
   464  				expectedCount: 0,
   465  			},
   466  		}
   467  
   468  		ws := memdb.NewWatchSet()
   469  		for _, tC := range testCases {
   470  			t.Run(tC.desc, func(t *testing.T) {
   471  				iter, err := testState.GetVariablesByNamespace(ws, tC.namespace)
   472  				require.NoError(t, err)
   473  
   474  				var count int = 0
   475  				for raw := iter.Next(); raw != nil; raw = iter.Next() {
   476  					count++
   477  					sv := raw.(*structs.VariableEncrypted)
   478  					require.Equal(t, tC.namespace, sv.Namespace)
   479  				}
   480  			})
   481  		}
   482  	})
   483  
   484  	t.Run("ByNamespaceAndPrefix", func(t *testing.T) {
   485  		testCases := []struct {
   486  			desc          string
   487  			namespace     string
   488  			prefix        string
   489  			expectedCount int
   490  		}{
   491  			{
   492  				desc:          "ns1 with good path",
   493  				namespace:     "default",
   494  				prefix:        "a",
   495  				expectedCount: 2,
   496  			},
   497  			{
   498  				desc:          "ns2 with good path",
   499  				namespace:     "other",
   500  				prefix:        "a",
   501  				expectedCount: 3,
   502  			},
   503  			{
   504  				desc:          "ns1 path valid for ns2",
   505  				namespace:     "default",
   506  				prefix:        "a/b/c",
   507  				expectedCount: 1,
   508  			},
   509  			{
   510  				desc:          "ns2 empty prefix",
   511  				namespace:     "other",
   512  				prefix:        "",
   513  				expectedCount: 3,
   514  			},
   515  			{
   516  				desc:          "nonexistent ns",
   517  				namespace:     "BAD",
   518  				prefix:        "",
   519  				expectedCount: 0,
   520  			},
   521  		}
   522  
   523  		ws := memdb.NewWatchSet()
   524  		for _, tC := range testCases {
   525  			t.Run(tC.desc, func(t *testing.T) {
   526  				iter, err := testState.GetVariablesByNamespaceAndPrefix(ws, tC.namespace, tC.prefix)
   527  				require.NoError(t, err)
   528  
   529  				var count int = 0
   530  				for raw := iter.Next(); raw != nil; raw = iter.Next() {
   531  					count++
   532  					sv := raw.(*structs.VariableEncrypted)
   533  					require.Equal(t, tC.namespace, sv.Namespace)
   534  					require.True(t, strings.HasPrefix(sv.Path, tC.prefix))
   535  				}
   536  				require.Equal(t, tC.expectedCount, count)
   537  			})
   538  		}
   539  	})
   540  
   541  	t.Run("ByPrefix", func(t *testing.T) {
   542  		testCases := []struct {
   543  			desc          string
   544  			prefix        string
   545  			expectedCount int
   546  		}{
   547  			{
   548  				desc:          "bad prefix",
   549  				prefix:        "bad",
   550  				expectedCount: 0,
   551  			},
   552  			{
   553  				desc:          "multiple ns",
   554  				prefix:        "a/b/c",
   555  				expectedCount: 2,
   556  			},
   557  			{
   558  				desc:          "all",
   559  				prefix:        "",
   560  				expectedCount: 6,
   561  			},
   562  		}
   563  
   564  		ws := memdb.NewWatchSet()
   565  		for _, tC := range testCases {
   566  			t.Run(tC.desc, func(t *testing.T) {
   567  				iter, err := testState.GetVariablesByPrefix(ws, tC.prefix)
   568  				require.NoError(t, err)
   569  
   570  				var count int = 0
   571  				for raw := iter.Next(); raw != nil; raw = iter.Next() {
   572  					count++
   573  					sv := raw.(*structs.VariableEncrypted)
   574  					require.True(t, strings.HasPrefix(sv.Path, tC.prefix))
   575  				}
   576  				require.Equal(t, tC.expectedCount, count)
   577  			})
   578  		}
   579  	})
   580  }
   581  func TestStateStore_ListVariablesByKeyID(t *testing.T) {
   582  	ci.Parallel(t)
   583  	testState := testStateStore(t)
   584  
   585  	// Generate some test variables and upsert them.
   586  	svs := []*structs.VariableEncrypted{}
   587  	for i := 0; i < 7; i++ {
   588  		sv := mock.VariableEncrypted()
   589  		sv.Path = uuid.Generate()
   590  		svs = append(svs, sv)
   591  	}
   592  
   593  	keyID := uuid.Generate()
   594  
   595  	expectedForKey := []string{}
   596  	for i := 0; i < 5; i++ {
   597  		svs[i].KeyID = keyID
   598  		expectedForKey = append(expectedForKey, svs[i].Path)
   599  		sort.Strings(expectedForKey)
   600  	}
   601  
   602  	expectedOrphaned := []string{svs[5].Path, svs[6].Path}
   603  
   604  	initialIndex := uint64(10)
   605  
   606  	for _, sv := range svs {
   607  		svReq := &structs.VarApplyStateRequest{
   608  			Op:  structs.VarOpSet,
   609  			Var: sv,
   610  		}
   611  		initialIndex++
   612  		resp := testState.VarSet(initialIndex, svReq)
   613  		require.NoError(t, resp.Error)
   614  	}
   615  
   616  	ws := memdb.NewWatchSet()
   617  	iter, err := testState.GetVariablesByKeyID(ws, keyID)
   618  	require.NoError(t, err)
   619  
   620  	var count int
   621  	for raw := iter.Next(); raw != nil; raw = iter.Next() {
   622  		sv := raw.(*structs.VariableEncrypted)
   623  		require.Equal(t, keyID, sv.KeyID)
   624  		require.Equal(t, expectedForKey[count], sv.Path)
   625  		require.NotContains(t, expectedOrphaned, sv.Path)
   626  		count++
   627  	}
   628  	require.Equal(t, 5, count)
   629  }
   630  
   631  func printVariable(tsv *structs.VariableEncrypted) string {
   632  	b, _ := json.Marshal(tsv)
   633  	return string(b)
   634  }
   635  
   636  func printVariables(tsvs []*structs.VariableEncrypted) string {
   637  	if len(tsvs) == 0 {
   638  		return ""
   639  	}
   640  	var out strings.Builder
   641  	for _, tsv := range tsvs {
   642  		out.WriteString(printVariable(tsv) + "\n")
   643  	}
   644  	return out.String()
   645  }
   646  
   647  // TestStateStore_Variables_DeleteCAS
   648  func TestStateStore_Variables_DeleteCAS(t *testing.T) {
   649  	ci.Parallel(t)
   650  	ts := testStateStore(t)
   651  
   652  	varNotExist := structs.VariableEncrypted{
   653  		VariableMetadata: structs.VariableMetadata{
   654  			Namespace:   "default",
   655  			Path:        "does/not/exist",
   656  			ModifyIndex: 0,
   657  		},
   658  	}
   659  
   660  	t.Run("missing_var-cas_0", func(t *testing.T) {
   661  		ci.Parallel(t)
   662  		varNotExist := varNotExist
   663  		// A CAS delete with index 0 should succeed when the variable does not
   664  		// exist in the state store.
   665  		resp := ts.VarDeleteCAS(10, &structs.VarApplyStateRequest{
   666  			Op:  structs.VarOpDelete,
   667  			Var: &varNotExist,
   668  		})
   669  		require.True(t, resp.IsOk())
   670  	})
   671  	t.Run("missing_var-cas_1", func(t *testing.T) {
   672  		ci.Parallel(t)
   673  		varZero := varNotExist
   674  		varNotExist := varNotExist
   675  		// A CAS delete with a non-zero index should return a conflict when the
   676  		// variable does not exist in the state store. The conflict value should
   677  		// be a zero value having the same namespace and path.
   678  		varNotExist.ModifyIndex = 1
   679  		req := &structs.VarApplyStateRequest{
   680  			Op:  structs.VarOpDelete,
   681  			Var: &varNotExist,
   682  		}
   683  		resp := ts.VarDeleteCAS(10, req)
   684  		require.True(t, resp.IsConflict())
   685  		require.NotNil(t, resp.Conflict)
   686  		require.Equal(t, varZero.VariableMetadata, resp.Conflict.VariableMetadata)
   687  	})
   688  	t.Run("real_var-cas_0", func(t *testing.T) {
   689  		ci.Parallel(t)
   690  		sv := mock.VariableEncrypted()
   691  		sv.CreateIndex = 0
   692  		sv.ModifyIndex = 0
   693  		sv.Path = "real_var/cas_0"
   694  		// Need to make a copy because VarSet mutates Var.
   695  		svZero := sv.Copy()
   696  		resp := ts.VarSet(10, &structs.VarApplyStateRequest{
   697  			Op:  structs.VarOpSet,
   698  			Var: sv,
   699  		})
   700  		require.True(t, resp.IsOk(), "resp: %+v", resp)
   701  
   702  		// A CAS delete with a zero index should return a conflict when the
   703  		// variable exists in the state store. The conflict value should
   704  		// be the current state of the variable at the path.
   705  		req := &structs.VarApplyStateRequest{
   706  			Op:  structs.VarOpDelete,
   707  			Var: &svZero,
   708  		}
   709  		resp = ts.VarDeleteCAS(0, req)
   710  		require.True(t, resp.IsConflict(), "resp: %+v", resp)
   711  		require.NotNil(t, resp.Conflict)
   712  		require.Equal(t, sv.VariableMetadata, resp.Conflict.VariableMetadata)
   713  	})
   714  	t.Run("real_var-cas_ok", func(t *testing.T) {
   715  		ci.Parallel(t)
   716  		sv := mock.VariableEncrypted()
   717  		sv.Path = "real_var/cas_ok"
   718  		resp := ts.VarSet(10, &structs.VarApplyStateRequest{
   719  			Op:  structs.VarOpSet,
   720  			Var: sv,
   721  		})
   722  		require.True(t, resp.IsOk())
   723  
   724  		// A CAS delete with a correct index should succeed.
   725  		req := &structs.VarApplyStateRequest{
   726  			Op:  structs.VarOpDelete,
   727  			Var: sv,
   728  		}
   729  		resp = ts.VarDeleteCAS(0, req)
   730  		require.True(t, resp.IsOk())
   731  	})
   732  }