github.com/cilium/cilium@v1.16.2/pkg/clustermesh/common/clustermesh_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package common 5 6 import ( 7 "context" 8 "fmt" 9 "path/filepath" 10 "testing" 11 12 "github.com/cilium/hive/hivetest" 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 16 "github.com/cilium/cilium/pkg/clustermesh/types" 17 "github.com/cilium/cilium/pkg/clustermesh/utils" 18 "github.com/cilium/cilium/pkg/kvstore" 19 "github.com/cilium/cilium/pkg/lock" 20 "github.com/cilium/cilium/pkg/testutils" 21 ) 22 23 func TestClusterMeshMultipleAddRemove(t *testing.T) { 24 testutils.IntegrationTest(t) 25 kvstore.SetupDummy(t, "etcd") 26 27 baseDir := t.TempDir() 28 path := func(name string) string { return filepath.Join(baseDir, name) } 29 30 for i, cluster := range []string{"cluster1", "cluster2", "cluster3", "cluster4"} { 31 writeFile(t, path(cluster), fmt.Sprintf("endpoints:\n- %s\n", kvstore.EtcdDummyAddress())) 32 cfg := types.CiliumClusterConfig{ID: uint32(i + 1)} 33 require.NoError(t, utils.SetClusterConfig(context.Background(), cluster, cfg, kvstore.Client())) 34 } 35 36 var ready lock.Map[string, bool] 37 isReady := func(name string) bool { 38 rdy, _ := ready.Load(name) 39 return rdy 40 } 41 42 var blockRemoval lock.Map[string, chan struct{}] 43 blockRemoval.Store("cluster1", make(chan struct{})) 44 blockRemoval.Store("cluster2", make(chan struct{})) 45 blockRemoval.Store("cluster3", make(chan struct{})) 46 blockRemoval.Store("cluster4", make(chan struct{})) 47 48 gcm := NewClusterMesh(Configuration{ 49 Config: Config{ClusterMeshConfig: baseDir}, 50 ClusterInfo: types.ClusterInfo{ID: 255, Name: "local"}, 51 NewRemoteCluster: func(name string, _ StatusFunc) RemoteCluster { 52 return &fakeRemoteCluster{ 53 onRun: func(context.Context) { ready.Store(name, true) }, 54 onRemove: func(ctx context.Context) { 55 wait, _ := blockRemoval.Load(name) 56 select { 57 case <-wait: 58 case <-ctx.Done(): 59 } 60 }, 61 } 62 }, 63 Metrics: MetricsProvider("clustermesh")(), 64 }) 65 hivetest.Lifecycle(t).Append(gcm) 66 cm := gcm.(*clusterMesh) 67 68 // Directly call the add/remove methods, rather than creating/removing the 69 // files to prevent race conditions due to the interplay with the watcher. 70 cm.add("cluster1", path("cluster1")) 71 require.EventuallyWithT(t, func(c *assert.CollectT) { assert.True(c, isReady("cluster1")) }, timeout, tick, "Cluster1 is not ready") 72 73 // A blocked cluster removal operation should not block parallel cluster additions 74 cm.remove("cluster1") 75 76 cm.add("cluster2", path("cluster2")) 77 cm.add("cluster3", path("cluster3")) 78 79 require.EventuallyWithT(t, func(c *assert.CollectT) { assert.True(c, isReady("cluster2")) }, timeout, tick, "Cluster2 is not ready") 80 require.EventuallyWithT(t, func(c *assert.CollectT) { assert.True(c, isReady("cluster3")) }, timeout, tick, "Cluster3 is not ready") 81 82 // Unblock the cluster removal 83 block, _ := blockRemoval.Load("cluster1") 84 close(block) 85 86 // Multiple removals and additions, ending with an addition should lead to a ready cluster 87 ready.Store("cluster2", false) 88 cm.remove("cluster2") 89 cm.add("cluster2", path("cluster2")) 90 cm.remove("cluster2") 91 cm.add("cluster2", path("cluster2")) 92 93 require.False(t, isReady("cluster2"), "Cluster2 is ready, although it shouldn't") 94 95 // Unblock the cluster removal 96 block, _ = blockRemoval.Load("cluster2") 97 close(block) 98 99 require.EventuallyWithT(t, func(c *assert.CollectT) { assert.True(c, isReady("cluster2")) }, timeout, tick, "Cluster2 is not ready") 100 101 // Multiple removals and additions, ending with a removal should lead to a non-ready cluster 102 ready.Store("cluster3", false) 103 cm.remove("cluster3") 104 cm.add("cluster3", path("cluster3")) 105 cm.remove("cluster3") 106 cm.add("cluster3", path("cluster3")) 107 cm.remove("cluster3") 108 109 require.False(t, isReady("cluster3"), "Cluster3 is ready, although it shouldn't") 110 111 // Unblock the cluster removal 112 block, _ = blockRemoval.Load("cluster3") 113 close(block) 114 115 // Make sure that the deletion go routine terminated before checking 116 cm.wg.Wait() 117 require.False(t, isReady("cluster3"), "Cluster3 is ready, although it shouldn't") 118 119 cm.add("cluster4", path("cluster4")) 120 require.EventuallyWithT(t, func(c *assert.CollectT) { assert.True(c, isReady("cluster4")) }, timeout, tick, "Cluster4 is not ready") 121 122 // Never unblock the cluster removal, and assert that the stop hook terminates 123 // regardless due to the context being closed. 124 cm.remove("cluster4") 125 }