From 4bb2bd05322e127658e55a06995c021b3c5fbc41 Mon Sep 17 00:00:00 2001 From: Lukasz Szaszkiewicz Date: Thu, 25 Sep 2025 15:36:17 +0200 Subject: [PATCH] client-go/tools/cache/reflector: supports watchlist.DoesClientNotSupportWatchListSemantics --- .../k8s.io/client-go/tools/cache/reflector.go | 11 ++++ .../tools/cache/reflector_watchlist_test.go | 56 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/staging/src/k8s.io/client-go/tools/cache/reflector.go b/staging/src/k8s.io/client-go/tools/cache/reflector.go index 9b33e01ef2d..9cb76a2f5b6 100644 --- a/staging/src/k8s.io/client-go/tools/cache/reflector.go +++ b/staging/src/k8s.io/client-go/tools/cache/reflector.go @@ -41,6 +41,7 @@ import ( "k8s.io/apimachinery/pkg/watch" clientfeatures "k8s.io/client-go/features" "k8s.io/client-go/tools/pager" + "k8s.io/client-go/util/watchlist" "k8s.io/klog/v2" "k8s.io/utils/clock" "k8s.io/utils/ptr" @@ -297,6 +298,16 @@ func NewReflectorWithOptions(lw ListerWatcher, expectedType interface{}, store R } r.useWatchList = clientfeatures.FeatureGates().Enabled(clientfeatures.WatchListClient) + if r.useWatchList && watchlist.DoesClientNotSupportWatchListSemantics(lw) { + // Using klog.TODO() here because switching to a caller-provided contextual logger + // would require an API change and updating all existing call sites. + klog.TODO().V(2).Info( + "The provided ListWatcher doesn't support WatchList semantics. The feature will be disabled. If you are using a custom client, check the documentation of watchlist.DoesClientNotSupportWatchListSemantics() method", + "listWatcherType", fmt.Sprintf("%T", lw), + "feature", clientfeatures.WatchListClient, + ) + r.useWatchList = false + } return r } diff --git a/staging/src/k8s.io/client-go/tools/cache/reflector_watchlist_test.go b/staging/src/k8s.io/client-go/tools/cache/reflector_watchlist_test.go index 0a9260c4c75..50ebe3e620c 100644 --- a/staging/src/k8s.io/client-go/tools/cache/reflector_watchlist_test.go +++ b/staging/src/k8s.io/client-go/tools/cache/reflector_watchlist_test.go @@ -43,6 +43,62 @@ import ( "k8s.io/utils/ptr" ) +type lwSupportsWatchListSemantics struct{ fakeListWatcher } + +func (lw *lwSupportsWatchListSemantics) IsWatchListSemanticsUnSupported() bool { return false } + +type lwDoesNotSupportWatchListSemantics struct{ fakeListWatcher } + +func (lw *lwDoesNotSupportWatchListSemantics) IsWatchListSemanticsUnSupported() bool { return true } + +type lwNoWatchListSemanticsUnSupportedExposed struct{ fakeListWatcher } + +func TestNewReflectorWithDisablementWatchList(t *testing.T) { + scenarios := []struct { + name string + enableWatchListClientFG bool + lw ListerWatcher + expectUseWatchListValue bool + }{ + { + name: "WatchListClient feature gate off, client supports WatchList semantics", + enableWatchListClientFG: false, + lw: &lwSupportsWatchListSemantics{}, + expectUseWatchListValue: false, + }, + { + name: "WatchListClient feature gate on, client supports WatchList semantics", + enableWatchListClientFG: true, + lw: &lwSupportsWatchListSemantics{}, + expectUseWatchListValue: true, + }, + { + name: "WatchListClient feature gate on, client doesn't support the WatchList semantics", + enableWatchListClientFG: true, + lw: &lwDoesNotSupportWatchListSemantics{}, + expectUseWatchListValue: false, + }, + { + name: "WatchListClient feature gate on, client doesn't expose the IsWatchListSemanticsUnSupported method", + enableWatchListClientFG: true, + lw: &lwNoWatchListSemanticsUnSupportedExposed{}, + expectUseWatchListValue: true, + }, + } + + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + clientfeaturestesting.SetFeatureDuringTest(t, clientfeatures.WatchListClient, scenario.enableWatchListClientFG) + + r := NewReflectorWithOptions(scenario.lw, struct{}{}, &fakeStore{}, ReflectorOptions{}) + + if r.useWatchList != scenario.expectUseWatchListValue { + t.Fatalf("got: %v, want: %v", r.useWatchList, scenario.expectUseWatchListValue) + } + }) + } +} + func TestInitialEventsEndBookmarkTicker(t *testing.T) { assertNoEvents := func(t *testing.T, c <-chan time.Time) { select {