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  }