github.com/aporeto-inc/trireme-lib@v10.358.0+incompatible/monitor/internal/k8s/pod_cache_test.go (about) 1 package k8smonitor 2 3 import ( 4 "context" 5 "fmt" 6 "reflect" 7 "sync" 8 "testing" 9 "time" 10 11 corev1 "k8s.io/api/core/v1" 12 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 "k8s.io/client-go/kubernetes" 14 "k8s.io/client-go/kubernetes/fake" 15 //kubernetesTesting "k8s.io/client-go/testing" 16 ) 17 18 func Test_podCache_Delete(t *testing.T) { 19 updateEvent := func(context.Context, string) error { 20 return nil 21 } 22 23 tests := []struct { 24 name string 25 c *podCache 26 sandboxID string 27 }{ 28 { 29 name: "cache uninitialized", 30 c: nil, 31 sandboxID: "does-not-matter", 32 }, 33 { 34 name: "cache initialized", 35 c: newPodCache(updateEvent), 36 sandboxID: "does-not-mater", 37 }, 38 } 39 for _, tt := range tests { 40 t.Run(tt.name, func(t *testing.T) { 41 tt.c.Delete(tt.sandboxID) 42 }) 43 } 44 } 45 46 func Test_podCache_Set(t *testing.T) { 47 updateEvent := func(context.Context, string) error { 48 return nil 49 } 50 type args struct { 51 sandboxID string 52 pod *corev1.Pod 53 } 54 tests := []struct { 55 name string 56 c *podCache 57 args args 58 wantErr bool 59 wantErrError error 60 }{ 61 { 62 name: "cache uninitialized", 63 c: nil, 64 wantErr: true, 65 wantErrError: errCacheUninitialized, 66 }, 67 { 68 name: "cache has unintialized map", 69 c: &podCache{}, 70 wantErr: true, 71 wantErrError: errCacheUninitialized, 72 args: args{ 73 sandboxID: "does-not-matter", 74 pod: &corev1.Pod{}, 75 }, 76 }, 77 { 78 name: "no sandboxID", 79 c: newPodCache(updateEvent), 80 wantErr: true, 81 wantErrError: errSandboxEmpty, 82 args: args{ 83 sandboxID: "", 84 pod: &corev1.Pod{}, 85 }, 86 }, 87 { 88 name: "pod is nil", 89 c: newPodCache(updateEvent), 90 wantErr: true, 91 wantErrError: errPodNil, 92 args: args{ 93 sandboxID: "does-not-matter", 94 pod: nil, 95 }, 96 }, 97 { 98 name: "successful update entry", 99 c: newPodCache(updateEvent), 100 wantErr: false, 101 args: args{ 102 sandboxID: "does-not-matter", 103 pod: &corev1.Pod{}, 104 }, 105 }, 106 } 107 for _, tt := range tests { 108 t.Run(tt.name, func(t *testing.T) { 109 err := tt.c.Set(tt.args.sandboxID, tt.args.pod) 110 if (err != nil) != tt.wantErr { 111 t.Errorf("podCache.Set() error = %v, wantErr %v", err, tt.wantErr) 112 } 113 if tt.wantErr { 114 if err != tt.wantErrError { 115 t.Errorf("podCache.Set() error = %v, wantErrError %v", err, tt.wantErrError) 116 } 117 } 118 }) 119 } 120 } 121 122 func Test_podCache_Get(t *testing.T) { 123 updateEvent := func(context.Context, string) error { 124 return nil 125 } 126 cacheWithEntry := newPodCache(updateEvent) 127 if err := cacheWithEntry.Set("entry", &corev1.Pod{}); err != nil { 128 panic(err) 129 } 130 tests := []struct { 131 name string 132 sandboxID string 133 c *podCache 134 want *corev1.Pod 135 }{ 136 { 137 name: "uninitialized podCache", 138 c: nil, 139 want: nil, 140 }, 141 { 142 name: "uninitialized map in podCache", 143 c: &podCache{}, 144 want: nil, 145 }, 146 { 147 name: "entry does not exist", 148 c: newPodCache(updateEvent), 149 sandboxID: "does-not-exist", 150 want: nil, 151 }, 152 { 153 name: "entry exists", 154 c: cacheWithEntry, 155 sandboxID: "entry", 156 want: &corev1.Pod{}, 157 }, 158 } 159 for _, tt := range tests { 160 t.Run(tt.name, func(t *testing.T) { 161 if got := tt.c.Get(tt.sandboxID); !reflect.DeepEqual(got, tt.want) { 162 t.Errorf("podCache.Get() = %v, want %v", got, tt.want) 163 } 164 }) 165 } 166 } 167 168 func Test_podCache_FindSandboxID(t *testing.T) { 169 updateEvent := func(context.Context, string) error { 170 return nil 171 } 172 cacheWithEntry := newPodCache(updateEvent) 173 if err := cacheWithEntry.Set("entry", &corev1.Pod{ 174 ObjectMeta: metav1.ObjectMeta{ 175 Name: "my-pod", 176 Namespace: "default", 177 }, 178 }); err != nil { 179 panic(err) 180 } 181 type args struct { 182 name string 183 namespace string 184 } 185 tests := []struct { 186 name string 187 c *podCache 188 args args 189 want string 190 wantErr bool 191 wantErrError error 192 }{ 193 { 194 name: "cache uninitialized", 195 c: nil, 196 wantErr: true, 197 wantErrError: errCacheUninitialized, 198 }, 199 { 200 name: "pods uninitialized", 201 c: &podCache{}, 202 wantErr: true, 203 wantErrError: errCacheUninitialized, 204 }, 205 { 206 name: "pod name empty", 207 c: newPodCache(updateEvent), 208 args: args{ 209 name: "", 210 namespace: "default", 211 }, 212 wantErr: true, 213 wantErrError: errPodNameEmpty, 214 }, 215 { 216 name: "pod namespace empty", 217 c: newPodCache(updateEvent), 218 args: args{ 219 name: "my-pod", 220 namespace: "", 221 }, 222 wantErr: true, 223 wantErrError: errPodNamespaceEmpty, 224 }, 225 { 226 name: "sandbox not found", 227 c: newPodCache(updateEvent), 228 args: args{ 229 name: "my-pod", 230 namespace: "default", 231 }, 232 wantErr: true, 233 wantErrError: errSandboxNotFound, 234 }, 235 { 236 name: "sandbox found", 237 c: cacheWithEntry, 238 args: args{ 239 name: "my-pod", 240 namespace: "default", 241 }, 242 wantErr: false, 243 want: "entry", 244 }, 245 } 246 for _, tt := range tests { 247 t.Run(tt.name, func(t *testing.T) { 248 got, err := tt.c.FindSandboxID(tt.args.name, tt.args.namespace) 249 if (err != nil) != tt.wantErr { 250 t.Errorf("podCache.FindSandboxID() error = %v, wantErr %v", err, tt.wantErr) 251 return 252 } 253 if got != tt.want { 254 t.Errorf("podCache.FindSandboxID() = %v, want %v", got, tt.want) 255 } 256 if tt.wantErr { 257 if err != tt.wantErrError { 258 t.Errorf("podCache.FindSandboxID() error = %v, wantErrError %v", err, tt.wantErrError) 259 } 260 } 261 }) 262 } 263 } 264 265 type unitTestUpdateEvent interface { 266 f() updateEventFunc 267 wait() 268 called() bool 269 } 270 type unitTestUpdateEventHandler struct { 271 sync.RWMutex 272 wg sync.WaitGroup 273 wgCounter int 274 wasCalled bool 275 err error 276 } 277 278 func (h *unitTestUpdateEventHandler) updateEvent(context.Context, string) error { 279 h.Lock() 280 defer h.Unlock() 281 h.wasCalled = true 282 if h.wgCounter > 0 { 283 h.wgCounter-- 284 } 285 if h.wgCounter >= 0 { 286 h.wg.Done() 287 } 288 return h.err 289 } 290 291 func (h *unitTestUpdateEventHandler) f() updateEventFunc { 292 return h.updateEvent 293 } 294 295 func (h *unitTestUpdateEventHandler) wait() { 296 h.wg.Wait() 297 } 298 299 func (h *unitTestUpdateEventHandler) called() bool { 300 h.RLock() 301 defer h.RUnlock() 302 return h.wasCalled 303 } 304 305 func newUnitTestUpdateEventHandler(n int, err error) unitTestUpdateEvent { 306 h := &unitTestUpdateEventHandler{ 307 err: err, 308 wgCounter: n, 309 } 310 h.wg.Add(n) 311 return h 312 } 313 314 func Test_podCache_SetupInformer(t *testing.T) { 315 podTemplate := &corev1.Pod{ 316 ObjectMeta: metav1.ObjectMeta{ 317 Name: "my-pod", 318 Namespace: "default", 319 }, 320 Spec: corev1.PodSpec{ 321 NodeName: "test", 322 }, 323 } 324 running := corev1.ContainerStateRunning{} 325 pending := corev1.ContainerStateWaiting{} 326 327 hostpodTemplate := &corev1.Pod{ 328 ObjectMeta: metav1.ObjectMeta{ 329 Name: "my-host-pod", 330 Namespace: "default", 331 }, 332 Spec: corev1.PodSpec{ 333 NodeName: "test", 334 HostNetwork: true, 335 }, 336 Status: corev1.PodStatus{ 337 Phase: corev1.PodRunning, 338 InitContainerStatuses: []corev1.ContainerStatus{ 339 { 340 ContainerID: "testing://containerID", 341 Ready: true, 342 State: corev1.ContainerState{ 343 Waiting: &pending, 344 }, 345 }, 346 }, 347 ContainerStatuses: []corev1.ContainerStatus{ 348 { 349 ContainerID: "broken-container-id-needs-to-be-skipped", 350 Ready: true, 351 State: corev1.ContainerState{ 352 Waiting: &pending, 353 }, 354 }, 355 }, 356 }, 357 } 358 updateHostPodTemplate := hostpodTemplate.DeepCopy() 359 updateHostPodTemplate.Status.InitContainerStatuses[0].State.Running = &running 360 updateHostPodTemplate2 := updateHostPodTemplate.DeepCopy() 361 updateHostPodTemplate2.Labels = map[string]string{ 362 "a": "b", 363 } 364 365 updatedPodTemplate := podTemplate.DeepCopy() 366 updatedPodTemplate.Labels = map[string]string{ 367 "a": "b", 368 } 369 updatedPodTemplate2 := updatedPodTemplate.DeepCopy() 370 updatedPodTemplate2.Annotations = map[string]string{ 371 "annotated": "", 372 } 373 374 untrackedPodOnSameHostTemplate := &corev1.Pod{ 375 ObjectMeta: metav1.ObjectMeta{ 376 Name: "untracked-same-host", 377 Namespace: "default", 378 }, 379 Spec: corev1.PodSpec{ 380 NodeName: "test", 381 }, 382 } 383 untrackedPodOnDifferentHostTemplate := &corev1.Pod{ 384 ObjectMeta: metav1.ObjectMeta{ 385 Name: "untracked-different-host", 386 Namespace: "default", 387 }, 388 Spec: corev1.PodSpec{ 389 NodeName: "different", 390 }, 391 } 392 393 c := fake.NewSimpleClientset( 394 podTemplate.DeepCopy(), 395 untrackedPodOnSameHostTemplate.DeepCopy(), 396 untrackedPodOnDifferentHostTemplate.DeepCopy(), 397 hostpodTemplate.DeepCopy(), 398 ) 399 400 type fields struct { 401 pods map[string]*corev1.Pod 402 } 403 type args struct { 404 ctx context.Context 405 kubeClient kubernetes.Interface 406 nodeName string 407 needsUpdate needsUpdateFunc 408 } 409 tests := []struct { 410 name string 411 updateEventHandler unitTestUpdateEvent 412 fields fields 413 args args 414 action func(*testing.T, *podCache) 415 expectedUpdateEvent bool 416 expectedPods map[string]*corev1.Pod 417 }{ 418 { 419 name: "update to a pod which we have in cache which requires update event", 420 updateEventHandler: newUnitTestUpdateEventHandler(1, fmt.Errorf("increase coverage")), 421 fields: fields{ 422 pods: map[string]*corev1.Pod{ 423 "entry": podTemplate.DeepCopy(), 424 }, 425 }, 426 args: args{ 427 ctx: context.Background(), 428 kubeClient: c, 429 nodeName: "test", 430 needsUpdate: defaultNeedsUpdate, 431 }, 432 action: func(_ *testing.T, _ *podCache) { 433 _, err := c.CoreV1().Pods("default").Update(context.Background(), updatedPodTemplate.DeepCopy(), metav1.UpdateOptions{}) 434 if err != nil { 435 panic(err) 436 } 437 }, 438 expectedUpdateEvent: true, 439 expectedPods: map[string]*corev1.Pod{ 440 "entry": updatedPodTemplate.DeepCopy(), 441 }, 442 }, 443 { 444 name: "update to a pod which we have in cache which does not require an update event", 445 updateEventHandler: newUnitTestUpdateEventHandler(0, nil), 446 fields: fields{ 447 pods: map[string]*corev1.Pod{ 448 "entry": podTemplate.DeepCopy(), 449 }, 450 }, 451 args: args{ 452 ctx: context.Background(), 453 kubeClient: c, 454 nodeName: "test", 455 needsUpdate: defaultNeedsUpdate, 456 }, 457 action: func(_ *testing.T, _ *podCache) { 458 _, err := c.CoreV1().Pods("default").Update(context.Background(), updatedPodTemplate2.DeepCopy(), metav1.UpdateOptions{}) 459 if err != nil { 460 panic(err) 461 } 462 time.Sleep(time.Millisecond * 100) 463 }, 464 expectedUpdateEvent: false, 465 expectedPods: map[string]*corev1.Pod{ 466 "entry": updatedPodTemplate2.DeepCopy(), 467 }, 468 }, 469 { 470 name: "update to a pod from different host which we do not track", 471 updateEventHandler: newUnitTestUpdateEventHandler(0, nil), 472 fields: fields{ 473 pods: map[string]*corev1.Pod{ 474 "entry": podTemplate.DeepCopy(), 475 }, 476 }, 477 args: args{ 478 ctx: context.Background(), 479 kubeClient: c, 480 nodeName: "test", 481 needsUpdate: defaultNeedsUpdate, 482 }, 483 action: func(_ *testing.T, _ *podCache) { 484 updated := untrackedPodOnDifferentHostTemplate.DeepCopy() 485 updated.Labels = map[string]string{ 486 "update": "", 487 } 488 _, err := c.CoreV1().Pods("default").Update(context.Background(), updated.DeepCopy(), metav1.UpdateOptions{}) 489 if err != nil { 490 panic(err) 491 } 492 }, 493 expectedUpdateEvent: false, 494 expectedPods: map[string]*corev1.Pod{ 495 "entry": podTemplate.DeepCopy(), 496 }, 497 }, 498 { 499 name: "update to a pod from the same host which we do not track", 500 updateEventHandler: newUnitTestUpdateEventHandler(0, nil), 501 fields: fields{ 502 pods: map[string]*corev1.Pod{ 503 "entry": podTemplate.DeepCopy(), 504 }, 505 }, 506 args: args{ 507 ctx: context.Background(), 508 kubeClient: c, 509 nodeName: "test", 510 needsUpdate: defaultNeedsUpdate, 511 }, 512 action: func(_ *testing.T, _ *podCache) { 513 updated := untrackedPodOnSameHostTemplate.DeepCopy() 514 updated.Labels = map[string]string{ 515 "update": "", 516 } 517 _, err := c.CoreV1().Pods("default").Update(context.Background(), updated.DeepCopy(), metav1.UpdateOptions{}) 518 if err != nil { 519 panic(err) 520 } 521 }, 522 expectedUpdateEvent: false, 523 expectedPods: map[string]*corev1.Pod{ 524 "entry": podTemplate.DeepCopy(), 525 }, 526 }, 527 } 528 for _, tt := range tests { 529 t.Run(tt.name, func(t *testing.T) { 530 c := &podCache{ 531 pods: tt.fields.pods, 532 updateEvent: tt.updateEventHandler.f(), 533 } 534 ctx, cancel := context.WithCancel(tt.args.ctx) 535 c.SetupInformer(ctx, tt.args.kubeClient, tt.args.nodeName, tt.args.needsUpdate) 536 tt.action(t, c) 537 tt.updateEventHandler.wait() 538 c.RLock() 539 if !reflect.DeepEqual(c.pods, tt.expectedPods) { 540 t.Errorf("c.pods = %v, want %v", c.pods, tt.expectedPods) 541 } 542 c.RUnlock() 543 if tt.expectedUpdateEvent != tt.updateEventHandler.called() { 544 t.Errorf("updateEventHandler.called() = %v, want %v", tt.updateEventHandler.called(), tt.expectedUpdateEvent) 545 } 546 cancel() 547 }) 548 } 549 } 550 551 func Test_defaultNeedsUpdate(t *testing.T) { 552 type args struct { 553 prev *corev1.Pod 554 obj *corev1.Pod 555 } 556 tests := []struct { 557 name string 558 args args 559 want bool 560 }{ 561 { 562 name: "labels are both nil", 563 args: args{ 564 prev: &corev1.Pod{}, 565 obj: &corev1.Pod{}, 566 }, 567 want: false, 568 }, 569 { 570 name: "labels are empty", 571 args: args{ 572 prev: &corev1.Pod{ 573 ObjectMeta: metav1.ObjectMeta{ 574 Labels: map[string]string{}, 575 }, 576 }, 577 obj: &corev1.Pod{ 578 ObjectMeta: metav1.ObjectMeta{ 579 Labels: map[string]string{}, 580 }, 581 }, 582 }, 583 want: false, 584 }, 585 { 586 name: "labels are the same", 587 args: args{ 588 prev: &corev1.Pod{ 589 ObjectMeta: metav1.ObjectMeta{ 590 Labels: map[string]string{ 591 "a": "b", 592 }, 593 }, 594 }, 595 obj: &corev1.Pod{ 596 ObjectMeta: metav1.ObjectMeta{ 597 Labels: map[string]string{ 598 "a": "b", 599 }, 600 }, 601 }, 602 }, 603 want: false, 604 }, 605 { 606 name: "labels are the same, but one has annotations", 607 args: args{ 608 prev: &corev1.Pod{ 609 ObjectMeta: metav1.ObjectMeta{ 610 Labels: map[string]string{ 611 "a": "b", 612 }, 613 }, 614 }, 615 obj: &corev1.Pod{ 616 ObjectMeta: metav1.ObjectMeta{ 617 Labels: map[string]string{ 618 "a": "b", 619 }, 620 Annotations: map[string]string{ 621 "a": "b", 622 }, 623 }, 624 }, 625 }, 626 want: false, 627 }, 628 { 629 name: "labels are nil in prev, but set on new", 630 args: args{ 631 prev: &corev1.Pod{}, 632 obj: &corev1.Pod{ 633 ObjectMeta: metav1.ObjectMeta{ 634 Labels: map[string]string{ 635 "a": "b", 636 }, 637 }, 638 }, 639 }, 640 want: true, 641 }, 642 { 643 name: "labels are empty in prev, but set on new", 644 args: args{ 645 prev: &corev1.Pod{ 646 ObjectMeta: metav1.ObjectMeta{ 647 Labels: map[string]string{}, 648 }, 649 }, 650 obj: &corev1.Pod{ 651 ObjectMeta: metav1.ObjectMeta{ 652 Labels: map[string]string{ 653 "a": "b", 654 }, 655 }, 656 }, 657 }, 658 want: true, 659 }, 660 { 661 name: "labels have same key but different value", 662 args: args{ 663 prev: &corev1.Pod{ 664 ObjectMeta: metav1.ObjectMeta{ 665 Labels: map[string]string{ 666 "a": "a", 667 }, 668 }, 669 }, 670 obj: &corev1.Pod{ 671 ObjectMeta: metav1.ObjectMeta{ 672 Labels: map[string]string{ 673 "a": "b", 674 }, 675 }, 676 }, 677 }, 678 want: true, 679 }, 680 { 681 name: "prev has one label more", 682 args: args{ 683 prev: &corev1.Pod{ 684 ObjectMeta: metav1.ObjectMeta{ 685 Labels: map[string]string{ 686 "a": "b", 687 "b": "b", 688 }, 689 }, 690 }, 691 obj: &corev1.Pod{ 692 ObjectMeta: metav1.ObjectMeta{ 693 Labels: map[string]string{ 694 "a": "b", 695 }, 696 }, 697 }, 698 }, 699 want: true, 700 }, 701 { 702 name: "prev has different labels", 703 args: args{ 704 prev: &corev1.Pod{ 705 ObjectMeta: metav1.ObjectMeta{ 706 Labels: map[string]string{ 707 "b": "b", 708 }, 709 }, 710 }, 711 obj: &corev1.Pod{ 712 ObjectMeta: metav1.ObjectMeta{ 713 Labels: map[string]string{ 714 "a": "b", 715 }, 716 }, 717 }, 718 }, 719 want: true, 720 }, 721 } 722 for _, tt := range tests { 723 t.Run(tt.name, func(t *testing.T) { 724 if got := defaultNeedsUpdate(tt.args.prev, tt.args.obj); got != tt.want { 725 t.Errorf("defaultNeedsUpdate() = %v, want %v", got, tt.want) 726 } 727 }) 728 } 729 }