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 }