github.com/looshlee/beatles@v0.0.0-20220727174639-742810ab631c/pkg/kvstore/allocator/allocator_test.go (about) 1 // Copyright 2016-2020 Authors of Cilium 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // +build !privileged_tests 16 17 package allocator 18 19 import ( 20 "context" 21 "fmt" 22 "path" 23 "testing" 24 "time" 25 26 "github.com/cilium/cilium/pkg/allocator" 27 "github.com/cilium/cilium/pkg/idpool" 28 "github.com/cilium/cilium/pkg/kvstore" 29 "github.com/cilium/cilium/pkg/testutils" 30 31 . "gopkg.in/check.v1" 32 ) 33 34 const ( 35 testPrefix = "test-prefix" 36 ) 37 38 func Test(t *testing.T) { 39 TestingT(t) 40 } 41 42 type AllocatorSuite struct { 43 backend string 44 } 45 46 type AllocatorEtcdSuite struct { 47 AllocatorSuite 48 } 49 50 var _ = Suite(&AllocatorEtcdSuite{}) 51 52 func (e *AllocatorEtcdSuite) SetUpTest(c *C) { 53 e.backend = "etcd" 54 kvstore.SetupDummy("etcd") 55 } 56 57 func (e *AllocatorEtcdSuite) TearDownTest(c *C) { 58 kvstore.Client().DeletePrefix(testPrefix) 59 kvstore.Client().Close() 60 } 61 62 type AllocatorConsulSuite struct { 63 AllocatorSuite 64 } 65 66 var _ = Suite(&AllocatorConsulSuite{}) 67 68 func (e *AllocatorConsulSuite) SetUpTest(c *C) { 69 e.backend = "consul" 70 kvstore.SetupDummy("consul") 71 } 72 73 func (e *AllocatorConsulSuite) TearDownTest(c *C) { 74 kvstore.Client().DeletePrefix(testPrefix) 75 kvstore.Client().Close() 76 } 77 78 //FIXME: this should be named better, it implements pkg/allocator.Backend 79 type TestAllocatorKey string 80 81 func (t TestAllocatorKey) GetKey() string { return string(t) } 82 func (t TestAllocatorKey) GetAsMap() map[string]string { return map[string]string{string(t): string(t)} } 83 func (t TestAllocatorKey) String() string { return string(t) } 84 func (t TestAllocatorKey) PutKey(v string) allocator.AllocatorKey { 85 return TestAllocatorKey(v) 86 } 87 func (t TestAllocatorKey) PutKeyFromMap(m map[string]string) allocator.AllocatorKey { 88 for _, v := range m { 89 return TestAllocatorKey(v) 90 } 91 92 panic("empty map") 93 } 94 95 func randomTestName() string { 96 return testutils.RandomRuneWithPrefix(testPrefix, 12) 97 } 98 99 func (s *AllocatorSuite) BenchmarkAllocate(c *C) { 100 allocatorName := randomTestName() 101 maxID := idpool.ID(256 + c.N) 102 backend, err := NewKVStoreBackend(allocatorName, "a", TestAllocatorKey(""), kvstore.Client()) 103 c.Assert(err, IsNil) 104 a, err := allocator.NewAllocator(TestAllocatorKey(""), backend, allocator.WithMax(maxID)) 105 c.Assert(err, IsNil) 106 c.Assert(a, Not(IsNil)) 107 defer a.DeleteAllKeys() 108 109 c.ResetTimer() 110 for i := 0; i < c.N; i++ { 111 _, _, err := a.Allocate(context.Background(), TestAllocatorKey(fmt.Sprintf("key%04d", i))) 112 c.Assert(err, IsNil) 113 } 114 c.StopTimer() 115 116 } 117 118 func (s *AllocatorSuite) TestRunLocksGC(c *C) { 119 allocatorName := randomTestName() 120 maxID := idpool.ID(256 + c.N) 121 // FIXME: Did this previousy use allocatorName := randomTestName() ? so TestAllocatorKey(randomeTestName()) 122 backend1, err := NewKVStoreBackend(allocatorName, "a", TestAllocatorKey(""), kvstore.Client()) 123 c.Assert(err, IsNil) 124 allocator, err := allocator.NewAllocator(TestAllocatorKey(""), backend1, allocator.WithMax(maxID), allocator.WithoutGC()) 125 c.Assert(err, IsNil) 126 shortKey := TestAllocatorKey("1;") 127 128 staleLocks := map[string]kvstore.Value{} 129 staleLocks, err = allocator.RunLocksGC(staleLocks) 130 c.Assert(err, IsNil) 131 c.Assert(len(staleLocks), Equals, 0) 132 133 var ( 134 lock1, lock2 kvstore.KVLocker 135 gotLock1 = make(chan struct{}) 136 gotLock2 = make(chan struct{}) 137 ) 138 go func() { 139 var ( 140 err error 141 ) 142 lock1, err = backend1.Lock(context.Background(), shortKey) 143 c.Assert(err, IsNil) 144 close(gotLock1) 145 var client kvstore.BackendOperations 146 switch s.backend { 147 case "etcd": 148 client, _ = kvstore.NewClient( 149 s.backend, 150 map[string]string{ 151 kvstore.EtcdAddrOption: kvstore.EtcdDummyAddress(), 152 }, 153 nil, 154 ) 155 case "consul": 156 client, _ = kvstore.NewClient( 157 s.backend, 158 map[string]string{ 159 kvstore.ConsulAddrOption: kvstore.ConsulDummyAddress(), 160 kvstore.ConsulOptionConfig: kvstore.ConsulDummyConfigFile(), 161 }, 162 nil, 163 ) 164 } 165 lock2, err = client.LockPath(context.Background(), allocatorName+"/locks/"+kvstore.Client().Encode([]byte(shortKey.GetKey()))) 166 c.Assert(err, IsNil) 167 close(gotLock2) 168 }() 169 170 // Wait until lock1 is gotten. 171 c.Assert(testutils.WaitUntil(func() bool { 172 select { 173 case <-gotLock1: 174 return true 175 default: 176 return false 177 } 178 }, 5*time.Second), IsNil) 179 180 // wait until client2, in line 160, tries to grab the lock. 181 // We can't detect when that actually happen so we have to assume it will 182 // happen within one second. 183 time.Sleep(time.Second) 184 185 // Check which locks are stale, it should be lock1 and lock2 186 staleLocks, err = allocator.RunLocksGC(staleLocks) 187 c.Assert(err, IsNil) 188 switch s.backend { 189 case "consul": 190 // Contrary to etcd, consul does not create a lock in the kvstore 191 // if a lock is already being held. 192 c.Assert(len(staleLocks), Equals, 1) 193 case "etcd": 194 c.Assert(len(staleLocks), Equals, 2) 195 } 196 197 var ( 198 oldestRev uint64 199 oldestLeaseID int64 200 sessionID string 201 ) 202 // Stale locks contains 2 locks, which is expected but we only want to GC 203 // the oldest one so we can unlock all the remaining clients waiting to hold 204 // the lock. 205 for _, v := range staleLocks { 206 if v.ModRevision < oldestRev { 207 oldestRev = v.ModRevision 208 oldestLeaseID = v.LeaseID 209 sessionID = v.SessionID 210 } 211 } 212 staleLocks[allocatorName+"/locks/"+shortKey.GetKey()] = kvstore.Value{ 213 ModRevision: oldestRev, 214 LeaseID: oldestLeaseID, 215 SessionID: sessionID, 216 } 217 218 // GC lock1 because it's the oldest lock being held. 219 staleLocks, err = allocator.RunLocksGC(staleLocks) 220 c.Assert(err, IsNil) 221 c.Assert(len(staleLocks), Equals, 0) 222 223 // Wait until lock2 is gotten as it should have happen since we have 224 // GC lock1. 225 c.Assert(testutils.WaitUntil(func() bool { 226 select { 227 case <-gotLock2: 228 return true 229 default: 230 return false 231 } 232 }, 10*time.Second), IsNil) 233 234 // Unlock lock1 because we still hold the local locks. 235 err = lock1.Unlock() 236 c.Assert(err, IsNil) 237 err = lock2.Unlock() 238 c.Assert(err, IsNil) 239 } 240 241 func (s *AllocatorSuite) TestGC(c *C) { 242 allocatorName := randomTestName() 243 maxID := idpool.ID(256 + c.N) 244 // FIXME: Did this previousy use allocatorName := randomTestName() ? so TestAllocatorKey(randomeTestName()) 245 backend, err := NewKVStoreBackend(allocatorName, "a", TestAllocatorKey(""), kvstore.Client()) 246 c.Assert(err, IsNil) 247 allocator, err := allocator.NewAllocator(TestAllocatorKey(""), backend, allocator.WithMax(maxID), allocator.WithoutGC()) 248 c.Assert(err, IsNil) 249 c.Assert(allocator, Not(IsNil)) 250 defer allocator.DeleteAllKeys() 251 defer allocator.Delete() 252 253 allocator.DeleteAllKeys() 254 255 shortKey := TestAllocatorKey("1;") 256 shortID, _, err := allocator.Allocate(context.Background(), shortKey) 257 c.Assert(err, IsNil) 258 c.Assert(shortID, Not(Equals), 0) 259 260 longKey := TestAllocatorKey("1;2;") 261 longID, _, err := allocator.Allocate(context.Background(), longKey) 262 c.Assert(err, IsNil) 263 c.Assert(longID, Not(Equals), 0) 264 265 allocator.Release(context.Background(), shortKey) 266 267 keysToDelete := map[string]uint64{} 268 keysToDelete, err = allocator.RunGC(keysToDelete) 269 c.Assert(err, IsNil) 270 c.Assert(len(keysToDelete), Equals, 1) 271 keysToDelete, err = allocator.RunGC(keysToDelete) 272 c.Assert(err, IsNil) 273 c.Assert(len(keysToDelete), Equals, 0) 274 275 // wait for cache to be updated via delete notification 276 c.Assert(testutils.WaitUntil(func() bool { 277 key, err := allocator.GetByID(shortID) 278 if err != nil { 279 c.Error(err) 280 return false 281 } 282 //FIXME: This isn't nil but it probably should be. This is because TestAllocatorKey is setup with "" and returns itself, a non-nil 283 return key == nil || key.String() == "" 284 }, 5*time.Second), IsNil) 285 286 key, err := allocator.GetByID(shortID) 287 c.Assert(err, IsNil) 288 c.Assert(key, Equals, TestAllocatorKey("")) 289 } 290 291 func testAllocator(c *C, maxID idpool.ID, allocatorName string, suffix string) { 292 backend, err := NewKVStoreBackend(allocatorName, "a", TestAllocatorKey(""), kvstore.Client()) 293 c.Assert(err, IsNil) 294 a, err := allocator.NewAllocator(TestAllocatorKey(""), backend, 295 allocator.WithMax(maxID), allocator.WithoutGC()) 296 c.Assert(err, IsNil) 297 c.Assert(a, Not(IsNil)) 298 299 // remove any keys which might be leftover 300 a.DeleteAllKeys() 301 302 // allocate all available IDs 303 for i := idpool.ID(1); i <= maxID; i++ { 304 key := TestAllocatorKey(fmt.Sprintf("key%04d", i)) 305 id, new, err := a.Allocate(context.Background(), key) 306 c.Assert(err, IsNil) 307 c.Assert(id, Not(Equals), 0) 308 c.Assert(new, Equals, true) 309 } 310 311 // allocate all IDs again using the same set of keys, refcnt should go to 2 312 for i := idpool.ID(1); i <= maxID; i++ { 313 key := TestAllocatorKey(fmt.Sprintf("key%04d", i)) 314 id, new, err := a.Allocate(context.Background(), key) 315 c.Assert(err, IsNil) 316 c.Assert(id, Not(Equals), 0) 317 c.Assert(new, Equals, false) 318 } 319 320 // Create a 2nd allocator, refill it 321 backend2, err := NewKVStoreBackend(allocatorName, "r", TestAllocatorKey(""), kvstore.Client()) 322 c.Assert(err, IsNil) 323 a2, err := allocator.NewAllocator(TestAllocatorKey(""), backend2, 324 allocator.WithMax(maxID), allocator.WithoutGC()) 325 c.Assert(err, IsNil) 326 c.Assert(a2, Not(IsNil)) 327 328 // allocate all IDs again using the same set of keys, refcnt should go to 2 329 for i := idpool.ID(1); i <= maxID; i++ { 330 key := TestAllocatorKey(fmt.Sprintf("key%04d", i)) 331 id, new, err := a2.Allocate(context.Background(), key) 332 c.Assert(err, IsNil) 333 c.Assert(id, Not(Equals), 0) 334 c.Assert(new, Equals, false) 335 336 a2.Release(context.Background(), key) 337 } 338 339 // release 2nd reference of all IDs 340 for i := idpool.ID(1); i <= maxID; i++ { 341 a.Release(context.Background(), TestAllocatorKey(fmt.Sprintf("key%04d", i))) 342 } 343 344 staleKeysPreviousRound := map[string]uint64{} 345 // running the GC should not evict any entries 346 staleKeysPreviousRound, err = a.RunGC(staleKeysPreviousRound) 347 c.Assert(err, IsNil) 348 349 v, err := kvstore.Client().ListPrefix(path.Join(allocatorName, "id")) 350 c.Assert(err, IsNil) 351 c.Assert(len(v), Equals, int(maxID)) 352 353 // release final reference of all IDs 354 for i := idpool.ID(1); i <= maxID; i++ { 355 a.Release(context.Background(), TestAllocatorKey(fmt.Sprintf("key%04d", i))) 356 } 357 358 // running the GC should evict all entries 359 staleKeysPreviousRound, err = a.RunGC(staleKeysPreviousRound) 360 c.Assert(err, IsNil) 361 _, err = a.RunGC(staleKeysPreviousRound) 362 c.Assert(err, IsNil) 363 364 v, err = kvstore.Client().ListPrefix(path.Join(allocatorName, "id")) 365 c.Assert(err, IsNil) 366 c.Assert(len(v), Equals, 0) 367 368 a.DeleteAllKeys() 369 a.Delete() 370 a2.Delete() 371 } 372 373 func (s *AllocatorSuite) TestAllocateCached(c *C) { 374 testAllocator(c, idpool.ID(32), randomTestName(), "a") // enable use of local cache 375 } 376 377 func (s *AllocatorSuite) TestKeyToID(c *C) { 378 allocatorName := randomTestName() 379 backend, err := NewKVStoreBackend(allocatorName, "a", TestAllocatorKey(""), kvstore.Client()) 380 c.Assert(err, IsNil) 381 a, err := allocator.NewAllocator(TestAllocatorKey(""), backend) 382 c.Assert(err, IsNil) 383 c.Assert(a, Not(IsNil)) 384 385 // An error is returned because the path is outside the prefix (allocatorName/id) 386 id, err := backend.keyToID(path.Join(allocatorName, "invalid")) 387 c.Assert(err, Not(IsNil)) 388 c.Assert(id, Equals, idpool.NoID) 389 390 // An error is returned because the path contains the prefix 391 // (allocatorName/id) but cannot be parsed ("invalid") 392 id, err = backend.keyToID(path.Join(allocatorName, "id", "invalid")) 393 c.Assert(err, Not(IsNil)) 394 c.Assert(id, Equals, idpool.NoID) 395 396 // A valid lookup that finds an ID 397 id, err = backend.keyToID(path.Join(allocatorName, "id", "10")) 398 c.Assert(err, IsNil) 399 c.Assert(id, Equals, idpool.ID(10)) 400 } 401 402 func testGetNoCache(c *C, maxID idpool.ID, suffix string) { 403 allocatorName := randomTestName() 404 backend, err := NewKVStoreBackend(allocatorName, "a", TestAllocatorKey(""), kvstore.Client()) 405 c.Assert(err, IsNil) 406 allocator, err := allocator.NewAllocator(TestAllocatorKey(""), backend, allocator.WithMax(maxID), allocator.WithoutGC()) 407 c.Assert(err, IsNil) 408 c.Assert(allocator, Not(IsNil)) 409 410 // remove any keys which might be leftover 411 allocator.DeleteAllKeys() 412 defer allocator.DeleteAllKeys() 413 414 labelsLong := "foo;/;bar;" 415 key := TestAllocatorKey(fmt.Sprintf("%s%010d", labelsLong, 0)) 416 longID, new, err := allocator.Allocate(context.Background(), key) 417 c.Assert(err, IsNil) 418 c.Assert(longID, Not(Equals), 0) 419 c.Assert(new, Equals, true) 420 421 observedID, err := allocator.GetNoCache(context.Background(), key) 422 c.Assert(err, IsNil) 423 c.Assert(observedID, Not(Equals), 0) 424 425 labelsShort := "foo;/;" 426 shortKey := TestAllocatorKey(labelsShort) 427 observedID, err = allocator.GetNoCache(context.Background(), shortKey) 428 c.Assert(err, IsNil) 429 c.Assert(observedID, Equals, idpool.NoID) 430 431 shortID, new, err := allocator.Allocate(context.Background(), shortKey) 432 c.Assert(err, IsNil) 433 c.Assert(shortID, Not(Equals), 0) 434 c.Assert(new, Equals, true) 435 436 observedID, err = allocator.GetNoCache(context.Background(), shortKey) 437 c.Assert(err, IsNil) 438 c.Assert(observedID, Equals, shortID) 439 } 440 441 func (s *AllocatorSuite) TestprefixMatchesKey(c *C) { 442 // cilium/state/identities/v1/value/label;foo;bar;/172.0.124.60 443 444 tests := []struct { 445 prefix string 446 key string 447 expected bool 448 }{ 449 { 450 prefix: "foo", 451 key: "foo/bar", 452 expected: true, 453 }, 454 { 455 prefix: "foo/;bar;baz;/;a;", 456 key: "foo/;bar;baz;/;a;/alice", 457 expected: true, 458 }, 459 { 460 prefix: "foo/;bar;baz;", 461 key: "foo/;bar;baz;/;a;/alice", 462 expected: false, 463 }, 464 { 465 prefix: "foo/;bar;baz;/;a;/baz", 466 key: "foo/;bar;baz;/;a;/alice", 467 expected: false, 468 }, 469 } 470 471 for _, tt := range tests { 472 c.Logf("prefixMatchesKey(%q, %q) expected to be %t", tt.prefix, tt.key, tt.expected) 473 result := prefixMatchesKey(tt.prefix, tt.key) 474 c.Assert(result, Equals, tt.expected) 475 } 476 } 477 478 func (s *AllocatorSuite) TestGetNoCache(c *C) { 479 testGetNoCache(c, idpool.ID(256), "a") // enable use of local cache 480 } 481 482 func (s *AllocatorSuite) TestRemoteCache(c *C) { 483 testName := randomTestName() 484 backend, err := NewKVStoreBackend(testName, "a", TestAllocatorKey(""), kvstore.Client()) 485 c.Assert(err, IsNil) 486 a, err := allocator.NewAllocator(TestAllocatorKey(""), backend, allocator.WithMax(idpool.ID(256))) 487 c.Assert(err, IsNil) 488 c.Assert(a, Not(IsNil)) 489 490 // remove any keys which might be leftover 491 a.DeleteAllKeys() 492 493 // allocate all available IDs 494 for i := idpool.ID(1); i <= idpool.ID(4); i++ { 495 key := TestAllocatorKey(fmt.Sprintf("key%04d", i)) 496 _, _, err := a.Allocate(context.Background(), key) 497 c.Assert(err, IsNil) 498 } 499 500 // wait for main cache to be populated 501 c.Assert(testutils.WaitUntil(func() bool { 502 cacheLen := 0 503 a.ForeachCache(func(id idpool.ID, val allocator.AllocatorKey) { 504 cacheLen++ 505 }) 506 return cacheLen == 4 507 }, 5*time.Second), IsNil) 508 509 // count identical allocations returned 510 cache := map[idpool.ID]int{} 511 a.ForeachCache(func(id idpool.ID, val allocator.AllocatorKey) { 512 cache[id]++ 513 }) 514 515 // ForeachCache must have returned 4 allocations all unique 516 c.Assert(len(cache), Equals, 4) 517 for i := range cache { 518 c.Assert(cache[i], Equals, 1) 519 } 520 521 // watch the prefix in the same kvstore via a 2nd watcher 522 backend2, err := NewKVStoreBackend(testName, "a", TestAllocatorKey(""), kvstore.Client()) 523 c.Assert(err, IsNil) 524 a2, err := allocator.NewAllocator(TestAllocatorKey(""), backend2, allocator.WithMax(idpool.ID(256))) 525 c.Assert(err, IsNil) 526 rc := a.WatchRemoteKVStore(a2) 527 c.Assert(rc, Not(IsNil)) 528 529 // wait for remote cache to be populated 530 c.Assert(testutils.WaitUntil(func() bool { 531 cacheLen := 0 532 a.ForeachCache(func(id idpool.ID, val allocator.AllocatorKey) { 533 cacheLen++ 534 }) 535 // 4 local + 4 remote 536 return cacheLen == 8 537 }, 5*time.Second), IsNil) 538 539 // count the allocations in the main cache *AND* the remote cache 540 cache = map[idpool.ID]int{} 541 a.ForeachCache(func(id idpool.ID, val allocator.AllocatorKey) { 542 cache[id]++ 543 }) 544 545 // Foreach must have returned 4 allocations each duplicated, once in 546 // the main cache, once in the remote cache 547 c.Assert(len(cache), Equals, 4) 548 for i := range cache { 549 c.Assert(cache[i], Equals, 2) 550 } 551 552 rc.Close() 553 554 a.DeleteAllKeys() 555 a.Delete() 556 }