github.com/cilium/cilium@v1.16.2/operator/pkg/ciliumidentity/cache_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package ciliumidentity
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"reflect"
    10  	"sync"
    11  	"testing"
    12  
    13  	"github.com/cilium/cilium/pkg/labels"
    14  
    15  	"github.com/stretchr/testify/assert"
    16  
    17  	cestest "github.com/cilium/cilium/operator/pkg/ciliumendpointslice/testutils"
    18  	"github.com/cilium/cilium/pkg/identity/key"
    19  	capi_v2a1 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1"
    20  	"github.com/cilium/cilium/pkg/labelsfilter"
    21  )
    22  
    23  var (
    24  	k8sLables_A           = map[string]string{"a1": "1", "a2": "2"}
    25  	k8sLables_B           = map[string]string{"b1": "1", "b2": "2"}
    26  	k8sLables_B_duplicate = map[string]string{"b1": "1", "b2": "2"}
    27  	k8sLables_C           = map[string]string{"c1": "1", "c2": "2"}
    28  )
    29  
    30  func TestMain(m *testing.M) {
    31  	labelsfilter.ParseLabelPrefixCfg(nil, nil, "")
    32  
    33  	os.Exit(m.Run())
    34  }
    35  
    36  func TestCIDState(t *testing.T) {
    37  	// The subtests below share the same state to serially test insert, lookup and
    38  	// remove operations of CIDState.
    39  	state := NewCIDState()
    40  	k1 := key.GetCIDKeyFromLabels(k8sLables_A, labels.LabelSourceK8s)
    41  	k2 := key.GetCIDKeyFromLabels(k8sLables_B, labels.LabelSourceK8s)
    42  	k3 := key.GetCIDKeyFromLabels(k8sLables_B_duplicate, labels.LabelSourceK8s)
    43  
    44  	t.Run("Insert into CID state", func(t *testing.T) {
    45  		state.Upsert("1", k1)
    46  		expectedState := &CIDState{
    47  			idToLabels: map[string]*key.GlobalIdentity{"1": k1},
    48  			labelsToID: map[string]*SecIDs{
    49  				k1.GetKey(): {
    50  					selectedID: "1",
    51  					ids:        map[string]struct{}{"1": {}},
    52  				},
    53  			},
    54  		}
    55  
    56  		assert.NoError(t, validateCIDState(state, expectedState), "cid 1 added")
    57  
    58  		state.Upsert("2", k2)
    59  		expectedState = &CIDState{
    60  			idToLabels: map[string]*key.GlobalIdentity{"1": k1, "2": k2},
    61  			labelsToID: map[string]*SecIDs{
    62  				k1.GetKey(): {
    63  					selectedID: "1",
    64  					ids:        map[string]struct{}{"1": {}},
    65  				},
    66  				k2.GetKey(): {
    67  					selectedID: "2",
    68  					ids:        map[string]struct{}{"2": {}},
    69  				},
    70  			},
    71  		}
    72  
    73  		assert.NoError(t, validateCIDState(state, expectedState), "cid 2 added")
    74  
    75  		state.Upsert("3", k3)
    76  		expectedState = &CIDState{
    77  			idToLabels: map[string]*key.GlobalIdentity{"1": k1, "2": k2, "3": k3},
    78  			labelsToID: map[string]*SecIDs{
    79  				k1.GetKey(): {
    80  					selectedID: "1",
    81  					ids:        map[string]struct{}{"1": {}},
    82  				},
    83  				k2.GetKey(): {
    84  					selectedID: "2",
    85  					ids:        map[string]struct{}{"2": {}, "3": {}},
    86  				},
    87  			},
    88  		}
    89  
    90  		assert.NoError(t, validateCIDState(state, expectedState), "cid 3 added - duplicate")
    91  	})
    92  
    93  	t.Run("Lookup CID state", func(t *testing.T) {
    94  		_, exists := state.LookupByID("0")
    95  		assert.Equal(t, false, exists, "cid 0 LookupByID - not found")
    96  
    97  		cidKey, exists := state.LookupByID("1")
    98  		assert.Equal(t, true, exists, "cid 1 LookupByID - found")
    99  		assert.Equal(t, key.GetCIDKeyFromLabels(k8sLables_A, labels.LabelSourceK8s), cidKey, "cid 1 LookupByID - correct key")
   100  
   101  		_, exists = state.LookupByKey(key.GetCIDKeyFromLabels(k8sLables_C, labels.LabelSourceK8s))
   102  		assert.Equal(t, false, exists, "labels C LookupByKey - not found")
   103  
   104  		cidName, exists := state.LookupByKey(key.GetCIDKeyFromLabels(k8sLables_A, labels.LabelSourceK8s))
   105  		assert.Equal(t, true, exists, "labels C LookupByKey - not found")
   106  		assert.Equal(t, "1", cidName, "labels C LookupByKey - correct CID")
   107  	})
   108  
   109  	t.Run("Remove from CID state", func(t *testing.T) {
   110  		state.Remove("2")
   111  		expectedState := &CIDState{
   112  			idToLabels: map[string]*key.GlobalIdentity{"1": k1, "3": k3},
   113  			labelsToID: map[string]*SecIDs{
   114  				k1.GetKey(): {
   115  					selectedID: "1",
   116  					ids:        map[string]struct{}{"1": {}},
   117  				},
   118  				k2.GetKey(): {
   119  					selectedID: "3",
   120  					ids:        map[string]struct{}{"3": {}},
   121  				},
   122  			},
   123  		}
   124  
   125  		assert.NoError(t, validateCIDState(state, expectedState), "cid 2 removed")
   126  
   127  		_, exists := state.LookupByID("2")
   128  		assert.Equal(t, false, exists, "cid 2 LookupByID - not found")
   129  
   130  		state.Remove("3")
   131  		expectedState = &CIDState{
   132  			idToLabels: map[string]*key.GlobalIdentity{"1": k1},
   133  			labelsToID: map[string]*SecIDs{
   134  				k1.GetKey(): {
   135  					selectedID: "1",
   136  					ids:        map[string]struct{}{"1": {}},
   137  				},
   138  			},
   139  		}
   140  		assert.NoError(t, validateCIDState(state, expectedState), "cid 3 removed")
   141  	})
   142  }
   143  
   144  func TestCIDStateThreadSafety(t *testing.T) {
   145  	// This test ensures that no changes to the CID state break its thread safety.
   146  	// Multiple go routines in parallel continuously keep using CIDState.
   147  	state := NewCIDState()
   148  
   149  	k := key.GetCIDKeyFromLabels(k8sLables_A, labels.LabelSourceK8s)
   150  	k2 := key.GetCIDKeyFromLabels(k8sLables_B, labels.LabelSourceK8s)
   151  
   152  	wg := sync.WaitGroup{}
   153  	queryStateFunc := func() {
   154  		for i := 0; i < 500; i++ {
   155  			state.LookupByID("1000")
   156  			state.Upsert("1000", k)
   157  			state.LookupByKey(k)
   158  			state.Upsert("2000", k2)
   159  			state.Remove("1000")
   160  			state.LookupByID("2000")
   161  		}
   162  
   163  		wg.Done()
   164  	}
   165  
   166  	for i := 0; i < 5; i++ {
   167  		wg.Add(1)
   168  		go queryStateFunc()
   169  	}
   170  
   171  	wg.Wait()
   172  }
   173  
   174  func validateCIDState(state, expectedState *CIDState) error {
   175  	if !reflect.DeepEqual(state.idToLabels, expectedState.idToLabels) {
   176  		return fmt.Errorf("failed to validate the state, expected idToLabels %v, got %v", expectedState.idToLabels, state.idToLabels)
   177  	}
   178  
   179  	if !reflect.DeepEqual(state.labelsToID, expectedState.labelsToID) {
   180  		return fmt.Errorf("failed to validate the state, expected labelsToID %v, got %v", expectedState.labelsToID, state.labelsToID)
   181  	}
   182  
   183  	return nil
   184  }
   185  
   186  func TestCIDUsageInPods(t *testing.T) {
   187  	state := NewCIDUsageInPods()
   188  
   189  	assertTxt := "Empty state"
   190  	cidName1 := "1000"
   191  	podName1 := "pod1"
   192  	assert.Equal(t, 0, state.CIDUsageCount(cidName1), assertTxt)
   193  
   194  	usedCID, exists := state.CIDUsedByPod(podName1)
   195  	assert.Equal(t, false, exists, assertTxt)
   196  	assert.Equal(t, "", usedCID, assertTxt)
   197  
   198  	prevCID, count, err := state.RemovePod(podName1)
   199  	assert.Error(t, err)
   200  	assert.Equal(t, "", prevCID, assertTxt)
   201  	assert.Equal(t, 0, count, assertTxt)
   202  
   203  	assertTxt = "Assign CID to Pod 1"
   204  	prevCID, count = state.AssignCIDToPod(podName1, cidName1)
   205  	assert.Equal(t, "", prevCID, assertTxt)
   206  	assert.Equal(t, 0, count, assertTxt)
   207  	assert.Equal(t, 1, state.CIDUsageCount(cidName1), assertTxt)
   208  
   209  	usedCID, exists = state.CIDUsedByPod(podName1)
   210  	assert.Equal(t, true, exists, assertTxt)
   211  	assert.Equal(t, cidName1, usedCID, assertTxt)
   212  
   213  	assertTxt = "Assign CID to Pod 2"
   214  	podName2 := "pod2"
   215  	prevCID, count = state.AssignCIDToPod(podName2, cidName1)
   216  	assert.Equal(t, "", prevCID, assertTxt)
   217  	assert.Equal(t, 0, count, assertTxt)
   218  	assert.Equal(t, 2, state.CIDUsageCount(cidName1), assertTxt)
   219  
   220  	usedCID, exists = state.CIDUsedByPod(podName2)
   221  	assert.Equal(t, true, exists, assertTxt)
   222  	assert.Equal(t, cidName1, usedCID, assertTxt)
   223  
   224  	assertTxt = "Assign CID 2 to Pod 2"
   225  	cidName2 := "2000"
   226  	prevCID, count = state.AssignCIDToPod(podName2, cidName2)
   227  	assert.Equal(t, cidName1, prevCID, assertTxt)
   228  	assert.Equal(t, 1, count, assertTxt)
   229  	assert.Equal(t, 1, state.CIDUsageCount(cidName2), assertTxt)
   230  
   231  	usedCID, exists = state.CIDUsedByPod(podName2)
   232  	assert.Equal(t, true, exists, assertTxt)
   233  	assert.Equal(t, cidName2, usedCID, assertTxt)
   234  
   235  	assertTxt = "Assign CID 2 to Pod 1"
   236  	prevCID, count = state.AssignCIDToPod(podName1, cidName2)
   237  	assert.Equal(t, cidName1, prevCID, assertTxt)
   238  	assert.Equal(t, 0, count, assertTxt)
   239  	assert.Equal(t, 2, state.CIDUsageCount(cidName2), assertTxt)
   240  	assert.Equal(t, 0, state.CIDUsageCount(cidName1), assertTxt)
   241  
   242  	usedCID, exists = state.CIDUsedByPod(podName1)
   243  	assert.Equal(t, true, exists, assertTxt)
   244  	assert.Equal(t, cidName2, usedCID, assertTxt)
   245  
   246  	assertTxt = "Again assign CID 2 to Pod 1"
   247  	prevCID, count = state.AssignCIDToPod(podName1, cidName2)
   248  	assert.Equal(t, cidName2, prevCID, assertTxt)
   249  	assert.Equal(t, 2, count, assertTxt)
   250  	assert.Equal(t, 2, state.CIDUsageCount(cidName2), assertTxt)
   251  	assert.Equal(t, 0, state.CIDUsageCount(cidName1), assertTxt)
   252  
   253  	assertTxt = "Remove Pod 1"
   254  	prevCID, count, err = state.RemovePod(podName1)
   255  	assert.NoError(t, err)
   256  	assert.Equal(t, cidName2, prevCID, assertTxt)
   257  	assert.Equal(t, 1, count, assertTxt)
   258  	assert.Equal(t, 1, state.CIDUsageCount(cidName2), assertTxt)
   259  
   260  	usedCID, exists = state.CIDUsedByPod(podName1)
   261  	assert.Equal(t, false, exists, assertTxt)
   262  	assert.Equal(t, "", usedCID, assertTxt)
   263  
   264  	assertTxt = "Remove Pod 2"
   265  	prevCID, count, err = state.RemovePod(podName2)
   266  	assert.NoError(t, err)
   267  	assert.Equal(t, cidName2, prevCID, assertTxt)
   268  	assert.Equal(t, 0, count, assertTxt)
   269  	assert.Equal(t, 0, state.CIDUsageCount(cidName2), assertTxt)
   270  
   271  	usedCID, exists = state.CIDUsedByPod(podName2)
   272  	assert.Equal(t, false, exists, assertTxt)
   273  	assert.Equal(t, "", usedCID, assertTxt)
   274  }
   275  
   276  func TestCIDUsageInCES(t *testing.T) {
   277  	cep1 := cestest.CreateManagerEndpoint("cep1", 1000)
   278  	cep2 := cestest.CreateManagerEndpoint("cep2", 1000)
   279  	cep3 := cestest.CreateManagerEndpoint("cep3", 2000)
   280  	cep4 := cestest.CreateManagerEndpoint("cep4", 3000)
   281  	ces1 := cestest.CreateStoreEndpointSlice("ces1", "ns", []capi_v2a1.CoreCiliumEndpoint{cep1, cep2, cep3, cep4})
   282  
   283  	cep5 := cestest.CreateManagerEndpoint("cep5", 1000)
   284  	cep6 := cestest.CreateManagerEndpoint("cep6", 1000)
   285  	cep7 := cestest.CreateManagerEndpoint("cep7", 2000)
   286  	ces2 := cestest.CreateStoreEndpointSlice("ces2", "ns", []capi_v2a1.CoreCiliumEndpoint{cep5, cep6, cep7})
   287  
   288  	assertTxt := "CES 1 is added"
   289  	state := NewCIDUsageInCES()
   290  	unusedCIDs := state.ProcessCESUpsert(ces1.Name, ces1.Endpoints)
   291  	assert.Equal(t, 0, len(unusedCIDs), assertTxt)
   292  	assert.Equal(t, 2, state.CIDUsageCount("1000"), assertTxt)
   293  	assert.Equal(t, 1, state.CIDUsageCount("2000"), assertTxt)
   294  	assert.Equal(t, 1, state.CIDUsageCount("3000"), assertTxt)
   295  
   296  	assertTxt = "CES 2 is added"
   297  	unusedCIDs = state.ProcessCESUpsert(ces2.Name, ces2.Endpoints)
   298  	assert.Equal(t, 0, len(unusedCIDs), assertTxt)
   299  	assert.Equal(t, 4, state.CIDUsageCount("1000"), assertTxt)
   300  	assert.Equal(t, 2, state.CIDUsageCount("2000"), assertTxt)
   301  	assert.Equal(t, 1, state.CIDUsageCount("3000"), assertTxt)
   302  
   303  	assertTxt = "Endpoint with CID 3000 is removed from CES 1"
   304  	ces1 = cestest.CreateStoreEndpointSlice("ces1", "ns", []capi_v2a1.CoreCiliumEndpoint{cep1, cep2, cep3})
   305  	unusedCIDs = state.ProcessCESUpsert(ces1.Name, ces1.Endpoints)
   306  	assert.Equal(t, 1, len(unusedCIDs), assertTxt)
   307  	if len(unusedCIDs) > 0 {
   308  		assert.Equal(t, int64(3000), unusedCIDs[0], assertTxt)
   309  	}
   310  	assert.Equal(t, 4, state.CIDUsageCount("1000"), assertTxt)
   311  	assert.Equal(t, 2, state.CIDUsageCount("2000"), assertTxt)
   312  	assert.Equal(t, 0, state.CIDUsageCount("3000"), assertTxt)
   313  
   314  	assertTxt = "CES 1 is removed"
   315  	unusedCIDs = state.ProcessCESDelete(ces1.Name, ces1.Endpoints)
   316  	assert.Equal(t, 0, len(unusedCIDs), assertTxt)
   317  	assert.Equal(t, 2, state.CIDUsageCount("1000"), assertTxt)
   318  	assert.Equal(t, 1, state.CIDUsageCount("2000"), assertTxt)
   319  
   320  	assertTxt = "CES 2 is removed"
   321  	unusedCIDs = state.ProcessCESDelete(ces1.Name, ces1.Endpoints)
   322  	assert.Equal(t, 2, len(unusedCIDs), assertTxt)
   323  	assert.Equal(t, 0, state.CIDUsageCount("1000"), assertTxt)
   324  	assert.Equal(t, 0, state.CIDUsageCount("2000"), assertTxt)
   325  }