github.com/hernad/nomad@v1.6.112/nomad/state/state_store_variables_test.go (about)

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