k8s.io/apiserver@v0.29.3/pkg/storage/cacher/watch_cache_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 cacher 18 19 import ( 20 "context" 21 "fmt" 22 "strconv" 23 "strings" 24 "testing" 25 "time" 26 27 v1 "k8s.io/api/core/v1" 28 apiequality "k8s.io/apimachinery/pkg/api/equality" 29 "k8s.io/apimachinery/pkg/api/errors" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/fields" 32 "k8s.io/apimachinery/pkg/labels" 33 "k8s.io/apimachinery/pkg/runtime" 34 "k8s.io/apimachinery/pkg/runtime/schema" 35 "k8s.io/apimachinery/pkg/util/wait" 36 "k8s.io/apimachinery/pkg/watch" 37 "k8s.io/apiserver/pkg/features" 38 "k8s.io/apiserver/pkg/storage" 39 utilfeature "k8s.io/apiserver/pkg/util/feature" 40 "k8s.io/client-go/tools/cache" 41 featuregatetesting "k8s.io/component-base/featuregate/testing" 42 "k8s.io/utils/clock" 43 testingclock "k8s.io/utils/clock/testing" 44 ) 45 46 func makeTestPod(name string, resourceVersion uint64) *v1.Pod { 47 return makeTestPodDetails(name, resourceVersion, "some-node", map[string]string{"k8s-app": "my-app"}) 48 } 49 50 func makeTestPodDetails(name string, resourceVersion uint64, nodeName string, labels map[string]string) *v1.Pod { 51 return &v1.Pod{ 52 ObjectMeta: metav1.ObjectMeta{ 53 Namespace: "ns", 54 Name: name, 55 ResourceVersion: strconv.FormatUint(resourceVersion, 10), 56 Labels: labels, 57 }, 58 Spec: v1.PodSpec{ 59 NodeName: nodeName, 60 }, 61 } 62 } 63 64 func makeTestStoreElement(pod *v1.Pod) *storeElement { 65 return &storeElement{ 66 Key: "prefix/ns/" + pod.Name, 67 Object: pod, 68 Labels: labels.Set(pod.Labels), 69 Fields: fields.Set{"spec.nodeName": pod.Spec.NodeName}, 70 } 71 } 72 73 type testWatchCache struct { 74 *watchCache 75 76 bookmarkRevision chan int64 77 stopCh chan struct{} 78 } 79 80 func (w *testWatchCache) getAllEventsSince(resourceVersion uint64) ([]*watchCacheEvent, error) { 81 cacheInterval, err := w.getCacheIntervalForEvents(resourceVersion) 82 if err != nil { 83 return nil, err 84 } 85 86 result := []*watchCacheEvent{} 87 for { 88 event, err := cacheInterval.Next() 89 if err != nil { 90 return nil, err 91 } 92 if event == nil { 93 break 94 } 95 result = append(result, event) 96 } 97 98 return result, nil 99 } 100 101 func (w *testWatchCache) getCacheIntervalForEvents(resourceVersion uint64) (*watchCacheInterval, error) { 102 w.RLock() 103 defer w.RUnlock() 104 105 return w.getAllEventsSinceLocked(resourceVersion) 106 } 107 108 // newTestWatchCache just adds a fake clock. 109 func newTestWatchCache(capacity int, indexers *cache.Indexers) *testWatchCache { 110 keyFunc := func(obj runtime.Object) (string, error) { 111 return storage.NamespaceKeyFunc("prefix", obj) 112 } 113 getAttrsFunc := func(obj runtime.Object) (labels.Set, fields.Set, error) { 114 pod, ok := obj.(*v1.Pod) 115 if !ok { 116 return nil, nil, fmt.Errorf("not a pod") 117 } 118 return labels.Set(pod.Labels), fields.Set{"spec.nodeName": pod.Spec.NodeName}, nil 119 } 120 versioner := storage.APIObjectVersioner{} 121 mockHandler := func(*watchCacheEvent) {} 122 wc := &testWatchCache{} 123 wc.bookmarkRevision = make(chan int64, 1) 124 wc.stopCh = make(chan struct{}) 125 pr := newConditionalProgressRequester(wc.RequestWatchProgress, &immediateTickerFactory{}, nil) 126 go pr.Run(wc.stopCh) 127 wc.watchCache = newWatchCache(keyFunc, mockHandler, getAttrsFunc, versioner, indexers, testingclock.NewFakeClock(time.Now()), schema.GroupResource{Resource: "pods"}, pr) 128 // To preserve behavior of tests that assume a given capacity, 129 // resize it to th expected size. 130 wc.capacity = capacity 131 wc.cache = make([]*watchCacheEvent, capacity) 132 wc.lowerBoundCapacity = min(capacity, defaultLowerBoundCapacity) 133 wc.upperBoundCapacity = max(capacity, defaultUpperBoundCapacity) 134 135 return wc 136 } 137 138 type immediateTickerFactory struct{} 139 140 func (t *immediateTickerFactory) NewTicker(d time.Duration) clock.Ticker { 141 return &immediateTicker{stopCh: make(chan struct{})} 142 } 143 144 type immediateTicker struct { 145 stopCh chan struct{} 146 } 147 148 func (t *immediateTicker) C() <-chan time.Time { 149 ch := make(chan time.Time) 150 go func() { 151 for { 152 select { 153 case ch <- time.Now(): 154 case <-t.stopCh: 155 return 156 } 157 } 158 }() 159 return ch 160 } 161 162 func (t *immediateTicker) Stop() { 163 close(t.stopCh) 164 } 165 166 func (w *testWatchCache) RequestWatchProgress(ctx context.Context) error { 167 go func() { 168 select { 169 case rev := <-w.bookmarkRevision: 170 w.UpdateResourceVersion(fmt.Sprintf("%d", rev)) 171 case <-ctx.Done(): 172 return 173 } 174 }() 175 return nil 176 } 177 178 func (w *testWatchCache) Stop() { 179 close(w.stopCh) 180 } 181 182 func TestWatchCacheBasic(t *testing.T) { 183 store := newTestWatchCache(2, &cache.Indexers{}) 184 defer store.Stop() 185 186 // Test Add/Update/Delete. 187 pod1 := makeTestPod("pod", 1) 188 if err := store.Add(pod1); err != nil { 189 t.Errorf("unexpected error: %v", err) 190 } 191 if item, ok, _ := store.Get(pod1); !ok { 192 t.Errorf("didn't find pod") 193 } else { 194 expected := makeTestStoreElement(makeTestPod("pod", 1)) 195 if !apiequality.Semantic.DeepEqual(expected, item) { 196 t.Errorf("expected %v, got %v", expected, item) 197 } 198 } 199 pod2 := makeTestPod("pod", 2) 200 if err := store.Update(pod2); err != nil { 201 t.Errorf("unexpected error: %v", err) 202 } 203 if item, ok, _ := store.Get(pod2); !ok { 204 t.Errorf("didn't find pod") 205 } else { 206 expected := makeTestStoreElement(makeTestPod("pod", 2)) 207 if !apiequality.Semantic.DeepEqual(expected, item) { 208 t.Errorf("expected %v, got %v", expected, item) 209 } 210 } 211 pod3 := makeTestPod("pod", 3) 212 if err := store.Delete(pod3); err != nil { 213 t.Errorf("unexpected error: %v", err) 214 } 215 if _, ok, _ := store.Get(pod3); ok { 216 t.Errorf("found pod") 217 } 218 219 // Test List. 220 store.Add(makeTestPod("pod1", 4)) 221 store.Add(makeTestPod("pod2", 5)) 222 store.Add(makeTestPod("pod3", 6)) 223 { 224 expected := map[string]storeElement{ 225 "prefix/ns/pod1": *makeTestStoreElement(makeTestPod("pod1", 4)), 226 "prefix/ns/pod2": *makeTestStoreElement(makeTestPod("pod2", 5)), 227 "prefix/ns/pod3": *makeTestStoreElement(makeTestPod("pod3", 6)), 228 } 229 items := make(map[string]storeElement) 230 for _, item := range store.List() { 231 elem := item.(*storeElement) 232 items[elem.Key] = *elem 233 } 234 if !apiequality.Semantic.DeepEqual(expected, items) { 235 t.Errorf("expected %v, got %v", expected, items) 236 } 237 } 238 239 // Test Replace. 240 store.Replace([]interface{}{ 241 makeTestPod("pod4", 7), 242 makeTestPod("pod5", 8), 243 }, "8") 244 { 245 expected := map[string]storeElement{ 246 "prefix/ns/pod4": *makeTestStoreElement(makeTestPod("pod4", 7)), 247 "prefix/ns/pod5": *makeTestStoreElement(makeTestPod("pod5", 8)), 248 } 249 items := make(map[string]storeElement) 250 for _, item := range store.List() { 251 elem := item.(*storeElement) 252 items[elem.Key] = *elem 253 } 254 if !apiequality.Semantic.DeepEqual(expected, items) { 255 t.Errorf("expected %v, got %v", expected, items) 256 } 257 } 258 } 259 260 func TestEvents(t *testing.T) { 261 store := newTestWatchCache(5, &cache.Indexers{}) 262 defer store.Stop() 263 264 // no dynamic-size cache to fit old tests. 265 store.lowerBoundCapacity = 5 266 store.upperBoundCapacity = 5 267 268 store.Add(makeTestPod("pod", 3)) 269 270 // Test for Added event. 271 { 272 _, err := store.getAllEventsSince(1) 273 if err == nil { 274 t.Errorf("expected error too old") 275 } 276 if _, ok := err.(*errors.StatusError); !ok { 277 t.Errorf("expected error to be of type StatusError") 278 } 279 } 280 { 281 result, err := store.getAllEventsSince(2) 282 if err != nil { 283 t.Errorf("unexpected error: %v", err) 284 } 285 if len(result) != 1 { 286 t.Fatalf("unexpected events: %v", result) 287 } 288 if result[0].Type != watch.Added { 289 t.Errorf("unexpected event type: %v", result[0].Type) 290 } 291 pod := makeTestPod("pod", uint64(3)) 292 if !apiequality.Semantic.DeepEqual(pod, result[0].Object) { 293 t.Errorf("unexpected item: %v, expected: %v", result[0].Object, pod) 294 } 295 if result[0].PrevObject != nil { 296 t.Errorf("unexpected item: %v", result[0].PrevObject) 297 } 298 } 299 300 store.Update(makeTestPod("pod", 4)) 301 store.Update(makeTestPod("pod", 5)) 302 303 // Test with not full cache. 304 { 305 _, err := store.getAllEventsSince(1) 306 if err == nil { 307 t.Errorf("expected error too old") 308 } 309 } 310 { 311 result, err := store.getAllEventsSince(3) 312 if err != nil { 313 t.Errorf("unexpected error: %v", err) 314 } 315 if len(result) != 2 { 316 t.Fatalf("unexpected events: %v", result) 317 } 318 for i := 0; i < 2; i++ { 319 if result[i].Type != watch.Modified { 320 t.Errorf("unexpected event type: %v", result[i].Type) 321 } 322 pod := makeTestPod("pod", uint64(i+4)) 323 if !apiequality.Semantic.DeepEqual(pod, result[i].Object) { 324 t.Errorf("unexpected item: %v, expected: %v", result[i].Object, pod) 325 } 326 prevPod := makeTestPod("pod", uint64(i+3)) 327 if !apiequality.Semantic.DeepEqual(prevPod, result[i].PrevObject) { 328 t.Errorf("unexpected item: %v, expected: %v", result[i].PrevObject, prevPod) 329 } 330 } 331 } 332 333 for i := 6; i < 10; i++ { 334 store.Update(makeTestPod("pod", uint64(i))) 335 } 336 337 // Test with full cache - there should be elements from 5 to 9. 338 { 339 _, err := store.getAllEventsSince(3) 340 if err == nil { 341 t.Errorf("expected error too old") 342 } 343 } 344 { 345 result, err := store.getAllEventsSince(4) 346 if err != nil { 347 t.Errorf("unexpected error: %v", err) 348 } 349 if len(result) != 5 { 350 t.Fatalf("unexpected events: %v", result) 351 } 352 for i := 0; i < 5; i++ { 353 pod := makeTestPod("pod", uint64(i+5)) 354 if !apiequality.Semantic.DeepEqual(pod, result[i].Object) { 355 t.Errorf("unexpected item: %v, expected: %v", result[i].Object, pod) 356 } 357 } 358 } 359 360 // Test for delete event. 361 store.Delete(makeTestPod("pod", uint64(10))) 362 363 { 364 result, err := store.getAllEventsSince(9) 365 if err != nil { 366 t.Errorf("unexpected error: %v", err) 367 } 368 if len(result) != 1 { 369 t.Fatalf("unexpected events: %v", result) 370 } 371 if result[0].Type != watch.Deleted { 372 t.Errorf("unexpected event type: %v", result[0].Type) 373 } 374 pod := makeTestPod("pod", uint64(10)) 375 if !apiequality.Semantic.DeepEqual(pod, result[0].Object) { 376 t.Errorf("unexpected item: %v, expected: %v", result[0].Object, pod) 377 } 378 prevPod := makeTestPod("pod", uint64(9)) 379 if !apiequality.Semantic.DeepEqual(prevPod, result[0].PrevObject) { 380 t.Errorf("unexpected item: %v, expected: %v", result[0].PrevObject, prevPod) 381 } 382 } 383 } 384 385 func TestMarker(t *testing.T) { 386 store := newTestWatchCache(3, &cache.Indexers{}) 387 defer store.Stop() 388 389 // First thing that is called when propagated from storage is Replace. 390 store.Replace([]interface{}{ 391 makeTestPod("pod1", 5), 392 makeTestPod("pod2", 9), 393 }, "9") 394 395 _, err := store.getAllEventsSince(8) 396 if err == nil || !strings.Contains(err.Error(), "too old resource version") { 397 t.Errorf("unexpected error: %v", err) 398 } 399 // Getting events from 8 should return no events, 400 // even though there is a marker there. 401 result, err := store.getAllEventsSince(9) 402 if err != nil { 403 t.Fatalf("unexpected error: %v", err) 404 } 405 if len(result) != 0 { 406 t.Errorf("unexpected result: %#v, expected no events", result) 407 } 408 409 pod := makeTestPod("pods", 12) 410 store.Add(pod) 411 // Getting events from 8 should still work and return one event. 412 result, err = store.getAllEventsSince(9) 413 if err != nil { 414 t.Fatalf("unexpected error: %v", err) 415 } 416 if len(result) != 1 || !apiequality.Semantic.DeepEqual(result[0].Object, pod) { 417 t.Errorf("unexpected result: %#v, expected %v", result, pod) 418 } 419 } 420 421 func TestWaitUntilFreshAndList(t *testing.T) { 422 ctx := context.Background() 423 store := newTestWatchCache(3, &cache.Indexers{ 424 "l:label": func(obj interface{}) ([]string, error) { 425 pod, ok := obj.(*v1.Pod) 426 if !ok { 427 return nil, fmt.Errorf("not a pod %#v", obj) 428 } 429 if value, ok := pod.Labels["label"]; ok { 430 return []string{value}, nil 431 } 432 return nil, nil 433 }, 434 "f:spec.nodeName": func(obj interface{}) ([]string, error) { 435 pod, ok := obj.(*v1.Pod) 436 if !ok { 437 return nil, fmt.Errorf("not a pod %#v", obj) 438 } 439 return []string{pod.Spec.NodeName}, nil 440 }, 441 }) 442 defer store.Stop() 443 // In background, update the store. 444 go func() { 445 store.Add(makeTestPodDetails("pod1", 2, "node1", map[string]string{"label": "value1"})) 446 store.Add(makeTestPodDetails("pod2", 3, "node1", map[string]string{"label": "value1"})) 447 store.Add(makeTestPodDetails("pod3", 5, "node2", map[string]string{"label": "value2"})) 448 }() 449 450 // list by empty MatchValues. 451 list, resourceVersion, indexUsed, err := store.WaitUntilFreshAndList(ctx, 5, nil) 452 if err != nil { 453 t.Fatalf("unexpected error: %v", err) 454 } 455 if resourceVersion != 5 { 456 t.Errorf("unexpected resourceVersion: %v, expected: 5", resourceVersion) 457 } 458 if len(list) != 3 { 459 t.Errorf("unexpected list returned: %#v", list) 460 } 461 if indexUsed != "" { 462 t.Errorf("Used index %q but expected none to be used", indexUsed) 463 } 464 465 // list by label index. 466 matchValues := []storage.MatchValue{ 467 {IndexName: "l:label", Value: "value1"}, 468 {IndexName: "f:spec.nodeName", Value: "node2"}, 469 } 470 list, resourceVersion, indexUsed, err = store.WaitUntilFreshAndList(ctx, 5, matchValues) 471 if err != nil { 472 t.Fatalf("unexpected error: %v", err) 473 } 474 if resourceVersion != 5 { 475 t.Errorf("unexpected resourceVersion: %v, expected: 5", resourceVersion) 476 } 477 if len(list) != 2 { 478 t.Errorf("unexpected list returned: %#v", list) 479 } 480 if indexUsed != "l:label" { 481 t.Errorf("Used index %q but expected %q", indexUsed, "l:label") 482 } 483 484 // list with spec.nodeName index. 485 matchValues = []storage.MatchValue{ 486 {IndexName: "l:not-exist-label", Value: "whatever"}, 487 {IndexName: "f:spec.nodeName", Value: "node2"}, 488 } 489 list, resourceVersion, indexUsed, err = store.WaitUntilFreshAndList(ctx, 5, matchValues) 490 if err != nil { 491 t.Fatalf("unexpected error: %v", err) 492 } 493 if resourceVersion != 5 { 494 t.Errorf("unexpected resourceVersion: %v, expected: 5", resourceVersion) 495 } 496 if len(list) != 1 { 497 t.Errorf("unexpected list returned: %#v", list) 498 } 499 if indexUsed != "f:spec.nodeName" { 500 t.Errorf("Used index %q but expected %q", indexUsed, "f:spec.nodeName") 501 } 502 503 // list with index not exists. 504 matchValues = []storage.MatchValue{ 505 {IndexName: "l:not-exist-label", Value: "whatever"}, 506 } 507 list, resourceVersion, indexUsed, err = store.WaitUntilFreshAndList(ctx, 5, matchValues) 508 if err != nil { 509 t.Fatalf("unexpected error: %v", err) 510 } 511 if resourceVersion != 5 { 512 t.Errorf("unexpected resourceVersion: %v, expected: 5", resourceVersion) 513 } 514 if len(list) != 3 { 515 t.Errorf("unexpected list returned: %#v", list) 516 } 517 if indexUsed != "" { 518 t.Errorf("Used index %q but expected none to be used", indexUsed) 519 } 520 } 521 522 func TestWaitUntilFreshAndListFromCache(t *testing.T) { 523 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ConsistentListFromCache, true)() 524 ctx := context.Background() 525 store := newTestWatchCache(3, &cache.Indexers{}) 526 defer store.Stop() 527 // In background, update the store. 528 go func() { 529 store.Add(makeTestPod("pod1", 2)) 530 store.bookmarkRevision <- 3 531 }() 532 533 // list from future revision. Requires watch cache to request bookmark to get it. 534 list, resourceVersion, indexUsed, err := store.WaitUntilFreshAndList(ctx, 3, nil) 535 if err != nil { 536 t.Fatalf("unexpected error: %v", err) 537 } 538 if resourceVersion != 3 { 539 t.Errorf("unexpected resourceVersion: %v, expected: 6", resourceVersion) 540 } 541 if len(list) != 1 { 542 t.Errorf("unexpected list returned: %#v", list) 543 } 544 if indexUsed != "" { 545 t.Errorf("Used index %q but expected none to be used", indexUsed) 546 } 547 } 548 549 func TestWaitUntilFreshAndGet(t *testing.T) { 550 ctx := context.Background() 551 store := newTestWatchCache(3, &cache.Indexers{}) 552 defer store.Stop() 553 554 // In background, update the store. 555 go func() { 556 store.Add(makeTestPod("foo", 2)) 557 store.Add(makeTestPod("bar", 5)) 558 }() 559 560 obj, exists, resourceVersion, err := store.WaitUntilFreshAndGet(ctx, 5, "prefix/ns/bar") 561 if err != nil { 562 t.Fatalf("unexpected error: %v", err) 563 } 564 if resourceVersion != 5 { 565 t.Errorf("unexpected resourceVersion: %v, expected: 5", resourceVersion) 566 } 567 if !exists { 568 t.Fatalf("no results returned: %#v", obj) 569 } 570 expected := makeTestStoreElement(makeTestPod("bar", 5)) 571 if !apiequality.Semantic.DeepEqual(expected, obj) { 572 t.Errorf("expected %v, got %v", expected, obj) 573 } 574 } 575 576 func TestWaitUntilFreshAndListTimeout(t *testing.T) { 577 tcs := []struct { 578 name string 579 ConsistentListFromCache bool 580 }{ 581 { 582 name: "FromStorage", 583 ConsistentListFromCache: false, 584 }, 585 { 586 name: "FromCache", 587 ConsistentListFromCache: true, 588 }, 589 } 590 for _, tc := range tcs { 591 t.Run(tc.name, func(t *testing.T) { 592 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ConsistentListFromCache, tc.ConsistentListFromCache)() 593 ctx := context.Background() 594 store := newTestWatchCache(3, &cache.Indexers{}) 595 defer store.Stop() 596 fc := store.clock.(*testingclock.FakeClock) 597 598 // In background, step clock after the below call starts the timer. 599 go func() { 600 for !fc.HasWaiters() { 601 time.Sleep(time.Millisecond) 602 } 603 store.Add(makeTestPod("foo", 2)) 604 store.bookmarkRevision <- 3 605 fc.Step(blockTimeout) 606 607 // Add an object to make sure the test would 608 // eventually fail instead of just waiting 609 // forever. 610 time.Sleep(30 * time.Second) 611 store.Add(makeTestPod("bar", 4)) 612 }() 613 614 _, _, _, err := store.WaitUntilFreshAndList(ctx, 4, nil) 615 if !errors.IsTimeout(err) { 616 t.Errorf("expected timeout error but got: %v", err) 617 } 618 if !storage.IsTooLargeResourceVersion(err) { 619 t.Errorf("expected 'Too large resource version' cause in error but got: %v", err) 620 } 621 }) 622 } 623 } 624 625 type testLW struct { 626 ListFunc func(options metav1.ListOptions) (runtime.Object, error) 627 WatchFunc func(options metav1.ListOptions) (watch.Interface, error) 628 } 629 630 func (t *testLW) List(options metav1.ListOptions) (runtime.Object, error) { 631 return t.ListFunc(options) 632 } 633 func (t *testLW) Watch(options metav1.ListOptions) (watch.Interface, error) { 634 return t.WatchFunc(options) 635 } 636 637 func TestReflectorForWatchCache(t *testing.T) { 638 ctx := context.Background() 639 store := newTestWatchCache(5, &cache.Indexers{}) 640 defer store.Stop() 641 642 { 643 _, version, _, err := store.WaitUntilFreshAndList(ctx, 0, nil) 644 if err != nil { 645 t.Fatalf("unexpected error: %v", err) 646 } 647 if version != 0 { 648 t.Errorf("unexpected resource version: %d", version) 649 } 650 } 651 652 lw := &testLW{ 653 WatchFunc: func(_ metav1.ListOptions) (watch.Interface, error) { 654 fw := watch.NewFake() 655 go fw.Stop() 656 return fw, nil 657 }, 658 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 659 return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}}, nil 660 }, 661 } 662 r := cache.NewReflector(lw, &v1.Pod{}, store, 0) 663 r.ListAndWatch(wait.NeverStop) 664 665 { 666 _, version, _, err := store.WaitUntilFreshAndList(ctx, 10, nil) 667 if err != nil { 668 t.Fatalf("unexpected error: %v", err) 669 } 670 if version != 10 { 671 t.Errorf("unexpected resource version: %d", version) 672 } 673 } 674 } 675 676 func TestDynamicCache(t *testing.T) { 677 tests := []struct { 678 name string 679 eventCount int 680 cacheCapacity int 681 startIndex int 682 // interval is time duration between adjacent events. 683 lowerBoundCapacity int 684 upperBoundCapacity int 685 interval time.Duration 686 expectCapacity int 687 expectStartIndex int 688 }{ 689 { 690 name: "[capacity not equals 4*n] events inside eventFreshDuration cause cache expanding", 691 eventCount: 5, 692 cacheCapacity: 5, 693 lowerBoundCapacity: 5 / 2, 694 upperBoundCapacity: 5 * 2, 695 interval: eventFreshDuration / 6, 696 expectCapacity: 10, 697 expectStartIndex: 0, 698 }, 699 { 700 name: "[capacity not equals 4*n] events outside eventFreshDuration without change cache capacity", 701 eventCount: 5, 702 cacheCapacity: 5, 703 lowerBoundCapacity: 5 / 2, 704 upperBoundCapacity: 5 * 2, 705 interval: eventFreshDuration / 4, 706 expectCapacity: 5, 707 expectStartIndex: 0, 708 }, 709 { 710 name: "[capacity not equals 4*n] quarter of recent events outside eventFreshDuration cause cache shrinking", 711 eventCount: 5, 712 cacheCapacity: 5, 713 lowerBoundCapacity: 5 / 2, 714 upperBoundCapacity: 5 * 2, 715 interval: eventFreshDuration + time.Second, 716 expectCapacity: 2, 717 expectStartIndex: 3, 718 }, 719 { 720 name: "[capacity not equals 4*n] quarter of recent events outside eventFreshDuration cause cache shrinking with given lowerBoundCapacity", 721 eventCount: 5, 722 cacheCapacity: 5, 723 lowerBoundCapacity: 3, 724 upperBoundCapacity: 5 * 2, 725 interval: eventFreshDuration + time.Second, 726 expectCapacity: 3, 727 expectStartIndex: 2, 728 }, 729 { 730 name: "[capacity not equals 4*n] events inside eventFreshDuration cause cache expanding with given upperBoundCapacity", 731 eventCount: 5, 732 cacheCapacity: 5, 733 lowerBoundCapacity: 5 / 2, 734 upperBoundCapacity: 8, 735 interval: eventFreshDuration / 6, 736 expectCapacity: 8, 737 expectStartIndex: 0, 738 }, 739 { 740 name: "[capacity not equals 4*n] [startIndex not equal 0] events inside eventFreshDuration cause cache expanding", 741 eventCount: 5, 742 cacheCapacity: 5, 743 startIndex: 3, 744 lowerBoundCapacity: 5 / 2, 745 upperBoundCapacity: 5 * 2, 746 interval: eventFreshDuration / 6, 747 expectCapacity: 10, 748 expectStartIndex: 3, 749 }, 750 { 751 name: "[capacity not equals 4*n] [startIndex not equal 0] events outside eventFreshDuration without change cache capacity", 752 eventCount: 5, 753 cacheCapacity: 5, 754 startIndex: 3, 755 lowerBoundCapacity: 5 / 2, 756 upperBoundCapacity: 5 * 2, 757 interval: eventFreshDuration / 4, 758 expectCapacity: 5, 759 expectStartIndex: 3, 760 }, 761 { 762 name: "[capacity not equals 4*n] [startIndex not equal 0] quarter of recent events outside eventFreshDuration cause cache shrinking", 763 eventCount: 5, 764 cacheCapacity: 5, 765 startIndex: 3, 766 lowerBoundCapacity: 5 / 2, 767 upperBoundCapacity: 5 * 2, 768 interval: eventFreshDuration + time.Second, 769 expectCapacity: 2, 770 expectStartIndex: 6, 771 }, 772 { 773 name: "[capacity not equals 4*n] [startIndex not equal 0] quarter of recent events outside eventFreshDuration cause cache shrinking with given lowerBoundCapacity", 774 eventCount: 5, 775 cacheCapacity: 5, 776 startIndex: 3, 777 lowerBoundCapacity: 3, 778 upperBoundCapacity: 5 * 2, 779 interval: eventFreshDuration + time.Second, 780 expectCapacity: 3, 781 expectStartIndex: 5, 782 }, 783 { 784 name: "[capacity not equals 4*n] [startIndex not equal 0] events inside eventFreshDuration cause cache expanding with given upperBoundCapacity", 785 eventCount: 5, 786 cacheCapacity: 5, 787 startIndex: 3, 788 lowerBoundCapacity: 5 / 2, 789 upperBoundCapacity: 8, 790 interval: eventFreshDuration / 6, 791 expectCapacity: 8, 792 expectStartIndex: 3, 793 }, 794 { 795 name: "[capacity equals 4*n] events inside eventFreshDuration cause cache expanding", 796 eventCount: 8, 797 cacheCapacity: 8, 798 lowerBoundCapacity: 8 / 2, 799 upperBoundCapacity: 8 * 2, 800 interval: eventFreshDuration / 9, 801 expectCapacity: 16, 802 expectStartIndex: 0, 803 }, 804 { 805 name: "[capacity equals 4*n] events outside eventFreshDuration without change cache capacity", 806 eventCount: 8, 807 cacheCapacity: 8, 808 lowerBoundCapacity: 8 / 2, 809 upperBoundCapacity: 8 * 2, 810 interval: eventFreshDuration / 8, 811 expectCapacity: 8, 812 expectStartIndex: 0, 813 }, 814 { 815 name: "[capacity equals 4*n] quarter of recent events outside eventFreshDuration cause cache shrinking", 816 eventCount: 8, 817 cacheCapacity: 8, 818 lowerBoundCapacity: 8 / 2, 819 upperBoundCapacity: 8 * 2, 820 interval: eventFreshDuration/2 + time.Second, 821 expectCapacity: 4, 822 expectStartIndex: 4, 823 }, 824 { 825 name: "[capacity equals 4*n] quarter of recent events outside eventFreshDuration cause cache shrinking with given lowerBoundCapacity", 826 eventCount: 8, 827 cacheCapacity: 8, 828 lowerBoundCapacity: 7, 829 upperBoundCapacity: 8 * 2, 830 interval: eventFreshDuration/2 + time.Second, 831 expectCapacity: 7, 832 expectStartIndex: 1, 833 }, 834 { 835 name: "[capacity equals 4*n] events inside eventFreshDuration cause cache expanding with given upperBoundCapacity", 836 eventCount: 8, 837 cacheCapacity: 8, 838 lowerBoundCapacity: 8 / 2, 839 upperBoundCapacity: 10, 840 interval: eventFreshDuration / 9, 841 expectCapacity: 10, 842 expectStartIndex: 0, 843 }, 844 { 845 name: "[capacity equals 4*n] [startIndex not equal 0] events inside eventFreshDuration cause cache expanding", 846 eventCount: 8, 847 cacheCapacity: 8, 848 startIndex: 3, 849 lowerBoundCapacity: 8 / 2, 850 upperBoundCapacity: 8 * 2, 851 interval: eventFreshDuration / 9, 852 expectCapacity: 16, 853 expectStartIndex: 3, 854 }, 855 { 856 name: "[capacity equals 4*n] [startIndex not equal 0] events outside eventFreshDuration without change cache capacity", 857 eventCount: 8, 858 cacheCapacity: 8, 859 startIndex: 3, 860 lowerBoundCapacity: 8 / 2, 861 upperBoundCapacity: 8 * 2, 862 interval: eventFreshDuration / 8, 863 expectCapacity: 8, 864 expectStartIndex: 3, 865 }, 866 { 867 name: "[capacity equals 4*n] [startIndex not equal 0] quarter of recent events outside eventFreshDuration cause cache shrinking", 868 eventCount: 8, 869 cacheCapacity: 8, 870 startIndex: 3, 871 lowerBoundCapacity: 8 / 2, 872 upperBoundCapacity: 8 * 2, 873 interval: eventFreshDuration/2 + time.Second, 874 expectCapacity: 4, 875 expectStartIndex: 7, 876 }, 877 { 878 name: "[capacity equals 4*n] [startIndex not equal 0] quarter of recent events outside eventFreshDuration cause cache shrinking with given lowerBoundCapacity", 879 eventCount: 8, 880 cacheCapacity: 8, 881 startIndex: 3, 882 lowerBoundCapacity: 7, 883 upperBoundCapacity: 8 * 2, 884 interval: eventFreshDuration/2 + time.Second, 885 expectCapacity: 7, 886 expectStartIndex: 4, 887 }, 888 { 889 name: "[capacity equals 4*n] [startIndex not equal 0] events inside eventFreshDuration cause cache expanding with given upperBoundCapacity", 890 eventCount: 8, 891 cacheCapacity: 8, 892 startIndex: 3, 893 lowerBoundCapacity: 8 / 2, 894 upperBoundCapacity: 10, 895 interval: eventFreshDuration / 9, 896 expectCapacity: 10, 897 expectStartIndex: 3, 898 }, 899 } 900 901 for _, test := range tests { 902 t.Run(test.name, func(t *testing.T) { 903 store := newTestWatchCache(test.cacheCapacity, &cache.Indexers{}) 904 defer store.Stop() 905 store.cache = make([]*watchCacheEvent, test.cacheCapacity) 906 store.startIndex = test.startIndex 907 store.lowerBoundCapacity = test.lowerBoundCapacity 908 store.upperBoundCapacity = test.upperBoundCapacity 909 loadEventWithDuration(store, test.eventCount, test.interval) 910 nextInterval := store.clock.Now().Add(time.Duration(test.interval.Nanoseconds() * int64(test.eventCount))) 911 store.resizeCacheLocked(nextInterval) 912 if store.capacity != test.expectCapacity { 913 t.Errorf("expect capacity %d, but get %d", test.expectCapacity, store.capacity) 914 } 915 916 // check cache's startIndex, endIndex and all elements. 917 if store.startIndex != test.expectStartIndex { 918 t.Errorf("expect startIndex %d, but get %d", test.expectStartIndex, store.startIndex) 919 } 920 if store.endIndex != test.startIndex+test.eventCount { 921 t.Errorf("expect endIndex %d get %d", test.startIndex+test.eventCount, store.endIndex) 922 } 923 if !checkCacheElements(store) { 924 t.Errorf("some elements locations in cache is wrong") 925 } 926 }) 927 } 928 } 929 930 func loadEventWithDuration(cache *testWatchCache, count int, interval time.Duration) { 931 for i := 0; i < count; i++ { 932 event := &watchCacheEvent{ 933 Key: fmt.Sprintf("event-%d", i+cache.startIndex), 934 RecordTime: cache.clock.Now().Add(time.Duration(interval.Nanoseconds() * int64(i))), 935 } 936 cache.cache[(i+cache.startIndex)%cache.capacity] = event 937 } 938 cache.endIndex = cache.startIndex + count 939 } 940 941 func checkCacheElements(cache *testWatchCache) bool { 942 for i := cache.startIndex; i < cache.endIndex; i++ { 943 location := i % cache.capacity 944 if cache.cache[location].Key != fmt.Sprintf("event-%d", i) { 945 return false 946 } 947 } 948 return true 949 } 950 951 func TestCacheIncreaseDoesNotBreakWatch(t *testing.T) { 952 store := newTestWatchCache(2, &cache.Indexers{}) 953 defer store.Stop() 954 955 now := store.clock.Now() 956 addEvent := func(key string, rv uint64, t time.Time) { 957 event := &watchCacheEvent{ 958 Key: key, 959 ResourceVersion: rv, 960 RecordTime: t, 961 } 962 store.updateCache(event) 963 } 964 965 // Initial LIST comes from the moment of RV=10. 966 store.Replace(nil, "10") 967 968 addEvent("key1", 20, now) 969 970 // Force "key1" to rotate our of cache. 971 later := now.Add(2 * eventFreshDuration) 972 addEvent("key2", 30, later) 973 addEvent("key3", 40, later) 974 975 // Force cache resize. 976 addEvent("key4", 50, later.Add(time.Second)) 977 978 _, err := store.getAllEventsSince(15) 979 if err == nil || !strings.Contains(err.Error(), "too old resource version") { 980 t.Errorf("unexpected error: %v", err) 981 } 982 } 983 984 func TestSuggestedWatchChannelSize(t *testing.T) { 985 testCases := []struct { 986 name string 987 capacity int 988 indexExists bool 989 triggerUsed bool 990 expected int 991 }{ 992 { 993 name: "capacity=100, indexExists, triggerUsed", 994 capacity: 100, 995 indexExists: true, 996 triggerUsed: true, 997 expected: 10, 998 }, 999 { 1000 name: "capacity=100, indexExists, !triggerUsed", 1001 capacity: 100, 1002 indexExists: true, 1003 triggerUsed: false, 1004 expected: 10, 1005 }, 1006 { 1007 name: "capacity=100, !indexExists", 1008 capacity: 100, 1009 indexExists: false, 1010 triggerUsed: false, 1011 expected: 10, 1012 }, 1013 { 1014 name: "capacity=750, indexExists, triggerUsed", 1015 capacity: 750, 1016 indexExists: true, 1017 triggerUsed: true, 1018 expected: 10, 1019 }, 1020 { 1021 name: "capacity=750, indexExists, !triggerUsed", 1022 capacity: 750, 1023 indexExists: true, 1024 triggerUsed: false, 1025 expected: 10, 1026 }, 1027 { 1028 name: "capacity=750, !indexExists", 1029 capacity: 750, 1030 indexExists: false, 1031 triggerUsed: false, 1032 expected: 10, 1033 }, 1034 { 1035 name: "capacity=7500, indexExists, triggerUsed", 1036 capacity: 7500, 1037 indexExists: true, 1038 triggerUsed: true, 1039 expected: 10, 1040 }, 1041 { 1042 name: "capacity=7500, indexExists, !triggerUsed", 1043 capacity: 7500, 1044 indexExists: true, 1045 triggerUsed: false, 1046 expected: 100, 1047 }, 1048 { 1049 name: "capacity=7500, !indexExists", 1050 capacity: 7500, 1051 indexExists: false, 1052 triggerUsed: false, 1053 expected: 100, 1054 }, 1055 { 1056 name: "capacity=75000, indexExists, triggerUsed", 1057 capacity: 75000, 1058 indexExists: true, 1059 triggerUsed: true, 1060 expected: 10, 1061 }, 1062 { 1063 name: "capacity=75000, indexExists, !triggerUsed", 1064 capacity: 75000, 1065 indexExists: true, 1066 triggerUsed: false, 1067 expected: 1000, 1068 }, 1069 { 1070 name: "capacity=75000, !indexExists", 1071 capacity: 75000, 1072 indexExists: false, 1073 triggerUsed: false, 1074 expected: 100, 1075 }, 1076 { 1077 name: "capacity=750000, indexExists, triggerUsed", 1078 capacity: 750000, 1079 indexExists: true, 1080 triggerUsed: true, 1081 expected: 10, 1082 }, 1083 { 1084 name: "capacity=750000, indexExists, !triggerUsed", 1085 capacity: 750000, 1086 indexExists: true, 1087 triggerUsed: false, 1088 expected: 1000, 1089 }, 1090 { 1091 name: "capacity=750000, !indexExists", 1092 capacity: 750000, 1093 indexExists: false, 1094 triggerUsed: false, 1095 expected: 100, 1096 }, 1097 } 1098 1099 for _, test := range testCases { 1100 t.Run(test.name, func(t *testing.T) { 1101 store := newTestWatchCache(test.capacity, &cache.Indexers{}) 1102 defer store.Stop() 1103 got := store.suggestedWatchChannelSize(test.indexExists, test.triggerUsed) 1104 if got != test.expected { 1105 t.Errorf("unexpected channel size got: %v, expected: %v", got, test.expected) 1106 } 1107 }) 1108 } 1109 } 1110 1111 func BenchmarkWatchCache_updateCache(b *testing.B) { 1112 store := newTestWatchCache(defaultUpperBoundCapacity, &cache.Indexers{}) 1113 defer store.Stop() 1114 store.cache = store.cache[:0] 1115 store.upperBoundCapacity = defaultUpperBoundCapacity 1116 loadEventWithDuration(store, defaultUpperBoundCapacity, 0) 1117 add := &watchCacheEvent{ 1118 Key: fmt.Sprintf("event-%d", defaultUpperBoundCapacity), 1119 RecordTime: store.clock.Now(), 1120 } 1121 b.ResetTimer() 1122 for i := 0; i < b.N; i++ { 1123 store.updateCache(add) 1124 } 1125 }