client-go/util/watchlist: intro CanUseWatchListForListRequest
This commit is contained in:
		
							
								
								
									
										82
									
								
								staging/src/k8s.io/client-go/util/watchlist/watch_list.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								staging/src/k8s.io/client-go/util/watchlist/watch_list.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,82 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2024 The Kubernetes Authors.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package watchlist
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
 | 
			
		||||
	metainternalversionvalidation "k8s.io/apimachinery/pkg/apis/meta/internalversion/validation"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
			
		||||
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 | 
			
		||||
	clientfeatures "k8s.io/client-go/features"
 | 
			
		||||
	"k8s.io/utils/ptr"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var scheme = runtime.NewScheme()
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	utilruntime.Must(metainternalversion.AddToScheme(scheme))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PrepareWatchListOptionsFromListOptions creates a new ListOptions
 | 
			
		||||
// that can be used for a watch-list request from the given listOptions.
 | 
			
		||||
//
 | 
			
		||||
// This function also determines if the given listOptions can be used to form a watch-list request,
 | 
			
		||||
// which would result in streaming semantically equivalent data from the server.
 | 
			
		||||
func PrepareWatchListOptionsFromListOptions(listOptions metav1.ListOptions) (metav1.ListOptions, bool, error) {
 | 
			
		||||
	if !clientfeatures.FeatureGates().Enabled(clientfeatures.WatchListClient) {
 | 
			
		||||
		return metav1.ListOptions{}, false, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	internalListOptions := &metainternalversion.ListOptions{}
 | 
			
		||||
	if err := scheme.Convert(&listOptions, internalListOptions, nil); err != nil {
 | 
			
		||||
		return metav1.ListOptions{}, false, err
 | 
			
		||||
	}
 | 
			
		||||
	if errs := metainternalversionvalidation.ValidateListOptions(internalListOptions, true); len(errs) > 0 {
 | 
			
		||||
		return metav1.ListOptions{}, false, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	watchListOptions := listOptions
 | 
			
		||||
	// this is our legacy case, the cache ignores LIMIT for
 | 
			
		||||
	// ResourceVersion == 0 and RVM=unset|NotOlderThan
 | 
			
		||||
	if listOptions.Limit > 0 && listOptions.ResourceVersion != "0" {
 | 
			
		||||
		return metav1.ListOptions{}, false, nil
 | 
			
		||||
	}
 | 
			
		||||
	watchListOptions.Limit = 0
 | 
			
		||||
 | 
			
		||||
	// to ensure that we can create a watch-list request that returns
 | 
			
		||||
	// semantically equivalent data for the given listOptions,
 | 
			
		||||
	// we need to validate that the RVM for the list is supported by watch-list requests.
 | 
			
		||||
	if listOptions.ResourceVersionMatch == metav1.ResourceVersionMatchExact {
 | 
			
		||||
		return metav1.ListOptions{}, false, nil
 | 
			
		||||
	}
 | 
			
		||||
	watchListOptions.ResourceVersionMatch = metav1.ResourceVersionMatchNotOlderThan
 | 
			
		||||
 | 
			
		||||
	watchListOptions.Watch = true
 | 
			
		||||
	watchListOptions.AllowWatchBookmarks = true
 | 
			
		||||
	watchListOptions.SendInitialEvents = ptr.To(true)
 | 
			
		||||
 | 
			
		||||
	internalWatchListOptions := &metainternalversion.ListOptions{}
 | 
			
		||||
	if err := scheme.Convert(&watchListOptions, internalWatchListOptions, nil); err != nil {
 | 
			
		||||
		return metav1.ListOptions{}, false, err
 | 
			
		||||
	}
 | 
			
		||||
	if errs := metainternalversionvalidation.ValidateListOptions(internalWatchListOptions, true); len(errs) > 0 {
 | 
			
		||||
		return metav1.ListOptions{}, false, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return watchListOptions, true, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										200
									
								
								staging/src/k8s.io/client-go/util/watchlist/watch_list_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								staging/src/k8s.io/client-go/util/watchlist/watch_list_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,200 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2024 The Kubernetes Authors.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package watchlist
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	clientfeatures "k8s.io/client-go/features"
 | 
			
		||||
	clientfeaturestesting "k8s.io/client-go/features/testing"
 | 
			
		||||
	"k8s.io/utils/ptr"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TestPrepareWatchListOptionsFromListOptions test the following cases:
 | 
			
		||||
//
 | 
			
		||||
// +--------------------------+-----------------+---------+-----------------+
 | 
			
		||||
// |   ResourceVersionMatch   | ResourceVersion |  Limit  |  Continuation   |
 | 
			
		||||
// +--------------------------+-----------------+---------+-----------------+
 | 
			
		||||
// | unset/NotOlderThan/Exact | unset/0/100     | unset/4 | unset/FakeToken |
 | 
			
		||||
// +--------------------------+-----------------+---------+-----------------+
 | 
			
		||||
func TestPrepareWatchListOptionsFromListOptions(t *testing.T) {
 | 
			
		||||
	scenarios := []struct {
 | 
			
		||||
		name              string
 | 
			
		||||
		listOptions       metav1.ListOptions
 | 
			
		||||
		enableWatchListFG bool
 | 
			
		||||
 | 
			
		||||
		expectToPrepareWatchListOptions bool
 | 
			
		||||
		expectedWatchListOptions        metav1.ListOptions
 | 
			
		||||
	}{
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			name:                            "can't enable watch list for: WatchListClient=off, RVM=unset, RV=unset, Limit=unset, Continuation=unset",
 | 
			
		||||
			enableWatchListFG:               false,
 | 
			
		||||
			expectToPrepareWatchListOptions: false,
 | 
			
		||||
		},
 | 
			
		||||
		//		+----------------------+-----------------+-------+--------------+
 | 
			
		||||
		//		| ResourceVersionMatch | ResourceVersion | Limit | Continuation |
 | 
			
		||||
		//		+----------------------+-----------------+-------+--------------+
 | 
			
		||||
		//		| unset                | unset           | unset | unset        |
 | 
			
		||||
		//		| unset                | 0               | unset | unset        |
 | 
			
		||||
		//		| unset                | 100             | unset | unset        |
 | 
			
		||||
		//		| unset                | 0               | 4     | unset        |
 | 
			
		||||
		//		| unset                | 0               | unset | FakeToken    |
 | 
			
		||||
		//		+----------------------+-----------------+-------+--------------+
 | 
			
		||||
		{
 | 
			
		||||
			name:                            "can enable watch list for: RVM=unset, RV=unset, Limit=unset, Continuation=unset",
 | 
			
		||||
			enableWatchListFG:               true,
 | 
			
		||||
			expectToPrepareWatchListOptions: true,
 | 
			
		||||
			expectedWatchListOptions:        expectedWatchListOptionsFor(""),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                            "can enable watch list for: RVM=unset, RV=0, Limit=unset, Continuation=unset",
 | 
			
		||||
			listOptions:                     metav1.ListOptions{ResourceVersion: "0"},
 | 
			
		||||
			enableWatchListFG:               true,
 | 
			
		||||
			expectToPrepareWatchListOptions: true,
 | 
			
		||||
			expectedWatchListOptions:        expectedWatchListOptionsFor("0"),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                            "can enable watch list for: RVM=unset, RV=100, Limit=unset, Continuation=unset",
 | 
			
		||||
			listOptions:                     metav1.ListOptions{ResourceVersion: "100"},
 | 
			
		||||
			enableWatchListFG:               true,
 | 
			
		||||
			expectToPrepareWatchListOptions: true,
 | 
			
		||||
			expectedWatchListOptions:        expectedWatchListOptionsFor("100"),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                            "legacy: can enable watch list for: RVM=unset, RV=0, Limit=4,  Continuation=unset",
 | 
			
		||||
			listOptions:                     metav1.ListOptions{ResourceVersion: "0", Limit: 4},
 | 
			
		||||
			enableWatchListFG:               true,
 | 
			
		||||
			expectToPrepareWatchListOptions: true,
 | 
			
		||||
			expectedWatchListOptions:        expectedWatchListOptionsFor("0"),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                            "can't enable watch list for: RVM=unset, RV=0, Limit=unset, Continuation=FakeToken",
 | 
			
		||||
			listOptions:                     metav1.ListOptions{ResourceVersion: "0", Continue: "FakeToken"},
 | 
			
		||||
			enableWatchListFG:               true,
 | 
			
		||||
			expectToPrepareWatchListOptions: false,
 | 
			
		||||
		},
 | 
			
		||||
		//		+----------------------+-----------------+-------+--------------+
 | 
			
		||||
		//		| ResourceVersionMatch | ResourceVersion | Limit | Continuation |
 | 
			
		||||
		//		+----------------------+-----------------+-------+--------------+
 | 
			
		||||
		//		| NotOlderThan         | unset           | unset | unset        |
 | 
			
		||||
		//		| NotOlderThan         | 0               | unset | unset        |
 | 
			
		||||
		//		| NotOlderThan         | 100             | unset | unset        |
 | 
			
		||||
		//		| NotOlderThan         | 0               | 4     | unset        |
 | 
			
		||||
		//		| NotOlderThan         | 0               | unset | FakeToken    |
 | 
			
		||||
		//		+----------------------+-----------------+-------+--------------+
 | 
			
		||||
		{
 | 
			
		||||
			name:                            "can't enable watch list for: RVM=NotOlderThan, RV=unset, Limit=unset, Continuation=unset",
 | 
			
		||||
			listOptions:                     metav1.ListOptions{ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan},
 | 
			
		||||
			enableWatchListFG:               true,
 | 
			
		||||
			expectToPrepareWatchListOptions: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                            "can enable watch list for: RVM=NotOlderThan, RV=0, Limit=unset, Continuation=unset",
 | 
			
		||||
			listOptions:                     metav1.ListOptions{ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, ResourceVersion: "0"},
 | 
			
		||||
			enableWatchListFG:               true,
 | 
			
		||||
			expectToPrepareWatchListOptions: true,
 | 
			
		||||
			expectedWatchListOptions:        expectedWatchListOptionsFor("0"),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                            "can enable watch list for: RVM=NotOlderThan, RV=100, Limit=unset, Continuation=unset",
 | 
			
		||||
			listOptions:                     metav1.ListOptions{ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, ResourceVersion: "100"},
 | 
			
		||||
			enableWatchListFG:               true,
 | 
			
		||||
			expectToPrepareWatchListOptions: true,
 | 
			
		||||
			expectedWatchListOptions:        expectedWatchListOptionsFor("100"),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                            "legacy: can enable watch list for: RVM=NotOlderThan, RV=0, Limit=4, Continuation=unset",
 | 
			
		||||
			listOptions:                     metav1.ListOptions{ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, ResourceVersion: "0", Limit: 4},
 | 
			
		||||
			enableWatchListFG:               true,
 | 
			
		||||
			expectToPrepareWatchListOptions: true,
 | 
			
		||||
			expectedWatchListOptions:        expectedWatchListOptionsFor("0"),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                            "can't enable watch list for: RVM=NotOlderThan, RV=0, Limit=unset, Continuation=FakeToken",
 | 
			
		||||
			listOptions:                     metav1.ListOptions{ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, ResourceVersion: "0", Continue: "FakeToken"},
 | 
			
		||||
			enableWatchListFG:               true,
 | 
			
		||||
			expectToPrepareWatchListOptions: false,
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		//		+----------------------+-----------------+-------+--------------+
 | 
			
		||||
		//		| ResourceVersionMatch | ResourceVersion | Limit | Continuation |
 | 
			
		||||
		//		+----------------------+-----------------+-------+--------------+
 | 
			
		||||
		//		| Exact                | unset           | unset | unset        |
 | 
			
		||||
		//		| Exact                | 0               | unset | unset        |
 | 
			
		||||
		//		| Exact                | 100             | unset | unset        |
 | 
			
		||||
		//		| Exact                | 0               | 4     | unset        |
 | 
			
		||||
		//		| Exact                | 0               | unset | FakeToken    |
 | 
			
		||||
		//		+----------------------+-----------------+-------+--------------+
 | 
			
		||||
		{
 | 
			
		||||
			name:                            "can't enable watch list for: RVM=Exact, RV=unset, Limit=unset, Continuation=unset",
 | 
			
		||||
			listOptions:                     metav1.ListOptions{ResourceVersionMatch: metav1.ResourceVersionMatchExact},
 | 
			
		||||
			enableWatchListFG:               true,
 | 
			
		||||
			expectToPrepareWatchListOptions: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                            "can enable watch list for: RVM=Exact, RV=0, Limit=unset, Continuation=unset",
 | 
			
		||||
			listOptions:                     metav1.ListOptions{ResourceVersionMatch: metav1.ResourceVersionMatchExact, ResourceVersion: "0"},
 | 
			
		||||
			enableWatchListFG:               true,
 | 
			
		||||
			expectToPrepareWatchListOptions: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                            "can enable watch list for: RVM=Exact, RV=100, Limit=unset, Continuation=unset",
 | 
			
		||||
			listOptions:                     metav1.ListOptions{ResourceVersionMatch: metav1.ResourceVersionMatchExact, ResourceVersion: "100"},
 | 
			
		||||
			enableWatchListFG:               true,
 | 
			
		||||
			expectToPrepareWatchListOptions: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                            "can't enable watch list for: RVM=Exact, RV=0, Limit=4,  Continuation=unset",
 | 
			
		||||
			listOptions:                     metav1.ListOptions{ResourceVersionMatch: metav1.ResourceVersionMatchExact, ResourceVersion: "0", Limit: 4},
 | 
			
		||||
			enableWatchListFG:               true,
 | 
			
		||||
			expectToPrepareWatchListOptions: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                            "can't enable watch list for: RVM=Exact, RV=0, Limit=unset, Continuation=FakeToken",
 | 
			
		||||
			listOptions:                     metav1.ListOptions{ResourceVersionMatch: metav1.ResourceVersionMatchExact, ResourceVersion: "0", Continue: "FakeToken"},
 | 
			
		||||
			enableWatchListFG:               true,
 | 
			
		||||
			expectToPrepareWatchListOptions: false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, scenario := range scenarios {
 | 
			
		||||
		t.Run(scenario.name, func(t *testing.T) {
 | 
			
		||||
			clientfeaturestesting.SetFeatureDuringTest(t, clientfeatures.WatchListClient, scenario.enableWatchListFG)
 | 
			
		||||
 | 
			
		||||
			watchListOptions, hasWatchListOptionsPrepared, err := PrepareWatchListOptionsFromListOptions(scenario.listOptions)
 | 
			
		||||
 | 
			
		||||
			require.NoError(t, err)
 | 
			
		||||
			require.Equal(t, scenario.expectToPrepareWatchListOptions, hasWatchListOptionsPrepared)
 | 
			
		||||
			require.Equal(t, scenario.expectedWatchListOptions, watchListOptions)
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func expectedWatchListOptionsFor(rv string) metav1.ListOptions {
 | 
			
		||||
	var watchListOptions metav1.ListOptions
 | 
			
		||||
 | 
			
		||||
	watchListOptions.ResourceVersion = rv
 | 
			
		||||
	watchListOptions.ResourceVersionMatch = metav1.ResourceVersionMatchNotOlderThan
 | 
			
		||||
	watchListOptions.Watch = true
 | 
			
		||||
	watchListOptions.AllowWatchBookmarks = true
 | 
			
		||||
	watchListOptions.SendInitialEvents = ptr.To(true)
 | 
			
		||||
 | 
			
		||||
	return watchListOptions
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user