github.phpd.cn/cilium/cilium@v1.6.12/pkg/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 "testing" 23 24 "github.com/cilium/cilium/pkg/idpool" 25 "github.com/cilium/cilium/pkg/kvstore" 26 "github.com/cilium/cilium/pkg/lock" 27 "github.com/cilium/cilium/pkg/testutils" 28 29 . "gopkg.in/check.v1" 30 ) 31 32 const ( 33 testPrefix = "test-prefix" 34 ) 35 36 func Test(t *testing.T) { 37 TestingT(t) 38 } 39 40 type AllocatorSuite struct{} 41 42 var _ = Suite(&AllocatorSuite{}) 43 44 type dummyBackend struct { 45 mutex lock.RWMutex 46 identities map[idpool.ID]AllocatorKey 47 handler CacheMutations 48 } 49 50 func newDummyBackend() Backend { 51 return &dummyBackend{ 52 identities: map[idpool.ID]AllocatorKey{}, 53 } 54 } 55 56 func (d *dummyBackend) Encode(v string) string { 57 return v 58 } 59 60 func (d *dummyBackend) DeleteAllKeys() { 61 d.mutex.Lock() 62 defer d.mutex.Unlock() 63 d.identities = map[idpool.ID]AllocatorKey{} 64 } 65 66 func (d *dummyBackend) AllocateID(ctx context.Context, id idpool.ID, key AllocatorKey) error { 67 d.mutex.Lock() 68 defer d.mutex.Unlock() 69 70 if _, ok := d.identities[id]; ok { 71 return fmt.Errorf("identity already exists") 72 } 73 74 d.identities[id] = key 75 76 if d.handler != nil { 77 d.handler.OnAdd(id, key) 78 } 79 80 return nil 81 } 82 83 func (d *dummyBackend) AllocateIDIfLocked(ctx context.Context, id idpool.ID, key AllocatorKey, lock kvstore.KVLocker) error { 84 return d.AllocateID(ctx, id, key) 85 } 86 87 func (d *dummyBackend) AcquireReference(ctx context.Context, id idpool.ID, key AllocatorKey, lock kvstore.KVLocker) error { 88 d.mutex.Lock() 89 defer d.mutex.Unlock() 90 91 if _, ok := d.identities[id]; !ok { 92 return fmt.Errorf("identity does not exist") 93 } 94 95 if d.handler != nil { 96 d.handler.OnModify(id, key) 97 } 98 99 return nil 100 } 101 102 type dummyLock struct{} 103 104 func (d *dummyLock) Unlock() error { 105 return nil 106 } 107 108 func (d *dummyLock) Comparator() interface{} { 109 return nil 110 } 111 112 func (d *dummyBackend) Lock(ctx context.Context, key AllocatorKey) (kvstore.KVLocker, error) { 113 return &dummyLock{}, nil 114 } 115 116 func (d *dummyBackend) UpdateKey(ctx context.Context, id idpool.ID, key AllocatorKey, reliablyMissing bool) error { 117 d.mutex.Lock() 118 defer d.mutex.Unlock() 119 d.identities[id] = key 120 return nil 121 } 122 123 func (d *dummyBackend) UpdateKeyIfLocked(ctx context.Context, id idpool.ID, key AllocatorKey, reliablyMissing bool, lock kvstore.KVLocker) error { 124 return d.UpdateKey(ctx, id, key, reliablyMissing) 125 } 126 127 func (d *dummyBackend) Get(ctx context.Context, key AllocatorKey) (idpool.ID, error) { 128 d.mutex.RLock() 129 defer d.mutex.RUnlock() 130 for id, k := range d.identities { 131 if key.GetKey() == k.GetKey() { 132 return id, nil 133 } 134 } 135 return idpool.NoID, nil 136 } 137 138 func (d *dummyBackend) GetIfLocked(ctx context.Context, key AllocatorKey, lock kvstore.KVLocker) (idpool.ID, error) { 139 return d.Get(ctx, key) 140 } 141 142 func (d *dummyBackend) GetByID(id idpool.ID) (AllocatorKey, error) { 143 d.mutex.RLock() 144 defer d.mutex.RUnlock() 145 if key, ok := d.identities[id]; ok { 146 return key, nil 147 } 148 return nil, nil 149 } 150 151 func (d *dummyBackend) Release(ctx context.Context, id idpool.ID, key AllocatorKey) error { 152 d.mutex.Lock() 153 defer d.mutex.Unlock() 154 for idtyID, k := range d.identities { 155 if k.GetKey() == key.GetKey() && 156 idtyID == id { 157 delete(d.identities, id) 158 if d.handler != nil { 159 d.handler.OnDelete(id, k) 160 } 161 return nil 162 } 163 } 164 return fmt.Errorf("identity does not exist") 165 } 166 167 func (d *dummyBackend) ListAndWatch(handler CacheMutations, stopChan chan struct{}) { 168 d.mutex.Lock() 169 d.handler = handler 170 for id, k := range d.identities { 171 d.handler.OnModify(id, k) 172 } 173 d.mutex.Unlock() 174 d.handler.OnListDone() 175 <-stopChan 176 } 177 178 func (d *dummyBackend) RunLocksGC(map[string]kvstore.Value) (map[string]kvstore.Value, error) { 179 return nil, nil 180 } 181 182 func (d *dummyBackend) RunGC(map[string]uint64) (map[string]uint64, error) { 183 return nil, nil 184 } 185 186 func (d *dummyBackend) Status() (string, error) { 187 return "", nil 188 } 189 190 type TestAllocatorKey string 191 192 func (t TestAllocatorKey) GetKey() string { return string(t) } 193 func (t TestAllocatorKey) GetAsMap() map[string]string { return map[string]string{string(t): string(t)} } 194 func (t TestAllocatorKey) String() string { return string(t) } 195 func (t TestAllocatorKey) PutKey(v string) AllocatorKey { 196 return TestAllocatorKey(v) 197 } 198 func (t TestAllocatorKey) PutKeyFromMap(m map[string]string) AllocatorKey { 199 for _, v := range m { 200 return TestAllocatorKey(v) 201 } 202 203 panic("empty map") 204 } 205 206 func randomTestName() string { 207 return testutils.RandomRuneWithPrefix(testPrefix, 12) 208 } 209 210 func (s *AllocatorSuite) TestSelectID(c *C) { 211 minID, maxID := idpool.ID(1), idpool.ID(5) 212 backend := newDummyBackend() 213 a, err := NewAllocator(TestAllocatorKey(""), backend, WithMin(minID), WithMax(maxID)) 214 c.Assert(err, IsNil) 215 c.Assert(a, Not(IsNil)) 216 217 // allocate all available IDs 218 for i := minID; i <= maxID; i++ { 219 id, val, unmaskedID := a.selectAvailableID() 220 c.Assert(id, Not(Equals), idpool.NoID) 221 c.Assert(val, Equals, id.String()) 222 c.Assert(id, Equals, unmaskedID) 223 a.mainCache.cache[id] = TestAllocatorKey(fmt.Sprintf("key-%d", i)) 224 } 225 226 // we should be out of IDs 227 id, val, unmaskedID := a.selectAvailableID() 228 c.Assert(id, Equals, idpool.ID(0)) 229 c.Assert(id, Equals, unmaskedID) 230 c.Assert(val, Equals, "") 231 } 232 233 func (s *AllocatorSuite) TestPrefixMask(c *C) { 234 minID, maxID := idpool.ID(1), idpool.ID(5) 235 backend := newDummyBackend() 236 a, err := NewAllocator(TestAllocatorKey(""), backend, WithMin(minID), WithMax(maxID), WithPrefixMask(1<<16)) 237 c.Assert(err, IsNil) 238 c.Assert(a, Not(IsNil)) 239 240 // allocate all available IDs 241 for i := minID; i <= maxID; i++ { 242 id, val, unmaskedID := a.selectAvailableID() 243 c.Assert(id, Not(Equals), idpool.NoID) 244 c.Assert(id>>16, Equals, idpool.ID(1)) 245 c.Assert(id, Not(Equals), unmaskedID) 246 c.Assert(val, Equals, id.String()) 247 } 248 249 a.Delete() 250 } 251 252 func testAllocator(c *C, maxID idpool.ID, allocatorName string, suffix string) { 253 backend := newDummyBackend() 254 allocator, err := NewAllocator(TestAllocatorKey(""), backend, WithMax(maxID), WithoutGC()) 255 c.Assert(err, IsNil) 256 c.Assert(allocator, Not(IsNil)) 257 258 // remove any keys which might be leftover 259 allocator.DeleteAllKeys() 260 261 // allocate all available IDs 262 for i := idpool.ID(1); i <= maxID; i++ { 263 key := TestAllocatorKey(fmt.Sprintf("key%04d", i)) 264 id, new, err := allocator.Allocate(context.Background(), key) 265 c.Assert(err, IsNil) 266 c.Assert(id, Not(Equals), 0) 267 c.Assert(new, Equals, true) 268 269 // refcnt must be 1 270 c.Assert(allocator.localKeys.keys[allocator.encodeKey(key)].refcnt, Equals, uint64(1)) 271 } 272 273 saved := allocator.backoffTemplate.Factor 274 allocator.backoffTemplate.Factor = 1.0 275 276 // we should be out of id space here 277 _, new, err := allocator.Allocate(context.Background(), TestAllocatorKey(fmt.Sprintf("key%04d", maxID+1))) 278 c.Assert(err, Not(IsNil)) 279 c.Assert(new, Equals, false) 280 281 allocator.backoffTemplate.Factor = saved 282 283 // allocate all IDs again using the same set of keys, refcnt should go to 2 284 for i := idpool.ID(1); i <= maxID; i++ { 285 key := TestAllocatorKey(fmt.Sprintf("key%04d", i)) 286 id, new, err := allocator.Allocate(context.Background(), key) 287 c.Assert(err, IsNil) 288 c.Assert(id, Not(Equals), 0) 289 c.Assert(new, Equals, false) 290 291 // refcnt must now be 2 292 c.Assert(allocator.localKeys.keys[allocator.encodeKey(key)].refcnt, Equals, uint64(2)) 293 } 294 295 // Create a 2nd allocator, refill it 296 allocator2, err := NewAllocator(TestAllocatorKey(""), backend, WithMax(maxID), WithoutGC()) 297 c.Assert(err, IsNil) 298 c.Assert(allocator2, Not(IsNil)) 299 300 // allocate all IDs again using the same set of keys, refcnt should go to 2 301 for i := idpool.ID(1); i <= maxID; i++ { 302 key := TestAllocatorKey(fmt.Sprintf("key%04d", i)) 303 id, new, err := allocator2.Allocate(context.Background(), key) 304 c.Assert(err, IsNil) 305 c.Assert(id, Not(Equals), 0) 306 c.Assert(new, Equals, false) 307 308 localKey := allocator2.localKeys.keys[allocator.encodeKey(key)] 309 c.Assert(localKey, Not(IsNil)) 310 311 // refcnt in the 2nd allocator is 1 312 c.Assert(localKey.refcnt, Equals, uint64(1)) 313 314 allocator2.Release(context.Background(), key) 315 } 316 317 // release 2nd reference of all IDs 318 for i := idpool.ID(1); i <= maxID; i++ { 319 allocator.Release(context.Background(), TestAllocatorKey(fmt.Sprintf("key%04d", i))) 320 } 321 322 // refcnt should be back to 1 323 for i := idpool.ID(1); i <= maxID; i++ { 324 key := TestAllocatorKey(fmt.Sprintf("key%04d", i)) 325 c.Assert(allocator.localKeys.keys[allocator.encodeKey(key)].refcnt, Equals, uint64(1)) 326 } 327 328 // running the GC should not evict any entries 329 allocator.RunGC(nil) 330 331 // release final reference of all IDs 332 for i := idpool.ID(1); i <= maxID; i++ { 333 allocator.Release(context.Background(), TestAllocatorKey(fmt.Sprintf("key%04d", i))) 334 } 335 336 for i := idpool.ID(1); i <= maxID; i++ { 337 key := TestAllocatorKey(fmt.Sprintf("key%04d", i)) 338 c.Assert(allocator.localKeys.keys[allocator.encodeKey(key)], IsNil) 339 } 340 341 // running the GC should evict all entries 342 allocator.RunGC(nil) 343 344 allocator.DeleteAllKeys() 345 allocator.Delete() 346 allocator2.Delete() 347 } 348 349 func (s *AllocatorSuite) TestAllocateCached(c *C) { 350 testAllocator(c, idpool.ID(256), randomTestName(), "a") // enable use of local cache 351 } 352 353 // The following tests are currently disabled as they are not 100% reliable in 354 // the Jenkins CI. 355 // These were copied from pkg/kvstore/allocator/allocator_test.go and don't 356 // compile anymore. They assume that dummyBackend can be shared between many 357 // allocators in order to test parallel allocations. 358 // 359 //func testParallelAllocator(c *C, maxID idpool.ID, allocatorName string, suffix string) { 360 // allocator, err := NewAllocator(allocatorName, TestAllocatorKey(""), WithMax(maxID), WithSuffix(suffix)) 361 // c.Assert(err, IsNil) 362 // c.Assert(allocator, Not(IsNil)) 363 // 364 // // allocate all available IDs 365 // for i := idpool.ID(1); i <= maxID; i++ { 366 // key := TestAllocatorKey(fmt.Sprintf("key%04d", i)) 367 // id, _, err := allocator.Allocate(context.Background(), key) 368 // c.Assert(err, IsNil) 369 // c.Assert(id, Not(Equals), 0) 370 // 371 // // refcnt must be 1 372 // c.Assert(allocator.localKeys.keys[key.GetKey()].refcnt, Equals, uint64(1)) 373 // } 374 // 375 // saved := allocator.backoffTemplate.Factor 376 // allocator.backoffTemplate.Factor = 1.0 377 // 378 // // we should be out of id space here 379 // _, new, err := allocator.Allocate(context.Background(), TestAllocatorKey(fmt.Sprintf("key%04d", maxID+1))) 380 // c.Assert(err, Not(IsNil)) 381 // c.Assert(new, Equals, false) 382 // 383 // allocator.backoffTemplate.Factor = saved 384 // 385 // // allocate all IDs again using the same set of keys, refcnt should go to 2 386 // for i := idpool.ID(1); i <= maxID; i++ { 387 // key := TestAllocatorKey(fmt.Sprintf("key%04d", i)) 388 // id, _, err := allocator.Allocate(context.Background(), key) 389 // c.Assert(err, IsNil) 390 // c.Assert(id, Not(Equals), 0) 391 // 392 // // refcnt must now be 2 393 // c.Assert(allocator.localKeys.keys[key.GetKey()].refcnt, Equals, uint64(2)) 394 // } 395 // 396 // for i := idpool.ID(1); i <= maxID; i++ { 397 // allocator.Release(context.Background(), TestAllocatorKey(fmt.Sprintf("key%04d", i))) 398 // } 399 // 400 // // release final reference of all IDs 401 // for i := idpool.ID(1); i <= maxID; i++ { 402 // allocator.Release(context.Background(), TestAllocatorKey(fmt.Sprintf("key%04d", i))) 403 // } 404 // 405 // // running the GC should evict all entries 406 // allocator.RunGC() 407 // 408 // v, err := kvstore.ListPrefix(allocator.idPrefix) 409 // c.Assert(err, IsNil) 410 // c.Assert(len(v), Equals, 0) 411 // 412 // allocator.Delete() 413 //} 414 // 415 //func (s *AllocatorSuite) TestParallelAllocation(c *C) { 416 // var ( 417 // wg sync.WaitGroup 418 // allocatorName = randomTestName() 419 // ) 420 // 421 // // create dummy allocator to delete all keys 422 // a, err := NewAllocator(allocatorName, TestAllocatorKey(""), WithSuffix("a")) 423 // c.Assert(err, IsNil) 424 // c.Assert(a, Not(IsNil)) 425 // defer a.DeleteAllKeys() 426 // defer a.Delete() 427 // 428 // for i := 0; i < 2; i++ { 429 // wg.Add(1) 430 // go func() { 431 // defer wg.Done() 432 // testParallelAllocator(c, idpool.ID(64), allocatorName, fmt.Sprintf("node-%d", i)) 433 // }() 434 // } 435 // 436 // wg.Wait() 437 //}