package main import ( "context" "fmt" "math/rand" "sync" "testing" "time" "github.com/stretchr/testify/require" ) const ( peerName1 = "01:00:00:00:00:00" peerName2 = "02:00:00:00:00:00" peerName3 = "03:00:00:00:00:00" nodeName1 = "fake-node-1" nodeName2 = "fake-node-2" ) func TestPeerListBasic(t *testing.T) { c := NewSimpleClientset() cml := newConfigMapAnnotations(configMapNamespace, configMapName, c) err := cml.Init() require.NoError(t, err) list1, err := addMyselfToPeerList(cml, c, peerName1, nodeName1) require.NoError(t, err) require.Equal(t, 1, len(list1.Peers)) list2, err := addMyselfToPeerList(cml, c, peerName2, nodeName2) require.NoError(t, err) require.Equal(t, 2, len(list2.Peers)) check1, err := checkIamInPeerList(cml, c, peerName1) require.NoError(t, err) require.Equal(t, true, check1) check3, err := checkIamInPeerList(cml, c, peerName3) require.NoError(t, err) require.Equal(t, false, check3) storedPeerList, err := cml.GetPeerList() require.NoError(t, err) storedPeerList.remove(peerName1) require.Equal(t, 1, len(storedPeerList.Peers)) err = cml.UpdatePeerList(context.Background(), *storedPeerList) require.NoError(t, err) check1, err = checkIamInPeerList(cml, c, peerName1) require.NoError(t, err) require.Equal(t, false, check1) } // Run function _f_ _iterations_ times, in _concurrency_ // number of goroutines func doConcurrentIterations(iterations, concurrency int, f func(int)) { iterationsPerThread := iterations / concurrency wg := sync.WaitGroup{} for i := 0; i < concurrency; i++ { wg.Add(1) go func(j int) { defer wg.Done() for k := 0; k < iterationsPerThread; k++ { f((j * iterationsPerThread) + k) } }(i) } wg.Wait() } func TestPeerListFuzz(t *testing.T) { rand.Seed(time.Now().UnixNano()) const ( testIters = 1000 maxNodes = 100 concurrency = 5 ) peerName := func(i int) string { return fmt.Sprintf("%02d:00:00:02:00:00", i) } nodeName := func(i int) string { return fmt.Sprintf("node%d", i) } stateLock := sync.Mutex{} nodes := []int{} nodeMap := map[int]bool{} // true if an operation is pending c := NewSimpleClientset() addNode := func() { i := rand.Intn(maxNodes) // add to test state first, then to Kubernetes stateLock.Lock() pending, found := nodeMap[i] if !found { nodes = append(nodes, i) } nodeMap[i] = true stateLock.Unlock() if pending { // someone else is adding or deleting this node return } cml := newConfigMapAnnotations(configMapNamespace, configMapName, c) err := cml.Init() require.NoError(t, err) _, err = addMyselfToPeerList(cml, c, peerName(i), nodeName(i)) require.NoError(t, err) stateLock.Lock() nodeMap[i] = false stateLock.Unlock() } removeNode := func() { stateLock.Lock() if len(nodes) == 0 { // can't remove if there aren't any nodes stateLock.Unlock() return } pos := rand.Intn(len(nodes)) i := nodes[pos] if pending, found := nodeMap[i]; found { if pending { stateLock.Unlock() return // someone else is adding or deleting this node } nodeMap[i] = true } else { t.Errorf("missing entry from node map: %d", i) } stateLock.Unlock() // remove from Kubernetes first, then from test state cml := newConfigMapAnnotations(configMapNamespace, configMapName, c) require.NoError(t, cml.Init()) storedPeerList, err := cml.GetPeerList() require.NoError(t, err) found := storedPeerList.contains(peerName(i)) require.True(t, found, "peer %d not found in stored list", i) _, err = reclaimPeer(mockWeave{}, cml, peerName(i), fmt.Sprintf("deleter-%d", i)) require.NoError(t, err) require.NoError(t, cml.Init()) storedPeerList, err = cml.GetPeerList() require.NoError(t, err) stateLock.Lock() delete(nodeMap, i) for pos = 0; pos < len(nodes); pos++ { if nodes[pos] == i { // Remove item from list by swapping it with last and reducing slice length by 1 nodes[pos] = nodes[len(nodes)-1] nodes = nodes[:len(nodes)-1] break } } stateLock.Unlock() } doConcurrentIterations(testIters, concurrency, func(iteration int) { r := rand.Float32() switch { case 0.0 <= r && r < 0.4: addNode() case (0.4 <= r && r < 0.8): removeNode() } }) cml := newConfigMapAnnotations(configMapNamespace, configMapName, c) err := cml.Init() require.NoError(t, err) storedPeerList, err := cml.GetPeerList() require.NoError(t, err) require.Equal(t, len(storedPeerList.Peers), len(nodes)) } type mockWeave struct{} func (mockWeave) RmPeer(peerName string) (string, error) { return "", nil }