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  }