k8s.io/client-go@v0.22.2/tools/cache/reflector_test.go (about) 1 /* 2 Copyright 2014 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 "errors" 21 "fmt" 22 "math/rand" 23 "reflect" 24 "strconv" 25 "syscall" 26 "testing" 27 "time" 28 29 v1 "k8s.io/api/core/v1" 30 apierrors "k8s.io/apimachinery/pkg/api/errors" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 33 "k8s.io/apimachinery/pkg/runtime" 34 "k8s.io/apimachinery/pkg/runtime/schema" 35 "k8s.io/apimachinery/pkg/util/clock" 36 "k8s.io/apimachinery/pkg/util/wait" 37 "k8s.io/apimachinery/pkg/watch" 38 ) 39 40 var nevererrc chan error 41 42 type testLW struct { 43 ListFunc func(options metav1.ListOptions) (runtime.Object, error) 44 WatchFunc func(options metav1.ListOptions) (watch.Interface, error) 45 } 46 47 func (t *testLW) List(options metav1.ListOptions) (runtime.Object, error) { 48 return t.ListFunc(options) 49 } 50 func (t *testLW) Watch(options metav1.ListOptions) (watch.Interface, error) { 51 return t.WatchFunc(options) 52 } 53 54 func TestCloseWatchChannelOnError(t *testing.T) { 55 r := NewReflector(&testLW{}, &v1.Pod{}, NewStore(MetaNamespaceKeyFunc), 0) 56 pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar"}} 57 fw := watch.NewFake() 58 r.listerWatcher = &testLW{ 59 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 60 return fw, nil 61 }, 62 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 63 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "1"}}, nil 64 }, 65 } 66 go r.ListAndWatch(wait.NeverStop) 67 fw.Error(pod) 68 select { 69 case _, ok := <-fw.ResultChan(): 70 if ok { 71 t.Errorf("Watch channel left open after cancellation") 72 } 73 case <-time.After(wait.ForeverTestTimeout): 74 t.Errorf("the cancellation is at least %s late", wait.ForeverTestTimeout.String()) 75 break 76 } 77 } 78 79 func TestRunUntil(t *testing.T) { 80 stopCh := make(chan struct{}) 81 store := NewStore(MetaNamespaceKeyFunc) 82 r := NewReflector(&testLW{}, &v1.Pod{}, store, 0) 83 fw := watch.NewFake() 84 r.listerWatcher = &testLW{ 85 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 86 return fw, nil 87 }, 88 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 89 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "1"}}, nil 90 }, 91 } 92 go r.Run(stopCh) 93 // Synchronously add a dummy pod into the watch channel so we 94 // know the RunUntil go routine is in the watch handler. 95 fw.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar"}}) 96 close(stopCh) 97 select { 98 case _, ok := <-fw.ResultChan(): 99 if ok { 100 t.Errorf("Watch channel left open after stopping the watch") 101 } 102 case <-time.After(wait.ForeverTestTimeout): 103 t.Errorf("the cancellation is at least %s late", wait.ForeverTestTimeout.String()) 104 break 105 } 106 } 107 108 func TestReflectorResyncChan(t *testing.T) { 109 s := NewStore(MetaNamespaceKeyFunc) 110 g := NewReflector(&testLW{}, &v1.Pod{}, s, time.Millisecond) 111 a, _ := g.resyncChan() 112 b := time.After(wait.ForeverTestTimeout) 113 select { 114 case <-a: 115 t.Logf("got timeout as expected") 116 case <-b: 117 t.Errorf("resyncChan() is at least 99 milliseconds late??") 118 } 119 } 120 121 func BenchmarkReflectorResyncChanMany(b *testing.B) { 122 s := NewStore(MetaNamespaceKeyFunc) 123 g := NewReflector(&testLW{}, &v1.Pod{}, s, 25*time.Millisecond) 124 // The improvement to this (calling the timer's Stop() method) makes 125 // this benchmark about 40% faster. 126 for i := 0; i < b.N; i++ { 127 g.resyncPeriod = time.Duration(rand.Float64() * float64(time.Millisecond) * 25) 128 _, stop := g.resyncChan() 129 stop() 130 } 131 } 132 133 func TestReflectorWatchHandlerError(t *testing.T) { 134 s := NewStore(MetaNamespaceKeyFunc) 135 g := NewReflector(&testLW{}, &v1.Pod{}, s, 0) 136 fw := watch.NewFake() 137 go func() { 138 fw.Stop() 139 }() 140 var resumeRV string 141 err := g.watchHandler(time.Now(), fw, &resumeRV, nevererrc, wait.NeverStop) 142 if err == nil { 143 t.Errorf("unexpected non-error") 144 } 145 } 146 147 func TestReflectorWatchHandler(t *testing.T) { 148 s := NewStore(MetaNamespaceKeyFunc) 149 g := NewReflector(&testLW{}, &v1.Pod{}, s, 0) 150 fw := watch.NewFake() 151 s.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}) 152 s.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar"}}) 153 go func() { 154 fw.Add(&v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "rejected"}}) 155 fw.Delete(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}) 156 fw.Modify(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar", ResourceVersion: "55"}}) 157 fw.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "baz", ResourceVersion: "32"}}) 158 fw.Stop() 159 }() 160 var resumeRV string 161 err := g.watchHandler(time.Now(), fw, &resumeRV, nevererrc, wait.NeverStop) 162 if err != nil { 163 t.Errorf("unexpected error %v", err) 164 } 165 166 mkPod := func(id string, rv string) *v1.Pod { 167 return &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: id, ResourceVersion: rv}} 168 } 169 170 table := []struct { 171 Pod *v1.Pod 172 exists bool 173 }{ 174 {mkPod("foo", ""), false}, 175 {mkPod("rejected", ""), false}, 176 {mkPod("bar", "55"), true}, 177 {mkPod("baz", "32"), true}, 178 } 179 for _, item := range table { 180 obj, exists, _ := s.Get(item.Pod) 181 if e, a := item.exists, exists; e != a { 182 t.Errorf("%v: expected %v, got %v", item.Pod, e, a) 183 } 184 if !exists { 185 continue 186 } 187 if e, a := item.Pod.ResourceVersion, obj.(*v1.Pod).ResourceVersion; e != a { 188 t.Errorf("%v: expected %v, got %v", item.Pod, e, a) 189 } 190 } 191 192 // RV should send the last version we see. 193 if e, a := "32", resumeRV; e != a { 194 t.Errorf("expected %v, got %v", e, a) 195 } 196 197 // last sync resource version should be the last version synced with store 198 if e, a := "32", g.LastSyncResourceVersion(); e != a { 199 t.Errorf("expected %v, got %v", e, a) 200 } 201 } 202 203 func TestReflectorStopWatch(t *testing.T) { 204 s := NewStore(MetaNamespaceKeyFunc) 205 g := NewReflector(&testLW{}, &v1.Pod{}, s, 0) 206 fw := watch.NewFake() 207 var resumeRV string 208 stopWatch := make(chan struct{}, 1) 209 stopWatch <- struct{}{} 210 err := g.watchHandler(time.Now(), fw, &resumeRV, nevererrc, stopWatch) 211 if err != errorStopRequested { 212 t.Errorf("expected stop error, got %q", err) 213 } 214 } 215 216 func TestReflectorListAndWatch(t *testing.T) { 217 createdFakes := make(chan *watch.FakeWatcher) 218 219 // The ListFunc says that it's at revision 1. Therefore, we expect our WatchFunc 220 // to get called at the beginning of the watch with 1, and again with 3 when we 221 // inject an error. 222 expectedRVs := []string{"1", "3"} 223 lw := &testLW{ 224 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 225 rv := options.ResourceVersion 226 fw := watch.NewFake() 227 if e, a := expectedRVs[0], rv; e != a { 228 t.Errorf("Expected rv %v, but got %v", e, a) 229 } 230 expectedRVs = expectedRVs[1:] 231 // channel is not buffered because the for loop below needs to block. But 232 // we don't want to block here, so report the new fake via a go routine. 233 go func() { createdFakes <- fw }() 234 return fw, nil 235 }, 236 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 237 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "1"}}, nil 238 }, 239 } 240 s := NewFIFO(MetaNamespaceKeyFunc) 241 r := NewReflector(lw, &v1.Pod{}, s, 0) 242 go r.ListAndWatch(wait.NeverStop) 243 244 ids := []string{"foo", "bar", "baz", "qux", "zoo"} 245 var fw *watch.FakeWatcher 246 for i, id := range ids { 247 if fw == nil { 248 fw = <-createdFakes 249 } 250 sendingRV := strconv.FormatUint(uint64(i+2), 10) 251 fw.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: id, ResourceVersion: sendingRV}}) 252 if sendingRV == "3" { 253 // Inject a failure. 254 fw.Stop() 255 fw = nil 256 } 257 } 258 259 // Verify we received the right ids with the right resource versions. 260 for i, id := range ids { 261 pod := Pop(s).(*v1.Pod) 262 if e, a := id, pod.Name; e != a { 263 t.Errorf("%v: Expected %v, got %v", i, e, a) 264 } 265 if e, a := strconv.FormatUint(uint64(i+2), 10), pod.ResourceVersion; e != a { 266 t.Errorf("%v: Expected %v, got %v", i, e, a) 267 } 268 } 269 270 if len(expectedRVs) != 0 { 271 t.Error("called watchStarter an unexpected number of times") 272 } 273 } 274 275 func TestReflectorListAndWatchWithErrors(t *testing.T) { 276 mkPod := func(id string, rv string) *v1.Pod { 277 return &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: id, ResourceVersion: rv}} 278 } 279 mkList := func(rv string, pods ...*v1.Pod) *v1.PodList { 280 list := &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: rv}} 281 for _, pod := range pods { 282 list.Items = append(list.Items, *pod) 283 } 284 return list 285 } 286 table := []struct { 287 list *v1.PodList 288 listErr error 289 events []watch.Event 290 watchErr error 291 }{ 292 { 293 list: mkList("1"), 294 events: []watch.Event{ 295 {Type: watch.Added, Object: mkPod("foo", "2")}, 296 {Type: watch.Added, Object: mkPod("bar", "3")}, 297 }, 298 }, { 299 list: mkList("3", mkPod("foo", "2"), mkPod("bar", "3")), 300 events: []watch.Event{ 301 {Type: watch.Deleted, Object: mkPod("foo", "4")}, 302 {Type: watch.Added, Object: mkPod("qux", "5")}, 303 }, 304 }, { 305 listErr: fmt.Errorf("a list error"), 306 }, { 307 list: mkList("5", mkPod("bar", "3"), mkPod("qux", "5")), 308 watchErr: fmt.Errorf("a watch error"), 309 }, { 310 list: mkList("5", mkPod("bar", "3"), mkPod("qux", "5")), 311 events: []watch.Event{ 312 {Type: watch.Added, Object: mkPod("baz", "6")}, 313 }, 314 }, { 315 list: mkList("6", mkPod("bar", "3"), mkPod("qux", "5"), mkPod("baz", "6")), 316 }, 317 } 318 319 s := NewFIFO(MetaNamespaceKeyFunc) 320 for line, item := range table { 321 if item.list != nil { 322 // Test that the list is what currently exists in the store. 323 current := s.List() 324 checkMap := map[string]string{} 325 for _, item := range current { 326 pod := item.(*v1.Pod) 327 checkMap[pod.Name] = pod.ResourceVersion 328 } 329 for _, pod := range item.list.Items { 330 if e, a := pod.ResourceVersion, checkMap[pod.Name]; e != a { 331 t.Errorf("%v: expected %v, got %v for pod %v", line, e, a, pod.Name) 332 } 333 } 334 if e, a := len(item.list.Items), len(checkMap); e != a { 335 t.Errorf("%v: expected %v, got %v", line, e, a) 336 } 337 } 338 watchRet, watchErr := item.events, item.watchErr 339 lw := &testLW{ 340 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 341 if watchErr != nil { 342 return nil, watchErr 343 } 344 watchErr = fmt.Errorf("second watch") 345 fw := watch.NewFake() 346 go func() { 347 for _, e := range watchRet { 348 fw.Action(e.Type, e.Object) 349 } 350 fw.Stop() 351 }() 352 return fw, nil 353 }, 354 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 355 return item.list, item.listErr 356 }, 357 } 358 r := NewReflector(lw, &v1.Pod{}, s, 0) 359 r.ListAndWatch(wait.NeverStop) 360 } 361 } 362 363 func TestReflectorListAndWatchInitConnBackoff(t *testing.T) { 364 maxBackoff := 50 * time.Millisecond 365 table := []struct { 366 numConnFails int 367 expLowerBound time.Duration 368 expUpperBound time.Duration 369 }{ 370 {5, 32 * time.Millisecond, 64 * time.Millisecond}, // case where maxBackoff is not hit, time should grow exponentially 371 {40, 35 * 2 * maxBackoff, 40 * 2 * maxBackoff}, // case where maxBoff is hit, backoff time should flatten 372 373 } 374 for _, test := range table { 375 t.Run(fmt.Sprintf("%d connection failures takes at least %d ms", test.numConnFails, 1<<test.numConnFails), 376 func(t *testing.T) { 377 stopCh := make(chan struct{}) 378 connFails := test.numConnFails 379 fakeClock := clock.NewFakeClock(time.Unix(0, 0)) 380 bm := wait.NewExponentialBackoffManager(time.Millisecond, maxBackoff, 100*time.Millisecond, 2.0, 1.0, fakeClock) 381 done := make(chan struct{}) 382 defer close(done) 383 go func() { 384 i := 0 385 for { 386 select { 387 case <-done: 388 return 389 default: 390 } 391 if fakeClock.HasWaiters() { 392 step := (1 << (i + 1)) * time.Millisecond 393 if step > maxBackoff*2 { 394 step = maxBackoff * 2 395 } 396 fakeClock.Step(step) 397 i++ 398 } 399 time.Sleep(100 * time.Microsecond) 400 } 401 }() 402 lw := &testLW{ 403 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 404 if connFails > 0 { 405 connFails-- 406 return nil, syscall.ECONNREFUSED 407 } 408 close(stopCh) 409 return watch.NewFake(), nil 410 }, 411 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 412 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "1"}}, nil 413 }, 414 } 415 r := &Reflector{ 416 name: "test-reflector", 417 listerWatcher: lw, 418 store: NewFIFO(MetaNamespaceKeyFunc), 419 initConnBackoffManager: bm, 420 clock: fakeClock, 421 watchErrorHandler: WatchErrorHandler(DefaultWatchErrorHandler), 422 } 423 start := fakeClock.Now() 424 err := r.ListAndWatch(stopCh) 425 elapsed := fakeClock.Since(start) 426 if err != nil { 427 t.Errorf("unexpected error %v", err) 428 } 429 if elapsed < (test.expLowerBound) { 430 t.Errorf("expected lower bound of ListAndWatch: %v, got %v", test.expLowerBound, elapsed) 431 } 432 if elapsed > (test.expUpperBound) { 433 t.Errorf("expected upper bound of ListAndWatch: %v, got %v", test.expUpperBound, elapsed) 434 } 435 }) 436 } 437 } 438 439 type fakeBackoff struct { 440 clock clock.Clock 441 calls int 442 } 443 444 func (f *fakeBackoff) Backoff() clock.Timer { 445 f.calls++ 446 return f.clock.NewTimer(time.Duration(0)) 447 } 448 449 func TestBackoffOnTooManyRequests(t *testing.T) { 450 err := apierrors.NewTooManyRequests("too many requests", 1) 451 clock := &clock.RealClock{} 452 bm := &fakeBackoff{clock: clock} 453 454 lw := &testLW{ 455 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 456 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "1"}}, nil 457 }, 458 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 459 switch bm.calls { 460 case 0: 461 return nil, err 462 case 1: 463 w := watch.NewFakeWithChanSize(1, false) 464 status := err.Status() 465 w.Error(&status) 466 return w, nil 467 default: 468 w := watch.NewFake() 469 w.Stop() 470 return w, nil 471 } 472 }, 473 } 474 475 r := &Reflector{ 476 name: "test-reflector", 477 listerWatcher: lw, 478 store: NewFIFO(MetaNamespaceKeyFunc), 479 initConnBackoffManager: bm, 480 clock: clock, 481 watchErrorHandler: WatchErrorHandler(DefaultWatchErrorHandler), 482 } 483 484 stopCh := make(chan struct{}) 485 r.ListAndWatch(stopCh) 486 close(stopCh) 487 if bm.calls != 2 { 488 t.Errorf("unexpected watch backoff calls: %d", bm.calls) 489 } 490 } 491 492 func TestReflectorResync(t *testing.T) { 493 iteration := 0 494 stopCh := make(chan struct{}) 495 rerr := errors.New("expected resync reached") 496 s := &FakeCustomStore{ 497 ResyncFunc: func() error { 498 iteration++ 499 if iteration == 2 { 500 return rerr 501 } 502 return nil 503 }, 504 } 505 506 lw := &testLW{ 507 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 508 fw := watch.NewFake() 509 return fw, nil 510 }, 511 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 512 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "0"}}, nil 513 }, 514 } 515 resyncPeriod := 1 * time.Millisecond 516 r := NewReflector(lw, &v1.Pod{}, s, resyncPeriod) 517 if err := r.ListAndWatch(stopCh); err != nil { 518 // error from Resync is not propaged up to here. 519 t.Errorf("expected error %v", err) 520 } 521 if iteration != 2 { 522 t.Errorf("exactly 2 iterations were expected, got: %v", iteration) 523 } 524 } 525 526 func TestReflectorWatchListPageSize(t *testing.T) { 527 stopCh := make(chan struct{}) 528 s := NewStore(MetaNamespaceKeyFunc) 529 530 lw := &testLW{ 531 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 532 // Stop once the reflector begins watching since we're only interested in the list. 533 close(stopCh) 534 fw := watch.NewFake() 535 return fw, nil 536 }, 537 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 538 if options.Limit != 4 { 539 t.Fatalf("Expected list Limit of 4 but got %d", options.Limit) 540 } 541 pods := make([]v1.Pod, 10) 542 for i := 0; i < 10; i++ { 543 pods[i] = v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("pod-%d", i), ResourceVersion: fmt.Sprintf("%d", i)}} 544 } 545 switch options.Continue { 546 case "": 547 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10", Continue: "C1"}, Items: pods[0:4]}, nil 548 case "C1": 549 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10", Continue: "C2"}, Items: pods[4:8]}, nil 550 case "C2": 551 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}, Items: pods[8:10]}, nil 552 default: 553 t.Fatalf("Unrecognized continue: %s", options.Continue) 554 } 555 return nil, nil 556 }, 557 } 558 r := NewReflector(lw, &v1.Pod{}, s, 0) 559 // Set resource version to test pagination also for not consistent reads. 560 r.setLastSyncResourceVersion("10") 561 // Set the reflector to paginate the list request in 4 item chunks. 562 r.WatchListPageSize = 4 563 r.ListAndWatch(stopCh) 564 565 results := s.List() 566 if len(results) != 10 { 567 t.Errorf("Expected 10 results, got %d", len(results)) 568 } 569 } 570 571 func TestReflectorNotPaginatingNotConsistentReads(t *testing.T) { 572 stopCh := make(chan struct{}) 573 s := NewStore(MetaNamespaceKeyFunc) 574 575 lw := &testLW{ 576 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 577 // Stop once the reflector begins watching since we're only interested in the list. 578 close(stopCh) 579 fw := watch.NewFake() 580 return fw, nil 581 }, 582 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 583 if options.ResourceVersion != "10" { 584 t.Fatalf("Expected ResourceVersion: \"10\", got: %s", options.ResourceVersion) 585 } 586 if options.Limit != 0 { 587 t.Fatalf("Expected list Limit of 0 but got %d", options.Limit) 588 } 589 pods := make([]v1.Pod, 10) 590 for i := 0; i < 10; i++ { 591 pods[i] = v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("pod-%d", i), ResourceVersion: fmt.Sprintf("%d", i)}} 592 } 593 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}, Items: pods}, nil 594 }, 595 } 596 r := NewReflector(lw, &v1.Pod{}, s, 0) 597 r.setLastSyncResourceVersion("10") 598 r.ListAndWatch(stopCh) 599 600 results := s.List() 601 if len(results) != 10 { 602 t.Errorf("Expected 10 results, got %d", len(results)) 603 } 604 } 605 606 func TestReflectorPaginatingNonConsistentReadsIfWatchCacheDisabled(t *testing.T) { 607 var stopCh chan struct{} 608 s := NewStore(MetaNamespaceKeyFunc) 609 610 lw := &testLW{ 611 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 612 // Stop once the reflector begins watching since we're only interested in the list. 613 close(stopCh) 614 fw := watch.NewFake() 615 return fw, nil 616 }, 617 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 618 // Check that default pager limit is set. 619 if options.Limit != 500 { 620 t.Fatalf("Expected list Limit of 500 but got %d", options.Limit) 621 } 622 pods := make([]v1.Pod, 10) 623 for i := 0; i < 10; i++ { 624 pods[i] = v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("pod-%d", i), ResourceVersion: fmt.Sprintf("%d", i)}} 625 } 626 switch options.Continue { 627 case "": 628 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10", Continue: "C1"}, Items: pods[0:4]}, nil 629 case "C1": 630 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10", Continue: "C2"}, Items: pods[4:8]}, nil 631 case "C2": 632 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}, Items: pods[8:10]}, nil 633 default: 634 t.Fatalf("Unrecognized continue: %s", options.Continue) 635 } 636 return nil, nil 637 }, 638 } 639 r := NewReflector(lw, &v1.Pod{}, s, 0) 640 641 // Initial list should initialize paginatedResult in the reflector. 642 stopCh = make(chan struct{}) 643 r.ListAndWatch(stopCh) 644 if results := s.List(); len(results) != 10 { 645 t.Errorf("Expected 10 results, got %d", len(results)) 646 } 647 648 // Since initial list for ResourceVersion="0" was paginated, the subsequent 649 // ones should also be paginated. 650 stopCh = make(chan struct{}) 651 r.ListAndWatch(stopCh) 652 if results := s.List(); len(results) != 10 { 653 t.Errorf("Expected 10 results, got %d", len(results)) 654 } 655 } 656 657 // TestReflectorResyncWithResourceVersion ensures that a reflector keeps track of the ResourceVersion and sends 658 // it in relist requests to prevent the reflector from traveling back in time if the relist is to a api-server or 659 // etcd that is partitioned and serving older data than the reflector has already processed. 660 func TestReflectorResyncWithResourceVersion(t *testing.T) { 661 stopCh := make(chan struct{}) 662 s := NewStore(MetaNamespaceKeyFunc) 663 listCallRVs := []string{} 664 665 lw := &testLW{ 666 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 667 // Stop once the reflector begins watching since we're only interested in the list. 668 close(stopCh) 669 fw := watch.NewFake() 670 return fw, nil 671 }, 672 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 673 listCallRVs = append(listCallRVs, options.ResourceVersion) 674 pods := make([]v1.Pod, 8) 675 for i := 0; i < 8; i++ { 676 pods[i] = v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("pod-%d", i), ResourceVersion: fmt.Sprintf("%d", i)}} 677 } 678 switch options.ResourceVersion { 679 case "0": 680 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}, Items: pods[0:4]}, nil 681 case "10": 682 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "11"}, Items: pods[0:8]}, nil 683 default: 684 t.Fatalf("Unrecognized ResourceVersion: %s", options.ResourceVersion) 685 } 686 return nil, nil 687 }, 688 } 689 r := NewReflector(lw, &v1.Pod{}, s, 0) 690 691 // Initial list should use RV=0 692 r.ListAndWatch(stopCh) 693 694 results := s.List() 695 if len(results) != 4 { 696 t.Errorf("Expected 4 results, got %d", len(results)) 697 } 698 699 // relist should use lastSyncResourceVersions (RV=10) 700 stopCh = make(chan struct{}) 701 r.ListAndWatch(stopCh) 702 703 results = s.List() 704 if len(results) != 8 { 705 t.Errorf("Expected 8 results, got %d", len(results)) 706 } 707 708 expectedRVs := []string{"0", "10"} 709 if !reflect.DeepEqual(listCallRVs, expectedRVs) { 710 t.Errorf("Expected series of list calls with resource versiosn of %v but got: %v", expectedRVs, listCallRVs) 711 } 712 } 713 714 // TestReflectorExpiredExactResourceVersion tests that a reflector handles the behavior of kubernetes 1.16 an earlier 715 // where if the exact ResourceVersion requested is not available for a List request for a non-zero ResourceVersion, 716 // an "Expired" error is returned if the ResourceVersion has expired (etcd has compacted it). 717 // (In kubernetes 1.17, or when the watch cache is enabled, the List will instead return the list that is no older than 718 // the requested ResourceVersion). 719 func TestReflectorExpiredExactResourceVersion(t *testing.T) { 720 stopCh := make(chan struct{}) 721 s := NewStore(MetaNamespaceKeyFunc) 722 listCallRVs := []string{} 723 724 lw := &testLW{ 725 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 726 // Stop once the reflector begins watching since we're only interested in the list. 727 close(stopCh) 728 fw := watch.NewFake() 729 return fw, nil 730 }, 731 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 732 listCallRVs = append(listCallRVs, options.ResourceVersion) 733 pods := make([]v1.Pod, 8) 734 for i := 0; i < 8; i++ { 735 pods[i] = v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("pod-%d", i), ResourceVersion: fmt.Sprintf("%d", i)}} 736 } 737 switch options.ResourceVersion { 738 case "0": 739 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}, Items: pods[0:4]}, nil 740 case "10": 741 // When watch cache is disabled, if the exact ResourceVersion requested is not available, a "Expired" error is returned. 742 return nil, apierrors.NewResourceExpired("The resourceVersion for the provided watch is too old.") 743 case "": 744 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "11"}, Items: pods[0:8]}, nil 745 default: 746 t.Fatalf("Unrecognized ResourceVersion: %s", options.ResourceVersion) 747 } 748 return nil, nil 749 }, 750 } 751 r := NewReflector(lw, &v1.Pod{}, s, 0) 752 753 // Initial list should use RV=0 754 r.ListAndWatch(stopCh) 755 756 results := s.List() 757 if len(results) != 4 { 758 t.Errorf("Expected 4 results, got %d", len(results)) 759 } 760 761 // relist should use lastSyncResourceVersions (RV=10) and since RV=10 is expired, it should retry with RV="". 762 stopCh = make(chan struct{}) 763 r.ListAndWatch(stopCh) 764 765 results = s.List() 766 if len(results) != 8 { 767 t.Errorf("Expected 8 results, got %d", len(results)) 768 } 769 770 expectedRVs := []string{"0", "10", ""} 771 if !reflect.DeepEqual(listCallRVs, expectedRVs) { 772 t.Errorf("Expected series of list calls with resource versiosn of %v but got: %v", expectedRVs, listCallRVs) 773 } 774 } 775 776 func TestReflectorFullListIfExpired(t *testing.T) { 777 stopCh := make(chan struct{}) 778 s := NewStore(MetaNamespaceKeyFunc) 779 listCallRVs := []string{} 780 781 lw := &testLW{ 782 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 783 // Stop once the reflector begins watching since we're only interested in the list. 784 close(stopCh) 785 fw := watch.NewFake() 786 return fw, nil 787 }, 788 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 789 listCallRVs = append(listCallRVs, options.ResourceVersion) 790 pods := make([]v1.Pod, 8) 791 for i := 0; i < 8; i++ { 792 pods[i] = v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("pod-%d", i), ResourceVersion: fmt.Sprintf("%d", i)}} 793 } 794 rvContinueLimit := func(rv, c string, l int64) metav1.ListOptions { 795 return metav1.ListOptions{ResourceVersion: rv, Continue: c, Limit: l} 796 } 797 switch rvContinueLimit(options.ResourceVersion, options.Continue, options.Limit) { 798 // initial limited list 799 case rvContinueLimit("0", "", 4): 800 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}, Items: pods[0:4]}, nil 801 // first page of the rv=10 list 802 case rvContinueLimit("10", "", 4): 803 return &v1.PodList{ListMeta: metav1.ListMeta{Continue: "C1", ResourceVersion: "11"}, Items: pods[0:4]}, nil 804 // second page of the above list 805 case rvContinueLimit("", "C1", 4): 806 return nil, apierrors.NewResourceExpired("The resourceVersion for the provided watch is too old.") 807 // rv=10 unlimited list 808 case rvContinueLimit("10", "", 0): 809 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "11"}, Items: pods[0:8]}, nil 810 default: 811 err := fmt.Errorf("unexpected list options: %#v", options) 812 t.Error(err) 813 return nil, err 814 } 815 return nil, nil 816 }, 817 } 818 r := NewReflector(lw, &v1.Pod{}, s, 0) 819 r.WatchListPageSize = 4 820 821 // Initial list should use RV=0 822 if err := r.ListAndWatch(stopCh); err != nil { 823 t.Fatal(err) 824 } 825 826 results := s.List() 827 if len(results) != 4 { 828 t.Errorf("Expected 4 results, got %d", len(results)) 829 } 830 831 // relist should use lastSyncResourceVersions (RV=10) and since second page of that expired, it should full list with RV=10 832 stopCh = make(chan struct{}) 833 if err := r.ListAndWatch(stopCh); err != nil { 834 t.Fatal(err) 835 } 836 837 results = s.List() 838 if len(results) != 8 { 839 t.Errorf("Expected 8 results, got %d", len(results)) 840 } 841 842 expectedRVs := []string{"0", "10", "", "10"} 843 if !reflect.DeepEqual(listCallRVs, expectedRVs) { 844 t.Errorf("Expected series of list calls with resource versiosn of %#v but got: %#v", expectedRVs, listCallRVs) 845 } 846 } 847 848 func TestReflectorFullListIfTooLarge(t *testing.T) { 849 stopCh := make(chan struct{}) 850 s := NewStore(MetaNamespaceKeyFunc) 851 listCallRVs := []string{} 852 853 lw := &testLW{ 854 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 855 // Stop once the reflector begins watching since we're only interested in the list. 856 close(stopCh) 857 fw := watch.NewFake() 858 return fw, nil 859 }, 860 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 861 listCallRVs = append(listCallRVs, options.ResourceVersion) 862 863 switch options.ResourceVersion { 864 // initial list 865 case "0": 866 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "20"}}, nil 867 // relist after the initial list 868 case "20": 869 err := apierrors.NewTimeoutError("too large resource version", 1) 870 err.ErrStatus.Details.Causes = []metav1.StatusCause{{Type: metav1.CauseTypeResourceVersionTooLarge}} 871 return nil, err 872 // relist after the initial list (covers the error format used in api server 1.17.0-1.18.5) 873 case "30": 874 err := apierrors.NewTimeoutError("too large resource version", 1) 875 err.ErrStatus.Details.Causes = []metav1.StatusCause{{Message: "Too large resource version"}} 876 return nil, err 877 // relist from etcd after "too large" error 878 case "": 879 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "30"}}, nil 880 default: 881 return nil, fmt.Errorf("unexpected List call: %s", options.ResourceVersion) 882 } 883 }, 884 } 885 r := NewReflector(lw, &v1.Pod{}, s, 0) 886 887 // Initial list should use RV=0 888 if err := r.ListAndWatch(stopCh); err != nil { 889 t.Fatal(err) 890 } 891 892 // Relist from the future version. 893 // This may happen, as watchcache is initialized from "current global etcd resource version" 894 // when kube-apiserver is starting and if no objects are changing after that each kube-apiserver 895 // may be synced to a different version and they will never converge. 896 // TODO: We should use etcd progress-notify feature to avoid this behavior but until this is 897 // done we simply try to relist from now to avoid continuous errors on relists. 898 for i := 1; i <= 2; i++ { 899 // relist twice to cover the two variants of TooLargeResourceVersion api errors 900 stopCh = make(chan struct{}) 901 if err := r.ListAndWatch(stopCh); err != nil { 902 t.Fatal(err) 903 } 904 } 905 906 expectedRVs := []string{"0", "20", "", "30", ""} 907 if !reflect.DeepEqual(listCallRVs, expectedRVs) { 908 t.Errorf("Expected series of list calls with resource version of %#v but got: %#v", expectedRVs, listCallRVs) 909 } 910 } 911 912 func TestReflectorSetExpectedType(t *testing.T) { 913 obj := &unstructured.Unstructured{} 914 gvk := schema.GroupVersionKind{ 915 Group: "mygroup", 916 Version: "v1", 917 Kind: "MyKind", 918 } 919 obj.SetGroupVersionKind(gvk) 920 testCases := map[string]struct { 921 inputType interface{} 922 expectedTypeName string 923 expectedType reflect.Type 924 expectedGVK *schema.GroupVersionKind 925 }{ 926 "Nil type": { 927 expectedTypeName: defaultExpectedTypeName, 928 }, 929 "Normal type": { 930 inputType: &v1.Pod{}, 931 expectedTypeName: "*v1.Pod", 932 expectedType: reflect.TypeOf(&v1.Pod{}), 933 }, 934 "Unstructured type without GVK": { 935 inputType: &unstructured.Unstructured{}, 936 expectedTypeName: "*unstructured.Unstructured", 937 expectedType: reflect.TypeOf(&unstructured.Unstructured{}), 938 }, 939 "Unstructured type with GVK": { 940 inputType: obj, 941 expectedTypeName: gvk.String(), 942 expectedType: reflect.TypeOf(&unstructured.Unstructured{}), 943 expectedGVK: &gvk, 944 }, 945 } 946 for testName, tc := range testCases { 947 t.Run(testName, func(t *testing.T) { 948 r := &Reflector{} 949 r.setExpectedType(tc.inputType) 950 if tc.expectedType != r.expectedType { 951 t.Fatalf("Expected expectedType %v, got %v", tc.expectedType, r.expectedType) 952 } 953 if tc.expectedTypeName != r.expectedTypeName { 954 t.Fatalf("Expected expectedTypeName %v, got %v", tc.expectedTypeName, r.expectedTypeName) 955 } 956 gvkNotEqual := (tc.expectedGVK == nil) != (r.expectedGVK == nil) 957 if tc.expectedGVK != nil && r.expectedGVK != nil { 958 gvkNotEqual = *tc.expectedGVK != *r.expectedGVK 959 } 960 if gvkNotEqual { 961 t.Fatalf("Expected expectedGVK %v, got %v", tc.expectedGVK, r.expectedGVK) 962 } 963 }) 964 } 965 } 966 967 type storeWithRV struct { 968 Store 969 970 // resourceVersions tracks values passed by UpdateResourceVersion 971 resourceVersions []string 972 } 973 974 func (s *storeWithRV) UpdateResourceVersion(resourceVersion string) { 975 s.resourceVersions = append(s.resourceVersions, resourceVersion) 976 } 977 978 func newStoreWithRV() *storeWithRV { 979 return &storeWithRV{ 980 Store: NewStore(MetaNamespaceKeyFunc), 981 } 982 } 983 984 func TestReflectorResourceVersionUpdate(t *testing.T) { 985 s := newStoreWithRV() 986 987 stopCh := make(chan struct{}) 988 fw := watch.NewFake() 989 990 lw := &testLW{ 991 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 992 return fw, nil 993 }, 994 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 995 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}}, nil 996 }, 997 } 998 r := NewReflector(lw, &v1.Pod{}, s, 0) 999 1000 makePod := func(rv string) *v1.Pod { 1001 return &v1.Pod{ObjectMeta: metav1.ObjectMeta{ResourceVersion: rv}} 1002 } 1003 1004 go func() { 1005 fw.Action(watch.Added, makePod("10")) 1006 fw.Action(watch.Modified, makePod("20")) 1007 fw.Action(watch.Bookmark, makePod("30")) 1008 fw.Action(watch.Deleted, makePod("40")) 1009 close(stopCh) 1010 }() 1011 1012 // Initial list should use RV=0 1013 if err := r.ListAndWatch(stopCh); err != nil { 1014 t.Fatal(err) 1015 } 1016 1017 expectedRVs := []string{"10", "20", "30", "40"} 1018 if !reflect.DeepEqual(s.resourceVersions, expectedRVs) { 1019 t.Errorf("Expected series of resource version updates of %#v but got: %#v", expectedRVs, s.resourceVersions) 1020 } 1021 }