k8s.io/apiserver@v0.29.3/pkg/storage/etcd3/watcher_test.go (about)

     1  /*
     2  Copyright 2016 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package etcd3
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"sync"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/google/go-cmp/cmp"
    28  	clientv3 "go.etcd.io/etcd/client/v3"
    29  
    30  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/fields"
    33  	"k8s.io/apimachinery/pkg/labels"
    34  	"k8s.io/apimachinery/pkg/watch"
    35  	"k8s.io/apiserver/pkg/apis/example"
    36  	"k8s.io/apiserver/pkg/features"
    37  	"k8s.io/apiserver/pkg/storage"
    38  	"k8s.io/apiserver/pkg/storage/etcd3/testserver"
    39  	storagetesting "k8s.io/apiserver/pkg/storage/testing"
    40  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    41  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    42  	"k8s.io/utils/ptr"
    43  )
    44  
    45  func TestWatch(t *testing.T) {
    46  	ctx, store, _ := testSetup(t)
    47  	storagetesting.RunTestWatch(ctx, t, store)
    48  }
    49  
    50  func TestClusterScopedWatch(t *testing.T) {
    51  	ctx, store, _ := testSetup(t)
    52  	storagetesting.RunTestClusterScopedWatch(ctx, t, store)
    53  }
    54  
    55  func TestNamespaceScopedWatch(t *testing.T) {
    56  	ctx, store, _ := testSetup(t)
    57  	storagetesting.RunTestNamespaceScopedWatch(ctx, t, store)
    58  }
    59  
    60  func TestDeleteTriggerWatch(t *testing.T) {
    61  	ctx, store, _ := testSetup(t)
    62  	storagetesting.RunTestDeleteTriggerWatch(ctx, t, store)
    63  }
    64  
    65  func TestWatchFromZero(t *testing.T) {
    66  	ctx, store, client := testSetup(t)
    67  	storagetesting.RunTestWatchFromZero(ctx, t, store, compactStorage(client))
    68  }
    69  
    70  // TestWatchFromNonZero tests that
    71  // - watch from non-0 should just watch changes after given version
    72  func TestWatchFromNoneZero(t *testing.T) {
    73  	ctx, store, _ := testSetup(t)
    74  	storagetesting.RunTestWatchFromNonZero(ctx, t, store)
    75  }
    76  
    77  func TestDelayedWatchDelivery(t *testing.T) {
    78  	ctx, store, _ := testSetup(t)
    79  	storagetesting.RunTestDelayedWatchDelivery(ctx, t, store)
    80  }
    81  
    82  func TestWatchError(t *testing.T) {
    83  	ctx, store, _ := testSetup(t)
    84  	storagetesting.RunTestWatchError(ctx, t, &storeWithPrefixTransformer{store})
    85  }
    86  
    87  func TestWatchContextCancel(t *testing.T) {
    88  	ctx, store, _ := testSetup(t)
    89  	storagetesting.RunTestWatchContextCancel(ctx, t, store)
    90  }
    91  
    92  func TestWatcherTimeout(t *testing.T) {
    93  	ctx, store, _ := testSetup(t)
    94  	storagetesting.RunTestWatcherTimeout(ctx, t, store)
    95  }
    96  
    97  func TestWatchDeleteEventObjectHaveLatestRV(t *testing.T) {
    98  	ctx, store, _ := testSetup(t)
    99  	storagetesting.RunTestWatchDeleteEventObjectHaveLatestRV(ctx, t, store)
   100  }
   101  
   102  func TestWatchInitializationSignal(t *testing.T) {
   103  	ctx, store, _ := testSetup(t)
   104  	storagetesting.RunTestWatchInitializationSignal(ctx, t, store)
   105  }
   106  
   107  func TestProgressNotify(t *testing.T) {
   108  	clusterConfig := testserver.NewTestConfig(t)
   109  	clusterConfig.ExperimentalWatchProgressNotifyInterval = time.Second
   110  	ctx, store, _ := testSetup(t, withClientConfig(clusterConfig))
   111  
   112  	storagetesting.RunOptionalTestProgressNotify(ctx, t, store)
   113  }
   114  
   115  // TestWatchDispatchBookmarkEvents makes sure that
   116  // setting allowWatchBookmarks query param against
   117  // etcd implementation doesn't have any effect.
   118  func TestWatchDispatchBookmarkEvents(t *testing.T) {
   119  	clusterConfig := testserver.NewTestConfig(t)
   120  	clusterConfig.ExperimentalWatchProgressNotifyInterval = time.Second
   121  	ctx, store, _ := testSetup(t, withClientConfig(clusterConfig))
   122  
   123  	storagetesting.RunTestWatchDispatchBookmarkEvents(ctx, t, store, false)
   124  }
   125  
   126  func TestSendInitialEventsBackwardCompatibility(t *testing.T) {
   127  	ctx, store, _ := testSetup(t)
   128  	storagetesting.RunSendInitialEventsBackwardCompatibility(ctx, t, store)
   129  }
   130  
   131  func TestEtcdWatchSemantics(t *testing.T) {
   132  	ctx, store, _ := testSetup(t)
   133  	storagetesting.RunWatchSemantics(ctx, t, store)
   134  }
   135  
   136  func TestEtcdWatchSemanticInitialEventsExtended(t *testing.T) {
   137  	ctx, store, _ := testSetup(t)
   138  	storagetesting.RunWatchSemanticInitialEventsExtended(ctx, t, store)
   139  }
   140  
   141  // =======================================================================
   142  // Implementation-specific tests are following.
   143  // The following tests are exercising the details of the implementation
   144  // not the actual user-facing contract of storage interface.
   145  // As such, they may focus e.g. on non-functional aspects like performance
   146  // impact.
   147  // =======================================================================
   148  
   149  func TestWatchErrResultNotBlockAfterCancel(t *testing.T) {
   150  	origCtx, store, _ := testSetup(t)
   151  	ctx, cancel := context.WithCancel(origCtx)
   152  	w := store.watcher.createWatchChan(ctx, "/abc", 0, false, false, storage.Everything)
   153  	// make resultChan and errChan blocking to ensure ordering.
   154  	w.resultChan = make(chan watch.Event)
   155  	w.errChan = make(chan error)
   156  	// The event flow goes like:
   157  	// - first we send an error, it should block on resultChan.
   158  	// - Then we cancel ctx. The blocking on resultChan should be freed up
   159  	//   and run() goroutine should return.
   160  	var wg sync.WaitGroup
   161  	wg.Add(1)
   162  	go func() {
   163  		w.run(false, true)
   164  		wg.Done()
   165  	}()
   166  	w.errChan <- fmt.Errorf("some error")
   167  	cancel()
   168  	wg.Wait()
   169  }
   170  
   171  // TestWatchErrorIncorrectConfiguration checks if an error
   172  // will be returned when the storage hasn't been properly
   173  // initialised for watch requests
   174  func TestWatchErrorIncorrectConfiguration(t *testing.T) {
   175  	scenarios := []struct {
   176  		name            string
   177  		setupFn         func(opts *setupOptions)
   178  		requestOpts     storage.ListOptions
   179  		enableWatchList bool
   180  		expectedErr     error
   181  	}{
   182  		{
   183  			name:        "no newFunc provided",
   184  			setupFn:     func(opts *setupOptions) { opts.newFunc = nil },
   185  			requestOpts: storage.ListOptions{ProgressNotify: true},
   186  			expectedErr: apierrors.NewInternalError(errors.New("progressNotify for watch is unsupported by the etcd storage because no newFunc was provided")),
   187  		},
   188  	}
   189  	for _, scenario := range scenarios {
   190  		t.Run(scenario.name, func(t *testing.T) {
   191  			if scenario.enableWatchList {
   192  				defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.WatchList, true)()
   193  			}
   194  			origCtx, store, _ := testSetup(t, scenario.setupFn)
   195  			ctx, cancel := context.WithCancel(origCtx)
   196  			defer cancel()
   197  
   198  			w, err := store.watcher.Watch(ctx, "/abc", 0, scenario.requestOpts)
   199  			if err == nil {
   200  				t.Fatalf("expected an error but got none")
   201  			}
   202  			if w != nil {
   203  				t.Fatalf("didn't expect a watcher because the test assumes incorrect store initialisation")
   204  			}
   205  			if err.Error() != scenario.expectedErr.Error() {
   206  				t.Fatalf("unexpected err = %v, expected = %v", err, scenario.expectedErr)
   207  			}
   208  		})
   209  	}
   210  }
   211  
   212  func TestTooLargeResourceVersionErrorForWatchList(t *testing.T) {
   213  	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.WatchList, true)()
   214  	origCtx, store, _ := testSetup(t)
   215  	ctx, cancel := context.WithCancel(origCtx)
   216  	defer cancel()
   217  	requestOpts := storage.ListOptions{
   218  		SendInitialEvents: ptr.To(true),
   219  		Recursive:         true,
   220  		Predicate: storage.SelectionPredicate{
   221  			Field:               fields.Everything(),
   222  			Label:               labels.Everything(),
   223  			AllowWatchBookmarks: true,
   224  		},
   225  	}
   226  	var expectedErr *apierrors.StatusError
   227  	if !errors.As(storage.NewTooLargeResourceVersionError(uint64(102), 1, 0), &expectedErr) {
   228  		t.Fatalf("Unable to convert NewTooLargeResourceVersionError to apierrors.StatusError")
   229  	}
   230  
   231  	w, err := store.watcher.Watch(ctx, "/abc", int64(102), requestOpts)
   232  	if err != nil {
   233  		t.Fatal(err)
   234  	}
   235  	defer w.Stop()
   236  
   237  	actualEvent := <-w.ResultChan()
   238  	if actualEvent.Type != watch.Error {
   239  		t.Fatalf("Unexpected type of the event: %v, expected: %v", actualEvent.Type, watch.Error)
   240  	}
   241  	actualErr, ok := actualEvent.Object.(*metav1.Status)
   242  	if !ok {
   243  		t.Fatalf("Expected *apierrors.StatusError, got: %#v", actualEvent.Object)
   244  	}
   245  
   246  	if actualErr.Details.RetryAfterSeconds <= 0 {
   247  		t.Fatalf("RetryAfterSeconds must be > 0, actual value: %v", actualErr.Details.RetryAfterSeconds)
   248  	}
   249  	// rewrite the Details as it contains retry seconds
   250  	// and validate the whole struct
   251  	expectedErr.ErrStatus.Details = actualErr.Details
   252  	if diff := cmp.Diff(*actualErr, expectedErr.ErrStatus); diff != "" {
   253  		t.Fatalf("Unexpected error returned, diff: %v", diff)
   254  	}
   255  }
   256  
   257  func TestWatchChanSync(t *testing.T) {
   258  	testCases := []struct {
   259  		name             string
   260  		watchKey         string
   261  		watcherMaxLimit  int64
   262  		expectEventCount int
   263  		expectGetCount   int
   264  	}{
   265  		{
   266  			name:            "None of the current objects match watchKey: sync with empty page",
   267  			watchKey:        "/pods/test/",
   268  			watcherMaxLimit: 1,
   269  			expectGetCount:  1,
   270  		},
   271  		{
   272  			name:             "The number of current objects is less than defaultWatcherMaxLimit: sync with one page",
   273  			watchKey:         "/pods/",
   274  			watcherMaxLimit:  3,
   275  			expectEventCount: 2,
   276  			expectGetCount:   1,
   277  		},
   278  		{
   279  			name:             "a new item added to etcd before returning a second page is not returned: sync with two page",
   280  			watchKey:         "/pods/",
   281  			watcherMaxLimit:  1,
   282  			expectEventCount: 2,
   283  			expectGetCount:   2,
   284  		},
   285  	}
   286  
   287  	for _, testCase := range testCases {
   288  		t.Run(testCase.name, func(t *testing.T) {
   289  			defaultWatcherMaxLimit = testCase.watcherMaxLimit
   290  
   291  			origCtx, store, _ := testSetup(t)
   292  			initList, err := initStoreData(origCtx, store)
   293  			if err != nil {
   294  				t.Fatal(err)
   295  			}
   296  
   297  			kvWrapper := newEtcdClientKVWrapper(store.client.KV)
   298  			kvWrapper.getReactors = append(kvWrapper.getReactors, func() {
   299  				barThird := &example.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "third", Name: "bar"}}
   300  				podKey := fmt.Sprintf("/pods/%s/%s", barThird.Namespace, barThird.Name)
   301  				storedObj := &example.Pod{}
   302  
   303  				err := store.Create(context.Background(), podKey, barThird, storedObj, 0)
   304  				if err != nil {
   305  					t.Errorf("failed to create object: %v", err)
   306  				}
   307  			})
   308  
   309  			store.client.KV = kvWrapper
   310  
   311  			w := store.watcher.createWatchChan(
   312  				origCtx,
   313  				testCase.watchKey,
   314  				0,
   315  				true,
   316  				false,
   317  				storage.Everything)
   318  
   319  			err = w.sync()
   320  			if err != nil {
   321  				t.Fatal(err)
   322  			}
   323  
   324  			// close incomingEventChan so we can read incomingEventChan non-blocking
   325  			close(w.incomingEventChan)
   326  
   327  			eventsReceived := 0
   328  			for event := range w.incomingEventChan {
   329  				eventsReceived++
   330  				storagetesting.ExpectContains(t, "incorrect list pods", initList, event.key)
   331  			}
   332  
   333  			if eventsReceived != testCase.expectEventCount {
   334  				t.Errorf("Unexpected number of events: %v, expected: %v", eventsReceived, testCase.expectEventCount)
   335  			}
   336  
   337  			if kvWrapper.getCallCounter != testCase.expectGetCount {
   338  				t.Errorf("Unexpected called times of client.KV.Get() : %v, expected: %v", kvWrapper.getCallCounter, testCase.expectGetCount)
   339  			}
   340  		})
   341  	}
   342  }
   343  
   344  // NOTE: it's not thread-safe
   345  type etcdClientKVWrapper struct {
   346  	clientv3.KV
   347  	// keeps track of the number of times Get method is called
   348  	getCallCounter int
   349  	// getReactors is called after the etcd KV's get function is executed.
   350  	getReactors []func()
   351  }
   352  
   353  func newEtcdClientKVWrapper(kv clientv3.KV) *etcdClientKVWrapper {
   354  	return &etcdClientKVWrapper{
   355  		KV:             kv,
   356  		getCallCounter: 0,
   357  	}
   358  }
   359  
   360  func (ecw *etcdClientKVWrapper) Get(ctx context.Context, key string, opts ...clientv3.OpOption) (*clientv3.GetResponse, error) {
   361  	resp, err := ecw.KV.Get(ctx, key, opts...)
   362  	ecw.getCallCounter++
   363  	if err != nil {
   364  		return nil, err
   365  	}
   366  
   367  	if len(ecw.getReactors) > 0 {
   368  		reactor := ecw.getReactors[0]
   369  		ecw.getReactors = ecw.getReactors[1:]
   370  		reactor()
   371  	}
   372  
   373  	return resp, nil
   374  }
   375  
   376  func initStoreData(ctx context.Context, store storage.Interface) ([]interface{}, error) {
   377  	barFirst := &example.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "first", Name: "bar"}}
   378  	barSecond := &example.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "second", Name: "bar"}}
   379  
   380  	preset := []struct {
   381  		key       string
   382  		obj       *example.Pod
   383  		storedObj *example.Pod
   384  	}{
   385  		{
   386  			key: fmt.Sprintf("/pods/%s/%s", barFirst.Namespace, barFirst.Name),
   387  			obj: barFirst,
   388  		},
   389  		{
   390  			key: fmt.Sprintf("/pods/%s/%s", barSecond.Namespace, barSecond.Name),
   391  			obj: barSecond,
   392  		},
   393  	}
   394  
   395  	for i, ps := range preset {
   396  		preset[i].storedObj = &example.Pod{}
   397  		err := store.Create(ctx, ps.key, ps.obj, preset[i].storedObj, 0)
   398  		if err != nil {
   399  			return nil, fmt.Errorf("failed to create object: %w", err)
   400  		}
   401  	}
   402  
   403  	var created []interface{}
   404  	for _, item := range preset {
   405  		created = append(created, item.key)
   406  	}
   407  	return created, nil
   408  }