github.com/cilium/cilium@v1.16.2/pkg/clustermesh/utils/clustercfg_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package utils 5 6 import ( 7 "context" 8 "errors" 9 "path" 10 "testing" 11 "time" 12 13 "github.com/sirupsen/logrus" 14 "github.com/stretchr/testify/assert" 15 "go.uber.org/goleak" 16 17 "github.com/stretchr/testify/require" 18 19 cmtypes "github.com/cilium/cilium/pkg/clustermesh/types" 20 "github.com/cilium/cilium/pkg/kvstore" 21 "github.com/cilium/cilium/pkg/lock" 22 ) 23 24 var mockerr = errors.New("error") 25 26 // Configure a generous timeout to prevent flakes when running in a noisy CI environment. 27 var ( 28 tick = 10 * time.Millisecond 29 timeout = 5 * time.Second 30 ) 31 32 type mockBackend struct { 33 data lock.Map[string, []byte] 34 errors lock.Map[string, error] 35 } 36 37 func (mb *mockBackend) withError(clusterName string) { 38 mb.errors.Store(path.Join(kvstore.ClusterConfigPrefix, clusterName), mockerr) 39 } 40 41 func (mb *mockBackend) Get(_ context.Context, key string) ([]byte, error) { 42 if err, ok := mb.errors.LoadAndDelete(key); ok { 43 return nil, err 44 } 45 46 value, _ := mb.data.Load(key) 47 return value, nil 48 } 49 50 func (mb *mockBackend) UpdateIfDifferent(_ context.Context, key string, value []byte, _ bool) (bool, error) { 51 if err, ok := mb.errors.LoadAndDelete(key); ok { 52 return false, err 53 } 54 55 mb.data.Store(key, value) 56 return true, nil 57 } 58 59 func TestGetSetClusterConfig(t *testing.T) { 60 ctx := context.Background() 61 mb := mockBackend{} 62 63 cfg1 := cmtypes.CiliumClusterConfig{ID: 11, Capabilities: cmtypes.CiliumClusterConfigCapabilities{SyncedCanaries: true}} 64 cfg2 := cmtypes.CiliumClusterConfig{ID: 22, Capabilities: cmtypes.CiliumClusterConfigCapabilities{Cached: true}} 65 cfg3 := cmtypes.CiliumClusterConfig{ID: 33, Capabilities: cmtypes.CiliumClusterConfigCapabilities{Cached: true}} 66 67 require.NoError(t, SetClusterConfig(ctx, "foo", cfg1, &mb), "failed to write cluster configuration") 68 require.NoError(t, SetClusterConfig(ctx, "bar", cfg2, &mb), "failed to write cluster configuration") 69 require.NoError(t, SetClusterConfig(ctx, "bar", cfg3, &mb), "failed to update cluster configuration") 70 require.NoError(t, SetClusterConfig(ctx, "bar", cfg3, &mb), "failed to update cluster configuration (same value)") 71 72 mb.withError("error") 73 require.ErrorIs(t, SetClusterConfig(ctx, "error", cfg1, &mb), mockerr, "kvstore error not propagated correctly") 74 75 got, err := GetClusterConfig(ctx, "foo", &mb) 76 require.NoError(t, err, "failed to read cluster configuration") 77 require.EqualValues(t, got, cfg1, "retrieved configuration does not match expected one") 78 79 got, err = GetClusterConfig(ctx, "bar", &mb) 80 require.NoError(t, err, "failed to read cluster configuration") 81 require.EqualValues(t, got, cfg3, "retrieved configuration does not match expected one") 82 83 _, err = GetClusterConfig(ctx, "not-existing", &mb) 84 require.ErrorIs(t, err, ErrClusterConfigNotFound, "incorrect error for not found configuration") 85 86 mb.withError("error") 87 _, err = GetClusterConfig(ctx, "error", &mb) 88 require.ErrorIs(t, err, mockerr, "kvstore error not propagated correctly") 89 90 // Simulate invalid data stored in the kvstore 91 mb.UpdateIfDifferent(ctx, path.Join(kvstore.ClusterConfigPrefix, "invalid"), []byte("invalid"), true) 92 _, err = GetClusterConfig(ctx, "invalid", &mb) 93 require.ErrorContains(t, err, "invalid character", "unmarshaling error not propagated correctly") 94 } 95 96 func TestEnforceClusterConfig(t *testing.T) { 97 defer goleak.VerifyNone(t) 98 99 // Configure a short run interval for testing purposes 100 defer func(orig time.Duration) { runInterval = orig }(runInterval) 101 runInterval = 25 * time.Millisecond 102 103 ctx := context.Background() 104 mb := mockBackend{} 105 log := logrus.New() 106 107 cfg1 := cmtypes.CiliumClusterConfig{ID: 11, Capabilities: cmtypes.CiliumClusterConfigCapabilities{SyncedCanaries: true}} 108 cfg2 := cmtypes.CiliumClusterConfig{ID: 22, Capabilities: cmtypes.CiliumClusterConfigCapabilities{Cached: true}} 109 110 stopAndWait1, err := EnforceClusterConfig(ctx, "foo", cfg1, &mb, log) 111 defer stopAndWait1() 112 require.NoError(t, err, "failed to write cluster configuration") 113 114 stopAndWait2, err := EnforceClusterConfig(ctx, "bar", cfg2, &mb, log) 115 defer stopAndWait2() 116 require.NoError(t, err, "failed to write cluster configuration") 117 118 mb.withError("error") 119 stopAndWait3, err := EnforceClusterConfig(ctx, "error", cfg2, &mb, log) 120 defer stopAndWait3() 121 require.ErrorIs(t, err, mockerr, "kvstore error not propagated correctly") 122 123 got, err := GetClusterConfig(ctx, "foo", &mb) 124 require.NoError(t, err, "failed to read cluster configuration") 125 require.EqualValues(t, got, cfg1, "retrieved configuration does not match expected one") 126 127 got, err = GetClusterConfig(ctx, "bar", &mb) 128 require.NoError(t, err, "failed to read cluster configuration") 129 require.EqualValues(t, got, cfg2, "retrieved configuration does not match expected one") 130 131 // Externally mutate the cluster configuration, and assert that it gets eventually reconciled. 132 require.NoError(t, SetClusterConfig(ctx, "bar", cfg1, &mb), "failed to override cluster configuration") 133 require.EventuallyWithT(t, func(c *assert.CollectT) { 134 got, err = GetClusterConfig(ctx, "bar", &mb) 135 assert.NoError(c, err, "failed to read cluster configuration") 136 assert.EqualValues(c, got, cfg2, "retrieved configuration does not match expected one") 137 }, timeout, tick) 138 }