diff --git a/util/consistencydetector/list_data_consistency_detector.go b/util/consistencydetector/list_data_consistency_detector.go deleted file mode 100644 index 61b8fe28..00000000 --- a/util/consistencydetector/list_data_consistency_detector.go +++ /dev/null @@ -1,76 +0,0 @@ -/* -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 consistencydetector - -import ( - "context" - "os" - "strconv" - - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" -) - -var dataConsistencyDetectionForListFromCacheEnabled = false - -func init() { - dataConsistencyDetectionForListFromCacheEnabled, _ = strconv.ParseBool(os.Getenv("KUBE_LIST_FROM_CACHE_INCONSISTENCY_DETECTOR")) -} - -// IsDataConsistencyDetectionForListEnabled returns true when -// the KUBE_LIST_FROM_CACHE_INCONSISTENCY_DETECTOR environment variable was set during a binary startup. -func IsDataConsistencyDetectionForListEnabled() bool { - return dataConsistencyDetectionForListFromCacheEnabled -} - -// CheckListFromCacheDataConsistencyIfRequested performs a data consistency check only when -// the KUBE_LIST_FROM_CACHE_INCONSISTENCY_DETECTOR environment variable was set during a binary startup -// for requests that have a high chance of being served from the watch-cache. -// -// The consistency check is meant to be enforced only in the CI, not in production. -// The check ensures that data retrieved by a list api call from the watch-cache -// is exactly the same as data received by the list api call from etcd. -// -// Note that this function will panic when data inconsistency is detected. -// This is intentional because we want to catch it in the CI. -// -// Note that this function doesn't examine the ListOptions to determine -// if the original request has hit the cache because it would be challenging -// to maintain consistency with the server-side implementation. -// For simplicity, we assume that the first request retrieved data from -// the cache (even though this might not be true for some requests) -// and issue the second call to get data from etcd for comparison. -func CheckListFromCacheDataConsistencyIfRequested[T runtime.Object](ctx context.Context, identity string, listItemsFn ListFunc[T], optionsUsedToReceiveList metav1.ListOptions, receivedList runtime.Object) { - if !IsDataConsistencyDetectionForListEnabled() { - return - } - checkListFromCacheDataConsistencyIfRequestedInternal(ctx, identity, listItemsFn, optionsUsedToReceiveList, receivedList) -} - -func checkListFromCacheDataConsistencyIfRequestedInternal[T runtime.Object](ctx context.Context, identity string, listItemsFn ListFunc[T], optionsUsedToReceiveList metav1.ListOptions, receivedList runtime.Object) { - receivedListMeta, err := meta.ListAccessor(receivedList) - if err != nil { - panic(err) - } - rawListItems, err := meta.ExtractListWithAlloc(receivedList) - if err != nil { - panic(err) // this should never happen - } - lastSyncedResourceVersion := receivedListMeta.GetResourceVersion() - CheckDataConsistency(ctx, identity, lastSyncedResourceVersion, listItemsFn, optionsUsedToReceiveList, func() []runtime.Object { return rawListItems }) -} diff --git a/util/consistencydetector/list_data_consistency_detector_test.go b/util/consistencydetector/list_data_consistency_detector_test.go deleted file mode 100644 index e84d47a2..00000000 --- a/util/consistencydetector/list_data_consistency_detector_test.go +++ /dev/null @@ -1,139 +0,0 @@ -/* -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 consistencydetector - -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" - - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/utils/ptr" -) - -var ( - emptyListFunc = func(_ context.Context, opts metav1.ListOptions) (*v1.PodList, error) { - return &v1.PodList{}, nil - } - emptyListOptions = metav1.ListOptions{} -) - -func TestDriveCheckWatchListFromCacheDataConsistencyIfRequested(t *testing.T) { - ctx := context.TODO() - - CheckWatchListFromCacheDataConsistencyIfRequested(ctx, "", emptyListFunc, emptyListOptions, &v1.PodList{}) -} - -func TestDriveCheckListFromCacheDataConsistencyIfRequested(t *testing.T) { - ctx := context.TODO() - - CheckListFromCacheDataConsistencyIfRequested(ctx, "", emptyListFunc, emptyListOptions, &v1.PodList{}) -} - -func TestCheckListFromCacheDataConsistencyIfRequestedInternalPanics(t *testing.T) { - ctx := context.TODO() - pod := makePod("p1", "1") - - wrappedTarget := func() { - checkListFromCacheDataConsistencyIfRequestedInternal(ctx, "", emptyListFunc, emptyListOptions, pod) - } - - require.PanicsWithError(t, "object does not implement the List interfaces", wrappedTarget) -} - -func TestCheckListFromCacheDataConsistencyIfRequestedInternalHappyPath(t *testing.T) { - scenarios := []struct { - name string - listResponse runtime.Object - retrievedList runtime.Object - retrievedListOptions metav1.ListOptions - - expectedRequestOptions metav1.ListOptions - }{ - { - name: "list detector works with a typed list", - listResponse: &v1.PodList{ - ListMeta: metav1.ListMeta{ResourceVersion: "2"}, - Items: []v1.Pod{*makePod("p1", "1"), *makePod("p2", "2")}, - }, - retrievedListOptions: metav1.ListOptions{TimeoutSeconds: ptr.To(int64(39))}, - retrievedList: &v1.PodList{ - ListMeta: metav1.ListMeta{ResourceVersion: "2"}, - Items: []v1.Pod{*makePod("p1", "1"), *makePod("p2", "2")}, - }, - expectedRequestOptions: metav1.ListOptions{ - ResourceVersion: "2", - ResourceVersionMatch: metav1.ResourceVersionMatchExact, - TimeoutSeconds: ptr.To(int64(39)), - }, - }, - { - name: "list detector works with a unstructured list", - listResponse: &unstructured.UnstructuredList{ - Object: map[string]interface{}{ - "apiVersion": "vTest", - "kind": "rTestList", - "metadata": map[string]interface{}{ - "resourceVersion": "3", - }, - }, - Items: []unstructured.Unstructured{ - *makeUnstructuredObject("vTest", "rTest", "item1"), - *makeUnstructuredObject("vTest", "rTest", "item2"), - *makeUnstructuredObject("vTest", "rTest", "item3"), - }, - }, - retrievedListOptions: metav1.ListOptions{TimeoutSeconds: ptr.To(int64(39))}, - retrievedList: &unstructured.UnstructuredList{ - Object: map[string]interface{}{ - "apiVersion": "vTest", - "kind": "rTestList", - "metadata": map[string]interface{}{ - "resourceVersion": "3", - }, - }, - Items: []unstructured.Unstructured{ - *makeUnstructuredObject("vTest", "rTest", "item1"), - *makeUnstructuredObject("vTest", "rTest", "item2"), - *makeUnstructuredObject("vTest", "rTest", "item3"), - }, - }, - expectedRequestOptions: metav1.ListOptions{ - ResourceVersion: "3", - ResourceVersionMatch: metav1.ResourceVersionMatchExact, - TimeoutSeconds: ptr.To(int64(39)), - }, - }, - } - for _, scenario := range scenarios { - t.Run(scenario.name, func(t *testing.T) { - ctx := context.TODO() - listOptions := metav1.ListOptions{TimeoutSeconds: ptr.To(int64(39))} - fakeLister := &listWrapper{response: scenario.listResponse} - - checkListFromCacheDataConsistencyIfRequestedInternal(ctx, "", fakeLister.List, listOptions, scenario.retrievedList) - - require.Equal(t, 1, fakeLister.counter) - require.Len(t, fakeLister.requestOptions, 1) - require.Equal(t, scenario.expectedRequestOptions, fakeLister.requestOptions[0]) - }) - } -} diff --git a/util/consistencydetector/watch_list_data_consistency_detector.go b/util/consistencydetector/watch_list_data_consistency_detector.go index cda5fc20..67b5a511 100644 --- a/util/consistencydetector/watch_list_data_consistency_detector.go +++ b/util/consistencydetector/watch_list_data_consistency_detector.go @@ -17,12 +17,8 @@ limitations under the License. package consistencydetector import ( - "context" "os" "strconv" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" ) var dataConsistencyDetectionForWatchListEnabled = false @@ -36,19 +32,3 @@ func init() { func IsDataConsistencyDetectionForWatchListEnabled() bool { return dataConsistencyDetectionForWatchListEnabled } - -// CheckWatchListFromCacheDataConsistencyIfRequested performs a data consistency check only when -// the KUBE_WATCHLIST_INCONSISTENCY_DETECTOR environment variable was set during a binary startup. -// -// The consistency check is meant to be enforced only in the CI, not in production. -// The check ensures that data retrieved by the watch-list api call -// is exactly the same as data received by the standard list api call against etcd. -// -// Note that this function will panic when data inconsistency is detected. -// This is intentional because we want to catch it in the CI. -func CheckWatchListFromCacheDataConsistencyIfRequested[T runtime.Object](ctx context.Context, identity string, listItemsFn ListFunc[T], optionsUsedToReceiveList metav1.ListOptions, receivedList runtime.Object) { - if !IsDataConsistencyDetectionForWatchListEnabled() { - return - } - checkListFromCacheDataConsistencyIfRequestedInternal(ctx, identity, listItemsFn, optionsUsedToReceiveList, receivedList) -}