github.com/cilium/cilium@v1.16.2/pkg/k8s/watchers/cilium_endpoint_slice_subscriber_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 package watchers 4 5 import ( 6 "fmt" 7 "testing" 8 9 "github.com/google/go-cmp/cmp" 10 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 12 "github.com/cilium/cilium/pkg/endpoint" 13 v2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" 14 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1" 15 slim_metav1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1" 16 "github.com/cilium/cilium/pkg/k8s/types" 17 ) 18 19 type endpointUpdate struct { 20 oldEp, newEp *types.CiliumEndpoint 21 } 22 23 func epToString(e *types.CiliumEndpoint) string { 24 if e == nil { 25 return "nil" 26 } 27 return fmt.Sprintf("ep(id: %d)", e.Identity.ID) 28 } 29 30 func (u endpointUpdate) toString() string { 31 return fmt.Sprintf("(%s, %s)", epToString(u.oldEp), epToString(u.newEp)) 32 } 33 34 func epEqual(e1, e2 *types.CiliumEndpoint) bool { 35 return ((e1 == nil) == (e2 == nil)) && (e1 == nil || e1.Identity.ID == e2.Identity.ID) 36 } 37 38 func updateEqual(u1, u2 endpointUpdate) bool { 39 return epEqual(u1.oldEp, u2.oldEp) && epEqual(u1.newEp, u2.newEp) 40 } 41 42 type fakeEPWatcher struct { 43 lastUpdate endpointUpdate 44 lastDelete *types.CiliumEndpoint 45 } 46 47 func createFakeEPWatcher() *fakeEPWatcher { 48 return &fakeEPWatcher{} 49 } 50 51 func (fw *fakeEPWatcher) endpointUpdated(oldC, newC *types.CiliumEndpoint) { 52 fw.lastUpdate = endpointUpdate{oldC, newC} 53 } 54 55 func (fw *fakeEPWatcher) endpointDeleted(c *types.CiliumEndpoint) { 56 fw.lastDelete = c 57 } 58 59 func (fw *fakeEPWatcher) assertUpdate(u endpointUpdate) (string, bool) { 60 if !updateEqual(fw.lastUpdate, u) { 61 return fmt.Sprintf("Expected %s, got %s", u.toString(), fw.lastUpdate.toString()), false 62 } 63 return "", true 64 } 65 66 func (fw *fakeEPWatcher) assertNoDelete() (string, bool) { 67 if fw.lastDelete != nil { 68 return fmt.Sprintf("Expected no delete, got %s", epToString(fw.lastDelete)), false 69 } 70 return "", true 71 } 72 73 func (fw *fakeEPWatcher) assertDelete(e *types.CiliumEndpoint) (string, bool) { 74 if !epEqual(fw.lastDelete, e) { 75 return fmt.Sprintf("Expected no delete, got %s", epToString(fw.lastDelete)), false 76 } 77 return "", true 78 } 79 80 type fakeEndpointCache struct{} 81 82 func (fe *fakeEndpointCache) LookupCEPName(namespacedName string) *endpoint.Endpoint { 83 return nil 84 } 85 86 func createCES(name, namespace string, endpoints []v2alpha1.CoreCiliumEndpoint) *v2alpha1.CiliumEndpointSlice { 87 return &v2alpha1.CiliumEndpointSlice{ 88 Namespace: namespace, 89 ObjectMeta: v1.ObjectMeta{ 90 Name: name, 91 }, 92 Endpoints: endpoints, 93 } 94 } 95 96 func createEndpoint(name, namespace string, id int64) *types.CiliumEndpoint { 97 return &types.CiliumEndpoint{ 98 ObjectMeta: slim_metav1.ObjectMeta{ 99 Namespace: namespace, 100 Name: name, 101 }, 102 Identity: &v2.EndpointIdentity{ID: id}, 103 Encryption: &v2.EncryptionSpec{}, 104 } 105 } 106 107 // TestCESSubscriber_CEPTransfer tests a CEP being transferred between two 108 // CESes. The order of events: 109 // 1. CES add event for the new CES with latest CEP state 110 // 2. CES add event for the old CES with old CEP state 111 // 3. CES delete event for the old CES. 112 func TestCESSubscriber_CEPTransferOnStartup(t *testing.T) { 113 cepNamespace := "ns1" 114 cepName := "cep1" 115 newCEPID := int64(3) 116 oldCEPID := int64(2) 117 fakeEPWatcher := createFakeEPWatcher() 118 fakeEndpointCache := &fakeEndpointCache{} 119 cesSub := &cesSubscriber{ 120 epWatcher: fakeEPWatcher, 121 epCache: fakeEndpointCache, 122 cepMap: newCEPToCESMap(), 123 } 124 // Add for new CES 125 cesSub.OnAdd( 126 createCES("new-ces", cepNamespace, []v2alpha1.CoreCiliumEndpoint{ 127 { 128 Name: cepName, 129 IdentityID: newCEPID, 130 }, 131 })) 132 diff, ok := fakeEPWatcher.assertUpdate(endpointUpdate{ 133 newEp: createEndpoint("cep1", "ns1", newCEPID), 134 }) 135 if !ok { 136 t.Fatal(diff) 137 } 138 // Add for old CES 139 cesSub.OnAdd( 140 createCES("old-ces", cepNamespace, []v2alpha1.CoreCiliumEndpoint{ 141 { 142 Name: cepName, 143 IdentityID: oldCEPID, 144 }, 145 })) 146 diff, ok = fakeEPWatcher.assertUpdate(endpointUpdate{ 147 oldEp: createEndpoint("cep1", "ns1", newCEPID), 148 newEp: createEndpoint("cep1", "ns1", oldCEPID), 149 }) 150 if !ok { 151 t.Fatal(diff) 152 } 153 // Delete the old CES 154 cesSub.OnDelete( 155 createCES("old-ces", cepNamespace, []v2alpha1.CoreCiliumEndpoint{ 156 { 157 Name: cepName, 158 IdentityID: oldCEPID, 159 }, 160 })) 161 diff, ok = fakeEPWatcher.assertUpdate(endpointUpdate{ 162 oldEp: createEndpoint("cep1", "ns1", oldCEPID), 163 newEp: createEndpoint("cep1", "ns1", newCEPID), 164 }) 165 if !ok { 166 t.Fatal(diff) 167 } 168 diff, ok = fakeEPWatcher.assertNoDelete() 169 if !ok { 170 t.Fatal(diff) 171 } 172 wantCEPMap := map[string]cesToCEPRef{ 173 "ns1/cep1": { 174 "new-ces": createEndpoint("cep1", "ns1", 3), 175 }, 176 } 177 if diff := cmp.Diff(wantCEPMap, cesSub.cepMap.cepMap); diff != "" { 178 t.Fatalf("Unexpected CEP map (-want +got):\n%s", diff) 179 } 180 } 181 182 // TestCESSubscriber_CEPTransferViaUpdate tests a CEP being transferred between 183 // two CESes. The order of events: 184 // 1. CES add event for the old CES with old CEP state 185 // 2. CES update event for the old CES with deleting old CEP state 186 // 3. CES update event for the new CES with latest CEP state 187 func TestCESSubscriber_CEPTransferViaUpdate(t *testing.T) { 188 cepNamespace := "ns1" 189 cepName := "cep1" 190 newCEPID := int64(3) 191 oldCEPID := int64(2) 192 fakeEPWatcher := createFakeEPWatcher() 193 fakeEndpointCache := &fakeEndpointCache{} 194 cesSub := &cesSubscriber{ 195 epWatcher: fakeEPWatcher, 196 epCache: fakeEndpointCache, 197 cepMap: newCEPToCESMap(), 198 } 199 // Add for old CES 200 cesSub.OnAdd( 201 createCES("old-ces", cepNamespace, []v2alpha1.CoreCiliumEndpoint{ 202 { 203 Name: cepName, 204 IdentityID: oldCEPID, 205 }, 206 })) 207 diff, ok := fakeEPWatcher.assertUpdate(endpointUpdate{ 208 newEp: createEndpoint("cep1", "ns1", oldCEPID), 209 }) 210 if !ok { 211 t.Fatal(diff) 212 } 213 // Update for old CES removing CEP 214 cesSub.OnUpdate( 215 createCES("old-ces", cepNamespace, []v2alpha1.CoreCiliumEndpoint{ 216 { 217 Name: cepName, 218 IdentityID: oldCEPID, 219 }, 220 }), 221 createCES("old-ces", cepNamespace, []v2alpha1.CoreCiliumEndpoint{})) 222 223 diff, ok = fakeEPWatcher.assertDelete(createEndpoint("cep1", "ns1", oldCEPID)) 224 if !ok { 225 t.Fatal(diff) 226 } 227 // Update for new CES 228 cesSub.OnUpdate( 229 createCES("new-ces", cepNamespace, []v2alpha1.CoreCiliumEndpoint{}), 230 createCES("new-ces", cepNamespace, []v2alpha1.CoreCiliumEndpoint{ 231 { 232 Name: cepName, 233 IdentityID: newCEPID, 234 }, 235 })) 236 diff, ok = fakeEPWatcher.assertUpdate(endpointUpdate{ 237 newEp: createEndpoint("cep1", "ns1", newCEPID), 238 }) 239 if !ok { 240 t.Fatal(diff) 241 } 242 wantCEPMap := map[string]cesToCEPRef{ 243 "ns1/cep1": { 244 "new-ces": createEndpoint("cep1", "ns1", 3), 245 }, 246 } 247 if diff := cmp.Diff(wantCEPMap, cesSub.cepMap.cepMap); diff != "" { 248 t.Fatalf("Unexpected CEP map (-want +got):\n%s", diff) 249 } 250 } 251 252 func TestCESSubscriber_deleteCEPfromCES(t *testing.T) { 253 for _, tc := range []struct { 254 desc string 255 initCEPMap map[string]cesToCEPRef 256 initCurrentCES map[string]string 257 deletedCesName string 258 deletedCep *types.CiliumEndpoint 259 expectedUpdate endpointUpdate 260 expectedDelete *types.CiliumEndpoint 261 }{ 262 { 263 desc: "delete CEP triggers deletion", 264 initCEPMap: map[string]cesToCEPRef{ 265 "ns1/cep1": { 266 "ces1": createEndpoint("cep1", "ns1", 3), 267 }, 268 }, 269 initCurrentCES: map[string]string{"ns1/cep1": "ces1"}, 270 deletedCesName: "ces1", 271 deletedCep: createEndpoint("cep1", "ns1", 3), 272 expectedDelete: createEndpoint("cep1", "ns1", 3), 273 }, 274 { 275 desc: "delete CEP triggers update", 276 initCEPMap: map[string]cesToCEPRef{ 277 "ns1/cep1": { 278 "ces1": createEndpoint("cep1", "ns1", 2), 279 "ces2": createEndpoint("cep1", "ns1", 3), 280 }, 281 }, 282 initCurrentCES: map[string]string{"ns1/cep1": "ces1"}, 283 deletedCesName: "ces1", 284 deletedCep: createEndpoint("cep1", "ns1", 2), 285 expectedUpdate: endpointUpdate{ 286 oldEp: createEndpoint("cep1", "ns1", 2), 287 newEp: createEndpoint("cep1", "ns1", 3), 288 }, 289 }, 290 { 291 desc: "delete CEP triggers no update or deletion", 292 initCEPMap: map[string]cesToCEPRef{ 293 "ns1/cep1": { 294 "ces1": createEndpoint("cep1", "ns1", 1), 295 "ces2": createEndpoint("cep1", "ns1", 2), 296 }, 297 }, 298 initCurrentCES: map[string]string{"ns1/cep1": "ces1"}, 299 deletedCesName: "ces2", 300 deletedCep: createEndpoint("cep1", "ns1", 2), 301 }, 302 } { 303 t.Run(tc.desc, func(t *testing.T) { 304 fakeEPWatcher := createFakeEPWatcher() 305 cesSub := &cesSubscriber{ 306 epWatcher: fakeEPWatcher, 307 cepMap: newCEPToCESMap(), 308 } 309 if tc.initCEPMap != nil { 310 cesSub.cepMap.cepMap = tc.initCEPMap 311 } 312 if tc.initCurrentCES != nil { 313 cesSub.cepMap.currentCES = tc.initCurrentCES 314 } 315 cepName := tc.deletedCep.Namespace + "/" + tc.deletedCep.Name 316 cesSub.deleteCEPfromCES(cepName, tc.deletedCesName, tc.deletedCep) 317 diff, ok := fakeEPWatcher.assertUpdate(tc.expectedUpdate) 318 if !ok { 319 t.Error(diff) 320 } 321 diff, ok = fakeEPWatcher.assertDelete(tc.expectedDelete) 322 if !ok { 323 t.Error(diff) 324 } 325 }) 326 } 327 } 328 329 func TestCEPToCESmap_insertCEP(t *testing.T) { 330 for _, tc := range []struct { 331 desc string 332 initCEPMap map[string]cesToCEPRef 333 initCurrentCES map[string]string 334 cesName string 335 cep *types.CiliumEndpoint 336 wantCEPMap map[string]cesToCEPRef 337 wantCurrentCES map[string]string 338 }{ 339 { 340 desc: "add new cep", 341 cep: createEndpoint("cep1", "ns1", 3), 342 cesName: "cesx", 343 wantCEPMap: map[string]cesToCEPRef{ 344 "ns1/cep1": { 345 "cesx": createEndpoint("cep1", "ns1", 3), 346 }, 347 }, 348 wantCurrentCES: map[string]string{"ns1/cep1": "cesx"}, 349 }, 350 { 351 desc: "update cep object", 352 initCEPMap: map[string]cesToCEPRef{ 353 "ns1/cep1": { 354 "cesx": createEndpoint("cep1", "ns1", 3), 355 }, 356 }, 357 initCurrentCES: map[string]string{"ns1/cep1": "cesx"}, 358 cep: createEndpoint("cep1", "ns1", 1), 359 cesName: "cesx", 360 wantCEPMap: map[string]cesToCEPRef{ 361 "ns1/cep1": { 362 "cesx": createEndpoint("cep1", "ns1", 1), 363 }, 364 }, 365 wantCurrentCES: map[string]string{"ns1/cep1": "cesx"}, 366 }, 367 { 368 desc: "add new ces for existing cep", 369 initCEPMap: map[string]cesToCEPRef{ 370 "ns1/cep1": { 371 "cesx": createEndpoint("cep1", "ns1", 3), 372 }, 373 }, 374 initCurrentCES: map[string]string{"ns1/cep1": "cesx"}, 375 cep: createEndpoint("cep1", "ns1", 1), 376 cesName: "cesy", 377 wantCEPMap: map[string]cesToCEPRef{ 378 "ns1/cep1": { 379 "cesx": createEndpoint("cep1", "ns1", 3), 380 "cesy": createEndpoint("cep1", "ns1", 1), 381 }, 382 }, 383 wantCurrentCES: map[string]string{"ns1/cep1": "cesy"}, 384 }, 385 } { 386 t.Run(tc.desc, func(t *testing.T) { 387 cepToCESmap := newCEPToCESMap() 388 if tc.initCEPMap != nil { 389 cepToCESmap.cepMap = tc.initCEPMap 390 } 391 if tc.initCurrentCES != nil { 392 cepToCESmap.currentCES = tc.initCurrentCES 393 } 394 cepName := tc.cep.Namespace + "/" + tc.cep.Name 395 cepToCESmap.insertCEPLocked(cepName, tc.cesName, tc.cep) 396 if diff := cmp.Diff(tc.wantCEPMap, cepToCESmap.cepMap); diff != "" { 397 t.Fatalf("Unexpected CEP map entries (-want +got):\n%s", diff) 398 } 399 if diff := cmp.Diff(tc.wantCurrentCES, cepToCESmap.currentCES); diff != "" { 400 t.Fatalf("Unexpected currentCES map entries (-want +got):\n%s", diff) 401 } 402 }) 403 } 404 } 405 406 func TestCEPToCESmap_deleteCEP(t *testing.T) { 407 for _, tc := range []struct { 408 desc string 409 initCEPMap map[string]cesToCEPRef 410 initCurrentCES map[string]string 411 cesName string 412 cepName string 413 wantCEPMap map[string]cesToCEPRef 414 wantCurrentCES map[string]string 415 }{ 416 { 417 desc: "missing ces does not delete any entries", 418 initCEPMap: map[string]cesToCEPRef{ 419 "ns1/cep1": { 420 "cesx": createEndpoint("cep1", "ns1", 3), 421 }, 422 }, 423 initCurrentCES: map[string]string{"ns1/cep1": "cesx"}, 424 cepName: "ns1/cep1", 425 cesName: "cesy", 426 wantCEPMap: map[string]cesToCEPRef{ 427 "ns1/cep1": { 428 "cesx": createEndpoint("cep1", "ns1", 3), 429 }, 430 }, 431 wantCurrentCES: map[string]string{"ns1/cep1": "cesx"}, 432 }, 433 { 434 desc: "missing cep does not delete any entries", 435 initCEPMap: map[string]cesToCEPRef{ 436 "ns1/cep1": { 437 "cesx": createEndpoint("cep1", "ns1", 3), 438 }, 439 }, 440 initCurrentCES: map[string]string{"ns1/cep1": "cesx"}, 441 cepName: "ns1/cep2", 442 cesName: "cesx", 443 wantCEPMap: map[string]cesToCEPRef{ 444 "ns1/cep1": { 445 "cesx": createEndpoint("cep1", "ns1", 3), 446 }, 447 }, 448 wantCurrentCES: map[string]string{"ns1/cep1": "cesx"}, 449 }, 450 { 451 desc: "last ces entry", 452 initCEPMap: map[string]cesToCEPRef{ 453 "ns1/cep1": { 454 "cesx": createEndpoint("cep1", "ns1", 3), 455 }, 456 }, 457 initCurrentCES: map[string]string{"ns1/cep1": "cesx"}, 458 cepName: "ns1/cep1", 459 cesName: "cesx", 460 wantCEPMap: map[string]cesToCEPRef{}, 461 wantCurrentCES: map[string]string{}, 462 }, 463 { 464 desc: "multiple ces entries", 465 initCEPMap: map[string]cesToCEPRef{ 466 "ns1/cep1": { 467 "cesx": createEndpoint("cep1", "ns1", 3), 468 "cesy": createEndpoint("cep1", "ns1", 2), 469 }, 470 }, 471 initCurrentCES: map[string]string{"ns1/cep1": "cesx"}, 472 cepName: "ns1/cep1", 473 cesName: "cesx", 474 wantCEPMap: map[string]cesToCEPRef{ 475 "ns1/cep1": { 476 "cesy": createEndpoint("cep1", "ns1", 2), 477 }, 478 }, 479 wantCurrentCES: map[string]string{"ns1/cep1": "cesy"}, 480 }, 481 } { 482 t.Run(tc.desc, func(t *testing.T) { 483 cepToCESmap := newCEPToCESMap() 484 if tc.initCEPMap != nil { 485 cepToCESmap.cepMap = tc.initCEPMap 486 } 487 if tc.initCurrentCES != nil { 488 cepToCESmap.currentCES = tc.initCurrentCES 489 } 490 cepToCESmap.deleteCEPLocked(tc.cepName, tc.cesName) 491 if diff := cmp.Diff(tc.wantCEPMap, cepToCESmap.cepMap); diff != "" { 492 t.Fatalf("Unexpected CEP map entries (-want +got):\n%s", diff) 493 } 494 if diff := cmp.Diff(tc.wantCurrentCES, cepToCESmap.currentCES); diff != "" { 495 t.Fatalf("Unexpected currentCES map entries (-want +got):\n%s", diff) 496 } 497 }) 498 } 499 }