github.com/cilium/cilium@v1.16.2/pkg/k8s/identitybackend/identity_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package identitybackend
     5  
     6  import (
     7  	"context"
     8  	"strconv"
     9  	"sync"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/stretchr/testify/require"
    14  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    15  	"k8s.io/apimachinery/pkg/util/validation/field"
    16  
    17  	"github.com/cilium/cilium/pkg/allocator"
    18  	"github.com/cilium/cilium/pkg/identity/key"
    19  	"github.com/cilium/cilium/pkg/idpool"
    20  	v2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2"
    21  	k8sClient "github.com/cilium/cilium/pkg/k8s/client"
    22  	"github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1/validation"
    23  	"github.com/cilium/cilium/pkg/labels"
    24  )
    25  
    26  func TestSanitizeK8sLabels(t *testing.T) {
    27  	path := field.NewPath("test", "labels")
    28  	testCases := []struct {
    29  		input            map[string]string
    30  		selected         map[string]string
    31  		skipped          map[string]string
    32  		validationErrors field.ErrorList
    33  	}{
    34  		{
    35  			input:            map[string]string{},
    36  			selected:         map[string]string{},
    37  			skipped:          map[string]string{},
    38  			validationErrors: field.ErrorList{},
    39  		},
    40  		{
    41  			input:            map[string]string{"k8s:foo": "bar"},
    42  			selected:         map[string]string{"foo": "bar"},
    43  			skipped:          map[string]string{},
    44  			validationErrors: field.ErrorList{},
    45  		},
    46  		{
    47  			input:            map[string]string{"k8s:foo": "bar", "k8s:abc": "def"},
    48  			selected:         map[string]string{"foo": "bar", "abc": "def"},
    49  			skipped:          map[string]string{},
    50  			validationErrors: field.ErrorList{},
    51  		},
    52  		{
    53  			input:            map[string]string{"k8s:foo": "bar", "k8s:abc": "def", "container:something": "else"},
    54  			selected:         map[string]string{"foo": "bar", "abc": "def"},
    55  			skipped:          map[string]string{"container:something": "else"},
    56  			validationErrors: field.ErrorList{},
    57  		},
    58  		{
    59  			input:    map[string]string{"k8s:some.really.really.really.really.really.really.really.long.label.name": "someval"},
    60  			selected: map[string]string{"some.really.really.really.really.really.really.really.long.label.name": "someval"},
    61  			skipped:  map[string]string{},
    62  			validationErrors: field.ErrorList{
    63  				&field.Error{
    64  					Type:     "FieldValueInvalid",
    65  					Field:    "test.labels",
    66  					BadValue: "some.really.really.really.really.really.really.really.long.label.name",
    67  					Detail:   "name part must be no more than 63 characters",
    68  				},
    69  			},
    70  		},
    71  		{
    72  			input:            map[string]string{"k8s:io.cilium.k8s.namespace.labels.some.really.really.long.namespace.label.name": "someval"},
    73  			selected:         map[string]string{},
    74  			skipped:          map[string]string{"k8s:io.cilium.k8s.namespace.labels.some.really.really.long.namespace.label.name": "someval"},
    75  			validationErrors: field.ErrorList{},
    76  		},
    77  	}
    78  
    79  	for _, test := range testCases {
    80  		selected, skipped := SanitizeK8sLabels(test.input)
    81  		require.EqualValues(t, test.selected, selected)
    82  		require.EqualValues(t, test.skipped, skipped)
    83  		require.EqualValues(t, test.validationErrors, validation.ValidateLabels(selected, path))
    84  	}
    85  }
    86  
    87  type FakeHandler struct {
    88  	onUpsertFunc func()
    89  }
    90  
    91  func (f FakeHandler) OnListDone() {}
    92  
    93  func (f FakeHandler) OnUpsert(id idpool.ID, key allocator.AllocatorKey) { f.onUpsertFunc() }
    94  func (f FakeHandler) OnDelete(id idpool.ID, key allocator.AllocatorKey) {}
    95  
    96  func getLabelsKey(rawMap map[string]string) allocator.AllocatorKey {
    97  	return &key.GlobalIdentity{LabelArray: labels.Map2Labels(rawMap, labels.LabelSourceK8s).LabelArray()}
    98  }
    99  
   100  func getLabelsMap(rawMap map[string]string) map[string]string {
   101  	return getLabelsKey(rawMap).GetAsMap()
   102  }
   103  
   104  func createCiliumIdentity(id int, labels map[string]string) v2.CiliumIdentity {
   105  	return v2.CiliumIdentity{
   106  		ObjectMeta: v1.ObjectMeta{
   107  			Name: strconv.Itoa(id),
   108  			CreationTimestamp: v1.Time{
   109  				Time: time.Now(),
   110  			},
   111  		},
   112  		SecurityLabels: getLabelsMap(labels),
   113  	}
   114  }
   115  
   116  func TestGetIdentity(t *testing.T) {
   117  	simpleMap := map[string]string{"key": "value"}
   118  	simpleMap2 := map[string]string{"ke2": "value2"}
   119  	simpleMap3 := map[string]string{"key3": "value3"}
   120  	duplicateMap1 := map[string]string{"key": "foo=value"}
   121  	duplicateMap2 := map[string]string{"key=foo": "value"}
   122  
   123  	testCases := []struct {
   124  		desc         string
   125  		identities   []v2.CiliumIdentity
   126  		requestedKey allocator.AllocatorKey
   127  		expectedId   string
   128  	}{
   129  		{
   130  			desc:         "Simple case",
   131  			identities:   []v2.CiliumIdentity{createCiliumIdentity(10, simpleMap)},
   132  			requestedKey: getLabelsKey(simpleMap),
   133  			expectedId:   "10",
   134  		},
   135  		{
   136  			desc: "Multiple identities",
   137  			identities: []v2.CiliumIdentity{
   138  				createCiliumIdentity(10, simpleMap),
   139  				createCiliumIdentity(11, simpleMap2),
   140  				createCiliumIdentity(12, simpleMap3),
   141  			},
   142  			requestedKey: getLabelsKey(simpleMap2),
   143  			expectedId:   "11",
   144  		},
   145  		{
   146  			desc: "Duplicated identity",
   147  			identities: []v2.CiliumIdentity{
   148  				createCiliumIdentity(10, duplicateMap1),
   149  				createCiliumIdentity(11, duplicateMap1),
   150  			},
   151  			requestedKey: getLabelsKey(duplicateMap1),
   152  			expectedId:   "10",
   153  		},
   154  		{
   155  			desc: "Duplicated key",
   156  			identities: []v2.CiliumIdentity{
   157  				createCiliumIdentity(10, duplicateMap1),
   158  				createCiliumIdentity(11, duplicateMap2),
   159  			},
   160  			requestedKey: getLabelsKey(duplicateMap2),
   161  			expectedId:   "11",
   162  		},
   163  		{
   164  			desc:         "No identities",
   165  			identities:   []v2.CiliumIdentity{},
   166  			requestedKey: getLabelsKey(simpleMap),
   167  			expectedId:   idpool.NoID.String(),
   168  		},
   169  		{
   170  			desc: "Identity not found",
   171  			identities: []v2.CiliumIdentity{
   172  				createCiliumIdentity(10, simpleMap),
   173  				createCiliumIdentity(11, simpleMap2),
   174  			},
   175  			requestedKey: getLabelsKey(simpleMap3),
   176  			expectedId:   idpool.NoID.String(),
   177  		},
   178  	}
   179  
   180  	for _, tc := range testCases {
   181  		t.Run(tc.desc, func(t *testing.T) {
   182  			t.Parallel()
   183  			_, client := k8sClient.NewFakeClientset()
   184  			backend, err := NewCRDBackend(CRDBackendConfiguration{
   185  				Store:   nil,
   186  				Client:  client,
   187  				KeyFunc: (&key.GlobalIdentity{}).PutKeyFromMap,
   188  			})
   189  			if err != nil {
   190  				t.Fatalf("Can't create CRD Backend: %s", err)
   191  			}
   192  
   193  			ctx := context.Background()
   194  			stopChan := make(chan struct{})
   195  			defer func() {
   196  				close(stopChan)
   197  			}()
   198  
   199  			addWaitGroup := sync.WaitGroup{}
   200  			addWaitGroup.Add(len(tc.identities))
   201  
   202  			// To avoid a race, we must create these before we start ListAndWatch, see #30873. There
   203  			// is no easy way of knowing when the watch is established. Specifically, 'HasSynced'
   204  			// does _not_ guarantee it: the fake object tracker doesn't do resource versioning and
   205  			// hence cannot replay events in the reflector's gap between list and watch. Ironically,
   206  			// therefore, if we waited for the informer's HasSynced, we'd _increase_ the likelihood
   207  			// of the race. Avoid the whole issue by creating the objects before the informer is
   208  			// even started, thus guaranteeing the objects are part of the initial list.
   209  			for _, identity := range tc.identities {
   210  				_, err = client.CiliumV2().CiliumIdentities().Create(ctx, &identity, v1.CreateOptions{})
   211  				if err != nil {
   212  					t.Fatalf("Can't create identity %s: %s", identity.Name, err)
   213  				}
   214  			}
   215  
   216  			go backend.ListAndWatch(ctx, FakeHandler{onUpsertFunc: func() { addWaitGroup.Done() }}, stopChan)
   217  
   218  			// Wait for watcher to process the identities in the background
   219  			addWaitGroup.Wait()
   220  
   221  			id, err := backend.Get(ctx, tc.requestedKey)
   222  			if err != nil {
   223  				t.Fatalf("Can't get identity by key %s: %s", tc.requestedKey.GetKey(), err)
   224  			}
   225  
   226  			if id == idpool.NoID && tc.expectedId != idpool.NoID.String() {
   227  				t.Errorf("Identity not found in the store")
   228  			}
   229  
   230  			if id.String() != tc.expectedId {
   231  				t.Errorf("Expected key %s, got %s", tc.expectedId, id.String())
   232  			}
   233  		})
   234  	}
   235  }