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  }