github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/serviceregistration/checks/checkstore/shim_test.go (about)

     1  package checkstore
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/hashicorp/nomad/ci"
     7  	"github.com/hashicorp/nomad/client/serviceregistration/checks"
     8  	"github.com/hashicorp/nomad/client/state"
     9  	"github.com/hashicorp/nomad/helper/testlog"
    10  	"github.com/hashicorp/nomad/helper/uuid"
    11  	"github.com/hashicorp/nomad/nomad/structs"
    12  	"github.com/shoenig/test/must"
    13  	"golang.org/x/exp/slices"
    14  )
    15  
    16  var (
    17  	success = structs.CheckSuccess
    18  	failure = structs.CheckFailure
    19  	pending = structs.CheckPending
    20  )
    21  
    22  func newQR(id string, status structs.CheckStatus) *structs.CheckQueryResult {
    23  	return &structs.CheckQueryResult{
    24  		ID:     structs.CheckID(id),
    25  		Status: status,
    26  	}
    27  }
    28  
    29  // alias for brevity
    30  type qrMap = map[structs.CheckID]*structs.CheckQueryResult
    31  
    32  func TestShim_New(t *testing.T) {
    33  	ci.Parallel(t)
    34  	logger := testlog.HCLogger(t)
    35  
    36  	t.Run("restore empty", func(t *testing.T) {
    37  		db := state.NewMemDB(logger)
    38  		s := NewStore(logger, db)
    39  		m := s.List("none")
    40  		must.MapEmpty(t, m)
    41  	})
    42  
    43  	t.Run("restore checks", func(t *testing.T) {
    44  		db := state.NewMemDB(logger)
    45  		must.NoError(t, db.PutCheckResult("alloc1", newQR("id1", success)))
    46  		must.NoError(t, db.PutCheckResult("alloc1", newQR("id2", failure)))
    47  		must.NoError(t, db.PutCheckResult("alloc2", newQR("id3", pending)))
    48  		s := NewStore(logger, db)
    49  		m1 := s.List("alloc1")
    50  		must.MapEq(t, qrMap{
    51  			"id1": newQR("id1", success),
    52  			"id2": newQR("id2", failure),
    53  		}, m1)
    54  		m2 := s.List("alloc2")
    55  		must.MapEq(t, qrMap{"id3": newQR("id3", pending)}, m2)
    56  	})
    57  }
    58  
    59  func TestShim_Set(t *testing.T) {
    60  	ci.Parallel(t)
    61  	logger := testlog.HCLogger(t)
    62  
    63  	t.Run("insert pending", func(t *testing.T) {
    64  		db := state.NewMemDB(logger)
    65  		s := NewStore(logger, db)
    66  
    67  		// insert initial pending check into empty database
    68  		qr1 := newQR("id1", pending)
    69  		qr1.Timestamp = 1
    70  		must.NoError(t, s.Set("alloc1", qr1))
    71  
    72  		// ensure underlying db has check
    73  		internal, err := db.GetCheckResults()
    74  		must.NoError(t, err)
    75  		must.Eq(t, checks.ClientResults{"alloc1": {"id1": qr1}}, internal)
    76  	})
    77  
    78  	t.Run("ignore followup pending", func(t *testing.T) {
    79  		db := state.NewMemDB(logger)
    80  		s := NewStore(logger, db)
    81  
    82  		// insert a check
    83  		qr1 := newQR("id1", success)
    84  		qr1.Timestamp = 1
    85  		must.NoError(t, s.Set("alloc1", qr1))
    86  
    87  		// insert a followup pending check (e.g. client restart)
    88  		qr2 := newQR("id1", pending)
    89  		qr2.Timestamp = 2
    90  		t.Run("into existing", func(t *testing.T) {
    91  			must.NoError(t, s.Set("alloc1", qr2))
    92  		})
    93  
    94  		// ensure shim maintains success result
    95  		list := s.List("alloc1")
    96  		must.Eq(t, qrMap{"id1": qr1}, list)
    97  
    98  		// ensure underlying db maintains success result
    99  		internal, err := db.GetCheckResults()
   100  		must.NoError(t, err)
   101  		must.Eq(t, checks.ClientResults{"alloc1": {"id1": qr1}}, internal)
   102  	})
   103  
   104  	t.Run("insert status change", func(t *testing.T) {
   105  		db := state.NewMemDB(logger)
   106  		s := NewStore(logger, db)
   107  
   108  		// insert initial check, success
   109  		qr1 := newQR("id1", success)
   110  		must.NoError(t, s.Set("alloc1", qr1))
   111  
   112  		// insert followup check, failure
   113  		qr2 := newQR("id1", failure)
   114  		must.NoError(t, s.Set("alloc1", qr2))
   115  
   116  		// ensure shim sees newest status result
   117  		list := s.List("alloc1")
   118  		must.Eq(t, qrMap{"id1": qr2}, list)
   119  
   120  		// ensure underlying db sees newest status result
   121  		internal, err := db.GetCheckResults()
   122  		must.NoError(t, err)
   123  		must.Eq(t, checks.ClientResults{"alloc1": {"id1": qr2}}, internal)
   124  	})
   125  
   126  	t.Run("insert status same", func(t *testing.T) {
   127  		db := state.NewMemDB(logger)
   128  		s := NewStore(logger, db)
   129  
   130  		// insert initial check, success
   131  		qr1 := newQR("id1", success)
   132  		qr1.Timestamp = 1
   133  		must.NoError(t, s.Set("alloc1", qr1))
   134  
   135  		// insert followup check, also success
   136  		qr2 := newQR("id1", success)
   137  		qr2.Timestamp = 2
   138  		must.NoError(t, s.Set("alloc1", qr2))
   139  
   140  		// ensure shim sees newest status result
   141  		list := s.List("alloc1")
   142  		must.Eq(t, qrMap{"id1": qr2}, list)
   143  
   144  		// ensure underlying db sees stale result (optimization)
   145  		internal, err := db.GetCheckResults()
   146  		must.NoError(t, err)
   147  		must.Eq(t, checks.ClientResults{"alloc1": {"id1": qr1}}, internal)
   148  	})
   149  }
   150  
   151  func TestShim_List(t *testing.T) {
   152  	ci.Parallel(t)
   153  	logger := testlog.HCLogger(t)
   154  
   155  	t.Run("list empty", func(t *testing.T) {
   156  		db := state.NewMemDB(logger)
   157  		s := NewStore(logger, db)
   158  
   159  		list := s.List("alloc1")
   160  		must.MapEmpty(t, list)
   161  	})
   162  
   163  	t.Run("list mix", func(t *testing.T) {
   164  		db := state.NewMemDB(logger)
   165  		s := NewStore(logger, db)
   166  
   167  		// insert some checks
   168  		must.NoError(t, s.Set("alloc1", newQR("id1", success)))
   169  		must.NoError(t, s.Set("alloc1", newQR("id2", failure)))
   170  		must.NoError(t, s.Set("alloc2", newQR("id1", pending)))
   171  
   172  		list1 := s.List("alloc1")
   173  		must.MapEq(t, qrMap{
   174  			"id1": newQR("id1", success),
   175  			"id2": newQR("id2", failure),
   176  		}, list1)
   177  
   178  		list2 := s.List("alloc2")
   179  		must.MapEq(t, qrMap{
   180  			"id1": newQR("id1", pending),
   181  		}, list2)
   182  
   183  		internal, err := db.GetCheckResults()
   184  		must.NoError(t, err)
   185  		must.MapEq(t, checks.ClientResults{
   186  			"alloc1": {
   187  				"id1": newQR("id1", success),
   188  				"id2": newQR("id2", failure),
   189  			},
   190  			"alloc2": {
   191  				"id1": newQR("id1", pending),
   192  			},
   193  		}, internal)
   194  	})
   195  }
   196  
   197  func TestShim_Difference(t *testing.T) {
   198  	ci.Parallel(t)
   199  	logger := testlog.HCLogger(t)
   200  
   201  	t.Run("empty store", func(t *testing.T) {
   202  		db := state.NewMemDB(logger)
   203  		s := NewStore(logger, db)
   204  
   205  		ids := []structs.CheckID{"id1", "id2", "id3"}
   206  		unwanted := s.Difference("alloc1", ids)
   207  		must.SliceEmpty(t, unwanted)
   208  	})
   209  
   210  	t.Run("empty unwanted", func(t *testing.T) {
   211  		db := state.NewMemDB(logger)
   212  		s := NewStore(logger, db)
   213  
   214  		// insert some checks
   215  		must.NoError(t, s.Set("alloc1", newQR("id1", success)))
   216  		must.NoError(t, s.Set("alloc1", newQR("id2", failure)))
   217  		must.NoError(t, s.Set("alloc2", newQR("id1", pending)))
   218  
   219  		var ids []structs.CheckID
   220  		var exp = []structs.CheckID{"id1", "id2"}
   221  		unwanted := s.Difference("alloc1", ids)
   222  		slices.Sort(unwanted)
   223  		must.Eq(t, exp, unwanted)
   224  	})
   225  
   226  	t.Run("subset unwanted", func(t *testing.T) {
   227  		db := state.NewMemDB(logger)
   228  		s := NewStore(logger, db)
   229  
   230  		// insert some checks
   231  		must.NoError(t, s.Set("alloc1", newQR("id1", success)))
   232  		must.NoError(t, s.Set("alloc1", newQR("id2", failure)))
   233  		must.NoError(t, s.Set("alloc1", newQR("id3", success)))
   234  		must.NoError(t, s.Set("alloc1", newQR("id4", success)))
   235  		must.NoError(t, s.Set("alloc1", newQR("id5", pending)))
   236  
   237  		ids := []structs.CheckID{"id1", "id3", "id4"}
   238  		exp := []structs.CheckID{"id2", "id5"}
   239  		unwanted := s.Difference("alloc1", ids)
   240  		slices.Sort(unwanted)
   241  		must.Eq(t, exp, unwanted)
   242  	})
   243  
   244  	t.Run("unexpected unwanted", func(t *testing.T) {
   245  		db := state.NewMemDB(logger)
   246  		s := NewStore(logger, db)
   247  
   248  		// insert some checks
   249  		must.NoError(t, s.Set("alloc1", newQR("id1", success)))
   250  		must.NoError(t, s.Set("alloc1", newQR("id2", failure)))
   251  		must.NoError(t, s.Set("alloc1", newQR("id3", success)))
   252  
   253  		ids := []structs.CheckID{"id1", "id4"}
   254  		exp := []structs.CheckID{"id2", "id3"}
   255  		unwanted := s.Difference("alloc1", ids)
   256  		slices.Sort(unwanted)
   257  		must.Eq(t, exp, unwanted)
   258  	})
   259  }
   260  
   261  func TestShim_Remove(t *testing.T) {
   262  	ci.Parallel(t)
   263  	logger := testlog.HCLogger(t)
   264  
   265  	t.Run("remove from empty store", func(t *testing.T) {
   266  		db := state.NewMemDB(logger)
   267  		s := NewStore(logger, db)
   268  
   269  		ids := []structs.CheckID{"id1", "id2"}
   270  		err := s.Remove("alloc1", ids)
   271  		must.NoError(t, err)
   272  	})
   273  
   274  	t.Run("remove empty set from store", func(t *testing.T) {
   275  		db := state.NewMemDB(logger)
   276  		s := NewStore(logger, db)
   277  
   278  		// insert some checks
   279  		must.NoError(t, s.Set("alloc1", newQR("id1", success)))
   280  		must.NoError(t, s.Set("alloc1", newQR("id2", failure)))
   281  		must.NoError(t, s.Set("alloc2", newQR("id1", pending)))
   282  
   283  		var ids []structs.CheckID
   284  		err := s.Remove("alloc1", ids)
   285  		must.NoError(t, err)
   286  
   287  		// ensure shim still contains checks
   288  		list := s.List("alloc1")
   289  		must.Eq(t, qrMap{"id1": newQR("id1", success), "id2": newQR("id2", failure)}, list)
   290  
   291  		// ensure underlying db still contains all checks
   292  		internal, err := db.GetCheckResults()
   293  		must.NoError(t, err)
   294  		must.Eq(t, checks.ClientResults{
   295  			"alloc1": {
   296  				"id1": newQR("id1", success),
   297  				"id2": newQR("id2", failure),
   298  			},
   299  			"alloc2": {
   300  				"id1": newQR("id1", pending),
   301  			},
   302  		}, internal)
   303  	})
   304  
   305  	t.Run("remove subset from store", func(t *testing.T) {
   306  		db := state.NewMemDB(logger)
   307  		s := NewStore(logger, db)
   308  
   309  		// insert some checks
   310  		must.NoError(t, s.Set("alloc1", newQR("id1", success)))
   311  		must.NoError(t, s.Set("alloc1", newQR("id2", failure)))
   312  		must.NoError(t, s.Set("alloc1", newQR("id3", success)))
   313  		must.NoError(t, s.Set("alloc1", newQR("id4", pending)))
   314  		must.NoError(t, s.Set("alloc2", newQR("id1", pending)))
   315  		must.NoError(t, s.Set("alloc2", newQR("id2", success)))
   316  
   317  		ids := []structs.CheckID{"id1", "id4"}
   318  		err := s.Remove("alloc1", ids)
   319  		must.NoError(t, err)
   320  
   321  		// ensure shim still contains remaining checks
   322  		list := s.List("alloc1")
   323  		must.Eq(t, qrMap{"id2": newQR("id2", failure), "id3": newQR("id3", success)}, list)
   324  
   325  		// ensure underlying db still contains remaining checks
   326  		internal, err := db.GetCheckResults()
   327  		must.NoError(t, err)
   328  		must.MapEq(t, checks.ClientResults{
   329  			"alloc1": {
   330  				"id2": newQR("id2", failure),
   331  				"id3": newQR("id3", success),
   332  			},
   333  			"alloc2": {
   334  				"id1": newQR("id1", pending),
   335  				"id2": newQR("id2", success),
   336  			},
   337  		}, internal)
   338  	})
   339  }
   340  
   341  func TestShim_Purge(t *testing.T) {
   342  	ci.Parallel(t)
   343  	logger := testlog.HCLogger(t)
   344  
   345  	t.Run("purge from empty", func(t *testing.T) {
   346  		db := state.NewMemDB(logger)
   347  		s := NewStore(logger, db)
   348  
   349  		err := s.Purge("alloc1")
   350  		must.NoError(t, err)
   351  	})
   352  
   353  	t.Run("purge one alloc", func(t *testing.T) {
   354  		db := state.NewMemDB(logger)
   355  		s := NewStore(logger, db)
   356  
   357  		// insert some checks
   358  		must.NoError(t, s.Set("alloc1", newQR("id1", success)))
   359  		must.NoError(t, s.Set("alloc1", newQR("id2", failure)))
   360  		must.NoError(t, s.Set("alloc2", newQR("id1", pending)))
   361  
   362  		err := s.Purge("alloc1")
   363  		must.NoError(t, err)
   364  
   365  		// ensure alloc1 is gone from shim
   366  		list1 := s.List("alloc1")
   367  		must.MapEmpty(t, list1)
   368  
   369  		// ensure alloc2 remains in shim
   370  		list2 := s.List("alloc2")
   371  		must.MapEq(t, qrMap{"id1": newQR("id1", pending)}, list2)
   372  
   373  		// ensure alloc is gone from underlying db
   374  		internal, err := db.GetCheckResults()
   375  		must.NoError(t, err)
   376  		must.MapEq(t, checks.ClientResults{
   377  			"alloc2": {"id1": newQR("id1", pending)},
   378  		}, internal)
   379  	})
   380  }
   381  
   382  func TestShim_Snapshot(t *testing.T) {
   383  	ci.Parallel(t)
   384  	logger := testlog.HCLogger(t)
   385  
   386  	db := state.NewMemDB(logger)
   387  	s := NewStore(logger, db)
   388  
   389  	id1, id2, id3 := uuid.Short(), uuid.Short(), uuid.Short()
   390  	must.NoError(t, s.Set("allocation1", &structs.CheckQueryResult{
   391  		ID:     structs.CheckID(id1),
   392  		Status: "passing",
   393  	}))
   394  	must.NoError(t, s.Set("allocation1", &structs.CheckQueryResult{
   395  		ID:     structs.CheckID(id2),
   396  		Status: "failing",
   397  	}))
   398  	must.NoError(t, s.Set("allocation2", &structs.CheckQueryResult{
   399  		ID:     structs.CheckID(id3),
   400  		Status: "passing",
   401  	}))
   402  
   403  	snap := s.Snapshot()
   404  	must.MapEq(t, map[string]string{
   405  		id1: "passing",
   406  		id2: "failing",
   407  		id3: "passing",
   408  	}, snap)
   409  }