@@ -24,6 +24,7 @@ import (
24
24
"reflect"
25
25
goruntime "runtime"
26
26
"strconv"
27
+ "strings"
27
28
"sync"
28
29
"testing"
29
30
"time"
@@ -45,10 +46,13 @@ import (
45
46
examplev1 "k8s.io/apiserver/pkg/apis/example/v1"
46
47
"k8s.io/apiserver/pkg/features"
47
48
"k8s.io/apiserver/pkg/storage"
49
+ "k8s.io/apiserver/pkg/storage/cacher/metrics"
48
50
etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing"
49
51
etcdfeature "k8s.io/apiserver/pkg/storage/feature"
50
52
utilfeature "k8s.io/apiserver/pkg/util/feature"
51
53
featuregatetesting "k8s.io/component-base/featuregate/testing"
54
+ k8smetrics "k8s.io/component-base/metrics"
55
+ "k8s.io/component-base/metrics/testutil"
52
56
"k8s.io/utils/clock"
53
57
testingclock "k8s.io/utils/clock/testing"
54
58
"k8s.io/utils/pointer"
@@ -288,6 +292,138 @@ func testGetListCacheBypass(t *testing.T, options storage.ListOptions, expectByp
288
292
}
289
293
}
290
294
295
+ func TestConsistentReadFallback (t * testing.T ) {
296
+ tcs := []struct {
297
+ name string
298
+ consistentReadsEnabled bool
299
+ watchCacheRV string
300
+ storageRV string
301
+ fallbackError bool
302
+
303
+ expectError bool
304
+ expectRV string
305
+ expectBlock bool
306
+ expectRequestsToStorage int
307
+ expectMetric string
308
+ }{
309
+ {
310
+ name : "Success" ,
311
+ consistentReadsEnabled : true ,
312
+ watchCacheRV : "42" ,
313
+ storageRV : "42" ,
314
+ expectRV : "42" ,
315
+ expectRequestsToStorage : 1 ,
316
+ expectMetric : `
317
+ # HELP apiserver_watch_cache_consistent_read_total [ALPHA] Counter for consistent reads from cache.
318
+ # TYPE apiserver_watch_cache_consistent_read_total counter
319
+ apiserver_watch_cache_consistent_read_total{fallback="false", resource="pods", success="true"} 1
320
+ ` ,
321
+ },
322
+ {
323
+ name : "Fallback" ,
324
+ consistentReadsEnabled : true ,
325
+ watchCacheRV : "2" ,
326
+ storageRV : "42" ,
327
+ expectRV : "42" ,
328
+ expectBlock : true ,
329
+ expectRequestsToStorage : 2 ,
330
+ expectMetric : `
331
+ # HELP apiserver_watch_cache_consistent_read_total [ALPHA] Counter for consistent reads from cache.
332
+ # TYPE apiserver_watch_cache_consistent_read_total counter
333
+ apiserver_watch_cache_consistent_read_total{fallback="true", resource="pods", success="true"} 1
334
+ ` ,
335
+ },
336
+ {
337
+ name : "Fallback Failure" ,
338
+ consistentReadsEnabled : true ,
339
+ watchCacheRV : "2" ,
340
+ storageRV : "42" ,
341
+ fallbackError : true ,
342
+ expectError : true ,
343
+ expectBlock : true ,
344
+ expectRequestsToStorage : 2 ,
345
+ expectMetric : `
346
+ # HELP apiserver_watch_cache_consistent_read_total [ALPHA] Counter for consistent reads from cache.
347
+ # TYPE apiserver_watch_cache_consistent_read_total counter
348
+ apiserver_watch_cache_consistent_read_total{fallback="true", resource="pods", success="false"} 1
349
+ ` ,
350
+ },
351
+ {
352
+ name : "Disabled" ,
353
+ watchCacheRV : "2" ,
354
+ storageRV : "42" ,
355
+ expectRV : "42" ,
356
+ expectRequestsToStorage : 1 ,
357
+ expectMetric : `` ,
358
+ },
359
+ }
360
+ for _ , tc := range tcs {
361
+ t .Run (tc .name , func (t * testing.T ) {
362
+ featuregatetesting .SetFeatureGateDuringTest (t , utilfeature .DefaultFeatureGate , features .ConsistentListFromCache , tc .consistentReadsEnabled )
363
+ if tc .consistentReadsEnabled {
364
+ forceRequestWatchProgressSupport (t )
365
+ }
366
+
367
+ registry := k8smetrics .NewKubeRegistry ()
368
+ metrics .ConsistentReadTotal .Reset ()
369
+ if err := registry .Register (metrics .ConsistentReadTotal ); err != nil {
370
+ t .Errorf ("unexpected error: %v" , err )
371
+ }
372
+ backingStorage := & dummyStorage {}
373
+ backingStorage .getListFn = func (_ context.Context , key string , opts storage.ListOptions , listObj runtime.Object ) error {
374
+ podList := listObj .(* example.PodList )
375
+ podList .ResourceVersion = tc .watchCacheRV
376
+ return nil
377
+ }
378
+ // TODO: Use fake clock for this test to reduce execution time.
379
+ cacher , _ , err := newTestCacher (backingStorage )
380
+ if err != nil {
381
+ t .Fatalf ("Couldn't create cacher: %v" , err )
382
+ }
383
+ defer cacher .Stop ()
384
+
385
+ if fmt .Sprintf ("%d" , cacher .watchCache .resourceVersion ) != tc .watchCacheRV {
386
+ t .Fatalf ("Expected watch cache RV to equal watchCacheRV, got: %d, want: %s" , cacher .watchCache .resourceVersion , tc .watchCacheRV )
387
+ }
388
+ requestToStorageCount := 0
389
+ backingStorage .getListFn = func (_ context.Context , key string , opts storage.ListOptions , listObj runtime.Object ) error {
390
+ requestToStorageCount += 1
391
+ podList := listObj .(* example.PodList )
392
+ if key == cacher .resourcePrefix {
393
+ podList .ResourceVersion = tc .storageRV
394
+ return nil
395
+ }
396
+ if tc .fallbackError {
397
+ return errDummy
398
+ }
399
+ podList .ResourceVersion = tc .storageRV
400
+ return nil
401
+ }
402
+ result := & example.PodList {}
403
+ start := cacher .clock .Now ()
404
+ err = cacher .GetList (context .TODO (), "pods/ns" , storage.ListOptions {ResourceVersion : "" }, result )
405
+ duration := cacher .clock .Since (start )
406
+ if (err != nil ) != tc .expectError {
407
+ t .Fatalf ("Unexpected error err: %v" , err )
408
+ }
409
+ if result .ResourceVersion != tc .expectRV {
410
+ t .Fatalf ("Unexpected List response RV, got: %q, want: %q" , result .ResourceVersion , tc .expectRV )
411
+ }
412
+ if requestToStorageCount != tc .expectRequestsToStorage {
413
+ t .Fatalf ("Unexpected number of requests to storage, got: %d, want: %d" , requestToStorageCount , tc .expectRequestsToStorage )
414
+ }
415
+ blocked := duration >= blockTimeout
416
+ if blocked != tc .expectBlock {
417
+ t .Fatalf ("Unexpected block, got: %v, want: %v" , blocked , tc .expectBlock )
418
+ }
419
+
420
+ if err := testutil .GatherAndCompare (registry , strings .NewReader (tc .expectMetric ), "apiserver_watch_cache_consistent_read_total" ); err != nil {
421
+ t .Errorf ("unexpected error: %v" , err )
422
+ }
423
+ })
424
+ }
425
+ }
426
+
291
427
func TestGetListNonRecursiveCacheBypass (t * testing.T ) {
292
428
featuregatetesting .SetFeatureGateDuringTest (t , utilfeature .DefaultFeatureGate , features .ConsistentListFromCache , false )
293
429
backingStorage := & dummyStorage {}
0 commit comments