client-go/tools/cache/reflector: supports watchlist.DoesClientNotSupportWatchListSemantics

This commit is contained in:
Lukasz Szaszkiewicz
2025-09-25 15:36:17 +02:00
parent 2453394c0a
commit 4bb2bd0532
2 changed files with 67 additions and 0 deletions

View File

@@ -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
}

View File

@@ -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 {