knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/controller/controller_test.go (about) 1 /* 2 Copyright 2017 The Knative Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package controller 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "sync" 24 "sync/atomic" 25 "testing" 26 "time" 27 28 "github.com/google/go-cmp/cmp" 29 30 coordinationv1 "k8s.io/api/coordination/v1" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/runtime/schema" 33 "k8s.io/apimachinery/pkg/types" 34 "k8s.io/apimachinery/pkg/util/wait" 35 fakekube "k8s.io/client-go/kubernetes/fake" 36 "k8s.io/client-go/tools/cache" 37 "k8s.io/client-go/tools/record" 38 "k8s.io/client-go/util/workqueue" 39 40 "knative.dev/pkg/leaderelection" 41 "knative.dev/pkg/ptr" 42 "knative.dev/pkg/reconciler" 43 "knative.dev/pkg/system" 44 45 . "knative.dev/pkg/logging/testing" 46 _ "knative.dev/pkg/system/testing" 47 . "knative.dev/pkg/testing" 48 ) 49 50 const ( 51 oldObj = "foo" 52 newObj = "bar" 53 ) 54 55 func TestPassNew(t *testing.T) { 56 PassNew(func(got interface{}) { 57 if newObj != got.(string) { 58 t.Errorf("PassNew() = %v, wanted %v", got, newObj) 59 } 60 })(oldObj, newObj) 61 } 62 63 func TestHandleAll(t *testing.T) { 64 ha := HandleAll(func(got interface{}) { 65 if newObj != got.(string) { 66 t.Errorf("HandleAll() = %v, wanted %v", got, newObj) 67 } 68 }) 69 70 ha.OnAdd(newObj, false) 71 ha.OnUpdate(oldObj, newObj) 72 ha.OnDelete(newObj) 73 } 74 75 var gvk = schema.GroupVersionKind{ 76 Group: "pkg.knative.dev", 77 Version: "v1meta1", 78 Kind: "Parent", 79 } 80 81 func TestFilterWithNameAndNamespace(t *testing.T) { 82 filter := FilterWithNameAndNamespace("test-namespace", "test-name") 83 84 tests := []struct { 85 name string 86 input interface{} 87 want bool 88 }{{ 89 name: "not a metav1.Object", 90 input: "foo", 91 }, { 92 name: "nil", 93 input: nil, 94 }, { 95 name: "name matches, namespace does not", 96 input: &Resource{ 97 ObjectMeta: metav1.ObjectMeta{ 98 Name: "test-name", 99 Namespace: "wrong-namespace", 100 }, 101 }, 102 }, { 103 name: "namespace matches, name does not", 104 input: &Resource{ 105 ObjectMeta: metav1.ObjectMeta{ 106 Name: "wrong-name", 107 Namespace: "test-namespace", 108 }, 109 }, 110 }, { 111 name: "neither matches", 112 input: &Resource{ 113 ObjectMeta: metav1.ObjectMeta{ 114 Name: "wrong-name", 115 Namespace: "wrong-namespace", 116 }, 117 }, 118 }, { 119 name: "matches", 120 input: &Resource{ 121 ObjectMeta: metav1.ObjectMeta{ 122 Name: "test-name", 123 Namespace: "test-namespace", 124 }, 125 }, 126 want: true, 127 }} 128 129 for _, test := range tests { 130 t.Run(test.name, func(t *testing.T) { 131 got := filter(test.input) 132 if test.want != got { 133 t.Errorf("FilterWithNameAndNamespace() = %v, wanted %v", got, test.want) 134 } 135 }) 136 } 137 } 138 139 func TestFilterWithName(t *testing.T) { 140 filter := FilterWithName("test-name") 141 142 tests := []struct { 143 name string 144 input interface{} 145 want bool 146 }{{ 147 name: "not a metav1.Object", 148 input: "foo", 149 }, { 150 name: "nil", 151 input: nil, 152 }, { 153 name: "name matches, namespace does not", 154 input: &Resource{ 155 ObjectMeta: metav1.ObjectMeta{ 156 Name: "test-name", 157 Namespace: "wrong-namespace", 158 }, 159 }, 160 want: true, // Unlike FilterWithNameAndNamespace this passes 161 }, { 162 name: "namespace matches, name does not", 163 input: &Resource{ 164 ObjectMeta: metav1.ObjectMeta{ 165 Name: "wrong-name", 166 Namespace: "test-namespace", 167 }, 168 }, 169 }, { 170 name: "neither matches", 171 input: &Resource{ 172 ObjectMeta: metav1.ObjectMeta{ 173 Name: "wrong-name", 174 Namespace: "wrong-namespace", 175 }, 176 }, 177 }, { 178 name: "matches", 179 input: &Resource{ 180 ObjectMeta: metav1.ObjectMeta{ 181 Name: "test-name", 182 Namespace: "test-namespace", 183 }, 184 }, 185 want: true, 186 }} 187 188 for _, test := range tests { 189 t.Run(test.name, func(t *testing.T) { 190 got := filter(test.input) 191 if test.want != got { 192 t.Errorf("FilterWithNameAndNamespace() = %v, wanted %v", got, test.want) 193 } 194 }) 195 } 196 } 197 198 func TestFilterGroupKind(t *testing.T) { 199 filter := FilterGroupKind(gvk.GroupKind()) 200 201 tests := []struct { 202 name string 203 input interface{} 204 want bool 205 }{{ 206 name: "not a metav1.Object", 207 input: "foo", 208 }, { 209 name: "nil", 210 input: nil, 211 }, { 212 name: "no owner reference", 213 input: &Resource{ 214 ObjectMeta: metav1.ObjectMeta{ 215 Name: "foo", 216 Namespace: "bar", 217 }, 218 }, 219 }, { 220 name: "wrong owner reference, not controller", 221 input: &Resource{ 222 ObjectMeta: metav1.ObjectMeta{ 223 Name: "foo", 224 Namespace: "bar", 225 OwnerReferences: []metav1.OwnerReference{{ 226 APIVersion: "another.knative.dev/v1beta3", 227 Kind: "Parent", 228 Controller: ptr.Bool(false), 229 }}, 230 }, 231 }, 232 }, { 233 name: "right owner reference, not controller", 234 input: &Resource{ 235 ObjectMeta: metav1.ObjectMeta{ 236 Name: "foo", 237 Namespace: "bar", 238 OwnerReferences: []metav1.OwnerReference{{ 239 APIVersion: gvk.GroupVersion().String(), 240 Kind: gvk.Kind, 241 Controller: ptr.Bool(false), 242 }}, 243 }, 244 }, 245 }, { 246 name: "wrong owner reference, but controller", 247 input: &Resource{ 248 ObjectMeta: metav1.ObjectMeta{ 249 Name: "foo", 250 Namespace: "bar", 251 OwnerReferences: []metav1.OwnerReference{{ 252 APIVersion: "another.knative.dev/v1beta3", 253 Kind: "Parent", 254 Controller: ptr.Bool(true), 255 }}, 256 }, 257 }, 258 want: false, 259 }, { 260 name: "right owner reference, is controller", 261 input: &Resource{ 262 ObjectMeta: metav1.ObjectMeta{ 263 Name: "foo", 264 Namespace: "bar", 265 OwnerReferences: []metav1.OwnerReference{{ 266 APIVersion: gvk.GroupVersion().String(), 267 Kind: gvk.Kind, 268 Controller: ptr.Bool(true), 269 }}, 270 }, 271 }, 272 want: true, 273 }, { 274 name: "right owner reference, is controller, different version", 275 input: &Resource{ 276 ObjectMeta: metav1.ObjectMeta{ 277 Name: "foo", 278 Namespace: "bar", 279 OwnerReferences: []metav1.OwnerReference{{ 280 APIVersion: schema.GroupVersion{Group: gvk.Group, Version: "other"}.String(), 281 Kind: gvk.Kind, 282 Controller: ptr.Bool(true), 283 }}, 284 }, 285 }, 286 want: true, 287 }} 288 289 for _, test := range tests { 290 t.Run(test.name, func(t *testing.T) { 291 got := filter(test.input) 292 if test.want != got { 293 t.Errorf("Filter() = %v, wanted %v", got, test.want) 294 } 295 }) 296 } 297 } 298 299 func TestFilterGroupVersionKind(t *testing.T) { 300 filter := FilterGroupVersionKind(gvk) 301 302 tests := []struct { 303 name string 304 input interface{} 305 want bool 306 }{{ 307 name: "not a metav1.Object", 308 input: "foo", 309 }, { 310 name: "nil", 311 input: nil, 312 }, { 313 name: "no owner reference", 314 input: &Resource{ 315 ObjectMeta: metav1.ObjectMeta{ 316 Name: "foo", 317 Namespace: "bar", 318 }, 319 }, 320 }, { 321 name: "wrong owner reference, not controller", 322 input: &Resource{ 323 ObjectMeta: metav1.ObjectMeta{ 324 Name: "foo", 325 Namespace: "bar", 326 OwnerReferences: []metav1.OwnerReference{{ 327 APIVersion: "another.knative.dev/v1beta3", 328 Kind: "Parent", 329 Controller: ptr.Bool(false), 330 }}, 331 }, 332 }, 333 }, { 334 name: "right owner reference, not controller", 335 input: &Resource{ 336 ObjectMeta: metav1.ObjectMeta{ 337 Name: "foo", 338 Namespace: "bar", 339 OwnerReferences: []metav1.OwnerReference{{ 340 APIVersion: gvk.GroupVersion().String(), 341 Kind: gvk.Kind, 342 Controller: ptr.Bool(false), 343 }}, 344 }, 345 }, 346 }, { 347 name: "wrong owner reference, but controller", 348 input: &Resource{ 349 ObjectMeta: metav1.ObjectMeta{ 350 Name: "foo", 351 Namespace: "bar", 352 OwnerReferences: []metav1.OwnerReference{{ 353 APIVersion: "another.knative.dev/v1beta3", 354 Kind: "Parent", 355 Controller: ptr.Bool(true), 356 }}, 357 }, 358 }, 359 }, { 360 name: "right owner reference, is controller", 361 input: &Resource{ 362 ObjectMeta: metav1.ObjectMeta{ 363 Name: "foo", 364 Namespace: "bar", 365 OwnerReferences: []metav1.OwnerReference{{ 366 APIVersion: gvk.GroupVersion().String(), 367 Kind: gvk.Kind, 368 Controller: ptr.Bool(true), 369 }}, 370 }, 371 }, 372 want: true, 373 }, { 374 name: "right owner reference, is controller, wrong version", 375 input: &Resource{ 376 ObjectMeta: metav1.ObjectMeta{ 377 Name: "foo", 378 Namespace: "bar", 379 OwnerReferences: []metav1.OwnerReference{{ 380 APIVersion: schema.GroupVersion{Group: gvk.Group, Version: "other"}.String(), 381 Kind: gvk.Kind, 382 Controller: ptr.Bool(true), 383 }}, 384 }, 385 }, 386 }} 387 388 for _, test := range tests { 389 t.Run(test.name, func(t *testing.T) { 390 got := filter(test.input) 391 if test.want != got { 392 t.Errorf("Filter() = %v, wanted %v", got, test.want) 393 } 394 }) 395 } 396 } 397 398 type nopReconciler struct{} 399 400 func (nr *nopReconciler) Reconcile(context.Context, string) error { 401 return nil 402 } 403 404 type testRateLimiter struct { 405 t *testing.T 406 delay time.Duration 407 } 408 409 func (t testRateLimiter) When(interface{}) time.Duration { return t.delay } 410 func (t testRateLimiter) Forget(interface{}) {} 411 func (t testRateLimiter) NumRequeues(interface{}) int { return 0 } 412 413 var _ workqueue.TypedRateLimiter[any] = (*testRateLimiter)(nil) 414 415 func TestEnqueue(t *testing.T) { 416 tests := []struct { 417 name string 418 work func(*Impl) 419 wantQueue []types.NamespacedName 420 }{{ 421 name: "do nothing", 422 work: func(*Impl) {}, 423 }, { 424 name: "enqueue key", 425 work: func(impl *Impl) { 426 impl.EnqueueKey(types.NamespacedName{Namespace: "foo", Name: "bar"}) 427 }, 428 wantQueue: []types.NamespacedName{{Namespace: "foo", Name: "bar"}}, 429 }, { 430 name: "enqueue duplicate key", 431 work: func(impl *Impl) { 432 impl.EnqueueKey(types.NamespacedName{Namespace: "foo", Name: "bar"}) 433 impl.EnqueueKey(types.NamespacedName{Namespace: "foo", Name: "bar"}) 434 }, 435 // The queue deduplicates. 436 wantQueue: []types.NamespacedName{{Namespace: "foo", Name: "bar"}}, 437 }, { 438 name: "enqueue different keys", 439 work: func(impl *Impl) { 440 impl.EnqueueKey(types.NamespacedName{Namespace: "foo", Name: "bar"}) 441 impl.EnqueueKey(types.NamespacedName{Namespace: "foo", Name: "baz"}) 442 }, 443 wantQueue: []types.NamespacedName{{Namespace: "foo", Name: "bar"}, {Namespace: "foo", Name: "baz"}}, 444 }, { 445 name: "enqueue resource", 446 work: func(impl *Impl) { 447 impl.Enqueue(&Resource{ 448 ObjectMeta: metav1.ObjectMeta{ 449 Name: "foo", 450 Namespace: "bar", 451 }, 452 }) 453 }, 454 wantQueue: []types.NamespacedName{{Namespace: "bar", Name: "foo"}}, 455 }, { 456 name: "enqueue resource slow", 457 work: func(impl *Impl) { 458 impl.EnqueueSlow(&Resource{ 459 ObjectMeta: metav1.ObjectMeta{ 460 Name: "foo", 461 Namespace: "bar", 462 }, 463 }) 464 }, 465 wantQueue: []types.NamespacedName{{Namespace: "bar", Name: "foo"}}, 466 }, { 467 name: "enqueue sentinel resource", 468 work: func(impl *Impl) { 469 e := impl.EnqueueSentinel(types.NamespacedName{Namespace: "foo", Name: "bar"}) 470 e(&Resource{ 471 ObjectMeta: metav1.ObjectMeta{ 472 Namespace: "foo", 473 Name: "baz", 474 }, 475 }) 476 }, 477 wantQueue: []types.NamespacedName{{Namespace: "foo", Name: "bar"}}, 478 }, { 479 name: "enqueue duplicate sentinel resource", 480 work: func(impl *Impl) { 481 e := impl.EnqueueSentinel(types.NamespacedName{Namespace: "foo", Name: "bar"}) 482 e(&Resource{ 483 ObjectMeta: metav1.ObjectMeta{ 484 Namespace: "foo", 485 Name: "baz-1", 486 }, 487 }) 488 e(&Resource{ 489 ObjectMeta: metav1.ObjectMeta{ 490 Namespace: "foo", 491 Name: "baz-2", 492 }, 493 }) 494 }, 495 wantQueue: []types.NamespacedName{{Namespace: "foo", Name: "bar"}}, 496 }, { 497 name: "enqueue bad resource", 498 work: func(impl *Impl) { 499 impl.Enqueue("baz/blah") 500 }, 501 }, { 502 name: "enqueue controller of bad resource", 503 work: func(impl *Impl) { 504 impl.EnqueueControllerOf("baz/blah") 505 }, 506 }, { 507 name: "enqueue controller of resource without owner", 508 work: func(impl *Impl) { 509 impl.EnqueueControllerOf(&Resource{ 510 ObjectMeta: metav1.ObjectMeta{ 511 Name: "foo", 512 Namespace: "bar", 513 }, 514 }) 515 }, 516 }, { 517 name: "enqueue controller of resource with owner", 518 work: func(impl *Impl) { 519 impl.EnqueueControllerOf(&Resource{ 520 ObjectMeta: metav1.ObjectMeta{ 521 Name: "foo", 522 Namespace: "bar", 523 OwnerReferences: []metav1.OwnerReference{{ 524 APIVersion: gvk.GroupVersion().String(), 525 Kind: gvk.Kind, 526 Name: "baz", 527 Controller: ptr.Bool(true), 528 }}, 529 }, 530 }) 531 }, 532 wantQueue: []types.NamespacedName{{Namespace: "bar", Name: "baz"}}, 533 }, { 534 name: "enqueue controller of deleted resource with owner", 535 work: func(impl *Impl) { 536 impl.EnqueueControllerOf(cache.DeletedFinalStateUnknown{ 537 Key: "foo/bar", 538 Obj: &Resource{ 539 ObjectMeta: metav1.ObjectMeta{ 540 Name: "foo", 541 Namespace: "bar", 542 OwnerReferences: []metav1.OwnerReference{{ 543 APIVersion: gvk.GroupVersion().String(), 544 Kind: gvk.Kind, 545 Name: "baz", 546 Controller: ptr.Bool(true), 547 }}, 548 }, 549 }, 550 }) 551 }, 552 wantQueue: []types.NamespacedName{{Namespace: "bar", Name: "baz"}}, 553 }, { 554 name: "enqueue controller of deleted bad resource", 555 work: func(impl *Impl) { 556 impl.EnqueueControllerOf(cache.DeletedFinalStateUnknown{ 557 Key: "foo/bar", 558 Obj: "bad-resource", 559 }) 560 }, 561 }, { 562 name: "enqueue label of namespaced resource bad resource", 563 work: func(impl *Impl) { 564 impl.EnqueueLabelOfNamespaceScopedResource("test-ns", "test-name")("baz/blah") 565 }, 566 }, { 567 name: "enqueue label of namespaced resource without label", 568 work: func(impl *Impl) { 569 impl.EnqueueLabelOfNamespaceScopedResource("ns-key", "name-key")(&Resource{ 570 ObjectMeta: metav1.ObjectMeta{ 571 Name: "foo", 572 Namespace: "bar", 573 Labels: map[string]string{ 574 "ns-key": "bar", 575 }, 576 }, 577 }) 578 }, 579 }, { 580 name: "enqueue label of namespaced resource without namespace label", 581 work: func(impl *Impl) { 582 impl.EnqueueLabelOfNamespaceScopedResource("ns-key", "name-key")(&Resource{ 583 ObjectMeta: metav1.ObjectMeta{ 584 Name: "foo", 585 Namespace: "bar", 586 Labels: map[string]string{ 587 "name-key": "baz", 588 }, 589 }, 590 }) 591 }, 592 }, { 593 name: "enqueue label of namespaced resource with labels", 594 work: func(impl *Impl) { 595 impl.EnqueueLabelOfNamespaceScopedResource("ns-key", "name-key")(&Resource{ 596 ObjectMeta: metav1.ObjectMeta{ 597 Name: "foo", 598 Namespace: "bar", 599 Labels: map[string]string{ 600 "ns-key": "qux", 601 "name-key": "baz", 602 }, 603 }, 604 }) 605 }, 606 wantQueue: []types.NamespacedName{{Namespace: "qux", Name: "baz"}}, 607 }, { 608 name: "enqueue label of namespaced resource with empty namespace label", 609 work: func(impl *Impl) { 610 impl.EnqueueLabelOfNamespaceScopedResource("", "name-key")(&Resource{ 611 ObjectMeta: metav1.ObjectMeta{ 612 Name: "foo", 613 Namespace: "bar", 614 Labels: map[string]string{ 615 "name-key": "baz", 616 }, 617 }, 618 }) 619 }, 620 wantQueue: []types.NamespacedName{{Namespace: "bar", Name: "baz"}}, 621 }, { 622 name: "enqueue label of deleted namespaced resource with label", 623 work: func(impl *Impl) { 624 impl.EnqueueLabelOfNamespaceScopedResource("ns-key", "name-key")(cache.DeletedFinalStateUnknown{ 625 Key: "foo/bar", 626 Obj: &Resource{ 627 ObjectMeta: metav1.ObjectMeta{ 628 Name: "foo", 629 Namespace: "bar", 630 Labels: map[string]string{ 631 "ns-key": "qux", 632 "name-key": "baz", 633 }, 634 }, 635 }, 636 }) 637 }, 638 wantQueue: []types.NamespacedName{{Namespace: "qux", Name: "baz"}}, 639 }, { 640 name: "enqueue label of deleted bad namespaced resource", 641 work: func(impl *Impl) { 642 impl.EnqueueLabelOfNamespaceScopedResource("ns-key", "name-key")(cache.DeletedFinalStateUnknown{ 643 Key: "foo/bar", 644 Obj: "bad-resource", 645 }) 646 }, 647 }, { 648 name: "enqueue label of cluster scoped resource bad resource", 649 work: func(impl *Impl) { 650 impl.EnqueueLabelOfClusterScopedResource("name-key")("baz") 651 }, 652 }, { 653 name: "enqueue label of cluster scoped resource without label", 654 work: func(impl *Impl) { 655 impl.EnqueueLabelOfClusterScopedResource("name-key")(&Resource{ 656 ObjectMeta: metav1.ObjectMeta{ 657 Name: "foo", 658 Namespace: "bar", 659 Labels: map[string]string{}, 660 }, 661 }) 662 }, 663 }, { 664 name: "enqueue label of cluster scoped resource with label", 665 work: func(impl *Impl) { 666 impl.EnqueueLabelOfClusterScopedResource("name-key")(&Resource{ 667 ObjectMeta: metav1.ObjectMeta{ 668 Name: "foo", 669 Namespace: "bar", 670 Labels: map[string]string{ 671 "name-key": "baz", 672 }, 673 }, 674 }) 675 }, 676 wantQueue: []types.NamespacedName{{Namespace: "", Name: "baz"}}, 677 }, { 678 name: "enqueue label of deleted cluster scoped resource with label", 679 work: func(impl *Impl) { 680 impl.EnqueueLabelOfClusterScopedResource("name-key")(cache.DeletedFinalStateUnknown{ 681 Key: "foo/bar", 682 Obj: &Resource{ 683 ObjectMeta: metav1.ObjectMeta{ 684 Name: "foo", 685 Namespace: "bar", 686 Labels: map[string]string{ 687 "name-key": "baz", 688 }, 689 }, 690 }, 691 }) 692 }, 693 wantQueue: []types.NamespacedName{{Namespace: "", Name: "baz"}}, 694 }, { 695 name: "enqueue namespace of object", 696 work: func(impl *Impl) { 697 impl.EnqueueNamespaceOf(&Resource{ 698 ObjectMeta: metav1.ObjectMeta{ 699 Name: "foo", 700 Namespace: "bar", 701 }, 702 }) 703 }, 704 wantQueue: []types.NamespacedName{{Name: "bar"}}, 705 }, { 706 name: "enqueue label of deleted bad cluster scoped resource", 707 work: func(impl *Impl) { 708 impl.EnqueueLabelOfClusterScopedResource("name-key")(cache.DeletedFinalStateUnknown{ 709 Key: "bar", 710 Obj: "bad-resource", 711 }) 712 }, 713 }} 714 715 for _, test := range tests { 716 t.Run(test.name, func(t *testing.T) { 717 var rl workqueue.TypedRateLimiter[any] = testRateLimiter{t, 100 * time.Millisecond} 718 impl := NewContext(context.TODO(), &nopReconciler{}, ControllerOptions{WorkQueueName: "Testing", Logger: TestLogger(t), RateLimiter: rl}) 719 test.work(impl) 720 721 impl.WorkQueue().ShutDown() 722 gotQueue := drainWorkQueue(impl.WorkQueue()) 723 724 if diff := cmp.Diff(test.wantQueue, gotQueue); diff != "" { 725 t.Error("unexpected queue (-want +got):", diff) 726 } 727 }) 728 } 729 } 730 731 const ( 732 // longDelay is longer than we expect the test to run. 733 longDelay = time.Minute 734 // shortDelay is short enough for the test to execute quickly, but long 735 // enough to reasonably delay the enqueuing of an item. 736 shortDelay = 50 * time.Millisecond 737 738 // time we allow the queue length checker to keep polling the 739 // workqueue. 740 queueCheckTimeout = shortDelay + 500*time.Millisecond 741 ) 742 743 func pollQ(q workqueue.TypedRateLimitingInterface[any], sig chan int) func(context.Context) (bool, error) { 744 return func(context.Context) (bool, error) { 745 if ql := q.Len(); ql > 0 { 746 sig <- ql 747 return true, nil 748 } 749 return false, nil 750 } 751 } 752 753 func TestEnqueueAfter(t *testing.T) { 754 impl := NewContext(context.TODO(), &nopReconciler{}, ControllerOptions{ 755 Logger: TestLogger(t), 756 WorkQueueName: "Testing", 757 }) 758 759 t.Cleanup(func() { 760 impl.WorkQueue().ShutDown() 761 }) 762 763 // Enqueue two items with a long delay. 764 impl.EnqueueAfter(&Resource{ 765 ObjectMeta: metav1.ObjectMeta{ 766 Name: "for", 767 Namespace: "waiting", 768 }, 769 }, longDelay) 770 impl.EnqueueAfter(&Resource{ 771 ObjectMeta: metav1.ObjectMeta{ 772 Name: "waterfall", 773 Namespace: "the", 774 }, 775 }, longDelay) 776 777 // Enqueue one item with a short delay. 778 enqueueTime := time.Now() 779 impl.EnqueueAfter(&Resource{ 780 ObjectMeta: metav1.ObjectMeta{ 781 Name: "fall", 782 Namespace: "to", 783 }, 784 }, shortDelay) 785 786 // Keep checking the queue length until 'to/fall' gets enqueued, send to channel to indicate success. 787 queuePopulated := make(chan int) 788 ctx, cancel := context.WithTimeout(context.Background(), queueCheckTimeout) 789 790 t.Cleanup(func() { 791 close(queuePopulated) 792 cancel() 793 }) 794 795 go wait.PollUntilContextCancel(ctx, 5*time.Millisecond, true, 796 pollQ(impl.WorkQueue(), queuePopulated)) 797 798 select { 799 case qlen := <-queuePopulated: 800 if enqueueDelay := time.Since(enqueueTime); enqueueDelay < shortDelay { 801 t.Errorf("Item enqueued within %v, expected at least a %v delay", enqueueDelay, shortDelay) 802 } 803 if got, want := qlen, 1; got != want { 804 t.Errorf("|Queue| = %d, want: %d", got, want) 805 } 806 807 case <-ctx.Done(): 808 t.Fatal("Timed out waiting for item to be put onto the workqueue") 809 } 810 811 impl.WorkQueue().ShutDown() 812 813 got, want := drainWorkQueue(impl.WorkQueue()), []types.NamespacedName{{Namespace: "to", Name: "fall"}} 814 if diff := cmp.Diff(want, got); diff != "" { 815 t.Errorf("Unexpected workqueue state (-:expect, +:got):\n%s", diff) 816 } 817 } 818 819 func TestEnqueueKeyAfter(t *testing.T) { 820 impl := NewContext(context.TODO(), &nopReconciler{}, ControllerOptions{ 821 Logger: TestLogger(t), 822 WorkQueueName: "Testing", 823 }) 824 t.Cleanup(func() { 825 impl.WorkQueue().ShutDown() 826 }) 827 828 // Enqueue two items with a long delay. 829 impl.EnqueueKeyAfter(types.NamespacedName{Namespace: "waiting", Name: "for"}, longDelay) 830 impl.EnqueueKeyAfter(types.NamespacedName{Namespace: "the", Name: "waterfall"}, longDelay) 831 832 // Enqueue one item with a short delay. 833 enqueueTime := time.Now() 834 impl.EnqueueKeyAfter(types.NamespacedName{Namespace: "to", Name: "fall"}, shortDelay) 835 836 // Keep checking the queue length until 'to/fall' gets enqueued, send to channel to indicate success. 837 queuePopulated := make(chan int) 838 839 ctx, cancel := context.WithTimeout(context.Background(), queueCheckTimeout) 840 841 t.Cleanup(func() { 842 close(queuePopulated) 843 cancel() 844 }) 845 846 go wait.PollUntilContextCancel(ctx, 5*time.Millisecond, true, 847 pollQ(impl.WorkQueue(), queuePopulated)) 848 849 select { 850 case qlen := <-queuePopulated: 851 if enqueueDelay := time.Since(enqueueTime); enqueueDelay < shortDelay { 852 t.Errorf("Item enqueued within %v, expected at least a %v delay", enqueueDelay, shortDelay) 853 } 854 if got, want := qlen, 1; got != want { 855 t.Errorf("|Queue| = %d, want: %d", got, want) 856 } 857 858 case <-ctx.Done(): 859 t.Fatal("Timed out waiting for item to be put onto the workqueue") 860 } 861 862 impl.WorkQueue().ShutDown() 863 864 got, want := drainWorkQueue(impl.WorkQueue()), []types.NamespacedName{{Namespace: "to", Name: "fall"}} 865 if diff := cmp.Diff(want, got); diff != "" { 866 t.Errorf("Unexpected workqueue state (-:expect, +:got):\n%s", diff) 867 } 868 } 869 870 type CountingReconciler struct { 871 count atomic.Int32 872 } 873 874 func (cr *CountingReconciler) Reconcile(context.Context, string) error { 875 cr.count.Add(1) 876 return nil 877 } 878 879 func TestStartAndShutdown(t *testing.T) { 880 r := &CountingReconciler{} 881 impl := NewContext(context.TODO(), &nopReconciler{}, ControllerOptions{ 882 Logger: TestLogger(t), 883 WorkQueueName: "Testing", 884 }) 885 886 ctx, cancel := context.WithCancel(context.Background()) 887 doneCh := make(chan struct{}) 888 go func() { 889 defer close(doneCh) 890 StartAll(ctx, impl) 891 }() 892 t.Cleanup(func() { 893 cancel() 894 <-doneCh 895 }) 896 897 select { 898 case <-time.After(10 * time.Millisecond): 899 // We don't expect completion before the context is cancelled. 900 case <-doneCh: 901 t.Error("StartAll finished early.") 902 } 903 cancel() 904 905 select { 906 case <-time.After(time.Second): 907 t.Error("Timed out waiting for controller to finish.") 908 case <-doneCh: 909 // We expect the work to complete. 910 } 911 912 if got, want := r.count.Load(), int32(0); got != want { 913 t.Errorf("count = %v, wanted %v", got, want) 914 } 915 } 916 917 type countingLeaderAwareReconciler struct { 918 reconciler.LeaderAwareFuncs 919 920 reconcileCount atomic.Int32 921 promotionCount atomic.Int32 922 } 923 924 var _ reconciler.LeaderAware = (*countingLeaderAwareReconciler)(nil) 925 926 func (cr *countingLeaderAwareReconciler) Promote(b reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error { 927 cr.promotionCount.Add(1) 928 return cr.LeaderAwareFuncs.Promote(b, enq) 929 } 930 931 func (cr *countingLeaderAwareReconciler) Reconcile(ctx context.Context, key string) error { 932 namespace, name, err := cache.SplitMetaNamespaceKey(key) 933 if err != nil { 934 return err 935 } 936 937 if cr.IsLeaderFor(types.NamespacedName{ 938 Namespace: namespace, 939 Name: name, 940 }) { 941 cr.reconcileCount.Add(1) 942 } 943 return nil 944 } 945 946 func TestStartAndShutdownWithLeaderAwareNoElection(t *testing.T) { 947 r := &countingLeaderAwareReconciler{ 948 LeaderAwareFuncs: reconciler.LeaderAwareFuncs{ 949 PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error { 950 t.Error("Promote should not be called when no leader election is enabled.") 951 return nil 952 }, 953 }, 954 } 955 956 impl := NewContext(context.TODO(), r, ControllerOptions{ 957 Logger: TestLogger(t), 958 WorkQueueName: "Testing", 959 }) 960 961 ctx, cancel := context.WithCancel(context.Background()) 962 doneCh := make(chan struct{}) 963 go func() { 964 defer close(doneCh) 965 StartAll(ctx, impl) 966 }() 967 t.Cleanup(func() { 968 cancel() 969 <-doneCh 970 }) 971 972 select { 973 case <-doneCh: 974 t.Fatal("StartAll finished early.") 975 case <-time.After(1 * time.Second): 976 // Give it some time to run. 977 } 978 979 cancel() 980 981 select { 982 case <-time.After(time.Second): 983 t.Error("Timed out waiting for controller to finish.") 984 case <-doneCh: 985 // We expect the work to complete. 986 } 987 988 if got, want := r.reconcileCount.Load(), int32(0); got != want { 989 t.Errorf("reconcile count = %v, wanted %v", got, want) 990 } 991 992 // Since the elector can't easily be mocked we assume: 993 // 1. We are using the unopposed elector 994 // 2. It has a single initial bucket (the Universe) 995 // 996 // Thus we expect two promotions to occur 997 // 1. It provides the initial set of buckets and these are promoted 998 // before reconciliation and leader election go routines start 999 // 2. When leader election starts the unopposed elector promotes 1000 // that same bucket again 1001 if got, want := r.promotionCount.Load(), int32(2); got != want { 1002 t.Errorf("promotion count = %v, wanted %v", got, want) 1003 } 1004 } 1005 1006 func TestStartAndShutdownWithLeaderAwareWithLostElection(t *testing.T) { 1007 promoted := make(chan struct{}) 1008 r := &countingLeaderAwareReconciler{ 1009 LeaderAwareFuncs: reconciler.LeaderAwareFuncs{ 1010 PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error { 1011 close(promoted) 1012 return nil 1013 }, 1014 }, 1015 } 1016 cc := leaderelection.ComponentConfig{ 1017 Component: "component", 1018 LeaseDuration: 15 * time.Second, 1019 RenewDeadline: 10 * time.Second, 1020 RetryPeriod: 2 * time.Second, 1021 } 1022 kc := fakekube.NewSimpleClientset( 1023 &coordinationv1.Lease{ 1024 ObjectMeta: metav1.ObjectMeta{ 1025 Namespace: system.Namespace(), 1026 Name: "component.testing.00-of-01", 1027 }, 1028 Spec: coordinationv1.LeaseSpec{ 1029 HolderIdentity: ptr.String("not-us"), 1030 LeaseDurationSeconds: ptr.Int32(3000), 1031 AcquireTime: &metav1.MicroTime{Time: time.Now()}, 1032 RenewTime: &metav1.MicroTime{Time: time.Now().Add(3000 * time.Second)}, 1033 }, 1034 }, 1035 ) 1036 1037 impl := NewContext(context.TODO(), &nopReconciler{}, ControllerOptions{ 1038 Logger: TestLogger(t), 1039 WorkQueueName: "Testing", 1040 }) 1041 1042 ctx, cancel := context.WithCancel(context.Background()) 1043 ctx = leaderelection.WithStandardLeaderElectorBuilder(ctx, kc, cc) 1044 doneCh := make(chan struct{}) 1045 go func() { 1046 defer close(doneCh) 1047 StartAll(ctx, impl) 1048 }() 1049 t.Cleanup(func() { 1050 cancel() 1051 <-doneCh 1052 }) 1053 1054 select { 1055 case <-promoted: 1056 t.Fatal("Unexpected promotion.") 1057 case <-time.After(3 * time.Second): 1058 // Wait for 3 seconds for good measure. 1059 case <-doneCh: 1060 t.Error("StartAll finished early.") 1061 } 1062 1063 cancel() 1064 1065 select { 1066 case <-time.After(time.Second): 1067 t.Error("Timed out waiting for controller to finish.") 1068 case <-doneCh: 1069 // We expect the work to complete. 1070 } 1071 1072 if got, want := r.reconcileCount.Load(), int32(0); got != want { 1073 t.Errorf("reconcile count = %v, wanted %v", got, want) 1074 } 1075 } 1076 1077 func TestStartAndShutdownWithWork(t *testing.T) { 1078 r := &CountingReconciler{} 1079 impl := NewContext(context.TODO(), r, ControllerOptions{ 1080 Logger: TestLogger(t), 1081 WorkQueueName: "Testing", 1082 }) 1083 1084 ctx, cancel := context.WithCancel(context.Background()) 1085 doneCh := make(chan struct{}) 1086 go func() { 1087 defer close(doneCh) 1088 StartAll(ctx, impl) 1089 }() 1090 t.Cleanup(func() { 1091 cancel() 1092 <-doneCh 1093 }) 1094 1095 impl.EnqueueKey(types.NamespacedName{Namespace: "foo", Name: "bar"}) 1096 1097 select { 1098 case <-time.After(10 * time.Millisecond): 1099 // We don't expect completion before the context is cancelled. 1100 case <-doneCh: 1101 t.Error("StartAll finished early.") 1102 } 1103 cancel() 1104 1105 select { 1106 case <-time.After(time.Second): 1107 t.Error("Timed out waiting for controller to finish.") 1108 case <-doneCh: 1109 // We expect the work to complete. 1110 } 1111 1112 if got, want := r.count.Load(), int32(1); got != want { 1113 t.Errorf("reconcile count = %v, wanted %v", got, want) 1114 } 1115 if got, want := impl.WorkQueue().NumRequeues(types.NamespacedName{Namespace: "foo", Name: "bar"}), 0; got != want { 1116 t.Errorf("requeues = %v, wanted %v", got, want) 1117 } 1118 } 1119 1120 type fakeError struct{} 1121 1122 var _ error = (*fakeError)(nil) 1123 1124 func (*fakeError) Error() string { 1125 return "I always error" 1126 } 1127 1128 func TestPermanentError(t *testing.T) { 1129 err := new(fakeError) 1130 permErr := NewPermanentError(err) 1131 if !IsPermanentError(permErr) { 1132 t.Errorf("Expected type %T to be a permanentError", permErr) 1133 } 1134 if IsPermanentError(err) { 1135 t.Errorf("Expected type %T to not be a permanentError", err) 1136 } 1137 1138 wrapPermErr := fmt.Errorf("wrapped: %w", permErr) 1139 if !IsPermanentError(wrapPermErr) { 1140 t.Error("Expected wrapped permanentError to be equivalent to a permanentError") 1141 } 1142 1143 unwrapErr := new(fakeError) 1144 if !errors.As(permErr, &unwrapErr) { 1145 t.Errorf("Could not unwrap %T from permanentError", unwrapErr) 1146 } 1147 } 1148 1149 func TestRequeueKey(t *testing.T) { 1150 err := new(fakeError) 1151 reqErr := NewRequeueImmediately() 1152 if ok, _ := IsRequeueKey(reqErr); !ok { 1153 t.Errorf("Expected type %T to be a requeueKeyError", reqErr) 1154 } 1155 if ok, _ := IsRequeueKey(err); ok { 1156 t.Errorf("Expected type %T to not be a requeueKeyError", err) 1157 } 1158 1159 want := 10 * time.Minute 1160 reqErr = NewRequeueAfter(want) 1161 wrapReqErr := fmt.Errorf("wrapped: %w", reqErr) 1162 if ok, got := IsRequeueKey(wrapReqErr); !ok { 1163 t.Error("Expected wrapped requeueKeyError to be equivalent to a requeueKeyError") 1164 } else if want != got { 1165 t.Errorf("IsRequeueKey() = (true, %v), wanted (true, %v)", got, want) 1166 } 1167 } 1168 1169 type errorReconciler struct{} 1170 1171 func (er *errorReconciler) Reconcile(context.Context, string) error { 1172 return new(fakeError) 1173 } 1174 1175 func TestStartAndShutdownWithErroringWork(t *testing.T) { 1176 const testTimeout = 500 * time.Millisecond 1177 1178 item := types.NamespacedName{Namespace: "", Name: "bar"} 1179 1180 impl := NewContext(context.TODO(), &errorReconciler{}, ControllerOptions{ 1181 Logger: TestLogger(t), 1182 WorkQueueName: "Testing", 1183 }) 1184 impl.EnqueueKey(item) 1185 1186 ctx, cancel := context.WithTimeout(context.Background(), testTimeout) 1187 doneCh := make(chan struct{}) 1188 go func() { 1189 defer close(doneCh) 1190 // StartAll blocks until all the worker threads finish, which shouldn't 1191 // be until we cancel the context. 1192 StartAll(ctx, impl) 1193 }() 1194 t.Cleanup(func() { 1195 cancel() 1196 <-doneCh 1197 }) 1198 1199 // Keep checking the number of requeues, send to channel to indicate success. 1200 itemRequeued := make(chan struct{}) 1201 defer close(itemRequeued) 1202 1203 var successCheck wait.ConditionWithContextFunc = func(context.Context) (bool, error) { 1204 // Check that the work was requeued in RateLimiter, as NumRequeues 1205 // can't fully reflect the real state of queue length. 1206 // Here we need to wait for NumRequeues to be more than 1, to ensure 1207 // the key get re-queued and reprocessed as expect. 1208 if impl.WorkQueue().NumRequeues(item) > 1 { 1209 itemRequeued <- struct{}{} 1210 return true, nil 1211 } 1212 return false, nil 1213 } 1214 go wait.PollUntilContextCancel(ctx, 5*time.Millisecond, true, successCheck) 1215 1216 select { 1217 case <-itemRequeued: 1218 // shut down reconciler 1219 cancel() 1220 1221 case <-doneCh: 1222 t.Fatal("StartAll finished early") 1223 1224 case <-ctx.Done(): 1225 t.Fatal("Timed out waiting for item to be requeued") 1226 } 1227 } 1228 1229 type permanentErrorReconciler struct{} 1230 1231 func (er *permanentErrorReconciler) Reconcile(context.Context, string) error { 1232 return NewPermanentError(new(fakeError)) 1233 } 1234 1235 func TestStartAndShutdownWithPermanentErroringWork(t *testing.T) { 1236 r := &permanentErrorReconciler{} 1237 impl := NewContext(context.TODO(), r, ControllerOptions{ 1238 Logger: TestLogger(t), 1239 WorkQueueName: "Testing", 1240 }) 1241 1242 ctx, cancel := context.WithCancel(context.Background()) 1243 doneCh := make(chan struct{}) 1244 go func() { 1245 defer close(doneCh) 1246 StartAll(ctx, impl) 1247 }() 1248 t.Cleanup(func() { 1249 cancel() 1250 <-doneCh 1251 }) 1252 1253 impl.EnqueueKey(types.NamespacedName{Namespace: "foo", Name: "bar"}) 1254 1255 select { 1256 case <-time.After(20 * time.Millisecond): 1257 // We don't expect completion before the context is cancelled. 1258 case <-doneCh: 1259 t.Error("StartAll finished early.") 1260 } 1261 cancel() 1262 1263 select { 1264 case <-time.After(time.Second): 1265 t.Error("Timed out waiting for controller to finish.") 1266 case <-doneCh: 1267 // We expect the work to complete. 1268 } 1269 1270 // Check that the work was not requeued in RateLimiter. 1271 if got, want := impl.WorkQueue().NumRequeues(types.NamespacedName{Namespace: "foo", Name: "bar"}), 0; got != want { 1272 t.Errorf("Requeue count = %v, wanted %v", got, want) 1273 } 1274 } 1275 1276 type requeueAfterReconciler struct { 1277 duration time.Duration 1278 } 1279 1280 func (er *requeueAfterReconciler) Reconcile(context.Context, string) error { 1281 return NewRequeueAfter(er.duration) 1282 } 1283 1284 func TestStartAndShutdownWithRequeuingWork(t *testing.T) { 1285 tests := []struct { 1286 name string 1287 minimum int 1288 duration time.Duration 1289 }{{ 1290 name: "no duration", 1291 minimum: 100, 1292 }, { 1293 name: "small duration", 1294 minimum: 2, 1295 duration: 100 * time.Millisecond, 1296 }} 1297 1298 for _, test := range tests { 1299 t.Run(test.name, func(t *testing.T) { 1300 r := &requeueAfterReconciler{duration: test.duration} 1301 impl := NewContext(context.TODO(), r, ControllerOptions{ 1302 Logger: TestLogger(t), 1303 WorkQueueName: "Testing", 1304 }) 1305 1306 ctx, cancel := context.WithCancel(context.Background()) 1307 doneCh := make(chan struct{}) 1308 go func() { 1309 defer close(doneCh) 1310 StartAll(ctx, impl) 1311 }() 1312 t.Cleanup(func() { 1313 cancel() 1314 <-doneCh 1315 }) 1316 1317 impl.EnqueueKey(types.NamespacedName{Namespace: "foo", Name: "bar"}) 1318 1319 select { 1320 case <-time.After(20 * time.Millisecond): 1321 // We don't expect completion before the context is cancelled. 1322 case <-doneCh: 1323 t.Error("StartAll finished early.") 1324 } 1325 cancel() 1326 1327 select { 1328 case <-time.After(time.Second): 1329 t.Error("Timed out waiting for controller to finish.") 1330 case <-doneCh: 1331 // We expect the work to complete. 1332 } 1333 1334 // Check that the work was not requeued in RateLimiter. 1335 if got, wantAtLeast := impl.WorkQueue().NumRequeues(types.NamespacedName{Namespace: "foo", Name: "bar"}), test.minimum; got >= wantAtLeast { 1336 t.Errorf("Requeue count = %v, wanted at least %v", got, wantAtLeast) 1337 } 1338 }) 1339 } 1340 } 1341 1342 func drainWorkQueue(wq workqueue.TypedRateLimitingInterface[any]) (hasQueue []types.NamespacedName) { 1343 for { 1344 key, shutdown := wq.Get() 1345 if key == nil && shutdown { 1346 break 1347 } 1348 hasQueue = append(hasQueue, key.(types.NamespacedName)) 1349 } 1350 return hasQueue 1351 } 1352 1353 type fakeInformer struct { 1354 cache.SharedInformer 1355 } 1356 1357 type fakeStore struct { 1358 cache.Store 1359 } 1360 1361 func (*fakeInformer) GetStore() cache.Store { 1362 return &fakeStore{} 1363 } 1364 1365 var ( 1366 fakeKeys = []string{"foo/bar", "bar/foo", "fizz/buzz"} 1367 fakeObjs = []interface{}{ 1368 &Resource{ 1369 ObjectMeta: metav1.ObjectMeta{ 1370 Name: "bar", 1371 Namespace: "foo", 1372 }, 1373 }, 1374 &Resource{ 1375 ObjectMeta: metav1.ObjectMeta{ 1376 Name: "foo", 1377 Namespace: "bar", 1378 }, 1379 }, 1380 &Resource{ 1381 ObjectMeta: metav1.ObjectMeta{ 1382 Name: "buzz", 1383 Namespace: "fizz", 1384 }, 1385 }, 1386 } 1387 ) 1388 1389 func (*fakeStore) ListKeys() []string { 1390 return fakeKeys 1391 } 1392 1393 func (*fakeStore) List() []interface{} { 1394 return fakeObjs 1395 } 1396 1397 func TestImplGlobalResync(t *testing.T) { 1398 r := &CountingReconciler{} 1399 impl := NewContext(context.TODO(), r, ControllerOptions{ 1400 Logger: TestLogger(t), 1401 WorkQueueName: "Testing", 1402 }) 1403 1404 ctx, cancel := context.WithCancel(context.Background()) 1405 doneCh := make(chan struct{}) 1406 go func() { 1407 defer close(doneCh) 1408 StartAll(ctx, impl) 1409 }() 1410 t.Cleanup(func() { 1411 cancel() 1412 <-doneCh 1413 }) 1414 1415 impl.GlobalResync(&fakeInformer{}) 1416 1417 // The global resync delays enqueuing things by a second with a jitter that 1418 // goes up to len(fakeObjs) times a second: time.Duration(1+len(fakeObjs)) * time.Second. 1419 // In this test, the fast lane is empty, so we can assume immediate enqueuing. 1420 select { 1421 case <-time.After(50 * time.Millisecond): 1422 // We don't expect completion before the context is cancelled. 1423 case <-doneCh: 1424 t.Error("StartAll finished early.") 1425 } 1426 cancel() 1427 1428 select { 1429 case <-time.After(time.Second): 1430 t.Error("Timed out waiting for controller to finish.") 1431 case <-doneCh: 1432 // We expect the work to complete. 1433 } 1434 1435 if got, want := r.count.Load(), int32(3); want != got { 1436 t.Errorf("GlobalResync: want = %v, got = %v", want, got) 1437 } 1438 } 1439 1440 type fixedInformer struct { 1441 m sync.Mutex 1442 sunk bool 1443 done bool 1444 } 1445 1446 var _ Informer = (*fixedInformer)(nil) 1447 1448 func (fi *fixedInformer) Run(stopCh <-chan struct{}) { 1449 <-stopCh 1450 1451 fi.m.Lock() 1452 defer fi.m.Unlock() 1453 fi.done = true 1454 } 1455 1456 func (fi *fixedInformer) HasSynced() bool { 1457 fi.m.Lock() 1458 defer fi.m.Unlock() 1459 return fi.sunk 1460 } 1461 1462 func (fi *fixedInformer) ToggleSynced(b bool) { 1463 fi.m.Lock() 1464 defer fi.m.Unlock() 1465 fi.sunk = b 1466 } 1467 1468 func (fi *fixedInformer) Done() bool { 1469 fi.m.Lock() 1470 defer fi.m.Unlock() 1471 return fi.done 1472 } 1473 1474 func TestStartInformersSuccess(t *testing.T) { 1475 errCh := make(chan error) 1476 defer close(errCh) 1477 1478 fi := &fixedInformer{sunk: true} 1479 1480 stopCh := make(chan struct{}) 1481 defer close(stopCh) 1482 go func() { 1483 errCh <- StartInformers(stopCh, fi) 1484 }() 1485 1486 select { 1487 case err := <-errCh: 1488 if err != nil { 1489 t.Error("Unexpected error:", err) 1490 } 1491 case <-time.After(time.Second): 1492 t.Error("Timed out waiting for informers to sync.") 1493 } 1494 } 1495 1496 func TestStartInformersEventualSuccess(t *testing.T) { 1497 errCh := make(chan error) 1498 defer close(errCh) 1499 1500 fi := &fixedInformer{sunk: false} 1501 1502 stopCh := make(chan struct{}) 1503 defer close(stopCh) 1504 go func() { 1505 errCh <- StartInformers(stopCh, fi) 1506 }() 1507 1508 select { 1509 case <-time.After(50 * time.Millisecond): 1510 // Wait a brief period to ensure nothing is sent. 1511 case err := <-errCh: 1512 t.Fatal("Unexpected send on errCh:", err) 1513 } 1514 1515 // Let the Sync complete. 1516 fi.ToggleSynced(true) 1517 1518 select { 1519 case err := <-errCh: 1520 if err != nil { 1521 t.Error("Unexpected error:", err) 1522 } 1523 case <-time.After(time.Second): 1524 t.Error("Timed out waiting for informers to sync.") 1525 } 1526 } 1527 1528 func TestStartInformersFailure(t *testing.T) { 1529 errCh := make(chan error) 1530 defer close(errCh) 1531 1532 fi := &fixedInformer{sunk: false} 1533 1534 stopCh := make(chan struct{}) 1535 go func() { 1536 errCh <- StartInformers(stopCh, fi) 1537 }() 1538 1539 select { 1540 case <-time.After(50 * time.Millisecond): 1541 // Wait a brief period to ensure nothing is sent. 1542 case err := <-errCh: 1543 t.Fatal("Unexpected send on errCh:", err) 1544 } 1545 1546 // Now close the stopCh and we should see an error sent. 1547 close(stopCh) 1548 1549 select { 1550 case err := <-errCh: 1551 if err == nil { 1552 t.Error("Unexpected success syncing informers after stopCh closed.") 1553 } 1554 case <-time.After(time.Second): 1555 t.Error("Timed out waiting for informers to sync.") 1556 } 1557 } 1558 1559 func TestRunInformersSuccess(t *testing.T) { 1560 errCh := make(chan error) 1561 defer close(errCh) 1562 1563 fi := &fixedInformer{sunk: true} 1564 1565 stopCh := make(chan struct{}) 1566 go func() { 1567 _, err := RunInformers(stopCh, fi) 1568 errCh <- err 1569 }() 1570 1571 select { 1572 case err := <-errCh: 1573 if err != nil { 1574 t.Fatal("Unexpected error:", err) 1575 } 1576 case <-time.After(time.Second): 1577 t.Fatal("Timed out waiting for informers to sync.") 1578 } 1579 1580 close(stopCh) 1581 } 1582 1583 func TestRunInformersEventualSuccess(t *testing.T) { 1584 errCh := make(chan error) 1585 defer close(errCh) 1586 1587 fi := &fixedInformer{sunk: false} 1588 1589 stopCh := make(chan struct{}) 1590 go func() { 1591 _, err := RunInformers(stopCh, fi) 1592 errCh <- err 1593 }() 1594 1595 select { 1596 case <-time.After(50 * time.Millisecond): 1597 // Wait a brief period to ensure nothing is sent. 1598 case err := <-errCh: 1599 t.Fatal("Unexpected send on errCh:", err) 1600 } 1601 1602 // Let the Sync complete. 1603 fi.ToggleSynced(true) 1604 1605 select { 1606 case err := <-errCh: 1607 if err != nil { 1608 t.Fatal("Unexpected error:", err) 1609 } 1610 case <-time.After(time.Second): 1611 t.Fatal("Timed out waiting for informers to sync.") 1612 } 1613 1614 close(stopCh) 1615 } 1616 1617 func TestRunInformersFailure(t *testing.T) { 1618 errCh := make(chan error) 1619 defer close(errCh) 1620 1621 fi := &fixedInformer{sunk: false} 1622 1623 stopCh := make(chan struct{}) 1624 go func() { 1625 _, err := RunInformers(stopCh, fi) 1626 errCh <- err 1627 }() 1628 1629 select { 1630 case <-time.After(50 * time.Millisecond): 1631 // Wait a brief period to ensure nothing is sent. 1632 case err := <-errCh: 1633 t.Fatal("Unexpected send on errCh:", err) 1634 } 1635 1636 // Now close the stopCh and we should see an error sent. 1637 close(stopCh) 1638 1639 select { 1640 case err := <-errCh: 1641 if err == nil { 1642 t.Fatal("Unexpected success syncing informers after stopCh closed.") 1643 } 1644 case <-time.After(time.Second): 1645 t.Fatal("Timed out waiting for informers to sync.") 1646 } 1647 } 1648 1649 func TestRunInformersFinished(t *testing.T) { 1650 fi := &fixedInformer{sunk: true} 1651 defer func() { 1652 if !fi.Done() { 1653 t.Fatalf("Test didn't wait for informers to finish") 1654 } 1655 }() 1656 1657 ctx, cancel := context.WithCancel(TestContextWithLogger(t)) 1658 t.Cleanup(cancel) 1659 1660 waitInformers, err := RunInformers(ctx.Done(), fi) 1661 if err != nil { 1662 t.Fatal("Failed to start informers:", err) 1663 } 1664 1665 cancel() 1666 1667 ch := make(chan struct{}) 1668 go func() { 1669 waitInformers() 1670 ch <- struct{}{} 1671 }() 1672 1673 select { 1674 case <-ch: 1675 case <-time.After(time.Second): 1676 t.Fatal("Timed out waiting for informers to finish.") 1677 } 1678 } 1679 1680 func TestGetResyncPeriod(t *testing.T) { 1681 ctx := context.Background() 1682 1683 if got := GetResyncPeriod(ctx); got != DefaultResyncPeriod { 1684 t.Errorf("GetResyncPeriod() = %v, wanted %v", got, nil) 1685 } 1686 1687 bob := 30 * time.Second 1688 ctx = WithResyncPeriod(ctx, bob) 1689 1690 if want, got := bob, GetResyncPeriod(ctx); got != want { 1691 t.Errorf("GetResyncPeriod() = %v, wanted %v", got, want) 1692 } 1693 1694 tribob := 90 * time.Second 1695 if want, got := tribob, GetTrackerLease(ctx); got != want { 1696 t.Errorf("GetTrackerLease() = %v, wanted %v", got, want) 1697 } 1698 } 1699 1700 func TestGetEventRecorder(t *testing.T) { 1701 ctx := context.Background() 1702 1703 if got := GetEventRecorder(ctx); got != nil { 1704 t.Errorf("GetEventRecorder() = %v, wanted nil", got) 1705 } 1706 1707 ctx = WithEventRecorder(ctx, record.NewFakeRecorder(1000)) 1708 1709 if got := GetEventRecorder(ctx); got == nil { 1710 t.Error("GetEventRecorder() = nil, wanted non-nil") 1711 } 1712 }