k8s.io/client-go@v0.31.1/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 "context" 21 "errors" 22 "fmt" 23 "math/rand" 24 "reflect" 25 goruntime "runtime" 26 "strconv" 27 "syscall" 28 "testing" 29 "time" 30 31 "github.com/stretchr/testify/assert" 32 "github.com/stretchr/testify/require" 33 34 v1 "k8s.io/api/core/v1" 35 apierrors "k8s.io/apimachinery/pkg/api/errors" 36 "k8s.io/apimachinery/pkg/api/meta" 37 "k8s.io/apimachinery/pkg/api/resource" 38 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 39 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 40 "k8s.io/apimachinery/pkg/runtime" 41 "k8s.io/apimachinery/pkg/runtime/schema" 42 "k8s.io/apimachinery/pkg/util/sets" 43 "k8s.io/apimachinery/pkg/util/wait" 44 "k8s.io/apimachinery/pkg/watch" 45 "k8s.io/utils/clock" 46 testingclock "k8s.io/utils/clock/testing" 47 ) 48 49 var nevererrc chan error 50 51 type testLW struct { 52 ListFunc func(options metav1.ListOptions) (runtime.Object, error) 53 WatchFunc func(options metav1.ListOptions) (watch.Interface, error) 54 } 55 56 func (t *testLW) List(options metav1.ListOptions) (runtime.Object, error) { 57 return t.ListFunc(options) 58 } 59 func (t *testLW) Watch(options metav1.ListOptions) (watch.Interface, error) { 60 return t.WatchFunc(options) 61 } 62 63 func TestCloseWatchChannelOnError(t *testing.T) { 64 r := NewReflector(&testLW{}, &v1.Pod{}, NewStore(MetaNamespaceKeyFunc), 0) 65 pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar"}} 66 fw := watch.NewFake() 67 r.listerWatcher = &testLW{ 68 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 69 return fw, nil 70 }, 71 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 72 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "1"}}, nil 73 }, 74 } 75 go r.ListAndWatch(wait.NeverStop) 76 fw.Error(pod) 77 select { 78 case _, ok := <-fw.ResultChan(): 79 if ok { 80 t.Errorf("Watch channel left open after cancellation") 81 } 82 case <-time.After(wait.ForeverTestTimeout): 83 t.Errorf("the cancellation is at least %s late", wait.ForeverTestTimeout.String()) 84 break 85 } 86 } 87 88 func TestRunUntil(t *testing.T) { 89 stopCh := make(chan struct{}) 90 store := NewStore(MetaNamespaceKeyFunc) 91 r := NewReflector(&testLW{}, &v1.Pod{}, store, 0) 92 fw := watch.NewFake() 93 r.listerWatcher = &testLW{ 94 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 95 return fw, nil 96 }, 97 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 98 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "1"}}, nil 99 }, 100 } 101 doneCh := make(chan struct{}) 102 go func() { 103 defer close(doneCh) 104 r.Run(stopCh) 105 }() 106 // Synchronously add a dummy pod into the watch channel so we 107 // know the RunUntil go routine is in the watch handler. 108 fw.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar"}}) 109 110 close(stopCh) 111 resultCh := fw.ResultChan() 112 for { 113 select { 114 case <-doneCh: 115 if resultCh == nil { 116 return // both closed 117 } 118 doneCh = nil 119 case _, ok := <-resultCh: 120 if ok { 121 t.Fatalf("Watch channel left open after stopping the watch") 122 } 123 if doneCh == nil { 124 return // both closed 125 } 126 resultCh = nil 127 case <-time.After(wait.ForeverTestTimeout): 128 t.Fatalf("the cancellation is at least %s late", wait.ForeverTestTimeout.String()) 129 } 130 } 131 } 132 133 func TestReflectorResyncChan(t *testing.T) { 134 s := NewStore(MetaNamespaceKeyFunc) 135 g := NewReflector(&testLW{}, &v1.Pod{}, s, time.Millisecond) 136 a, _ := g.resyncChan() 137 b := time.After(wait.ForeverTestTimeout) 138 select { 139 case <-a: 140 t.Logf("got timeout as expected") 141 case <-b: 142 t.Errorf("resyncChan() is at least 99 milliseconds late??") 143 } 144 } 145 146 // TestReflectorWatchStoppedBefore ensures that neither List nor Watch are 147 // called if the stop channel is closed before Reflector.watch is called. 148 func TestReflectorWatchStoppedBefore(t *testing.T) { 149 stopCh := make(chan struct{}) 150 close(stopCh) 151 152 lw := &ListWatch{ 153 ListFunc: func(_ metav1.ListOptions) (runtime.Object, error) { 154 t.Fatal("ListFunc called unexpectedly") 155 return nil, nil 156 }, 157 WatchFunc: func(_ metav1.ListOptions) (watch.Interface, error) { 158 // If WatchFunc is never called, the watcher it returns doesn't need to be stopped. 159 t.Fatal("WatchFunc called unexpectedly") 160 return nil, nil 161 }, 162 } 163 target := NewReflector(lw, &v1.Pod{}, nil, 0) 164 165 err := target.watch(nil, stopCh, nil) 166 require.NoError(t, err) 167 } 168 169 // TestReflectorWatchStoppedAfter ensures that neither the watcher is stopped if 170 // the stop channel is closed after Reflector.watch has started watching. 171 func TestReflectorWatchStoppedAfter(t *testing.T) { 172 stopCh := make(chan struct{}) 173 174 var watchers []*watch.FakeWatcher 175 176 lw := &ListWatch{ 177 ListFunc: func(_ metav1.ListOptions) (runtime.Object, error) { 178 t.Fatal("ListFunc called unexpectedly") 179 return nil, nil 180 }, 181 WatchFunc: func(_ metav1.ListOptions) (watch.Interface, error) { 182 // Simulate the stop channel being closed after watching has started 183 go func() { 184 time.Sleep(10 * time.Millisecond) 185 close(stopCh) 186 }() 187 // Use a fake watcher that never sends events 188 w := watch.NewFake() 189 watchers = append(watchers, w) 190 return w, nil 191 }, 192 } 193 target := NewReflector(lw, &v1.Pod{}, nil, 0) 194 195 err := target.watch(nil, stopCh, nil) 196 require.NoError(t, err) 197 require.Len(t, watchers, 1) 198 require.True(t, watchers[0].IsStopped()) 199 } 200 201 func BenchmarkReflectorResyncChanMany(b *testing.B) { 202 s := NewStore(MetaNamespaceKeyFunc) 203 g := NewReflector(&testLW{}, &v1.Pod{}, s, 25*time.Millisecond) 204 // The improvement to this (calling the timer's Stop() method) makes 205 // this benchmark about 40% faster. 206 for i := 0; i < b.N; i++ { 207 g.resyncPeriod = time.Duration(rand.Float64() * float64(time.Millisecond) * 25) 208 _, stop := g.resyncChan() 209 stop() 210 } 211 } 212 213 // TestReflectorHandleWatchStoppedBefore ensures that handleWatch stops when 214 // stopCh is already closed before handleWatch was called. It also ensures that 215 // ResultChan is only called once and that Stop is called after ResultChan. 216 func TestReflectorHandleWatchStoppedBefore(t *testing.T) { 217 s := NewStore(MetaNamespaceKeyFunc) 218 g := NewReflector(&testLW{}, &v1.Pod{}, s, 0) 219 stopCh := make(chan struct{}) 220 // Simulate the watch channel being closed before the watchHandler is called 221 close(stopCh) 222 var calls []string 223 resultCh := make(chan watch.Event) 224 fw := watch.MockWatcher{ 225 StopFunc: func() { 226 calls = append(calls, "Stop") 227 close(resultCh) 228 }, 229 ResultChanFunc: func() <-chan watch.Event { 230 calls = append(calls, "ResultChan") 231 return resultCh 232 }, 233 } 234 err := handleWatch(time.Now(), fw, s, g.expectedType, g.expectedGVK, g.name, g.typeDescription, g.setLastSyncResourceVersion, g.clock, nevererrc, stopCh) 235 if err == nil { 236 t.Errorf("unexpected non-error") 237 } 238 // Ensure the watcher methods are called exactly once in this exact order. 239 // TODO(karlkfi): Fix watchHandler to call Stop() 240 // assert.Equal(t, []string{"ResultChan", "Stop"}, calls) 241 assert.Equal(t, []string{"ResultChan"}, calls) 242 } 243 244 // TestReflectorHandleWatchStoppedAfter ensures that handleWatch stops when 245 // stopCh is closed after handleWatch was called. It also ensures that 246 // ResultChan is only called once and that Stop is called after ResultChan. 247 func TestReflectorHandleWatchStoppedAfter(t *testing.T) { 248 s := NewStore(MetaNamespaceKeyFunc) 249 g := NewReflector(&testLW{}, &v1.Pod{}, s, 0) 250 var calls []string 251 stopCh := make(chan struct{}) 252 resultCh := make(chan watch.Event) 253 fw := watch.MockWatcher{ 254 StopFunc: func() { 255 calls = append(calls, "Stop") 256 close(resultCh) 257 }, 258 ResultChanFunc: func() <-chan watch.Event { 259 calls = append(calls, "ResultChan") 260 resultCh = make(chan watch.Event) 261 // Simulate the watch handler being stopped asynchronously by the 262 // caller, after watching has started. 263 go func() { 264 time.Sleep(10 * time.Millisecond) 265 close(stopCh) 266 }() 267 return resultCh 268 }, 269 } 270 err := handleWatch(time.Now(), fw, s, g.expectedType, g.expectedGVK, g.name, g.typeDescription, g.setLastSyncResourceVersion, g.clock, nevererrc, stopCh) 271 if err == nil { 272 t.Errorf("unexpected non-error") 273 } 274 // Ensure the watcher methods are called exactly once in this exact order. 275 // TODO(karlkfi): Fix watchHandler to call Stop() 276 // assert.Equal(t, []string{"ResultChan", "Stop"}, calls) 277 assert.Equal(t, []string{"ResultChan"}, calls) 278 } 279 280 // TestReflectorHandleWatchResultChanClosedBefore ensures that handleWatch 281 // stops when the result channel is closed before handleWatch was called. 282 func TestReflectorHandleWatchResultChanClosedBefore(t *testing.T) { 283 s := NewStore(MetaNamespaceKeyFunc) 284 g := NewReflector(&testLW{}, &v1.Pod{}, s, 0) 285 var calls []string 286 resultCh := make(chan watch.Event) 287 fw := watch.MockWatcher{ 288 StopFunc: func() { 289 calls = append(calls, "Stop") 290 }, 291 ResultChanFunc: func() <-chan watch.Event { 292 calls = append(calls, "ResultChan") 293 return resultCh 294 }, 295 } 296 // Simulate the result channel being closed by the producer before handleWatch is called. 297 close(resultCh) 298 err := handleWatch(time.Now(), fw, s, g.expectedType, g.expectedGVK, g.name, g.typeDescription, g.setLastSyncResourceVersion, g.clock, nevererrc, wait.NeverStop) 299 if err == nil { 300 t.Errorf("unexpected non-error") 301 } 302 // Ensure the watcher methods are called exactly once in this exact order. 303 // TODO(karlkfi): Fix watchHandler to call Stop() 304 // assert.Equal(t, []string{"ResultChan", "Stop"}, calls) 305 assert.Equal(t, []string{"ResultChan"}, calls) 306 } 307 308 // TestReflectorHandleWatchResultChanClosedAfter ensures that handleWatch 309 // stops when the result channel is closed after handleWatch has started watching. 310 func TestReflectorHandleWatchResultChanClosedAfter(t *testing.T) { 311 s := NewStore(MetaNamespaceKeyFunc) 312 g := NewReflector(&testLW{}, &v1.Pod{}, s, 0) 313 var calls []string 314 resultCh := make(chan watch.Event) 315 fw := watch.MockWatcher{ 316 StopFunc: func() { 317 calls = append(calls, "Stop") 318 }, 319 ResultChanFunc: func() <-chan watch.Event { 320 calls = append(calls, "ResultChan") 321 resultCh = make(chan watch.Event) 322 // Simulate the result channel being closed by the producer, after 323 // watching has started. 324 go func() { 325 time.Sleep(10 * time.Millisecond) 326 close(resultCh) 327 }() 328 return resultCh 329 }, 330 } 331 err := handleWatch(time.Now(), fw, s, g.expectedType, g.expectedGVK, g.name, g.typeDescription, g.setLastSyncResourceVersion, g.clock, nevererrc, wait.NeverStop) 332 if err == nil { 333 t.Errorf("unexpected non-error") 334 } 335 // Ensure the watcher methods are called exactly once in this exact order. 336 // TODO(karlkfi): Fix watchHandler to call Stop() 337 // assert.Equal(t, []string{"ResultChan", "Stop"}, calls) 338 assert.Equal(t, []string{"ResultChan"}, calls) 339 } 340 341 func TestReflectorWatchHandler(t *testing.T) { 342 s := NewStore(MetaNamespaceKeyFunc) 343 g := NewReflector(&testLW{}, &v1.Pod{}, s, 0) 344 // Wrap setLastSyncResourceVersion so we can tell the watchHandler to stop 345 // watching after all the events have been consumed. This avoids race 346 // conditions which can happen if the producer calls Stop(), instead of the 347 // consumer. 348 stopCh := make(chan struct{}) 349 setLastSyncResourceVersion := func(rv string) { 350 g.setLastSyncResourceVersion(rv) 351 if rv == "32" { 352 close(stopCh) 353 } 354 } 355 fw := watch.NewFake() 356 s.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}) 357 s.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar"}}) 358 go func() { 359 fw.Add(&v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "rejected"}}) 360 fw.Delete(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}) 361 fw.Modify(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar", ResourceVersion: "55"}}) 362 fw.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "baz", ResourceVersion: "32"}}) 363 fw.Stop() 364 }() 365 err := handleWatch(time.Now(), fw, s, g.expectedType, g.expectedGVK, g.name, g.typeDescription, setLastSyncResourceVersion, g.clock, nevererrc, stopCh) 366 // TODO(karlkfi): Fix FakeWatcher to avoid race condition between watcher.Stop() & close(stopCh) 367 if err != nil && !errors.Is(err, errorStopRequested) { 368 t.Errorf("unexpected error %v", err) 369 } 370 371 mkPod := func(id string, rv string) *v1.Pod { 372 return &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: id, ResourceVersion: rv}} 373 } 374 375 // Validate that the Store was updated by the events 376 table := []struct { 377 Pod *v1.Pod 378 exists bool 379 }{ 380 {mkPod("foo", ""), false}, 381 {mkPod("rejected", ""), false}, 382 {mkPod("bar", "55"), true}, 383 {mkPod("baz", "32"), true}, 384 } 385 for _, item := range table { 386 obj, exists, _ := s.Get(item.Pod) 387 if e, a := item.exists, exists; e != a { 388 t.Errorf("%v: expected %v, got %v", item.Pod, e, a) 389 } 390 if !exists { 391 continue 392 } 393 if e, a := item.Pod.ResourceVersion, obj.(*v1.Pod).ResourceVersion; e != a { 394 t.Errorf("%v: expected %v, got %v", item.Pod, e, a) 395 } 396 } 397 398 // Validate that setLastSyncResourceVersion was called with the RV from the last event. 399 if e, a := "32", g.LastSyncResourceVersion(); e != a { 400 t.Errorf("expected %v, got %v", e, a) 401 } 402 } 403 404 func TestReflectorStopWatch(t *testing.T) { 405 s := NewStore(MetaNamespaceKeyFunc) 406 g := NewReflector(&testLW{}, &v1.Pod{}, s, 0) 407 fw := watch.NewFake() 408 stopWatch := make(chan struct{}) 409 close(stopWatch) 410 err := handleWatch(time.Now(), fw, s, g.expectedType, g.expectedGVK, g.name, g.typeDescription, g.setLastSyncResourceVersion, g.clock, nevererrc, stopWatch) 411 if err != errorStopRequested { 412 t.Errorf("expected stop error, got %q", err) 413 } 414 } 415 416 func TestReflectorListAndWatch(t *testing.T) { 417 createdFakes := make(chan *watch.FakeWatcher) 418 419 // The ListFunc says that it's at revision 1. Therefore, we expect our WatchFunc 420 // to get called at the beginning of the watch with 1, and again with 3 when we 421 // inject an error. 422 expectedRVs := []string{"1", "3"} 423 lw := &testLW{ 424 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 425 rv := options.ResourceVersion 426 fw := watch.NewFake() 427 if e, a := expectedRVs[0], rv; e != a { 428 t.Errorf("Expected rv %v, but got %v", e, a) 429 } 430 expectedRVs = expectedRVs[1:] 431 // channel is not buffered because the for loop below needs to block. But 432 // we don't want to block here, so report the new fake via a go routine. 433 go func() { createdFakes <- fw }() 434 return fw, nil 435 }, 436 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 437 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "1"}}, nil 438 }, 439 } 440 s := NewFIFO(MetaNamespaceKeyFunc) 441 r := NewReflector(lw, &v1.Pod{}, s, 0) 442 go r.ListAndWatch(wait.NeverStop) 443 444 ids := []string{"foo", "bar", "baz", "qux", "zoo"} 445 var fw *watch.FakeWatcher 446 for i, id := range ids { 447 if fw == nil { 448 fw = <-createdFakes 449 } 450 sendingRV := strconv.FormatUint(uint64(i+2), 10) 451 fw.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: id, ResourceVersion: sendingRV}}) 452 if sendingRV == "3" { 453 // Inject a failure. 454 fw.Stop() 455 fw = nil 456 } 457 } 458 459 // Verify we received the right ids with the right resource versions. 460 for i, id := range ids { 461 pod := Pop(s).(*v1.Pod) 462 if e, a := id, pod.Name; e != a { 463 t.Errorf("%v: Expected %v, got %v", i, e, a) 464 } 465 if e, a := strconv.FormatUint(uint64(i+2), 10), pod.ResourceVersion; e != a { 466 t.Errorf("%v: Expected %v, got %v", i, e, a) 467 } 468 } 469 470 if len(expectedRVs) != 0 { 471 t.Error("called watchStarter an unexpected number of times") 472 } 473 } 474 475 func TestReflectorListAndWatchWithErrors(t *testing.T) { 476 mkPod := func(id string, rv string) *v1.Pod { 477 return &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: id, ResourceVersion: rv}} 478 } 479 mkList := func(rv string, pods ...*v1.Pod) *v1.PodList { 480 list := &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: rv}} 481 for _, pod := range pods { 482 list.Items = append(list.Items, *pod) 483 } 484 return list 485 } 486 table := []struct { 487 list *v1.PodList 488 listErr error 489 events []watch.Event 490 watchErr error 491 }{ 492 { 493 list: mkList("1"), 494 events: []watch.Event{ 495 {Type: watch.Added, Object: mkPod("foo", "2")}, 496 {Type: watch.Added, Object: mkPod("bar", "3")}, 497 }, 498 }, { 499 list: mkList("3", mkPod("foo", "2"), mkPod("bar", "3")), 500 events: []watch.Event{ 501 {Type: watch.Deleted, Object: mkPod("foo", "4")}, 502 {Type: watch.Added, Object: mkPod("qux", "5")}, 503 }, 504 }, { 505 listErr: fmt.Errorf("a list error"), 506 }, { 507 list: mkList("5", mkPod("bar", "3"), mkPod("qux", "5")), 508 watchErr: fmt.Errorf("a watch error"), 509 }, { 510 list: mkList("5", mkPod("bar", "3"), mkPod("qux", "5")), 511 events: []watch.Event{ 512 {Type: watch.Added, Object: mkPod("baz", "6")}, 513 }, 514 }, { 515 list: mkList("6", mkPod("bar", "3"), mkPod("qux", "5"), mkPod("baz", "6")), 516 }, 517 } 518 519 s := NewFIFO(MetaNamespaceKeyFunc) 520 for line, item := range table { 521 if item.list != nil { 522 // Test that the list is what currently exists in the store. 523 current := s.List() 524 checkMap := map[string]string{} 525 for _, item := range current { 526 pod := item.(*v1.Pod) 527 checkMap[pod.Name] = pod.ResourceVersion 528 } 529 for _, pod := range item.list.Items { 530 if e, a := pod.ResourceVersion, checkMap[pod.Name]; e != a { 531 t.Errorf("%v: expected %v, got %v for pod %v", line, e, a, pod.Name) 532 } 533 } 534 if e, a := len(item.list.Items), len(checkMap); e != a { 535 t.Errorf("%v: expected %v, got %v", line, e, a) 536 } 537 } 538 watchRet, watchErr := item.events, item.watchErr 539 stopCh := make(chan struct{}) 540 lw := &testLW{ 541 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 542 if watchErr != nil { 543 return nil, watchErr 544 } 545 watchErr = fmt.Errorf("second watch") 546 fw := watch.NewFake() 547 go func() { 548 for _, e := range watchRet { 549 fw.Action(e.Type, e.Object) 550 } 551 // Because FakeWatcher doesn't buffer events, it's safe to 552 // close the stop channel immediately without missing events. 553 // But usually, the event producer would instead close the 554 // result channel, and wait for the consumer to stop the 555 // watcher, to avoid race conditions. 556 // TODO: Fix the FakeWatcher to separate watcher.Stop from close(resultCh) 557 close(stopCh) 558 }() 559 return fw, nil 560 }, 561 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 562 return item.list, item.listErr 563 }, 564 } 565 r := NewReflector(lw, &v1.Pod{}, s, 0) 566 err := r.ListAndWatch(stopCh) 567 if item.listErr != nil && !errors.Is(err, item.listErr) { 568 t.Errorf("unexpected ListAndWatch error: %v", err) 569 } 570 if item.watchErr != nil && !errors.Is(err, item.watchErr) { 571 t.Errorf("unexpected ListAndWatch error: %v", err) 572 } 573 if item.listErr == nil && item.watchErr == nil { 574 assert.NoError(t, err) 575 } 576 } 577 } 578 579 func TestReflectorListAndWatchInitConnBackoff(t *testing.T) { 580 maxBackoff := 50 * time.Millisecond 581 table := []struct { 582 numConnFails int 583 expLowerBound time.Duration 584 expUpperBound time.Duration 585 }{ 586 {5, 32 * time.Millisecond, 64 * time.Millisecond}, // case where maxBackoff is not hit, time should grow exponentially 587 {40, 35 * 2 * maxBackoff, 40 * 2 * maxBackoff}, // case where maxBoff is hit, backoff time should flatten 588 589 } 590 for _, test := range table { 591 t.Run(fmt.Sprintf("%d connection failures takes at least %d ms", test.numConnFails, 1<<test.numConnFails), 592 func(t *testing.T) { 593 stopCh := make(chan struct{}) 594 connFails := test.numConnFails 595 fakeClock := testingclock.NewFakeClock(time.Unix(0, 0)) 596 bm := wait.NewExponentialBackoffManager(time.Millisecond, maxBackoff, 100*time.Millisecond, 2.0, 1.0, fakeClock) 597 done := make(chan struct{}) 598 defer close(done) 599 go func() { 600 i := 0 601 for { 602 select { 603 case <-done: 604 return 605 default: 606 } 607 if fakeClock.HasWaiters() { 608 step := (1 << (i + 1)) * time.Millisecond 609 if step > maxBackoff*2 { 610 step = maxBackoff * 2 611 } 612 fakeClock.Step(step) 613 i++ 614 } 615 time.Sleep(100 * time.Microsecond) 616 } 617 }() 618 lw := &testLW{ 619 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 620 if connFails > 0 { 621 connFails-- 622 return nil, syscall.ECONNREFUSED 623 } 624 close(stopCh) 625 return watch.NewFake(), nil 626 }, 627 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 628 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "1"}}, nil 629 }, 630 } 631 r := &Reflector{ 632 name: "test-reflector", 633 listerWatcher: lw, 634 store: NewFIFO(MetaNamespaceKeyFunc), 635 backoffManager: bm, 636 clock: fakeClock, 637 watchErrorHandler: WatchErrorHandler(DefaultWatchErrorHandler), 638 } 639 start := fakeClock.Now() 640 err := r.ListAndWatch(stopCh) 641 elapsed := fakeClock.Since(start) 642 if err != nil { 643 t.Errorf("unexpected error %v", err) 644 } 645 if elapsed < (test.expLowerBound) { 646 t.Errorf("expected lower bound of ListAndWatch: %v, got %v", test.expLowerBound, elapsed) 647 } 648 if elapsed > (test.expUpperBound) { 649 t.Errorf("expected upper bound of ListAndWatch: %v, got %v", test.expUpperBound, elapsed) 650 } 651 }) 652 } 653 } 654 655 type fakeBackoff struct { 656 clock clock.Clock 657 calls int 658 } 659 660 func (f *fakeBackoff) Backoff() clock.Timer { 661 f.calls++ 662 return f.clock.NewTimer(time.Duration(0)) 663 } 664 665 func TestBackoffOnTooManyRequests(t *testing.T) { 666 err := apierrors.NewTooManyRequests("too many requests", 1) 667 clock := &clock.RealClock{} 668 bm := &fakeBackoff{clock: clock} 669 670 lw := &testLW{ 671 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 672 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "1"}}, nil 673 }, 674 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 675 switch bm.calls { 676 case 0: 677 return nil, err 678 case 1: 679 w := watch.NewFakeWithChanSize(1, false) 680 status := err.Status() 681 w.Error(&status) 682 return w, nil 683 default: 684 w := watch.NewFake() 685 w.Stop() 686 return w, nil 687 } 688 }, 689 } 690 691 r := &Reflector{ 692 name: "test-reflector", 693 listerWatcher: lw, 694 store: NewFIFO(MetaNamespaceKeyFunc), 695 backoffManager: bm, 696 clock: clock, 697 watchErrorHandler: WatchErrorHandler(DefaultWatchErrorHandler), 698 } 699 700 stopCh := make(chan struct{}) 701 if err := r.ListAndWatch(stopCh); err != nil { 702 t.Fatal(err) 703 } 704 close(stopCh) 705 if bm.calls != 2 { 706 t.Errorf("unexpected watch backoff calls: %d", bm.calls) 707 } 708 } 709 710 func TestNoRelistOnTooManyRequests(t *testing.T) { 711 err := apierrors.NewTooManyRequests("too many requests", 1) 712 clock := &clock.RealClock{} 713 bm := &fakeBackoff{clock: clock} 714 listCalls, watchCalls := 0, 0 715 716 lw := &testLW{ 717 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 718 listCalls++ 719 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "1"}}, nil 720 }, 721 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 722 watchCalls++ 723 if watchCalls < 5 { 724 return nil, err 725 } 726 w := watch.NewFake() 727 w.Stop() 728 return w, nil 729 }, 730 } 731 732 r := &Reflector{ 733 name: "test-reflector", 734 listerWatcher: lw, 735 store: NewFIFO(MetaNamespaceKeyFunc), 736 backoffManager: bm, 737 clock: clock, 738 watchErrorHandler: WatchErrorHandler(DefaultWatchErrorHandler), 739 } 740 741 stopCh := make(chan struct{}) 742 if err := r.ListAndWatch(stopCh); err != nil { 743 t.Fatal(err) 744 } 745 close(stopCh) 746 if listCalls != 1 { 747 t.Errorf("unexpected list calls: %d", listCalls) 748 } 749 if watchCalls != 5 { 750 t.Errorf("unexpected watch calls: %d", watchCalls) 751 } 752 } 753 754 func TestRetryInternalError(t *testing.T) { 755 testCases := []struct { 756 name string 757 maxInternalDuration time.Duration 758 rewindTime int 759 wantRetries int 760 }{ 761 { 762 name: "retries off", 763 maxInternalDuration: time.Duration(0), 764 wantRetries: 0, 765 }, 766 { 767 name: "retries on, all calls fail", 768 maxInternalDuration: time.Second * 30, 769 wantRetries: 31, 770 }, 771 { 772 name: "retries on, one call successful", 773 maxInternalDuration: time.Second * 30, 774 rewindTime: 10, 775 wantRetries: 40, 776 }, 777 } 778 779 for _, tc := range testCases { 780 err := apierrors.NewInternalError(fmt.Errorf("etcdserver: no leader")) 781 fakeClock := testingclock.NewFakeClock(time.Now()) 782 bm := &fakeBackoff{clock: fakeClock} 783 784 counter := 0 785 786 lw := &testLW{ 787 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 788 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "1"}}, nil 789 }, 790 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 791 counter = counter + 1 792 t.Logf("Counter: %v", counter) 793 if counter == tc.rewindTime { 794 t.Logf("Rewinding") 795 fakeClock.Step(time.Minute) 796 } 797 798 fakeClock.Step(time.Second) 799 w := watch.NewFakeWithChanSize(1, false) 800 status := err.Status() 801 w.Error(&status) 802 return w, nil 803 }, 804 } 805 806 r := &Reflector{ 807 name: "test-reflector", 808 listerWatcher: lw, 809 store: NewFIFO(MetaNamespaceKeyFunc), 810 backoffManager: bm, 811 clock: fakeClock, 812 watchErrorHandler: WatchErrorHandler(DefaultWatchErrorHandler), 813 } 814 815 r.MaxInternalErrorRetryDuration = tc.maxInternalDuration 816 817 stopCh := make(chan struct{}) 818 r.ListAndWatch(stopCh) 819 close(stopCh) 820 821 if counter-1 != tc.wantRetries { 822 t.Errorf("%v unexpected number of retries: %d", tc, counter-1) 823 } 824 } 825 } 826 827 func TestReflectorResync(t *testing.T) { 828 iteration := 0 829 stopCh := make(chan struct{}) 830 rerr := errors.New("expected resync reached") 831 s := &FakeCustomStore{ 832 ResyncFunc: func() error { 833 iteration++ 834 if iteration == 2 { 835 return rerr 836 } 837 return nil 838 }, 839 } 840 841 lw := &testLW{ 842 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 843 fw := watch.NewFake() 844 return fw, nil 845 }, 846 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 847 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "0"}}, nil 848 }, 849 } 850 resyncPeriod := 1 * time.Millisecond 851 r := NewReflector(lw, &v1.Pod{}, s, resyncPeriod) 852 if err := r.ListAndWatch(stopCh); err != nil { 853 // error from Resync is not propaged up to here. 854 t.Errorf("expected error %v", err) 855 } 856 if iteration != 2 { 857 t.Errorf("exactly 2 iterations were expected, got: %v", iteration) 858 } 859 } 860 861 func TestReflectorWatchListPageSize(t *testing.T) { 862 stopCh := make(chan struct{}) 863 s := NewStore(MetaNamespaceKeyFunc) 864 865 lw := &testLW{ 866 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 867 // Stop once the reflector begins watching since we're only interested in the list. 868 close(stopCh) 869 fw := watch.NewFake() 870 return fw, nil 871 }, 872 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 873 if options.Limit != 4 { 874 t.Fatalf("Expected list Limit of 4 but got %d", options.Limit) 875 } 876 pods := make([]v1.Pod, 10) 877 for i := 0; i < 10; i++ { 878 pods[i] = v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("pod-%d", i), ResourceVersion: fmt.Sprintf("%d", i)}} 879 } 880 switch options.Continue { 881 case "": 882 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10", Continue: "C1"}, Items: pods[0:4]}, nil 883 case "C1": 884 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10", Continue: "C2"}, Items: pods[4:8]}, nil 885 case "C2": 886 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}, Items: pods[8:10]}, nil 887 default: 888 t.Fatalf("Unrecognized continue: %s", options.Continue) 889 } 890 return nil, nil 891 }, 892 } 893 r := NewReflector(lw, &v1.Pod{}, s, 0) 894 // Set resource version to test pagination also for not consistent reads. 895 r.setLastSyncResourceVersion("10") 896 // Set the reflector to paginate the list request in 4 item chunks. 897 r.WatchListPageSize = 4 898 r.ListAndWatch(stopCh) 899 900 results := s.List() 901 if len(results) != 10 { 902 t.Errorf("Expected 10 results, got %d", len(results)) 903 } 904 } 905 906 func TestReflectorNotPaginatingNotConsistentReads(t *testing.T) { 907 stopCh := make(chan struct{}) 908 s := NewStore(MetaNamespaceKeyFunc) 909 910 lw := &testLW{ 911 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 912 // Stop once the reflector begins watching since we're only interested in the list. 913 close(stopCh) 914 fw := watch.NewFake() 915 return fw, nil 916 }, 917 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 918 if options.ResourceVersion != "10" { 919 t.Fatalf("Expected ResourceVersion: \"10\", got: %s", options.ResourceVersion) 920 } 921 if options.Limit != 0 { 922 t.Fatalf("Expected list Limit of 0 but got %d", options.Limit) 923 } 924 pods := make([]v1.Pod, 10) 925 for i := 0; i < 10; i++ { 926 pods[i] = v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("pod-%d", i), ResourceVersion: fmt.Sprintf("%d", i)}} 927 } 928 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}, Items: pods}, nil 929 }, 930 } 931 r := NewReflector(lw, &v1.Pod{}, s, 0) 932 r.setLastSyncResourceVersion("10") 933 r.ListAndWatch(stopCh) 934 935 results := s.List() 936 if len(results) != 10 { 937 t.Errorf("Expected 10 results, got %d", len(results)) 938 } 939 } 940 941 func TestReflectorPaginatingNonConsistentReadsIfWatchCacheDisabled(t *testing.T) { 942 var stopCh chan struct{} 943 s := NewStore(MetaNamespaceKeyFunc) 944 945 lw := &testLW{ 946 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 947 // Stop once the reflector begins watching since we're only interested in the list. 948 close(stopCh) 949 fw := watch.NewFake() 950 return fw, nil 951 }, 952 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 953 // Check that default pager limit is set. 954 if options.Limit != 500 { 955 t.Fatalf("Expected list Limit of 500 but got %d", options.Limit) 956 } 957 pods := make([]v1.Pod, 10) 958 for i := 0; i < 10; i++ { 959 pods[i] = v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("pod-%d", i), ResourceVersion: fmt.Sprintf("%d", i)}} 960 } 961 switch options.Continue { 962 case "": 963 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10", Continue: "C1"}, Items: pods[0:4]}, nil 964 case "C1": 965 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10", Continue: "C2"}, Items: pods[4:8]}, nil 966 case "C2": 967 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}, Items: pods[8:10]}, nil 968 default: 969 t.Fatalf("Unrecognized continue: %s", options.Continue) 970 } 971 return nil, nil 972 }, 973 } 974 r := NewReflector(lw, &v1.Pod{}, s, 0) 975 976 // Initial list should initialize paginatedResult in the reflector. 977 stopCh = make(chan struct{}) 978 r.ListAndWatch(stopCh) 979 if results := s.List(); len(results) != 10 { 980 t.Errorf("Expected 10 results, got %d", len(results)) 981 } 982 983 // Since initial list for ResourceVersion="0" was paginated, the subsequent 984 // ones should also be paginated. 985 stopCh = make(chan struct{}) 986 r.ListAndWatch(stopCh) 987 if results := s.List(); len(results) != 10 { 988 t.Errorf("Expected 10 results, got %d", len(results)) 989 } 990 } 991 992 // TestReflectorResyncWithResourceVersion ensures that a reflector keeps track of the ResourceVersion and sends 993 // it in relist requests to prevent the reflector from traveling back in time if the relist is to a api-server or 994 // etcd that is partitioned and serving older data than the reflector has already processed. 995 func TestReflectorResyncWithResourceVersion(t *testing.T) { 996 stopCh := make(chan struct{}) 997 s := NewStore(MetaNamespaceKeyFunc) 998 listCallRVs := []string{} 999 1000 lw := &testLW{ 1001 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 1002 // Stop once the reflector begins watching since we're only interested in the list. 1003 close(stopCh) 1004 fw := watch.NewFake() 1005 return fw, nil 1006 }, 1007 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 1008 listCallRVs = append(listCallRVs, options.ResourceVersion) 1009 pods := make([]v1.Pod, 8) 1010 for i := 0; i < 8; i++ { 1011 pods[i] = v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("pod-%d", i), ResourceVersion: fmt.Sprintf("%d", i)}} 1012 } 1013 switch options.ResourceVersion { 1014 case "0": 1015 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}, Items: pods[0:4]}, nil 1016 case "10": 1017 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "11"}, Items: pods[0:8]}, nil 1018 default: 1019 t.Fatalf("Unrecognized ResourceVersion: %s", options.ResourceVersion) 1020 } 1021 return nil, nil 1022 }, 1023 } 1024 r := NewReflector(lw, &v1.Pod{}, s, 0) 1025 1026 // Initial list should use RV=0 1027 r.ListAndWatch(stopCh) 1028 1029 results := s.List() 1030 if len(results) != 4 { 1031 t.Errorf("Expected 4 results, got %d", len(results)) 1032 } 1033 1034 // relist should use lastSyncResourceVersions (RV=10) 1035 stopCh = make(chan struct{}) 1036 r.ListAndWatch(stopCh) 1037 1038 results = s.List() 1039 if len(results) != 8 { 1040 t.Errorf("Expected 8 results, got %d", len(results)) 1041 } 1042 1043 expectedRVs := []string{"0", "10"} 1044 if !reflect.DeepEqual(listCallRVs, expectedRVs) { 1045 t.Errorf("Expected series of list calls with resource versiosn of %v but got: %v", expectedRVs, listCallRVs) 1046 } 1047 } 1048 1049 // TestReflectorExpiredExactResourceVersion tests that a reflector handles the behavior of kubernetes 1.16 an earlier 1050 // where if the exact ResourceVersion requested is not available for a List request for a non-zero ResourceVersion, 1051 // an "Expired" error is returned if the ResourceVersion has expired (etcd has compacted it). 1052 // (In kubernetes 1.17, or when the watch cache is enabled, the List will instead return the list that is no older than 1053 // the requested ResourceVersion). 1054 func TestReflectorExpiredExactResourceVersion(t *testing.T) { 1055 stopCh := make(chan struct{}) 1056 s := NewStore(MetaNamespaceKeyFunc) 1057 listCallRVs := []string{} 1058 1059 lw := &testLW{ 1060 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 1061 // Stop once the reflector begins watching since we're only interested in the list. 1062 close(stopCh) 1063 fw := watch.NewFake() 1064 return fw, nil 1065 }, 1066 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 1067 listCallRVs = append(listCallRVs, options.ResourceVersion) 1068 pods := make([]v1.Pod, 8) 1069 for i := 0; i < 8; i++ { 1070 pods[i] = v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("pod-%d", i), ResourceVersion: fmt.Sprintf("%d", i)}} 1071 } 1072 switch options.ResourceVersion { 1073 case "0": 1074 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}, Items: pods[0:4]}, nil 1075 case "10": 1076 // When watch cache is disabled, if the exact ResourceVersion requested is not available, a "Expired" error is returned. 1077 return nil, apierrors.NewResourceExpired("The resourceVersion for the provided watch is too old.") 1078 case "": 1079 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "11"}, Items: pods[0:8]}, nil 1080 default: 1081 t.Fatalf("Unrecognized ResourceVersion: %s", options.ResourceVersion) 1082 } 1083 return nil, nil 1084 }, 1085 } 1086 r := NewReflector(lw, &v1.Pod{}, s, 0) 1087 1088 // Initial list should use RV=0 1089 r.ListAndWatch(stopCh) 1090 1091 results := s.List() 1092 if len(results) != 4 { 1093 t.Errorf("Expected 4 results, got %d", len(results)) 1094 } 1095 1096 // relist should use lastSyncResourceVersions (RV=10) and since RV=10 is expired, it should retry with RV="". 1097 stopCh = make(chan struct{}) 1098 r.ListAndWatch(stopCh) 1099 1100 results = s.List() 1101 if len(results) != 8 { 1102 t.Errorf("Expected 8 results, got %d", len(results)) 1103 } 1104 1105 expectedRVs := []string{"0", "10", ""} 1106 if !reflect.DeepEqual(listCallRVs, expectedRVs) { 1107 t.Errorf("Expected series of list calls with resource versiosn of %v but got: %v", expectedRVs, listCallRVs) 1108 } 1109 } 1110 1111 func TestReflectorFullListIfExpired(t *testing.T) { 1112 stopCh := make(chan struct{}) 1113 s := NewStore(MetaNamespaceKeyFunc) 1114 listCallRVs := []string{} 1115 1116 lw := &testLW{ 1117 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 1118 // Stop once the reflector begins watching since we're only interested in the list. 1119 close(stopCh) 1120 fw := watch.NewFake() 1121 return fw, nil 1122 }, 1123 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 1124 listCallRVs = append(listCallRVs, options.ResourceVersion) 1125 pods := make([]v1.Pod, 8) 1126 for i := 0; i < 8; i++ { 1127 pods[i] = v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("pod-%d", i), ResourceVersion: fmt.Sprintf("%d", i)}} 1128 } 1129 rvContinueLimit := func(rv, c string, l int64) metav1.ListOptions { 1130 return metav1.ListOptions{ResourceVersion: rv, Continue: c, Limit: l} 1131 } 1132 switch rvContinueLimit(options.ResourceVersion, options.Continue, options.Limit) { 1133 // initial limited list 1134 case rvContinueLimit("0", "", 4): 1135 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}, Items: pods[0:4]}, nil 1136 // first page of the rv=10 list 1137 case rvContinueLimit("10", "", 4): 1138 return &v1.PodList{ListMeta: metav1.ListMeta{Continue: "C1", ResourceVersion: "11"}, Items: pods[0:4]}, nil 1139 // second page of the above list 1140 case rvContinueLimit("", "C1", 4): 1141 return nil, apierrors.NewResourceExpired("The resourceVersion for the provided watch is too old.") 1142 // rv=10 unlimited list 1143 case rvContinueLimit("10", "", 0): 1144 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "11"}, Items: pods[0:8]}, nil 1145 default: 1146 err := fmt.Errorf("unexpected list options: %#v", options) 1147 t.Error(err) 1148 return nil, err 1149 } 1150 }, 1151 } 1152 r := NewReflector(lw, &v1.Pod{}, s, 0) 1153 r.WatchListPageSize = 4 1154 1155 // Initial list should use RV=0 1156 if err := r.ListAndWatch(stopCh); err != nil { 1157 t.Fatal(err) 1158 } 1159 1160 results := s.List() 1161 if len(results) != 4 { 1162 t.Errorf("Expected 4 results, got %d", len(results)) 1163 } 1164 1165 // relist should use lastSyncResourceVersions (RV=10) and since second page of that expired, it should full list with RV=10 1166 stopCh = make(chan struct{}) 1167 if err := r.ListAndWatch(stopCh); err != nil { 1168 t.Fatal(err) 1169 } 1170 1171 results = s.List() 1172 if len(results) != 8 { 1173 t.Errorf("Expected 8 results, got %d", len(results)) 1174 } 1175 1176 expectedRVs := []string{"0", "10", "", "10"} 1177 if !reflect.DeepEqual(listCallRVs, expectedRVs) { 1178 t.Errorf("Expected series of list calls with resource versiosn of %#v but got: %#v", expectedRVs, listCallRVs) 1179 } 1180 } 1181 1182 func TestReflectorFullListIfTooLarge(t *testing.T) { 1183 stopCh := make(chan struct{}) 1184 s := NewStore(MetaNamespaceKeyFunc) 1185 listCallRVs := []string{} 1186 version := 30 1187 1188 lw := &testLW{ 1189 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 1190 // Stop once the reflector begins watching since we're only interested in the list. 1191 close(stopCh) 1192 fw := watch.NewFake() 1193 return fw, nil 1194 }, 1195 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 1196 listCallRVs = append(listCallRVs, options.ResourceVersion) 1197 resourceVersion := strconv.Itoa(version) 1198 1199 switch options.ResourceVersion { 1200 // initial list 1201 case "0": 1202 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "20"}}, nil 1203 // relist after the initial list 1204 case "20": 1205 err := apierrors.NewTimeoutError("too large resource version", 1) 1206 err.ErrStatus.Details.Causes = []metav1.StatusCause{{Type: metav1.CauseTypeResourceVersionTooLarge}} 1207 return nil, err 1208 // relist after the initial list (covers the error format used in api server 1.17.0-1.18.5) 1209 case "30": 1210 err := apierrors.NewTimeoutError("too large resource version", 1) 1211 err.ErrStatus.Details.Causes = []metav1.StatusCause{{Message: "Too large resource version"}} 1212 return nil, err 1213 // relist after the initial list (covers the error format used in api server before 1.17.0) 1214 case "40": 1215 err := apierrors.NewTimeoutError("Too large resource version", 1) 1216 return nil, err 1217 // relist from etcd after "too large" error 1218 case "": 1219 version += 10 1220 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: resourceVersion}}, nil 1221 default: 1222 return nil, fmt.Errorf("unexpected List call: %s", options.ResourceVersion) 1223 } 1224 }, 1225 } 1226 r := NewReflector(lw, &v1.Pod{}, s, 0) 1227 1228 // Initial list should use RV=0 1229 if err := r.ListAndWatch(stopCh); err != nil { 1230 t.Fatal(err) 1231 } 1232 1233 // Relist from the future version. 1234 // This may happen, as watchcache is initialized from "current global etcd resource version" 1235 // when kube-apiserver is starting and if no objects are changing after that each kube-apiserver 1236 // may be synced to a different version and they will never converge. 1237 // TODO: We should use etcd progress-notify feature to avoid this behavior but until this is 1238 // done we simply try to relist from now to avoid continuous errors on relists. 1239 for i := 1; i <= 3; i++ { 1240 // relist twice to cover the two variants of TooLargeResourceVersion api errors 1241 stopCh = make(chan struct{}) 1242 if err := r.ListAndWatch(stopCh); err != nil { 1243 t.Fatal(err) 1244 } 1245 } 1246 1247 expectedRVs := []string{"0", "20", "", "30", "", "40", ""} 1248 if !reflect.DeepEqual(listCallRVs, expectedRVs) { 1249 t.Errorf("Expected series of list calls with resource version of %#v but got: %#v", expectedRVs, listCallRVs) 1250 } 1251 } 1252 1253 func TestGetTypeDescriptionFromObject(t *testing.T) { 1254 obj := &unstructured.Unstructured{} 1255 gvk := schema.GroupVersionKind{ 1256 Group: "mygroup", 1257 Version: "v1", 1258 Kind: "MyKind", 1259 } 1260 obj.SetGroupVersionKind(gvk) 1261 1262 testCases := map[string]struct { 1263 inputType interface{} 1264 expectedTypeDescription string 1265 }{ 1266 "Nil type": { 1267 expectedTypeDescription: defaultExpectedTypeName, 1268 }, 1269 "Normal type": { 1270 inputType: &v1.Pod{}, 1271 expectedTypeDescription: "*v1.Pod", 1272 }, 1273 "Unstructured type without GVK": { 1274 inputType: &unstructured.Unstructured{}, 1275 expectedTypeDescription: "*unstructured.Unstructured", 1276 }, 1277 "Unstructured type with GVK": { 1278 inputType: obj, 1279 expectedTypeDescription: gvk.String(), 1280 }, 1281 } 1282 for testName, tc := range testCases { 1283 t.Run(testName, func(t *testing.T) { 1284 typeDescription := getTypeDescriptionFromObject(tc.inputType) 1285 if tc.expectedTypeDescription != typeDescription { 1286 t.Fatalf("Expected typeDescription %v, got %v", tc.expectedTypeDescription, typeDescription) 1287 } 1288 }) 1289 } 1290 } 1291 1292 func TestGetExpectedGVKFromObject(t *testing.T) { 1293 obj := &unstructured.Unstructured{} 1294 gvk := schema.GroupVersionKind{ 1295 Group: "mygroup", 1296 Version: "v1", 1297 Kind: "MyKind", 1298 } 1299 obj.SetGroupVersionKind(gvk) 1300 1301 testCases := map[string]struct { 1302 inputType interface{} 1303 expectedGVK *schema.GroupVersionKind 1304 }{ 1305 "Nil type": {}, 1306 "Some non Unstructured type": { 1307 inputType: &v1.Pod{}, 1308 }, 1309 "Unstructured type without GVK": { 1310 inputType: &unstructured.Unstructured{}, 1311 }, 1312 "Unstructured type with GVK": { 1313 inputType: obj, 1314 expectedGVK: &gvk, 1315 }, 1316 } 1317 for testName, tc := range testCases { 1318 t.Run(testName, func(t *testing.T) { 1319 expectedGVK := getExpectedGVKFromObject(tc.inputType) 1320 gvkNotEqual := (tc.expectedGVK == nil) != (expectedGVK == nil) 1321 if tc.expectedGVK != nil && expectedGVK != nil { 1322 gvkNotEqual = *tc.expectedGVK != *expectedGVK 1323 } 1324 if gvkNotEqual { 1325 t.Fatalf("Expected expectedGVK %v, got %v", tc.expectedGVK, expectedGVK) 1326 } 1327 }) 1328 } 1329 } 1330 1331 func TestWatchTimeout(t *testing.T) { 1332 1333 testCases := []struct { 1334 name string 1335 minWatchTimeout time.Duration 1336 expectedMinTimeoutSeconds int64 1337 }{ 1338 { 1339 name: "no timeout", 1340 expectedMinTimeoutSeconds: 5 * 60, 1341 }, 1342 { 1343 name: "small timeout not honored", 1344 minWatchTimeout: time.Second, 1345 expectedMinTimeoutSeconds: 5 * 60, 1346 }, 1347 { 1348 name: "30m timeout", 1349 minWatchTimeout: 30 * time.Minute, 1350 expectedMinTimeoutSeconds: 30 * 60, 1351 }, 1352 } 1353 1354 for _, tc := range testCases { 1355 t.Run(tc.name, func(t *testing.T) { 1356 stopCh := make(chan struct{}) 1357 s := NewStore(MetaNamespaceKeyFunc) 1358 var gotTimeoutSeconds int64 1359 1360 lw := &testLW{ 1361 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 1362 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}}, nil 1363 }, 1364 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 1365 if options.TimeoutSeconds != nil { 1366 gotTimeoutSeconds = *options.TimeoutSeconds 1367 } 1368 1369 // Stop once the reflector begins watching since we're only interested in the list. 1370 close(stopCh) 1371 return watch.NewFake(), nil 1372 }, 1373 } 1374 1375 opts := ReflectorOptions{ 1376 MinWatchTimeout: tc.minWatchTimeout, 1377 } 1378 r := NewReflectorWithOptions(lw, &v1.Pod{}, s, opts) 1379 if err := r.ListAndWatch(stopCh); err != nil { 1380 t.Fatal(err) 1381 } 1382 1383 minExpected := tc.expectedMinTimeoutSeconds 1384 maxExpected := 2 * tc.expectedMinTimeoutSeconds 1385 if gotTimeoutSeconds < minExpected || gotTimeoutSeconds > maxExpected { 1386 t.Errorf("unexpected TimeoutSecond, got %v, expected in [%v, %v]", gotTimeoutSeconds, minExpected, maxExpected) 1387 } 1388 }) 1389 } 1390 } 1391 1392 type storeWithRV struct { 1393 Store 1394 1395 // resourceVersions tracks values passed by UpdateResourceVersion 1396 resourceVersions []string 1397 } 1398 1399 func (s *storeWithRV) UpdateResourceVersion(resourceVersion string) { 1400 s.resourceVersions = append(s.resourceVersions, resourceVersion) 1401 } 1402 1403 func newStoreWithRV() *storeWithRV { 1404 return &storeWithRV{ 1405 Store: NewStore(MetaNamespaceKeyFunc), 1406 } 1407 } 1408 1409 func TestReflectorResourceVersionUpdate(t *testing.T) { 1410 s := newStoreWithRV() 1411 1412 stopCh := make(chan struct{}) 1413 fw := watch.NewFake() 1414 1415 lw := &testLW{ 1416 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 1417 return fw, nil 1418 }, 1419 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 1420 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}}, nil 1421 }, 1422 } 1423 r := NewReflector(lw, &v1.Pod{}, s, 0) 1424 1425 makePod := func(rv string) *v1.Pod { 1426 return &v1.Pod{ObjectMeta: metav1.ObjectMeta{ResourceVersion: rv}} 1427 } 1428 1429 go func() { 1430 fw.Action(watch.Added, makePod("10")) 1431 fw.Action(watch.Modified, makePod("20")) 1432 fw.Action(watch.Bookmark, makePod("30")) 1433 fw.Action(watch.Deleted, makePod("40")) 1434 close(stopCh) 1435 }() 1436 1437 // Initial list should use RV=0 1438 if err := r.ListAndWatch(stopCh); err != nil { 1439 t.Fatal(err) 1440 } 1441 1442 expectedRVs := []string{"10", "20", "30", "40"} 1443 if !reflect.DeepEqual(s.resourceVersions, expectedRVs) { 1444 t.Errorf("Expected series of resource version updates of %#v but got: %#v", expectedRVs, s.resourceVersions) 1445 } 1446 } 1447 1448 const ( 1449 fakeItemsNum = 100 1450 exemptObjectIndex = fakeItemsNum / 4 1451 pageNum = 3 1452 ) 1453 1454 func getPodListItems(start int, numItems int) (string, string, *v1.PodList) { 1455 out := &v1.PodList{ 1456 Items: make([]v1.Pod, numItems), 1457 } 1458 1459 for i := 0; i < numItems; i++ { 1460 1461 out.Items[i] = v1.Pod{ 1462 TypeMeta: metav1.TypeMeta{ 1463 APIVersion: "v1", 1464 Kind: "Pod", 1465 }, 1466 ObjectMeta: metav1.ObjectMeta{ 1467 Name: fmt.Sprintf("pod-%d", i+start), 1468 Namespace: "default", 1469 Labels: map[string]string{ 1470 "label-key-1": "label-value-1", 1471 }, 1472 Annotations: map[string]string{ 1473 "annotations-key-1": "annotations-value-1", 1474 }, 1475 }, 1476 Spec: v1.PodSpec{ 1477 Overhead: v1.ResourceList{ 1478 v1.ResourceCPU: resource.MustParse("3"), 1479 v1.ResourceMemory: resource.MustParse("8"), 1480 }, 1481 NodeSelector: map[string]string{ 1482 "foo": "bar", 1483 "baz": "quux", 1484 }, 1485 Affinity: &v1.Affinity{ 1486 NodeAffinity: &v1.NodeAffinity{ 1487 RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ 1488 NodeSelectorTerms: []v1.NodeSelectorTerm{ 1489 {MatchExpressions: []v1.NodeSelectorRequirement{{Key: `foo`}}}, 1490 }, 1491 }, 1492 PreferredDuringSchedulingIgnoredDuringExecution: []v1.PreferredSchedulingTerm{ 1493 {Preference: v1.NodeSelectorTerm{MatchExpressions: []v1.NodeSelectorRequirement{{Key: `foo`}}}}, 1494 }, 1495 }, 1496 }, 1497 TopologySpreadConstraints: []v1.TopologySpreadConstraint{ 1498 {TopologyKey: `foo`}, 1499 }, 1500 HostAliases: []v1.HostAlias{ 1501 {IP: "1.1.1.1"}, 1502 {IP: "2.2.2.2"}, 1503 }, 1504 ImagePullSecrets: []v1.LocalObjectReference{ 1505 {Name: "secret1"}, 1506 {Name: "secret2"}, 1507 }, 1508 Containers: []v1.Container{ 1509 { 1510 Name: "foobar", 1511 Image: "alpine", 1512 Resources: v1.ResourceRequirements{ 1513 Requests: v1.ResourceList{ 1514 v1.ResourceName(v1.ResourceCPU): resource.MustParse("1"), 1515 v1.ResourceName(v1.ResourceMemory): resource.MustParse("5"), 1516 }, 1517 Limits: v1.ResourceList{ 1518 v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"), 1519 v1.ResourceName(v1.ResourceMemory): resource.MustParse("10"), 1520 }, 1521 }, 1522 }, 1523 { 1524 Name: "foobar2", 1525 Image: "alpine", 1526 Resources: v1.ResourceRequirements{ 1527 Requests: v1.ResourceList{ 1528 v1.ResourceName(v1.ResourceCPU): resource.MustParse("4"), 1529 v1.ResourceName(v1.ResourceMemory): resource.MustParse("12"), 1530 }, 1531 Limits: v1.ResourceList{ 1532 v1.ResourceName(v1.ResourceCPU): resource.MustParse("8"), 1533 v1.ResourceName(v1.ResourceMemory): resource.MustParse("24"), 1534 }, 1535 }, 1536 }, 1537 }, 1538 InitContainers: []v1.Container{ 1539 { 1540 Name: "small-init", 1541 Image: "alpine", 1542 Resources: v1.ResourceRequirements{ 1543 Requests: v1.ResourceList{ 1544 v1.ResourceName(v1.ResourceCPU): resource.MustParse("1"), 1545 v1.ResourceName(v1.ResourceMemory): resource.MustParse("5"), 1546 }, 1547 Limits: v1.ResourceList{ 1548 v1.ResourceName(v1.ResourceCPU): resource.MustParse("1"), 1549 v1.ResourceName(v1.ResourceMemory): resource.MustParse("5"), 1550 }, 1551 }, 1552 }, 1553 { 1554 Name: "big-init", 1555 Image: "alpine", 1556 Resources: v1.ResourceRequirements{ 1557 Requests: v1.ResourceList{ 1558 v1.ResourceName(v1.ResourceCPU): resource.MustParse("40"), 1559 v1.ResourceName(v1.ResourceMemory): resource.MustParse("120"), 1560 }, 1561 Limits: v1.ResourceList{ 1562 v1.ResourceName(v1.ResourceCPU): resource.MustParse("80"), 1563 v1.ResourceName(v1.ResourceMemory): resource.MustParse("240"), 1564 }, 1565 }, 1566 }, 1567 }, 1568 Hostname: fmt.Sprintf("node-%d", i), 1569 }, 1570 Status: v1.PodStatus{ 1571 Phase: v1.PodRunning, 1572 ContainerStatuses: []v1.ContainerStatus{ 1573 { 1574 ContainerID: "docker://numbers", 1575 Image: "alpine", 1576 Name: "foobar", 1577 Ready: false, 1578 }, 1579 { 1580 ContainerID: "docker://numbers", 1581 Image: "alpine", 1582 Name: "foobar2", 1583 Ready: false, 1584 }, 1585 }, 1586 InitContainerStatuses: []v1.ContainerStatus{ 1587 { 1588 ContainerID: "docker://numbers", 1589 Image: "alpine", 1590 Name: "small-init", 1591 Ready: false, 1592 }, 1593 { 1594 ContainerID: "docker://numbers", 1595 Image: "alpine", 1596 Name: "big-init", 1597 Ready: false, 1598 }, 1599 }, 1600 Conditions: []v1.PodCondition{ 1601 { 1602 Type: v1.PodScheduled, 1603 Status: v1.ConditionTrue, 1604 Reason: "successfully", 1605 Message: "sync pod successfully", 1606 LastProbeTime: metav1.Now(), 1607 LastTransitionTime: metav1.Now(), 1608 }, 1609 }, 1610 }, 1611 } 1612 } 1613 1614 return out.Items[0].GetName(), out.Items[exemptObjectIndex].GetName(), out 1615 } 1616 1617 func getConfigmapListItems(start int, numItems int) (string, string, *v1.ConfigMapList) { 1618 out := &v1.ConfigMapList{ 1619 Items: make([]v1.ConfigMap, numItems), 1620 } 1621 1622 for i := 0; i < numItems; i++ { 1623 out.Items[i] = v1.ConfigMap{ 1624 TypeMeta: metav1.TypeMeta{ 1625 APIVersion: "v1", 1626 Kind: "ConfigMap", 1627 }, 1628 ObjectMeta: metav1.ObjectMeta{ 1629 Name: fmt.Sprintf("cm-%d", i+start), 1630 Namespace: "default", 1631 Labels: map[string]string{ 1632 "label-key-1": "label-value-1", 1633 }, 1634 Annotations: map[string]string{ 1635 "annotations-key-1": "annotations-value-1", 1636 }, 1637 }, 1638 Data: map[string]string{ 1639 "data-1": "value-1", 1640 "data-2": "value-2", 1641 }, 1642 } 1643 } 1644 1645 return out.Items[0].GetName(), out.Items[exemptObjectIndex].GetName(), out 1646 } 1647 1648 type TestPagingPodsLW struct { 1649 totalPageCount int 1650 fetchedPageCount int 1651 1652 detectedObjectNameList []string 1653 exemptObjectNameList []string 1654 } 1655 1656 func newPageTestLW(totalPageNum int) *TestPagingPodsLW { 1657 return &TestPagingPodsLW{ 1658 totalPageCount: totalPageNum, 1659 fetchedPageCount: 0, 1660 } 1661 } 1662 1663 func (t *TestPagingPodsLW) List(options metav1.ListOptions) (runtime.Object, error) { 1664 firstPodName, exemptPodName, list := getPodListItems(t.fetchedPageCount*fakeItemsNum, fakeItemsNum) 1665 t.detectedObjectNameList = append(t.detectedObjectNameList, firstPodName) 1666 t.exemptObjectNameList = append(t.exemptObjectNameList, exemptPodName) 1667 t.fetchedPageCount++ 1668 if t.fetchedPageCount >= t.totalPageCount { 1669 return list, nil 1670 } 1671 list.SetContinue("true") 1672 return list, nil 1673 } 1674 1675 func (t *TestPagingPodsLW) Watch(options metav1.ListOptions) (watch.Interface, error) { 1676 return nil, nil 1677 } 1678 1679 func TestReflectorListExtract(t *testing.T) { 1680 store := NewStore(func(obj interface{}) (string, error) { 1681 pod, ok := obj.(*v1.Pod) 1682 if !ok { 1683 return "", fmt.Errorf("expect *v1.Pod, but got %T", obj) 1684 } 1685 return pod.GetName(), nil 1686 }) 1687 1688 lw := newPageTestLW(5) 1689 reflector := NewReflector(lw, &v1.Pod{}, store, 0) 1690 reflector.WatchListPageSize = fakeItemsNum 1691 1692 // execute list to fill store 1693 stopCh := make(chan struct{}) 1694 if err := reflector.list(stopCh); err != nil { 1695 t.Fatal(err) 1696 } 1697 1698 // We will not delete exemptPod, 1699 // in order to see if the existence of this Pod causes other Pods that are not used to be unable to properly clear. 1700 for _, podName := range lw.exemptObjectNameList { 1701 _, exist, err := store.GetByKey(podName) 1702 if err != nil || !exist { 1703 t.Fatalf("%s should exist in pod store", podName) 1704 } 1705 } 1706 1707 // we will pay attention to whether the memory occupied by the first Pod is released 1708 // Golang's can only be SetFinalizer for the first element of the array, 1709 // so pod-0 will be the object of our attention 1710 detectedPodAlreadyBeCleared := make(chan struct{}, len(lw.detectedObjectNameList)) 1711 1712 for _, firstPodName := range lw.detectedObjectNameList { 1713 _, exist, err := store.GetByKey(firstPodName) 1714 if err != nil || !exist { 1715 t.Fatalf("%s should exist in pod store", firstPodName) 1716 } 1717 firstPod, exist, err := store.GetByKey(firstPodName) 1718 if err != nil || !exist { 1719 t.Fatalf("%s should exist in pod store", firstPodName) 1720 } 1721 goruntime.SetFinalizer(firstPod, func(obj interface{}) { 1722 t.Logf("%s already be gc\n", obj.(*v1.Pod).GetName()) 1723 detectedPodAlreadyBeCleared <- struct{}{} 1724 }) 1725 } 1726 1727 storedObjectKeys := store.ListKeys() 1728 for _, k := range storedObjectKeys { 1729 // delete all Pods except the exempted Pods. 1730 if sets.NewString(lw.exemptObjectNameList...).Has(k) { 1731 continue 1732 } 1733 obj, exist, err := store.GetByKey(k) 1734 if err != nil || !exist { 1735 t.Fatalf("%s should exist in pod store", k) 1736 } 1737 1738 if err := store.Delete(obj); err != nil { 1739 t.Fatalf("delete object: %v", err) 1740 } 1741 goruntime.GC() 1742 } 1743 1744 clearedNum := 0 1745 for { 1746 select { 1747 case <-detectedPodAlreadyBeCleared: 1748 clearedNum++ 1749 if clearedNum == len(lw.detectedObjectNameList) { 1750 return 1751 } 1752 } 1753 } 1754 } 1755 1756 func BenchmarkExtractList(b *testing.B) { 1757 _, _, podList := getPodListItems(0, fakeItemsNum) 1758 _, _, configMapList := getConfigmapListItems(0, fakeItemsNum) 1759 tests := []struct { 1760 name string 1761 list runtime.Object 1762 }{ 1763 { 1764 name: "PodList", 1765 list: podList, 1766 }, 1767 { 1768 name: "ConfigMapList", 1769 list: configMapList, 1770 }, 1771 } 1772 1773 for _, tc := range tests { 1774 b.Run(tc.name, func(b *testing.B) { 1775 b.ResetTimer() 1776 for i := 0; i < b.N; i++ { 1777 _, err := meta.ExtractList(tc.list) 1778 if err != nil { 1779 b.Errorf("extract list: %v", err) 1780 } 1781 } 1782 b.StopTimer() 1783 }) 1784 } 1785 } 1786 1787 func BenchmarkEachListItem(b *testing.B) { 1788 _, _, podList := getPodListItems(0, fakeItemsNum) 1789 _, _, configMapList := getConfigmapListItems(0, fakeItemsNum) 1790 tests := []struct { 1791 name string 1792 list runtime.Object 1793 }{ 1794 { 1795 name: "PodList", 1796 list: podList, 1797 }, 1798 { 1799 name: "ConfigMapList", 1800 list: configMapList, 1801 }, 1802 } 1803 1804 for _, tc := range tests { 1805 b.Run(tc.name, func(b *testing.B) { 1806 b.ResetTimer() 1807 for i := 0; i < b.N; i++ { 1808 err := meta.EachListItem(tc.list, func(object runtime.Object) error { 1809 return nil 1810 }) 1811 if err != nil { 1812 b.Errorf("each list: %v", err) 1813 } 1814 } 1815 b.StopTimer() 1816 }) 1817 } 1818 } 1819 1820 func BenchmarkExtractListWithAlloc(b *testing.B) { 1821 _, _, podList := getPodListItems(0, fakeItemsNum) 1822 _, _, configMapList := getConfigmapListItems(0, fakeItemsNum) 1823 tests := []struct { 1824 name string 1825 list runtime.Object 1826 }{ 1827 { 1828 name: "PodList", 1829 list: podList, 1830 }, 1831 { 1832 name: "ConfigMapList", 1833 list: configMapList, 1834 }, 1835 } 1836 1837 for _, tc := range tests { 1838 b.Run(tc.name, func(b *testing.B) { 1839 b.ResetTimer() 1840 for i := 0; i < b.N; i++ { 1841 _, err := meta.ExtractListWithAlloc(tc.list) 1842 if err != nil { 1843 b.Errorf("extract list with alloc: %v", err) 1844 } 1845 } 1846 b.StopTimer() 1847 }) 1848 } 1849 } 1850 1851 func BenchmarkEachListItemWithAlloc(b *testing.B) { 1852 _, _, podList := getPodListItems(0, fakeItemsNum) 1853 _, _, configMapList := getConfigmapListItems(0, fakeItemsNum) 1854 tests := []struct { 1855 name string 1856 list runtime.Object 1857 }{ 1858 { 1859 name: "PodList", 1860 list: podList, 1861 }, 1862 { 1863 name: "ConfigMapList", 1864 list: configMapList, 1865 }, 1866 } 1867 1868 for _, tc := range tests { 1869 b.Run(tc.name, func(b *testing.B) { 1870 b.ResetTimer() 1871 for i := 0; i < b.N; i++ { 1872 err := meta.EachListItemWithAlloc(tc.list, func(object runtime.Object) error { 1873 return nil 1874 }) 1875 if err != nil { 1876 b.Errorf("each list with alloc: %v", err) 1877 } 1878 } 1879 b.StopTimer() 1880 }) 1881 } 1882 } 1883 1884 func BenchmarkReflectorList(b *testing.B) { 1885 ctx, cancel := context.WithTimeout(context.Background(), wait.ForeverTestTimeout) 1886 defer cancel() 1887 1888 store := NewStore(func(obj interface{}) (string, error) { 1889 o, err := meta.Accessor(obj) 1890 if err != nil { 1891 return "", err 1892 } 1893 return o.GetName(), nil 1894 }) 1895 1896 _, _, podList := getPodListItems(0, fakeItemsNum) 1897 _, _, configMapList := getConfigmapListItems(0, fakeItemsNum) 1898 tests := []struct { 1899 name string 1900 sample func() interface{} 1901 list runtime.Object 1902 }{ 1903 { 1904 name: "PodList", 1905 sample: func() interface{} { 1906 return v1.Pod{} 1907 }, 1908 list: podList, 1909 }, 1910 { 1911 name: "ConfigMapList", 1912 sample: func() interface{} { 1913 return v1.ConfigMap{} 1914 }, 1915 list: configMapList, 1916 }, 1917 } 1918 1919 for _, tc := range tests { 1920 b.Run(tc.name, func(b *testing.B) { 1921 1922 sample := tc.sample() 1923 reflector := NewReflector(newPageTestLW(pageNum), &sample, store, 0) 1924 reflector.WatchListPageSize = fakeItemsNum 1925 1926 b.ResetTimer() 1927 for i := 0; i < b.N; i++ { 1928 err := reflector.list(ctx.Done()) 1929 if err != nil { 1930 b.Fatalf("reflect list: %v", err) 1931 } 1932 } 1933 b.StopTimer() 1934 }) 1935 } 1936 }