k8s.io/client-go@v0.31.1/tools/cache/controller_test.go (about) 1 /* 2 Copyright 2015 The Kubernetes 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 cache 18 19 import ( 20 "fmt" 21 "math/rand" 22 "sync" 23 "sync/atomic" 24 "testing" 25 "time" 26 27 v1 "k8s.io/api/core/v1" 28 apiequality "k8s.io/apimachinery/pkg/api/equality" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/apimachinery/pkg/util/sets" 32 "k8s.io/apimachinery/pkg/util/wait" 33 "k8s.io/apimachinery/pkg/watch" 34 fcache "k8s.io/client-go/tools/cache/testing" 35 36 fuzz "github.com/google/gofuzz" 37 ) 38 39 func Example() { 40 // source simulates an apiserver object endpoint. 41 source := fcache.NewFakeControllerSource() 42 43 // This will hold the downstream state, as we know it. 44 downstream := NewStore(DeletionHandlingMetaNamespaceKeyFunc) 45 46 // This will hold incoming changes. Note how we pass downstream in as a 47 // KeyLister, that way resync operations will result in the correct set 48 // of update/delete deltas. 49 fifo := NewDeltaFIFOWithOptions(DeltaFIFOOptions{ 50 KeyFunction: MetaNamespaceKeyFunc, 51 KnownObjects: downstream, 52 }) 53 54 // Let's do threadsafe output to get predictable test results. 55 deletionCounter := make(chan string, 1000) 56 57 cfg := &Config{ 58 Queue: fifo, 59 ListerWatcher: source, 60 ObjectType: &v1.Pod{}, 61 FullResyncPeriod: time.Millisecond * 100, 62 RetryOnError: false, 63 64 // Let's implement a simple controller that just deletes 65 // everything that comes in. 66 Process: func(obj interface{}, isInInitialList bool) error { 67 // Obj is from the Pop method of the Queue we make above. 68 newest := obj.(Deltas).Newest() 69 70 if newest.Type != Deleted { 71 // Update our downstream store. 72 err := downstream.Add(newest.Object) 73 if err != nil { 74 return err 75 } 76 77 // Delete this object. 78 source.Delete(newest.Object.(runtime.Object)) 79 } else { 80 // Update our downstream store. 81 err := downstream.Delete(newest.Object) 82 if err != nil { 83 return err 84 } 85 86 // fifo's KeyOf is easiest, because it handles 87 // DeletedFinalStateUnknown markers. 88 key, err := fifo.KeyOf(newest.Object) 89 if err != nil { 90 return err 91 } 92 93 // Report this deletion. 94 deletionCounter <- key 95 } 96 return nil 97 }, 98 } 99 100 // Create the controller and run it until we close stop. 101 stop := make(chan struct{}) 102 defer close(stop) 103 go New(cfg).Run(stop) 104 105 // Let's add a few objects to the source. 106 testIDs := []string{"a-hello", "b-controller", "c-framework"} 107 for _, name := range testIDs { 108 // Note that these pods are not valid-- the fake source doesn't 109 // call validation or anything. 110 source.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: name}}) 111 } 112 113 // Let's wait for the controller to process the things we just added. 114 outputSet := sets.String{} 115 for i := 0; i < len(testIDs); i++ { 116 outputSet.Insert(<-deletionCounter) 117 } 118 119 for _, key := range outputSet.List() { 120 fmt.Println(key) 121 } 122 // Output: 123 // a-hello 124 // b-controller 125 // c-framework 126 } 127 128 func ExampleNewInformer() { 129 // source simulates an apiserver object endpoint. 130 source := fcache.NewFakeControllerSource() 131 132 // Let's do threadsafe output to get predictable test results. 133 deletionCounter := make(chan string, 1000) 134 135 // Make a controller that immediately deletes anything added to it, and 136 // logs anything deleted. 137 _, controller := NewInformer( 138 source, 139 &v1.Pod{}, 140 time.Millisecond*100, 141 ResourceEventHandlerDetailedFuncs{ 142 AddFunc: func(obj interface{}, isInInitialList bool) { 143 source.Delete(obj.(runtime.Object)) 144 }, 145 DeleteFunc: func(obj interface{}) { 146 key, err := DeletionHandlingMetaNamespaceKeyFunc(obj) 147 if err != nil { 148 key = "oops something went wrong with the key" 149 } 150 151 // Report this deletion. 152 deletionCounter <- key 153 }, 154 }, 155 ) 156 157 // Run the controller and run it until we close stop. 158 stop := make(chan struct{}) 159 defer close(stop) 160 go controller.Run(stop) 161 162 // Let's add a few objects to the source. 163 testIDs := []string{"a-hello", "b-controller", "c-framework"} 164 for _, name := range testIDs { 165 // Note that these pods are not valid-- the fake source doesn't 166 // call validation or anything. 167 source.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: name}}) 168 } 169 170 // Let's wait for the controller to process the things we just added. 171 outputSet := sets.String{} 172 for i := 0; i < len(testIDs); i++ { 173 outputSet.Insert(<-deletionCounter) 174 } 175 176 for _, key := range outputSet.List() { 177 fmt.Println(key) 178 } 179 // Output: 180 // a-hello 181 // b-controller 182 // c-framework 183 } 184 185 func TestHammerController(t *testing.T) { 186 // This test executes a bunch of requests through the fake source and 187 // controller framework to make sure there's no locking/threading 188 // errors. If an error happens, it should hang forever or trigger the 189 // race detector. 190 191 // source simulates an apiserver object endpoint. 192 source := fcache.NewFakeControllerSource() 193 194 // Let's do threadsafe output to get predictable test results. 195 outputSetLock := sync.Mutex{} 196 // map of key to operations done on the key 197 outputSet := map[string][]string{} 198 199 recordFunc := func(eventType string, obj interface{}) { 200 key, err := DeletionHandlingMetaNamespaceKeyFunc(obj) 201 if err != nil { 202 t.Errorf("something wrong with key: %v", err) 203 key = "oops something went wrong with the key" 204 } 205 206 // Record some output when items are deleted. 207 outputSetLock.Lock() 208 defer outputSetLock.Unlock() 209 outputSet[key] = append(outputSet[key], eventType) 210 } 211 212 // Make a controller which just logs all the changes it gets. 213 _, controller := NewInformer( 214 source, 215 &v1.Pod{}, 216 time.Millisecond*100, 217 ResourceEventHandlerDetailedFuncs{ 218 AddFunc: func(obj interface{}, isInInitialList bool) { recordFunc("add", obj) }, 219 UpdateFunc: func(oldObj, newObj interface{}) { recordFunc("update", newObj) }, 220 DeleteFunc: func(obj interface{}) { recordFunc("delete", obj) }, 221 }, 222 ) 223 224 if controller.HasSynced() { 225 t.Errorf("Expected HasSynced() to return false before we started the controller") 226 } 227 228 // Run the controller and run it until we close stop. 229 stop := make(chan struct{}) 230 go controller.Run(stop) 231 232 // Let's wait for the controller to do its initial sync 233 wait.Poll(100*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) { 234 return controller.HasSynced(), nil 235 }) 236 if !controller.HasSynced() { 237 t.Errorf("Expected HasSynced() to return true after the initial sync") 238 } 239 240 wg := sync.WaitGroup{} 241 const threads = 3 242 wg.Add(threads) 243 for i := 0; i < threads; i++ { 244 go func() { 245 defer wg.Done() 246 // Let's add a few objects to the source. 247 currentNames := sets.String{} 248 rs := rand.NewSource(rand.Int63()) 249 f := fuzz.New().NilChance(.5).NumElements(0, 2).RandSource(rs) 250 for i := 0; i < 100; i++ { 251 var name string 252 var isNew bool 253 if currentNames.Len() == 0 || rand.Intn(3) == 1 { 254 f.Fuzz(&name) 255 isNew = true 256 } else { 257 l := currentNames.List() 258 name = l[rand.Intn(len(l))] 259 } 260 261 pod := &v1.Pod{} 262 f.Fuzz(pod) 263 pod.ObjectMeta.Name = name 264 pod.ObjectMeta.Namespace = "default" 265 // Add, update, or delete randomly. 266 // Note that these pods are not valid-- the fake source doesn't 267 // call validation or perform any other checking. 268 if isNew { 269 currentNames.Insert(name) 270 source.Add(pod) 271 continue 272 } 273 switch rand.Intn(2) { 274 case 0: 275 currentNames.Insert(name) 276 source.Modify(pod) 277 case 1: 278 currentNames.Delete(name) 279 source.Delete(pod) 280 } 281 } 282 }() 283 } 284 wg.Wait() 285 286 // Let's wait for the controller to finish processing the things we just added. 287 // TODO: look in the queue to see how many items need to be processed. 288 time.Sleep(100 * time.Millisecond) 289 close(stop) 290 291 // TODO: Verify that no goroutines were leaked here and that everything shut 292 // down cleanly. 293 294 outputSetLock.Lock() 295 t.Logf("got: %#v", outputSet) 296 } 297 298 func TestUpdate(t *testing.T) { 299 // This test is going to exercise the various paths that result in a 300 // call to update. 301 302 // source simulates an apiserver object endpoint. 303 source := fcache.NewFakeControllerSource() 304 305 const ( 306 FROM = "from" 307 TO = "to" 308 ) 309 310 // These are the transitions we expect to see; because this is 311 // asynchronous, there are a lot of valid possibilities. 312 type pair struct{ from, to string } 313 allowedTransitions := map[pair]bool{ 314 {FROM, TO}: true, 315 316 // Because a resync can happen when we've already observed one 317 // of the above but before the item is deleted. 318 {TO, TO}: true, 319 // Because a resync could happen before we observe an update. 320 {FROM, FROM}: true, 321 } 322 323 pod := func(name, check string, final bool) *v1.Pod { 324 p := &v1.Pod{ 325 ObjectMeta: metav1.ObjectMeta{ 326 Name: name, 327 Labels: map[string]string{"check": check}, 328 }, 329 } 330 if final { 331 p.Labels["final"] = "true" 332 } 333 return p 334 } 335 deletePod := func(p *v1.Pod) bool { 336 return p.Labels["final"] == "true" 337 } 338 339 tests := []func(string){ 340 func(name string) { 341 name = "a-" + name 342 source.Add(pod(name, FROM, false)) 343 source.Modify(pod(name, TO, true)) 344 }, 345 } 346 347 const threads = 3 348 349 var testDoneWG sync.WaitGroup 350 testDoneWG.Add(threads * len(tests)) 351 352 // Make a controller that deletes things once it observes an update. 353 // It calls Done() on the wait group on deletions so we can tell when 354 // everything we've added has been deleted. 355 watchCh := make(chan struct{}) 356 _, controller := NewInformer( 357 &testLW{ 358 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 359 watch, err := source.Watch(options) 360 close(watchCh) 361 return watch, err 362 }, 363 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 364 return source.List(options) 365 }, 366 }, 367 &v1.Pod{}, 368 0, 369 ResourceEventHandlerFuncs{ 370 UpdateFunc: func(oldObj, newObj interface{}) { 371 o, n := oldObj.(*v1.Pod), newObj.(*v1.Pod) 372 from, to := o.Labels["check"], n.Labels["check"] 373 if !allowedTransitions[pair{from, to}] { 374 t.Errorf("observed transition %q -> %q for %v", from, to, n.Name) 375 } 376 if deletePod(n) { 377 source.Delete(n) 378 } 379 }, 380 DeleteFunc: func(obj interface{}) { 381 testDoneWG.Done() 382 }, 383 }, 384 ) 385 386 // Run the controller and run it until we close stop. 387 // Once Run() is called, calls to testDoneWG.Done() might start, so 388 // all testDoneWG.Add() calls must happen before this point 389 stop := make(chan struct{}) 390 go controller.Run(stop) 391 <-watchCh 392 393 // run every test a few times, in parallel 394 var wg sync.WaitGroup 395 wg.Add(threads * len(tests)) 396 for i := 0; i < threads; i++ { 397 for j, f := range tests { 398 go func(name string, f func(string)) { 399 defer wg.Done() 400 f(name) 401 }(fmt.Sprintf("%v-%v", i, j), f) 402 } 403 } 404 wg.Wait() 405 406 // Let's wait for the controller to process the things we just added. 407 testDoneWG.Wait() 408 close(stop) 409 } 410 411 func TestPanicPropagated(t *testing.T) { 412 // source simulates an apiserver object endpoint. 413 source := fcache.NewFakeControllerSource() 414 415 // Make a controller that just panic if the AddFunc is called. 416 _, controller := NewInformer( 417 source, 418 &v1.Pod{}, 419 time.Millisecond*100, 420 ResourceEventHandlerDetailedFuncs{ 421 AddFunc: func(obj interface{}, isInInitialList bool) { 422 // Create a panic. 423 panic("Just panic.") 424 }, 425 }, 426 ) 427 428 // Run the controller and run it until we close stop. 429 stop := make(chan struct{}) 430 defer close(stop) 431 432 propagated := make(chan interface{}) 433 go func() { 434 defer func() { 435 if r := recover(); r != nil { 436 propagated <- r 437 } 438 }() 439 controller.Run(stop) 440 }() 441 // Let's add a object to the source. It will trigger a panic. 442 source.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "test"}}) 443 444 // Check if the panic propagated up. 445 select { 446 case p := <-propagated: 447 if p == "Just panic." { 448 t.Logf("Test Passed") 449 } else { 450 t.Errorf("unrecognized panic in controller run: %v", p) 451 } 452 case <-time.After(wait.ForeverTestTimeout): 453 t.Errorf("timeout: the panic failed to propagate from the controller run method!") 454 } 455 } 456 457 func TestTransformingInformer(t *testing.T) { 458 // source simulates an apiserver object endpoint. 459 source := fcache.NewFakeControllerSource() 460 461 makePod := func(name, generation string) *v1.Pod { 462 return &v1.Pod{ 463 ObjectMeta: metav1.ObjectMeta{ 464 Name: name, 465 Namespace: "namespace", 466 Labels: map[string]string{"generation": generation}, 467 }, 468 Spec: v1.PodSpec{ 469 Hostname: "hostname", 470 Subdomain: "subdomain", 471 }, 472 } 473 } 474 expectedPod := func(name, generation string) *v1.Pod { 475 pod := makePod(name, generation) 476 pod.Spec.Hostname = "new-hostname" 477 pod.Spec.Subdomain = "" 478 pod.Spec.NodeName = "nodename" 479 return pod 480 } 481 482 source.Add(makePod("pod1", "1")) 483 source.Modify(makePod("pod1", "2")) 484 485 type event struct { 486 eventType watch.EventType 487 previous interface{} 488 current interface{} 489 } 490 events := make(chan event, 10) 491 recordEvent := func(eventType watch.EventType, previous, current interface{}) { 492 events <- event{eventType: eventType, previous: previous, current: current} 493 } 494 verifyEvent := func(eventType watch.EventType, previous, current interface{}) { 495 select { 496 case event := <-events: 497 if event.eventType != eventType { 498 t.Errorf("expected type %v, got %v", eventType, event.eventType) 499 } 500 if !apiequality.Semantic.DeepEqual(event.previous, previous) { 501 t.Errorf("expected previous object %#v, got %#v", previous, event.previous) 502 } 503 if !apiequality.Semantic.DeepEqual(event.current, current) { 504 t.Errorf("expected object %#v, got %#v", current, event.current) 505 } 506 case <-time.After(wait.ForeverTestTimeout): 507 t.Errorf("failed to get event") 508 } 509 } 510 511 podTransformer := func(obj interface{}) (interface{}, error) { 512 pod, ok := obj.(*v1.Pod) 513 if !ok { 514 return nil, fmt.Errorf("unexpected object type: %T", obj) 515 } 516 pod.Spec.Hostname = "new-hostname" 517 pod.Spec.Subdomain = "" 518 pod.Spec.NodeName = "nodename" 519 520 // Clear out ResourceVersion to simplify comparisons. 521 pod.ResourceVersion = "" 522 523 return pod, nil 524 } 525 526 store, controller := NewTransformingInformer( 527 source, 528 &v1.Pod{}, 529 0, 530 ResourceEventHandlerDetailedFuncs{ 531 AddFunc: func(obj interface{}, isInInitialList bool) { recordEvent(watch.Added, nil, obj) }, 532 UpdateFunc: func(oldObj, newObj interface{}) { recordEvent(watch.Modified, oldObj, newObj) }, 533 DeleteFunc: func(obj interface{}) { recordEvent(watch.Deleted, obj, nil) }, 534 }, 535 podTransformer, 536 ) 537 538 verifyStore := func(expectedItems []interface{}) { 539 items := store.List() 540 if len(items) != len(expectedItems) { 541 t.Errorf("unexpected items %v, expected %v", items, expectedItems) 542 } 543 for _, expectedItem := range expectedItems { 544 found := false 545 for _, item := range items { 546 if apiequality.Semantic.DeepEqual(item, expectedItem) { 547 found = true 548 } 549 } 550 if !found { 551 t.Errorf("expected item %v not found in %v", expectedItem, items) 552 } 553 } 554 } 555 556 stopCh := make(chan struct{}) 557 go controller.Run(stopCh) 558 559 verifyEvent(watch.Added, nil, expectedPod("pod1", "2")) 560 verifyStore([]interface{}{expectedPod("pod1", "2")}) 561 562 source.Add(makePod("pod2", "1")) 563 verifyEvent(watch.Added, nil, expectedPod("pod2", "1")) 564 verifyStore([]interface{}{expectedPod("pod1", "2"), expectedPod("pod2", "1")}) 565 566 source.Add(makePod("pod3", "1")) 567 verifyEvent(watch.Added, nil, expectedPod("pod3", "1")) 568 569 source.Modify(makePod("pod2", "2")) 570 verifyEvent(watch.Modified, expectedPod("pod2", "1"), expectedPod("pod2", "2")) 571 572 source.Delete(makePod("pod1", "2")) 573 verifyEvent(watch.Deleted, expectedPod("pod1", "2"), nil) 574 verifyStore([]interface{}{expectedPod("pod2", "2"), expectedPod("pod3", "1")}) 575 576 close(stopCh) 577 } 578 579 func TestTransformingInformerRace(t *testing.T) { 580 // source simulates an apiserver object endpoint. 581 source := fcache.NewFakeControllerSource() 582 583 label := "to-be-transformed" 584 makePod := func(name string) *v1.Pod { 585 return &v1.Pod{ 586 ObjectMeta: metav1.ObjectMeta{ 587 Name: name, 588 Namespace: "namespace", 589 Labels: map[string]string{label: "true"}, 590 }, 591 Spec: v1.PodSpec{ 592 Hostname: "hostname", 593 }, 594 } 595 } 596 597 badTransform := atomic.Bool{} 598 podTransformer := func(obj interface{}) (interface{}, error) { 599 pod, ok := obj.(*v1.Pod) 600 if !ok { 601 return nil, fmt.Errorf("unexpected object type: %T", obj) 602 } 603 if pod.ObjectMeta.Labels[label] != "true" { 604 badTransform.Store(true) 605 return nil, fmt.Errorf("object already transformed: %#v", obj) 606 } 607 pod.ObjectMeta.Labels[label] = "false" 608 return pod, nil 609 } 610 611 numObjs := 5 612 for i := 0; i < numObjs; i++ { 613 source.Add(makePod(fmt.Sprintf("pod-%d", i))) 614 } 615 616 type event struct{} 617 events := make(chan event, numObjs) 618 recordEvent := func(eventType watch.EventType, previous, current interface{}) { 619 events <- event{} 620 } 621 checkEvents := func(count int) { 622 for i := 0; i < count; i++ { 623 <-events 624 } 625 } 626 store, controller := NewTransformingInformer( 627 source, 628 &v1.Pod{}, 629 5*time.Millisecond, 630 ResourceEventHandlerDetailedFuncs{ 631 AddFunc: func(obj interface{}, isInInitialList bool) { recordEvent(watch.Added, nil, obj) }, 632 UpdateFunc: func(oldObj, newObj interface{}) { recordEvent(watch.Modified, oldObj, newObj) }, 633 DeleteFunc: func(obj interface{}) { recordEvent(watch.Deleted, obj, nil) }, 634 }, 635 podTransformer, 636 ) 637 638 stopCh := make(chan struct{}) 639 go controller.Run(stopCh) 640 641 checkEvents(numObjs) 642 643 // Periodically fetch objects to ensure no access races. 644 wg := sync.WaitGroup{} 645 errors := make(chan error, numObjs) 646 for i := 0; i < numObjs; i++ { 647 wg.Add(1) 648 go func(index int) { 649 defer wg.Done() 650 key := fmt.Sprintf("namespace/pod-%d", index) 651 for { 652 select { 653 case <-stopCh: 654 return 655 default: 656 } 657 658 obj, ok, err := store.GetByKey(key) 659 if !ok || err != nil { 660 errors <- fmt.Errorf("couldn't get the object for %v", key) 661 return 662 } 663 pod := obj.(*v1.Pod) 664 if pod.ObjectMeta.Labels[label] != "false" { 665 errors <- fmt.Errorf("unexpected object: %#v", pod) 666 return 667 } 668 } 669 }(i) 670 } 671 672 // Let resyncs to happen for some time. 673 time.Sleep(time.Second) 674 675 close(stopCh) 676 wg.Wait() 677 close(errors) 678 for err := range errors { 679 t.Error(err) 680 } 681 682 if badTransform.Load() { 683 t.Errorf("unexpected transformation happened") 684 } 685 } 686 687 func TestDeletionHandlingObjectToName(t *testing.T) { 688 cm := &v1.ConfigMap{ 689 ObjectMeta: metav1.ObjectMeta{ 690 Name: "testname", 691 Namespace: "testnamespace", 692 }, 693 } 694 stringKey, err := MetaNamespaceKeyFunc(cm) 695 if err != nil { 696 t.Error(err) 697 } 698 deleted := DeletedFinalStateUnknown{ 699 Key: stringKey, 700 Obj: cm, 701 } 702 expected, err := ObjectToName(cm) 703 if err != nil { 704 t.Error(err) 705 } 706 actual, err := DeletionHandlingObjectToName(deleted) 707 if err != nil { 708 t.Error(err) 709 } 710 if expected != actual { 711 t.Errorf("Expected %#v, got %#v", expected, actual) 712 } 713 }