github.com/elfadel/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  //}