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 }