k8s.io/client-go@v0.31.1/tools/cache/reflector_watchlist_test.go (about) 1 /* 2 Copyright 2023 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 "sort" 23 "sync" 24 "testing" 25 "time" 26 27 "github.com/google/go-cmp/cmp" 28 "github.com/google/go-cmp/cmp/cmpopts" 29 "github.com/stretchr/testify/require" 30 31 v1 "k8s.io/api/core/v1" 32 apierrors "k8s.io/apimachinery/pkg/api/errors" 33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 34 "k8s.io/apimachinery/pkg/runtime" 35 "k8s.io/apimachinery/pkg/runtime/schema" 36 "k8s.io/apimachinery/pkg/types" 37 "k8s.io/apimachinery/pkg/watch" 38 testingclock "k8s.io/utils/clock/testing" 39 "k8s.io/utils/pointer" 40 "k8s.io/utils/ptr" 41 ) 42 43 func TestInitialEventsEndBookmarkTicker(t *testing.T) { 44 assertNoEvents := func(t *testing.T, c <-chan time.Time) { 45 select { 46 case e := <-c: 47 t.Errorf("Unexpected: %#v event received, expected no events", e) 48 default: 49 return 50 } 51 } 52 53 t.Run("testing NoopInitialEventsEndBookmarkTicker", func(t *testing.T) { 54 clock := testingclock.NewFakeClock(time.Now()) 55 target := newInitialEventsEndBookmarkTickerInternal("testName", clock, clock.Now(), time.Second, false) 56 57 clock.Step(30 * time.Second) 58 assertNoEvents(t, target.C()) 59 actualWarning := target.produceWarningIfExpired() 60 61 require.Empty(t, actualWarning, "didn't expect any warning") 62 // validate if the other methods don't produce panic 63 target.warnIfExpired() 64 target.observeLastEventTimeStamp(clock.Now()) 65 66 // make sure that after calling the other methods 67 // nothing hasn't changed 68 actualWarning = target.produceWarningIfExpired() 69 require.Empty(t, actualWarning, "didn't expect any warning") 70 assertNoEvents(t, target.C()) 71 72 target.Stop() 73 }) 74 75 t.Run("testing InitialEventsEndBookmarkTicker backed by a fake clock", func(t *testing.T) { 76 clock := testingclock.NewFakeClock(time.Now()) 77 target := newInitialEventsEndBookmarkTickerInternal("testName", clock, clock.Now(), time.Second, true) 78 clock.Step(500 * time.Millisecond) 79 assertNoEvents(t, target.C()) 80 81 clock.Step(500 * time.Millisecond) 82 <-target.C() 83 actualWarning := target.produceWarningIfExpired() 84 require.Equal(t, errors.New("testName: awaiting required bookmark event for initial events stream, no events received for 1s"), actualWarning) 85 86 clock.Step(time.Second) 87 <-target.C() 88 actualWarning = target.produceWarningIfExpired() 89 require.Equal(t, errors.New("testName: awaiting required bookmark event for initial events stream, no events received for 2s"), actualWarning) 90 91 target.observeLastEventTimeStamp(clock.Now()) 92 clock.Step(500 * time.Millisecond) 93 assertNoEvents(t, target.C()) 94 95 clock.Step(500 * time.Millisecond) 96 <-target.C() 97 actualWarning = target.produceWarningIfExpired() 98 require.Equal(t, errors.New("testName: hasn't received required bookmark event marking the end of initial events stream, received last event 1s ago"), actualWarning) 99 100 clock.Step(time.Second) 101 <-target.C() 102 actualWarning = target.produceWarningIfExpired() 103 require.Equal(t, errors.New("testName: hasn't received required bookmark event marking the end of initial events stream, received last event 2s ago"), actualWarning) 104 105 target.Stop() 106 assertNoEvents(t, target.C()) 107 }) 108 } 109 110 func TestWatchList(t *testing.T) { 111 scenarios := []struct { 112 name string 113 disableUseWatchList bool 114 115 // closes listWatcher after sending the specified number of watch events 116 closeAfterWatchEvents int 117 // closes listWatcher after getting the specified number of watch requests 118 closeAfterWatchRequests int 119 // closes listWatcher after getting the specified number of list requests 120 closeAfterListRequests int 121 122 // stops Watcher after sending the specified number of watch events 123 stopAfterWatchEvents int 124 125 watchOptionsPredicate func(options metav1.ListOptions) error 126 watchEvents []watch.Event 127 podList *v1.PodList 128 129 expectedRequestOptions []metav1.ListOptions 130 expectedWatchRequests int 131 expectedListRequests int 132 expectedStoreContent []v1.Pod 133 expectedError error 134 }{ 135 { 136 name: "the reflector won't be synced if the bookmark event has been received", 137 watchEvents: []watch.Event{{Type: watch.Added, Object: makePod("p1", "1")}}, 138 closeAfterWatchEvents: 1, 139 expectedWatchRequests: 1, 140 expectedRequestOptions: []metav1.ListOptions{{ 141 SendInitialEvents: pointer.Bool(true), 142 AllowWatchBookmarks: true, 143 ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, 144 TimeoutSeconds: pointer.Int64(1), 145 }}, 146 }, 147 { 148 name: "the reflector uses the old LIST/WATCH semantics if the UseWatchList is turned off", 149 disableUseWatchList: true, 150 closeAfterWatchRequests: 1, 151 podList: &v1.PodList{ 152 ListMeta: metav1.ListMeta{ResourceVersion: "1"}, 153 Items: []v1.Pod{*makePod("p1", "1")}, 154 }, 155 expectedWatchRequests: 1, 156 expectedListRequests: 1, 157 expectedRequestOptions: []metav1.ListOptions{ 158 { 159 ResourceVersion: "0", 160 Limit: 500, 161 }, 162 { 163 AllowWatchBookmarks: true, 164 ResourceVersion: "1", 165 TimeoutSeconds: pointer.Int64(1), 166 }}, 167 expectedStoreContent: []v1.Pod{*makePod("p1", "1")}, 168 }, 169 { 170 name: "returning any other error than apierrors.NewInvalid forces fallback", 171 watchOptionsPredicate: func(options metav1.ListOptions) error { 172 if options.SendInitialEvents != nil && *options.SendInitialEvents { 173 return fmt.Errorf("dummy error") 174 } 175 return nil 176 }, 177 podList: &v1.PodList{ 178 ListMeta: metav1.ListMeta{ResourceVersion: "1"}, 179 Items: []v1.Pod{*makePod("p1", "1")}, 180 }, 181 closeAfterWatchEvents: 1, 182 watchEvents: []watch.Event{{Type: watch.Added, Object: makePod("p2", "2")}}, 183 expectedWatchRequests: 2, 184 expectedListRequests: 1, 185 expectedStoreContent: []v1.Pod{*makePod("p1", "1"), *makePod("p2", "2")}, 186 expectedRequestOptions: []metav1.ListOptions{ 187 { 188 SendInitialEvents: pointer.Bool(true), 189 AllowWatchBookmarks: true, 190 ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, 191 TimeoutSeconds: pointer.Int64(1), 192 }, 193 { 194 ResourceVersion: "0", 195 Limit: 500, 196 }, 197 { 198 AllowWatchBookmarks: true, 199 ResourceVersion: "1", 200 TimeoutSeconds: pointer.Int64(1), 201 }, 202 }, 203 }, 204 { 205 name: "the reflector can fall back to old LIST/WATCH semantics when a server doesn't support streaming", 206 watchOptionsPredicate: func(options metav1.ListOptions) error { 207 if options.SendInitialEvents != nil && *options.SendInitialEvents { 208 return apierrors.NewInvalid(schema.GroupKind{}, "streaming is not allowed", nil) 209 } 210 return nil 211 }, 212 podList: &v1.PodList{ 213 ListMeta: metav1.ListMeta{ResourceVersion: "1"}, 214 Items: []v1.Pod{*makePod("p1", "1")}, 215 }, 216 closeAfterWatchEvents: 1, 217 watchEvents: []watch.Event{{Type: watch.Added, Object: makePod("p2", "2")}}, 218 expectedWatchRequests: 2, 219 expectedListRequests: 1, 220 expectedStoreContent: []v1.Pod{*makePod("p1", "1"), *makePod("p2", "2")}, 221 expectedRequestOptions: []metav1.ListOptions{ 222 { 223 SendInitialEvents: pointer.Bool(true), 224 AllowWatchBookmarks: true, 225 ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, 226 TimeoutSeconds: pointer.Int64(1), 227 }, 228 { 229 ResourceVersion: "0", 230 Limit: 500, 231 }, 232 { 233 AllowWatchBookmarks: true, 234 ResourceVersion: "1", 235 TimeoutSeconds: pointer.Int64(1), 236 }, 237 }, 238 }, 239 { 240 name: "prove that the reflector is synced after receiving a bookmark event", 241 closeAfterWatchEvents: 3, 242 watchEvents: []watch.Event{ 243 {Type: watch.Added, Object: makePod("p1", "1")}, 244 {Type: watch.Added, Object: makePod("p2", "2")}, 245 {Type: watch.Bookmark, Object: &v1.Pod{ 246 ObjectMeta: metav1.ObjectMeta{ 247 ResourceVersion: "2", 248 Annotations: map[string]string{metav1.InitialEventsAnnotationKey: "true"}, 249 }, 250 }}, 251 }, 252 expectedWatchRequests: 1, 253 expectedRequestOptions: []metav1.ListOptions{{ 254 SendInitialEvents: pointer.Bool(true), 255 AllowWatchBookmarks: true, 256 ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, 257 TimeoutSeconds: pointer.Int64(1), 258 }}, 259 expectedStoreContent: []v1.Pod{*makePod("p1", "1"), *makePod("p2", "2")}, 260 }, 261 { 262 name: "check if Updates and Deletes events are propagated during streaming (until the bookmark is received)", 263 closeAfterWatchEvents: 6, 264 watchEvents: []watch.Event{ 265 {Type: watch.Added, Object: makePod("p1", "1")}, 266 {Type: watch.Added, Object: makePod("p2", "2")}, 267 {Type: watch.Modified, Object: func() runtime.Object { 268 p1 := makePod("p1", "3") 269 p1.Spec.ActiveDeadlineSeconds = pointer.Int64(12) 270 return p1 271 }()}, 272 {Type: watch.Added, Object: makePod("p3", "4")}, 273 {Type: watch.Deleted, Object: makePod("p3", "5")}, 274 {Type: watch.Bookmark, Object: &v1.Pod{ 275 ObjectMeta: metav1.ObjectMeta{ 276 ResourceVersion: "5", 277 Annotations: map[string]string{metav1.InitialEventsAnnotationKey: "true"}, 278 }, 279 }}, 280 }, 281 expectedWatchRequests: 1, 282 expectedRequestOptions: []metav1.ListOptions{{ 283 SendInitialEvents: pointer.Bool(true), 284 AllowWatchBookmarks: true, 285 ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, 286 TimeoutSeconds: pointer.Int64(1), 287 }}, 288 expectedStoreContent: []v1.Pod{ 289 *makePod("p2", "2"), 290 func() v1.Pod { 291 p1 := *makePod("p1", "3") 292 p1.Spec.ActiveDeadlineSeconds = pointer.Int64(12) 293 return p1 294 }(), 295 }, 296 }, 297 { 298 name: "checks if the reflector retries 429", 299 watchOptionsPredicate: func() func(options metav1.ListOptions) error { 300 counter := 1 301 return func(options metav1.ListOptions) error { 302 if counter < 3 { 303 counter++ 304 return apierrors.NewTooManyRequests("busy, check again later", 1) 305 } 306 return nil 307 } 308 }(), 309 closeAfterWatchEvents: 2, 310 watchEvents: []watch.Event{ 311 {Type: watch.Added, Object: makePod("p1", "1")}, 312 {Type: watch.Bookmark, Object: &v1.Pod{ 313 ObjectMeta: metav1.ObjectMeta{ 314 ResourceVersion: "2", 315 Annotations: map[string]string{metav1.InitialEventsAnnotationKey: "true"}, 316 }, 317 }}, 318 }, 319 expectedWatchRequests: 3, 320 expectedRequestOptions: []metav1.ListOptions{ 321 { 322 SendInitialEvents: pointer.Bool(true), 323 AllowWatchBookmarks: true, 324 ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, 325 TimeoutSeconds: pointer.Int64(1), 326 }, 327 { 328 SendInitialEvents: pointer.Bool(true), 329 AllowWatchBookmarks: true, 330 ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, 331 TimeoutSeconds: pointer.Int64(1), 332 }, 333 { 334 SendInitialEvents: pointer.Bool(true), 335 AllowWatchBookmarks: true, 336 ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, 337 TimeoutSeconds: pointer.Int64(1), 338 }, 339 }, 340 expectedStoreContent: []v1.Pod{*makePod("p1", "1")}, 341 }, 342 { 343 name: "check if stopping a watcher before sync results in creating a new watch-list request", 344 stopAfterWatchEvents: 1, 345 closeAfterWatchEvents: 3, 346 watchEvents: []watch.Event{ 347 {Type: watch.Added, Object: makePod("p1", "1")}, 348 // second request 349 {Type: watch.Added, Object: makePod("p1", "1")}, 350 {Type: watch.Bookmark, Object: &v1.Pod{ 351 ObjectMeta: metav1.ObjectMeta{ 352 ResourceVersion: "1", 353 Annotations: map[string]string{metav1.InitialEventsAnnotationKey: "true"}, 354 }, 355 }}, 356 }, 357 expectedWatchRequests: 2, 358 expectedRequestOptions: []metav1.ListOptions{ 359 { 360 SendInitialEvents: pointer.Bool(true), 361 AllowWatchBookmarks: true, 362 ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, 363 TimeoutSeconds: pointer.Int64(1), 364 }, 365 { 366 SendInitialEvents: pointer.Bool(true), 367 AllowWatchBookmarks: true, 368 ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, 369 TimeoutSeconds: pointer.Int64(1), 370 }, 371 }, 372 expectedStoreContent: []v1.Pod{*makePod("p1", "1")}, 373 }, 374 { 375 name: "stopping a watcher after synchronization results in creating a new watch request", 376 stopAfterWatchEvents: 4, 377 closeAfterWatchEvents: 5, 378 watchEvents: []watch.Event{ 379 {Type: watch.Added, Object: makePod("p1", "1")}, 380 {Type: watch.Added, Object: makePod("p2", "2")}, 381 {Type: watch.Bookmark, Object: &v1.Pod{ 382 ObjectMeta: metav1.ObjectMeta{ 383 ResourceVersion: "2", 384 Annotations: map[string]string{metav1.InitialEventsAnnotationKey: "true"}, 385 }, 386 }}, 387 {Type: watch.Added, Object: makePod("p3", "3")}, 388 // second request 389 {Type: watch.Added, Object: makePod("p4", "4")}, 390 }, 391 expectedWatchRequests: 2, 392 expectedRequestOptions: []metav1.ListOptions{ 393 { 394 SendInitialEvents: pointer.Bool(true), 395 AllowWatchBookmarks: true, 396 ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, 397 TimeoutSeconds: pointer.Int64(1), 398 }, 399 { 400 AllowWatchBookmarks: true, 401 ResourceVersion: "3", 402 TimeoutSeconds: pointer.Int64(1), 403 }, 404 }, 405 expectedStoreContent: []v1.Pod{*makePod("p1", "1"), *makePod("p2", "2"), *makePod("p3", "3"), *makePod("p4", "4")}, 406 }, 407 { 408 name: "expiring an established watcher results in returning an error from the reflector", 409 watchOptionsPredicate: func() func(options metav1.ListOptions) error { 410 counter := 0 411 return func(options metav1.ListOptions) error { 412 counter++ 413 if counter == 2 { 414 return apierrors.NewResourceExpired("rv already expired") 415 } 416 return nil 417 } 418 }(), 419 stopAfterWatchEvents: 3, 420 watchEvents: []watch.Event{ 421 {Type: watch.Added, Object: makePod("p1", "1")}, 422 {Type: watch.Bookmark, Object: &v1.Pod{ 423 ObjectMeta: metav1.ObjectMeta{ 424 ResourceVersion: "2", 425 Annotations: map[string]string{metav1.InitialEventsAnnotationKey: "true"}, 426 }, 427 }}, 428 {Type: watch.Added, Object: makePod("p3", "3")}, 429 }, 430 expectedWatchRequests: 2, 431 expectedRequestOptions: []metav1.ListOptions{ 432 { 433 SendInitialEvents: pointer.Bool(true), 434 AllowWatchBookmarks: true, 435 ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, 436 TimeoutSeconds: pointer.Int64(1), 437 }, 438 { 439 AllowWatchBookmarks: true, 440 ResourceVersion: "3", 441 TimeoutSeconds: pointer.Int64(1), 442 }, 443 }, 444 expectedStoreContent: []v1.Pod{*makePod("p1", "1"), *makePod("p3", "3")}, 445 expectedError: apierrors.NewResourceExpired("rv already expired"), 446 }, 447 { 448 name: "prove that the reflector is checking the value of the initialEventsEnd annotation", 449 closeAfterWatchEvents: 3, 450 watchEvents: []watch.Event{ 451 {Type: watch.Added, Object: makePod("p1", "1")}, 452 {Type: watch.Added, Object: makePod("p2", "2")}, 453 {Type: watch.Bookmark, Object: &v1.Pod{ 454 ObjectMeta: metav1.ObjectMeta{ 455 ResourceVersion: "2", 456 Annotations: map[string]string{metav1.InitialEventsAnnotationKey: "false"}, 457 }, 458 }}, 459 }, 460 expectedWatchRequests: 1, 461 expectedRequestOptions: []metav1.ListOptions{{ 462 SendInitialEvents: pointer.Bool(true), 463 AllowWatchBookmarks: true, 464 ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, 465 TimeoutSeconds: pointer.Int64(1), 466 }}, 467 }, 468 } 469 for _, s := range scenarios { 470 t.Run(s.name, func(t *testing.T) { 471 scenario := s // capture as local variable 472 listWatcher, store, reflector, stopCh := testData() 473 go func() { 474 for i, e := range scenario.watchEvents { 475 listWatcher.fakeWatcher.Action(e.Type, e.Object) 476 if i+1 == scenario.stopAfterWatchEvents { 477 listWatcher.StopAndRecreateWatch() 478 continue 479 } 480 if i+1 == scenario.closeAfterWatchEvents { 481 close(stopCh) 482 } 483 } 484 }() 485 listWatcher.watchOptionsPredicate = scenario.watchOptionsPredicate 486 listWatcher.closeAfterWatchRequests = scenario.closeAfterWatchRequests 487 listWatcher.customListResponse = scenario.podList 488 listWatcher.closeAfterListRequests = scenario.closeAfterListRequests 489 if scenario.disableUseWatchList { 490 reflector.UseWatchList = ptr.To(false) 491 } 492 493 err := reflector.ListAndWatch(stopCh) 494 if scenario.expectedError != nil && err == nil { 495 t.Fatalf("expected error %q, got nil", scenario.expectedError) 496 } 497 if scenario.expectedError == nil && err != nil { 498 t.Fatalf("unexpected error: %v", err) 499 } 500 if scenario.expectedError != nil && err.Error() != scenario.expectedError.Error() { 501 t.Fatalf("expected error %q, got %q", scenario.expectedError, err.Error()) 502 } 503 504 verifyWatchCounter(t, listWatcher, scenario.expectedWatchRequests) 505 verifyListCounter(t, listWatcher, scenario.expectedListRequests) 506 verifyRequestOptions(t, listWatcher, scenario.expectedRequestOptions) 507 verifyStore(t, store, scenario.expectedStoreContent) 508 }) 509 } 510 } 511 512 func verifyRequestOptions(t *testing.T, lw *fakeListWatcher, expectedRequestOptions []metav1.ListOptions) { 513 if len(lw.requestOptions) != len(expectedRequestOptions) { 514 t.Fatalf("expected to receive exactly %v requests, got %v", len(expectedRequestOptions), len(lw.requestOptions)) 515 } 516 517 for index, expectedRequestOption := range expectedRequestOptions { 518 actualRequestOption := lw.requestOptions[index] 519 if actualRequestOption.TimeoutSeconds == nil && expectedRequestOption.TimeoutSeconds != nil { 520 t.Fatalf("expected the request to specify TimeoutSeconds option but it didn't, actual = %#v, expected = %#v", actualRequestOption, expectedRequestOption) 521 } 522 if actualRequestOption.TimeoutSeconds != nil && expectedRequestOption.TimeoutSeconds == nil { 523 t.Fatalf("unexpected TimeoutSeconds option specified, actual = %#v, expected = %#v", actualRequestOption, expectedRequestOption) 524 } 525 // ignore actual values 526 actualRequestOption.TimeoutSeconds = nil 527 expectedRequestOption.TimeoutSeconds = nil 528 if !cmp.Equal(actualRequestOption, expectedRequestOption) { 529 t.Fatalf("expected %#v, got %#v", expectedRequestOption, actualRequestOption) 530 } 531 } 532 } 533 534 func verifyListCounter(t *testing.T, lw *fakeListWatcher, expectedListCounter int) { 535 if lw.listCounter != expectedListCounter { 536 t.Fatalf("unexpected number of LIST requests, got: %v, expected: %v", lw.listCounter, expectedListCounter) 537 } 538 } 539 540 func verifyWatchCounter(t *testing.T, lw *fakeListWatcher, expectedWatchCounter int) { 541 if lw.watchCounter != expectedWatchCounter { 542 t.Fatalf("unexpected number of WATCH requests, got: %v, expected: %v", lw.watchCounter, expectedWatchCounter) 543 } 544 } 545 546 type byName []v1.Pod 547 548 func (a byName) Len() int { return len(a) } 549 func (a byName) Less(i, j int) bool { return a[i].Name < a[j].Name } 550 func (a byName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 551 552 func verifyStore(t *testing.T, s Store, expectedPods []v1.Pod) { 553 rawPods := s.List() 554 actualPods := []v1.Pod{} 555 for _, p := range rawPods { 556 actualPods = append(actualPods, *p.(*v1.Pod)) 557 } 558 559 sort.Sort(byName(actualPods)) 560 sort.Sort(byName(expectedPods)) 561 if !cmp.Equal(actualPods, expectedPods, cmpopts.EquateEmpty()) { 562 t.Fatalf("unexpected store content, diff: %s", cmp.Diff(actualPods, expectedPods)) 563 } 564 } 565 566 func makePod(name, rv string) *v1.Pod { 567 return &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: name, ResourceVersion: rv, UID: types.UID(name)}} 568 } 569 570 func testData() (*fakeListWatcher, Store, *Reflector, chan struct{}) { 571 s := NewStore(MetaNamespaceKeyFunc) 572 stopCh := make(chan struct{}) 573 lw := &fakeListWatcher{ 574 fakeWatcher: watch.NewFake(), 575 stop: func() { 576 close(stopCh) 577 }, 578 } 579 r := NewReflector(lw, &v1.Pod{}, s, 0) 580 r.UseWatchList = ptr.To(true) 581 582 return lw, s, r, stopCh 583 } 584 585 type fakeListWatcher struct { 586 lock sync.Mutex 587 fakeWatcher *watch.FakeWatcher 588 listCounter int 589 watchCounter int 590 closeAfterWatchRequests int 591 closeAfterListRequests int 592 stop func() 593 594 requestOptions []metav1.ListOptions 595 596 customListResponse *v1.PodList 597 watchOptionsPredicate func(options metav1.ListOptions) error 598 } 599 600 func (lw *fakeListWatcher) List(options metav1.ListOptions) (runtime.Object, error) { 601 lw.listCounter++ 602 lw.requestOptions = append(lw.requestOptions, options) 603 if lw.listCounter == lw.closeAfterListRequests { 604 lw.stop() 605 } 606 if lw.customListResponse != nil { 607 return lw.customListResponse, nil 608 } 609 return nil, fmt.Errorf("not implemented") 610 } 611 612 func (lw *fakeListWatcher) Watch(options metav1.ListOptions) (watch.Interface, error) { 613 lw.watchCounter++ 614 lw.requestOptions = append(lw.requestOptions, options) 615 if lw.watchCounter == lw.closeAfterWatchRequests { 616 lw.stop() 617 } 618 if lw.watchOptionsPredicate != nil { 619 if err := lw.watchOptionsPredicate(options); err != nil { 620 return nil, err 621 } 622 } 623 lw.lock.Lock() 624 defer lw.lock.Unlock() 625 return lw.fakeWatcher, nil 626 } 627 628 func (lw *fakeListWatcher) StopAndRecreateWatch() { 629 lw.lock.Lock() 630 defer lw.lock.Unlock() 631 lw.fakeWatcher.Stop() 632 lw.fakeWatcher = watch.NewFake() 633 }