k8s.io/client-go@v0.22.2/tools/cache/reflector_test.go (about)

     1  /*
     2  Copyright 2014 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 cache
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"math/rand"
    23  	"reflect"
    24  	"strconv"
    25  	"syscall"
    26  	"testing"
    27  	"time"
    28  
    29  	v1 "k8s.io/api/core/v1"
    30  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    33  	"k8s.io/apimachinery/pkg/runtime"
    34  	"k8s.io/apimachinery/pkg/runtime/schema"
    35  	"k8s.io/apimachinery/pkg/util/clock"
    36  	"k8s.io/apimachinery/pkg/util/wait"
    37  	"k8s.io/apimachinery/pkg/watch"
    38  )
    39  
    40  var nevererrc chan error
    41  
    42  type testLW struct {
    43  	ListFunc  func(options metav1.ListOptions) (runtime.Object, error)
    44  	WatchFunc func(options metav1.ListOptions) (watch.Interface, error)
    45  }
    46  
    47  func (t *testLW) List(options metav1.ListOptions) (runtime.Object, error) {
    48  	return t.ListFunc(options)
    49  }
    50  func (t *testLW) Watch(options metav1.ListOptions) (watch.Interface, error) {
    51  	return t.WatchFunc(options)
    52  }
    53  
    54  func TestCloseWatchChannelOnError(t *testing.T) {
    55  	r := NewReflector(&testLW{}, &v1.Pod{}, NewStore(MetaNamespaceKeyFunc), 0)
    56  	pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar"}}
    57  	fw := watch.NewFake()
    58  	r.listerWatcher = &testLW{
    59  		WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
    60  			return fw, nil
    61  		},
    62  		ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
    63  			return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "1"}}, nil
    64  		},
    65  	}
    66  	go r.ListAndWatch(wait.NeverStop)
    67  	fw.Error(pod)
    68  	select {
    69  	case _, ok := <-fw.ResultChan():
    70  		if ok {
    71  			t.Errorf("Watch channel left open after cancellation")
    72  		}
    73  	case <-time.After(wait.ForeverTestTimeout):
    74  		t.Errorf("the cancellation is at least %s late", wait.ForeverTestTimeout.String())
    75  		break
    76  	}
    77  }
    78  
    79  func TestRunUntil(t *testing.T) {
    80  	stopCh := make(chan struct{})
    81  	store := NewStore(MetaNamespaceKeyFunc)
    82  	r := NewReflector(&testLW{}, &v1.Pod{}, store, 0)
    83  	fw := watch.NewFake()
    84  	r.listerWatcher = &testLW{
    85  		WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
    86  			return fw, nil
    87  		},
    88  		ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
    89  			return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "1"}}, nil
    90  		},
    91  	}
    92  	go r.Run(stopCh)
    93  	// Synchronously add a dummy pod into the watch channel so we
    94  	// know the RunUntil go routine is in the watch handler.
    95  	fw.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar"}})
    96  	close(stopCh)
    97  	select {
    98  	case _, ok := <-fw.ResultChan():
    99  		if ok {
   100  			t.Errorf("Watch channel left open after stopping the watch")
   101  		}
   102  	case <-time.After(wait.ForeverTestTimeout):
   103  		t.Errorf("the cancellation is at least %s late", wait.ForeverTestTimeout.String())
   104  		break
   105  	}
   106  }
   107  
   108  func TestReflectorResyncChan(t *testing.T) {
   109  	s := NewStore(MetaNamespaceKeyFunc)
   110  	g := NewReflector(&testLW{}, &v1.Pod{}, s, time.Millisecond)
   111  	a, _ := g.resyncChan()
   112  	b := time.After(wait.ForeverTestTimeout)
   113  	select {
   114  	case <-a:
   115  		t.Logf("got timeout as expected")
   116  	case <-b:
   117  		t.Errorf("resyncChan() is at least 99 milliseconds late??")
   118  	}
   119  }
   120  
   121  func BenchmarkReflectorResyncChanMany(b *testing.B) {
   122  	s := NewStore(MetaNamespaceKeyFunc)
   123  	g := NewReflector(&testLW{}, &v1.Pod{}, s, 25*time.Millisecond)
   124  	// The improvement to this (calling the timer's Stop() method) makes
   125  	// this benchmark about 40% faster.
   126  	for i := 0; i < b.N; i++ {
   127  		g.resyncPeriod = time.Duration(rand.Float64() * float64(time.Millisecond) * 25)
   128  		_, stop := g.resyncChan()
   129  		stop()
   130  	}
   131  }
   132  
   133  func TestReflectorWatchHandlerError(t *testing.T) {
   134  	s := NewStore(MetaNamespaceKeyFunc)
   135  	g := NewReflector(&testLW{}, &v1.Pod{}, s, 0)
   136  	fw := watch.NewFake()
   137  	go func() {
   138  		fw.Stop()
   139  	}()
   140  	var resumeRV string
   141  	err := g.watchHandler(time.Now(), fw, &resumeRV, nevererrc, wait.NeverStop)
   142  	if err == nil {
   143  		t.Errorf("unexpected non-error")
   144  	}
   145  }
   146  
   147  func TestReflectorWatchHandler(t *testing.T) {
   148  	s := NewStore(MetaNamespaceKeyFunc)
   149  	g := NewReflector(&testLW{}, &v1.Pod{}, s, 0)
   150  	fw := watch.NewFake()
   151  	s.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}})
   152  	s.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar"}})
   153  	go func() {
   154  		fw.Add(&v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "rejected"}})
   155  		fw.Delete(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}})
   156  		fw.Modify(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar", ResourceVersion: "55"}})
   157  		fw.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "baz", ResourceVersion: "32"}})
   158  		fw.Stop()
   159  	}()
   160  	var resumeRV string
   161  	err := g.watchHandler(time.Now(), fw, &resumeRV, nevererrc, wait.NeverStop)
   162  	if err != nil {
   163  		t.Errorf("unexpected error %v", err)
   164  	}
   165  
   166  	mkPod := func(id string, rv string) *v1.Pod {
   167  		return &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: id, ResourceVersion: rv}}
   168  	}
   169  
   170  	table := []struct {
   171  		Pod    *v1.Pod
   172  		exists bool
   173  	}{
   174  		{mkPod("foo", ""), false},
   175  		{mkPod("rejected", ""), false},
   176  		{mkPod("bar", "55"), true},
   177  		{mkPod("baz", "32"), true},
   178  	}
   179  	for _, item := range table {
   180  		obj, exists, _ := s.Get(item.Pod)
   181  		if e, a := item.exists, exists; e != a {
   182  			t.Errorf("%v: expected %v, got %v", item.Pod, e, a)
   183  		}
   184  		if !exists {
   185  			continue
   186  		}
   187  		if e, a := item.Pod.ResourceVersion, obj.(*v1.Pod).ResourceVersion; e != a {
   188  			t.Errorf("%v: expected %v, got %v", item.Pod, e, a)
   189  		}
   190  	}
   191  
   192  	// RV should send the last version we see.
   193  	if e, a := "32", resumeRV; e != a {
   194  		t.Errorf("expected %v, got %v", e, a)
   195  	}
   196  
   197  	// last sync resource version should be the last version synced with store
   198  	if e, a := "32", g.LastSyncResourceVersion(); e != a {
   199  		t.Errorf("expected %v, got %v", e, a)
   200  	}
   201  }
   202  
   203  func TestReflectorStopWatch(t *testing.T) {
   204  	s := NewStore(MetaNamespaceKeyFunc)
   205  	g := NewReflector(&testLW{}, &v1.Pod{}, s, 0)
   206  	fw := watch.NewFake()
   207  	var resumeRV string
   208  	stopWatch := make(chan struct{}, 1)
   209  	stopWatch <- struct{}{}
   210  	err := g.watchHandler(time.Now(), fw, &resumeRV, nevererrc, stopWatch)
   211  	if err != errorStopRequested {
   212  		t.Errorf("expected stop error, got %q", err)
   213  	}
   214  }
   215  
   216  func TestReflectorListAndWatch(t *testing.T) {
   217  	createdFakes := make(chan *watch.FakeWatcher)
   218  
   219  	// The ListFunc says that it's at revision 1. Therefore, we expect our WatchFunc
   220  	// to get called at the beginning of the watch with 1, and again with 3 when we
   221  	// inject an error.
   222  	expectedRVs := []string{"1", "3"}
   223  	lw := &testLW{
   224  		WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   225  			rv := options.ResourceVersion
   226  			fw := watch.NewFake()
   227  			if e, a := expectedRVs[0], rv; e != a {
   228  				t.Errorf("Expected rv %v, but got %v", e, a)
   229  			}
   230  			expectedRVs = expectedRVs[1:]
   231  			// channel is not buffered because the for loop below needs to block. But
   232  			// we don't want to block here, so report the new fake via a go routine.
   233  			go func() { createdFakes <- fw }()
   234  			return fw, nil
   235  		},
   236  		ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   237  			return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "1"}}, nil
   238  		},
   239  	}
   240  	s := NewFIFO(MetaNamespaceKeyFunc)
   241  	r := NewReflector(lw, &v1.Pod{}, s, 0)
   242  	go r.ListAndWatch(wait.NeverStop)
   243  
   244  	ids := []string{"foo", "bar", "baz", "qux", "zoo"}
   245  	var fw *watch.FakeWatcher
   246  	for i, id := range ids {
   247  		if fw == nil {
   248  			fw = <-createdFakes
   249  		}
   250  		sendingRV := strconv.FormatUint(uint64(i+2), 10)
   251  		fw.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: id, ResourceVersion: sendingRV}})
   252  		if sendingRV == "3" {
   253  			// Inject a failure.
   254  			fw.Stop()
   255  			fw = nil
   256  		}
   257  	}
   258  
   259  	// Verify we received the right ids with the right resource versions.
   260  	for i, id := range ids {
   261  		pod := Pop(s).(*v1.Pod)
   262  		if e, a := id, pod.Name; e != a {
   263  			t.Errorf("%v: Expected %v, got %v", i, e, a)
   264  		}
   265  		if e, a := strconv.FormatUint(uint64(i+2), 10), pod.ResourceVersion; e != a {
   266  			t.Errorf("%v: Expected %v, got %v", i, e, a)
   267  		}
   268  	}
   269  
   270  	if len(expectedRVs) != 0 {
   271  		t.Error("called watchStarter an unexpected number of times")
   272  	}
   273  }
   274  
   275  func TestReflectorListAndWatchWithErrors(t *testing.T) {
   276  	mkPod := func(id string, rv string) *v1.Pod {
   277  		return &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: id, ResourceVersion: rv}}
   278  	}
   279  	mkList := func(rv string, pods ...*v1.Pod) *v1.PodList {
   280  		list := &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: rv}}
   281  		for _, pod := range pods {
   282  			list.Items = append(list.Items, *pod)
   283  		}
   284  		return list
   285  	}
   286  	table := []struct {
   287  		list     *v1.PodList
   288  		listErr  error
   289  		events   []watch.Event
   290  		watchErr error
   291  	}{
   292  		{
   293  			list: mkList("1"),
   294  			events: []watch.Event{
   295  				{Type: watch.Added, Object: mkPod("foo", "2")},
   296  				{Type: watch.Added, Object: mkPod("bar", "3")},
   297  			},
   298  		}, {
   299  			list: mkList("3", mkPod("foo", "2"), mkPod("bar", "3")),
   300  			events: []watch.Event{
   301  				{Type: watch.Deleted, Object: mkPod("foo", "4")},
   302  				{Type: watch.Added, Object: mkPod("qux", "5")},
   303  			},
   304  		}, {
   305  			listErr: fmt.Errorf("a list error"),
   306  		}, {
   307  			list:     mkList("5", mkPod("bar", "3"), mkPod("qux", "5")),
   308  			watchErr: fmt.Errorf("a watch error"),
   309  		}, {
   310  			list: mkList("5", mkPod("bar", "3"), mkPod("qux", "5")),
   311  			events: []watch.Event{
   312  				{Type: watch.Added, Object: mkPod("baz", "6")},
   313  			},
   314  		}, {
   315  			list: mkList("6", mkPod("bar", "3"), mkPod("qux", "5"), mkPod("baz", "6")),
   316  		},
   317  	}
   318  
   319  	s := NewFIFO(MetaNamespaceKeyFunc)
   320  	for line, item := range table {
   321  		if item.list != nil {
   322  			// Test that the list is what currently exists in the store.
   323  			current := s.List()
   324  			checkMap := map[string]string{}
   325  			for _, item := range current {
   326  				pod := item.(*v1.Pod)
   327  				checkMap[pod.Name] = pod.ResourceVersion
   328  			}
   329  			for _, pod := range item.list.Items {
   330  				if e, a := pod.ResourceVersion, checkMap[pod.Name]; e != a {
   331  					t.Errorf("%v: expected %v, got %v for pod %v", line, e, a, pod.Name)
   332  				}
   333  			}
   334  			if e, a := len(item.list.Items), len(checkMap); e != a {
   335  				t.Errorf("%v: expected %v, got %v", line, e, a)
   336  			}
   337  		}
   338  		watchRet, watchErr := item.events, item.watchErr
   339  		lw := &testLW{
   340  			WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   341  				if watchErr != nil {
   342  					return nil, watchErr
   343  				}
   344  				watchErr = fmt.Errorf("second watch")
   345  				fw := watch.NewFake()
   346  				go func() {
   347  					for _, e := range watchRet {
   348  						fw.Action(e.Type, e.Object)
   349  					}
   350  					fw.Stop()
   351  				}()
   352  				return fw, nil
   353  			},
   354  			ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   355  				return item.list, item.listErr
   356  			},
   357  		}
   358  		r := NewReflector(lw, &v1.Pod{}, s, 0)
   359  		r.ListAndWatch(wait.NeverStop)
   360  	}
   361  }
   362  
   363  func TestReflectorListAndWatchInitConnBackoff(t *testing.T) {
   364  	maxBackoff := 50 * time.Millisecond
   365  	table := []struct {
   366  		numConnFails  int
   367  		expLowerBound time.Duration
   368  		expUpperBound time.Duration
   369  	}{
   370  		{5, 32 * time.Millisecond, 64 * time.Millisecond}, // case where maxBackoff is not hit, time should grow exponentially
   371  		{40, 35 * 2 * maxBackoff, 40 * 2 * maxBackoff},    // case where maxBoff is hit, backoff time should flatten
   372  
   373  	}
   374  	for _, test := range table {
   375  		t.Run(fmt.Sprintf("%d connection failures takes at least %d ms", test.numConnFails, 1<<test.numConnFails),
   376  			func(t *testing.T) {
   377  				stopCh := make(chan struct{})
   378  				connFails := test.numConnFails
   379  				fakeClock := clock.NewFakeClock(time.Unix(0, 0))
   380  				bm := wait.NewExponentialBackoffManager(time.Millisecond, maxBackoff, 100*time.Millisecond, 2.0, 1.0, fakeClock)
   381  				done := make(chan struct{})
   382  				defer close(done)
   383  				go func() {
   384  					i := 0
   385  					for {
   386  						select {
   387  						case <-done:
   388  							return
   389  						default:
   390  						}
   391  						if fakeClock.HasWaiters() {
   392  							step := (1 << (i + 1)) * time.Millisecond
   393  							if step > maxBackoff*2 {
   394  								step = maxBackoff * 2
   395  							}
   396  							fakeClock.Step(step)
   397  							i++
   398  						}
   399  						time.Sleep(100 * time.Microsecond)
   400  					}
   401  				}()
   402  				lw := &testLW{
   403  					WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   404  						if connFails > 0 {
   405  							connFails--
   406  							return nil, syscall.ECONNREFUSED
   407  						}
   408  						close(stopCh)
   409  						return watch.NewFake(), nil
   410  					},
   411  					ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   412  						return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "1"}}, nil
   413  					},
   414  				}
   415  				r := &Reflector{
   416  					name:                   "test-reflector",
   417  					listerWatcher:          lw,
   418  					store:                  NewFIFO(MetaNamespaceKeyFunc),
   419  					initConnBackoffManager: bm,
   420  					clock:                  fakeClock,
   421  					watchErrorHandler:      WatchErrorHandler(DefaultWatchErrorHandler),
   422  				}
   423  				start := fakeClock.Now()
   424  				err := r.ListAndWatch(stopCh)
   425  				elapsed := fakeClock.Since(start)
   426  				if err != nil {
   427  					t.Errorf("unexpected error %v", err)
   428  				}
   429  				if elapsed < (test.expLowerBound) {
   430  					t.Errorf("expected lower bound of ListAndWatch: %v, got %v", test.expLowerBound, elapsed)
   431  				}
   432  				if elapsed > (test.expUpperBound) {
   433  					t.Errorf("expected upper bound of ListAndWatch: %v, got %v", test.expUpperBound, elapsed)
   434  				}
   435  			})
   436  	}
   437  }
   438  
   439  type fakeBackoff struct {
   440  	clock clock.Clock
   441  	calls int
   442  }
   443  
   444  func (f *fakeBackoff) Backoff() clock.Timer {
   445  	f.calls++
   446  	return f.clock.NewTimer(time.Duration(0))
   447  }
   448  
   449  func TestBackoffOnTooManyRequests(t *testing.T) {
   450  	err := apierrors.NewTooManyRequests("too many requests", 1)
   451  	clock := &clock.RealClock{}
   452  	bm := &fakeBackoff{clock: clock}
   453  
   454  	lw := &testLW{
   455  		ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   456  			return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "1"}}, nil
   457  		},
   458  		WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   459  			switch bm.calls {
   460  			case 0:
   461  				return nil, err
   462  			case 1:
   463  				w := watch.NewFakeWithChanSize(1, false)
   464  				status := err.Status()
   465  				w.Error(&status)
   466  				return w, nil
   467  			default:
   468  				w := watch.NewFake()
   469  				w.Stop()
   470  				return w, nil
   471  			}
   472  		},
   473  	}
   474  
   475  	r := &Reflector{
   476  		name:                   "test-reflector",
   477  		listerWatcher:          lw,
   478  		store:                  NewFIFO(MetaNamespaceKeyFunc),
   479  		initConnBackoffManager: bm,
   480  		clock:                  clock,
   481  		watchErrorHandler:      WatchErrorHandler(DefaultWatchErrorHandler),
   482  	}
   483  
   484  	stopCh := make(chan struct{})
   485  	r.ListAndWatch(stopCh)
   486  	close(stopCh)
   487  	if bm.calls != 2 {
   488  		t.Errorf("unexpected watch backoff calls: %d", bm.calls)
   489  	}
   490  }
   491  
   492  func TestReflectorResync(t *testing.T) {
   493  	iteration := 0
   494  	stopCh := make(chan struct{})
   495  	rerr := errors.New("expected resync reached")
   496  	s := &FakeCustomStore{
   497  		ResyncFunc: func() error {
   498  			iteration++
   499  			if iteration == 2 {
   500  				return rerr
   501  			}
   502  			return nil
   503  		},
   504  	}
   505  
   506  	lw := &testLW{
   507  		WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   508  			fw := watch.NewFake()
   509  			return fw, nil
   510  		},
   511  		ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   512  			return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "0"}}, nil
   513  		},
   514  	}
   515  	resyncPeriod := 1 * time.Millisecond
   516  	r := NewReflector(lw, &v1.Pod{}, s, resyncPeriod)
   517  	if err := r.ListAndWatch(stopCh); err != nil {
   518  		// error from Resync is not propaged up to here.
   519  		t.Errorf("expected error %v", err)
   520  	}
   521  	if iteration != 2 {
   522  		t.Errorf("exactly 2 iterations were expected, got: %v", iteration)
   523  	}
   524  }
   525  
   526  func TestReflectorWatchListPageSize(t *testing.T) {
   527  	stopCh := make(chan struct{})
   528  	s := NewStore(MetaNamespaceKeyFunc)
   529  
   530  	lw := &testLW{
   531  		WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   532  			// Stop once the reflector begins watching since we're only interested in the list.
   533  			close(stopCh)
   534  			fw := watch.NewFake()
   535  			return fw, nil
   536  		},
   537  		ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   538  			if options.Limit != 4 {
   539  				t.Fatalf("Expected list Limit of 4 but got %d", options.Limit)
   540  			}
   541  			pods := make([]v1.Pod, 10)
   542  			for i := 0; i < 10; i++ {
   543  				pods[i] = v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("pod-%d", i), ResourceVersion: fmt.Sprintf("%d", i)}}
   544  			}
   545  			switch options.Continue {
   546  			case "":
   547  				return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10", Continue: "C1"}, Items: pods[0:4]}, nil
   548  			case "C1":
   549  				return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10", Continue: "C2"}, Items: pods[4:8]}, nil
   550  			case "C2":
   551  				return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}, Items: pods[8:10]}, nil
   552  			default:
   553  				t.Fatalf("Unrecognized continue: %s", options.Continue)
   554  			}
   555  			return nil, nil
   556  		},
   557  	}
   558  	r := NewReflector(lw, &v1.Pod{}, s, 0)
   559  	// Set resource version to test pagination also for not consistent reads.
   560  	r.setLastSyncResourceVersion("10")
   561  	// Set the reflector to paginate the list request in 4 item chunks.
   562  	r.WatchListPageSize = 4
   563  	r.ListAndWatch(stopCh)
   564  
   565  	results := s.List()
   566  	if len(results) != 10 {
   567  		t.Errorf("Expected 10 results, got %d", len(results))
   568  	}
   569  }
   570  
   571  func TestReflectorNotPaginatingNotConsistentReads(t *testing.T) {
   572  	stopCh := make(chan struct{})
   573  	s := NewStore(MetaNamespaceKeyFunc)
   574  
   575  	lw := &testLW{
   576  		WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   577  			// Stop once the reflector begins watching since we're only interested in the list.
   578  			close(stopCh)
   579  			fw := watch.NewFake()
   580  			return fw, nil
   581  		},
   582  		ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   583  			if options.ResourceVersion != "10" {
   584  				t.Fatalf("Expected ResourceVersion: \"10\", got: %s", options.ResourceVersion)
   585  			}
   586  			if options.Limit != 0 {
   587  				t.Fatalf("Expected list Limit of 0 but got %d", options.Limit)
   588  			}
   589  			pods := make([]v1.Pod, 10)
   590  			for i := 0; i < 10; i++ {
   591  				pods[i] = v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("pod-%d", i), ResourceVersion: fmt.Sprintf("%d", i)}}
   592  			}
   593  			return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}, Items: pods}, nil
   594  		},
   595  	}
   596  	r := NewReflector(lw, &v1.Pod{}, s, 0)
   597  	r.setLastSyncResourceVersion("10")
   598  	r.ListAndWatch(stopCh)
   599  
   600  	results := s.List()
   601  	if len(results) != 10 {
   602  		t.Errorf("Expected 10 results, got %d", len(results))
   603  	}
   604  }
   605  
   606  func TestReflectorPaginatingNonConsistentReadsIfWatchCacheDisabled(t *testing.T) {
   607  	var stopCh chan struct{}
   608  	s := NewStore(MetaNamespaceKeyFunc)
   609  
   610  	lw := &testLW{
   611  		WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   612  			// Stop once the reflector begins watching since we're only interested in the list.
   613  			close(stopCh)
   614  			fw := watch.NewFake()
   615  			return fw, nil
   616  		},
   617  		ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   618  			// Check that default pager limit is set.
   619  			if options.Limit != 500 {
   620  				t.Fatalf("Expected list Limit of 500 but got %d", options.Limit)
   621  			}
   622  			pods := make([]v1.Pod, 10)
   623  			for i := 0; i < 10; i++ {
   624  				pods[i] = v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("pod-%d", i), ResourceVersion: fmt.Sprintf("%d", i)}}
   625  			}
   626  			switch options.Continue {
   627  			case "":
   628  				return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10", Continue: "C1"}, Items: pods[0:4]}, nil
   629  			case "C1":
   630  				return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10", Continue: "C2"}, Items: pods[4:8]}, nil
   631  			case "C2":
   632  				return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}, Items: pods[8:10]}, nil
   633  			default:
   634  				t.Fatalf("Unrecognized continue: %s", options.Continue)
   635  			}
   636  			return nil, nil
   637  		},
   638  	}
   639  	r := NewReflector(lw, &v1.Pod{}, s, 0)
   640  
   641  	// Initial list should initialize paginatedResult in the reflector.
   642  	stopCh = make(chan struct{})
   643  	r.ListAndWatch(stopCh)
   644  	if results := s.List(); len(results) != 10 {
   645  		t.Errorf("Expected 10 results, got %d", len(results))
   646  	}
   647  
   648  	// Since initial list for ResourceVersion="0" was paginated, the subsequent
   649  	// ones should also be paginated.
   650  	stopCh = make(chan struct{})
   651  	r.ListAndWatch(stopCh)
   652  	if results := s.List(); len(results) != 10 {
   653  		t.Errorf("Expected 10 results, got %d", len(results))
   654  	}
   655  }
   656  
   657  // TestReflectorResyncWithResourceVersion ensures that a reflector keeps track of the ResourceVersion and sends
   658  // it in relist requests to prevent the reflector from traveling back in time if the relist is to a api-server or
   659  // etcd that is partitioned and serving older data than the reflector has already processed.
   660  func TestReflectorResyncWithResourceVersion(t *testing.T) {
   661  	stopCh := make(chan struct{})
   662  	s := NewStore(MetaNamespaceKeyFunc)
   663  	listCallRVs := []string{}
   664  
   665  	lw := &testLW{
   666  		WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   667  			// Stop once the reflector begins watching since we're only interested in the list.
   668  			close(stopCh)
   669  			fw := watch.NewFake()
   670  			return fw, nil
   671  		},
   672  		ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   673  			listCallRVs = append(listCallRVs, options.ResourceVersion)
   674  			pods := make([]v1.Pod, 8)
   675  			for i := 0; i < 8; i++ {
   676  				pods[i] = v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("pod-%d", i), ResourceVersion: fmt.Sprintf("%d", i)}}
   677  			}
   678  			switch options.ResourceVersion {
   679  			case "0":
   680  				return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}, Items: pods[0:4]}, nil
   681  			case "10":
   682  				return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "11"}, Items: pods[0:8]}, nil
   683  			default:
   684  				t.Fatalf("Unrecognized ResourceVersion: %s", options.ResourceVersion)
   685  			}
   686  			return nil, nil
   687  		},
   688  	}
   689  	r := NewReflector(lw, &v1.Pod{}, s, 0)
   690  
   691  	// Initial list should use RV=0
   692  	r.ListAndWatch(stopCh)
   693  
   694  	results := s.List()
   695  	if len(results) != 4 {
   696  		t.Errorf("Expected 4 results, got %d", len(results))
   697  	}
   698  
   699  	// relist should use lastSyncResourceVersions (RV=10)
   700  	stopCh = make(chan struct{})
   701  	r.ListAndWatch(stopCh)
   702  
   703  	results = s.List()
   704  	if len(results) != 8 {
   705  		t.Errorf("Expected 8 results, got %d", len(results))
   706  	}
   707  
   708  	expectedRVs := []string{"0", "10"}
   709  	if !reflect.DeepEqual(listCallRVs, expectedRVs) {
   710  		t.Errorf("Expected series of list calls with resource versiosn of %v but got: %v", expectedRVs, listCallRVs)
   711  	}
   712  }
   713  
   714  // TestReflectorExpiredExactResourceVersion tests that a reflector handles the behavior of kubernetes 1.16 an earlier
   715  // where if the exact ResourceVersion requested is not available for a List request for a non-zero ResourceVersion,
   716  // an "Expired" error is returned if the ResourceVersion has expired (etcd has compacted it).
   717  // (In kubernetes 1.17, or when the watch cache is enabled, the List will instead return the list that is no older than
   718  // the requested ResourceVersion).
   719  func TestReflectorExpiredExactResourceVersion(t *testing.T) {
   720  	stopCh := make(chan struct{})
   721  	s := NewStore(MetaNamespaceKeyFunc)
   722  	listCallRVs := []string{}
   723  
   724  	lw := &testLW{
   725  		WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   726  			// Stop once the reflector begins watching since we're only interested in the list.
   727  			close(stopCh)
   728  			fw := watch.NewFake()
   729  			return fw, nil
   730  		},
   731  		ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   732  			listCallRVs = append(listCallRVs, options.ResourceVersion)
   733  			pods := make([]v1.Pod, 8)
   734  			for i := 0; i < 8; i++ {
   735  				pods[i] = v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("pod-%d", i), ResourceVersion: fmt.Sprintf("%d", i)}}
   736  			}
   737  			switch options.ResourceVersion {
   738  			case "0":
   739  				return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}, Items: pods[0:4]}, nil
   740  			case "10":
   741  				// When watch cache is disabled, if the exact ResourceVersion requested is not available, a "Expired" error is returned.
   742  				return nil, apierrors.NewResourceExpired("The resourceVersion for the provided watch is too old.")
   743  			case "":
   744  				return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "11"}, Items: pods[0:8]}, nil
   745  			default:
   746  				t.Fatalf("Unrecognized ResourceVersion: %s", options.ResourceVersion)
   747  			}
   748  			return nil, nil
   749  		},
   750  	}
   751  	r := NewReflector(lw, &v1.Pod{}, s, 0)
   752  
   753  	// Initial list should use RV=0
   754  	r.ListAndWatch(stopCh)
   755  
   756  	results := s.List()
   757  	if len(results) != 4 {
   758  		t.Errorf("Expected 4 results, got %d", len(results))
   759  	}
   760  
   761  	// relist should use lastSyncResourceVersions (RV=10) and since RV=10 is expired, it should retry with RV="".
   762  	stopCh = make(chan struct{})
   763  	r.ListAndWatch(stopCh)
   764  
   765  	results = s.List()
   766  	if len(results) != 8 {
   767  		t.Errorf("Expected 8 results, got %d", len(results))
   768  	}
   769  
   770  	expectedRVs := []string{"0", "10", ""}
   771  	if !reflect.DeepEqual(listCallRVs, expectedRVs) {
   772  		t.Errorf("Expected series of list calls with resource versiosn of %v but got: %v", expectedRVs, listCallRVs)
   773  	}
   774  }
   775  
   776  func TestReflectorFullListIfExpired(t *testing.T) {
   777  	stopCh := make(chan struct{})
   778  	s := NewStore(MetaNamespaceKeyFunc)
   779  	listCallRVs := []string{}
   780  
   781  	lw := &testLW{
   782  		WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   783  			// Stop once the reflector begins watching since we're only interested in the list.
   784  			close(stopCh)
   785  			fw := watch.NewFake()
   786  			return fw, nil
   787  		},
   788  		ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   789  			listCallRVs = append(listCallRVs, options.ResourceVersion)
   790  			pods := make([]v1.Pod, 8)
   791  			for i := 0; i < 8; i++ {
   792  				pods[i] = v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("pod-%d", i), ResourceVersion: fmt.Sprintf("%d", i)}}
   793  			}
   794  			rvContinueLimit := func(rv, c string, l int64) metav1.ListOptions {
   795  				return metav1.ListOptions{ResourceVersion: rv, Continue: c, Limit: l}
   796  			}
   797  			switch rvContinueLimit(options.ResourceVersion, options.Continue, options.Limit) {
   798  			// initial limited list
   799  			case rvContinueLimit("0", "", 4):
   800  				return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}, Items: pods[0:4]}, nil
   801  			// first page of the rv=10 list
   802  			case rvContinueLimit("10", "", 4):
   803  				return &v1.PodList{ListMeta: metav1.ListMeta{Continue: "C1", ResourceVersion: "11"}, Items: pods[0:4]}, nil
   804  			// second page of the above list
   805  			case rvContinueLimit("", "C1", 4):
   806  				return nil, apierrors.NewResourceExpired("The resourceVersion for the provided watch is too old.")
   807  			// rv=10 unlimited list
   808  			case rvContinueLimit("10", "", 0):
   809  				return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "11"}, Items: pods[0:8]}, nil
   810  			default:
   811  				err := fmt.Errorf("unexpected list options: %#v", options)
   812  				t.Error(err)
   813  				return nil, err
   814  			}
   815  			return nil, nil
   816  		},
   817  	}
   818  	r := NewReflector(lw, &v1.Pod{}, s, 0)
   819  	r.WatchListPageSize = 4
   820  
   821  	// Initial list should use RV=0
   822  	if err := r.ListAndWatch(stopCh); err != nil {
   823  		t.Fatal(err)
   824  	}
   825  
   826  	results := s.List()
   827  	if len(results) != 4 {
   828  		t.Errorf("Expected 4 results, got %d", len(results))
   829  	}
   830  
   831  	// relist should use lastSyncResourceVersions (RV=10) and since second page of that expired, it should full list with RV=10
   832  	stopCh = make(chan struct{})
   833  	if err := r.ListAndWatch(stopCh); err != nil {
   834  		t.Fatal(err)
   835  	}
   836  
   837  	results = s.List()
   838  	if len(results) != 8 {
   839  		t.Errorf("Expected 8 results, got %d", len(results))
   840  	}
   841  
   842  	expectedRVs := []string{"0", "10", "", "10"}
   843  	if !reflect.DeepEqual(listCallRVs, expectedRVs) {
   844  		t.Errorf("Expected series of list calls with resource versiosn of %#v but got: %#v", expectedRVs, listCallRVs)
   845  	}
   846  }
   847  
   848  func TestReflectorFullListIfTooLarge(t *testing.T) {
   849  	stopCh := make(chan struct{})
   850  	s := NewStore(MetaNamespaceKeyFunc)
   851  	listCallRVs := []string{}
   852  
   853  	lw := &testLW{
   854  		WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   855  			// Stop once the reflector begins watching since we're only interested in the list.
   856  			close(stopCh)
   857  			fw := watch.NewFake()
   858  			return fw, nil
   859  		},
   860  		ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   861  			listCallRVs = append(listCallRVs, options.ResourceVersion)
   862  
   863  			switch options.ResourceVersion {
   864  			// initial list
   865  			case "0":
   866  				return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "20"}}, nil
   867  			// relist after the initial list
   868  			case "20":
   869  				err := apierrors.NewTimeoutError("too large resource version", 1)
   870  				err.ErrStatus.Details.Causes = []metav1.StatusCause{{Type: metav1.CauseTypeResourceVersionTooLarge}}
   871  				return nil, err
   872  			// relist after the initial list (covers the error format used in api server 1.17.0-1.18.5)
   873  			case "30":
   874  				err := apierrors.NewTimeoutError("too large resource version", 1)
   875  				err.ErrStatus.Details.Causes = []metav1.StatusCause{{Message: "Too large resource version"}}
   876  				return nil, err
   877  			// relist from etcd after "too large" error
   878  			case "":
   879  				return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "30"}}, nil
   880  			default:
   881  				return nil, fmt.Errorf("unexpected List call: %s", options.ResourceVersion)
   882  			}
   883  		},
   884  	}
   885  	r := NewReflector(lw, &v1.Pod{}, s, 0)
   886  
   887  	// Initial list should use RV=0
   888  	if err := r.ListAndWatch(stopCh); err != nil {
   889  		t.Fatal(err)
   890  	}
   891  
   892  	// Relist from the future version.
   893  	// This may happen, as watchcache is initialized from "current global etcd resource version"
   894  	// when kube-apiserver is starting and if no objects are changing after that each kube-apiserver
   895  	// may be synced to a different version and they will never converge.
   896  	// TODO: We should use etcd progress-notify feature to avoid this behavior but until this is
   897  	// done we simply try to relist from now to avoid continuous errors on relists.
   898  	for i := 1; i <= 2; i++ {
   899  		// relist twice to cover the two variants of TooLargeResourceVersion api errors
   900  		stopCh = make(chan struct{})
   901  		if err := r.ListAndWatch(stopCh); err != nil {
   902  			t.Fatal(err)
   903  		}
   904  	}
   905  
   906  	expectedRVs := []string{"0", "20", "", "30", ""}
   907  	if !reflect.DeepEqual(listCallRVs, expectedRVs) {
   908  		t.Errorf("Expected series of list calls with resource version of %#v but got: %#v", expectedRVs, listCallRVs)
   909  	}
   910  }
   911  
   912  func TestReflectorSetExpectedType(t *testing.T) {
   913  	obj := &unstructured.Unstructured{}
   914  	gvk := schema.GroupVersionKind{
   915  		Group:   "mygroup",
   916  		Version: "v1",
   917  		Kind:    "MyKind",
   918  	}
   919  	obj.SetGroupVersionKind(gvk)
   920  	testCases := map[string]struct {
   921  		inputType        interface{}
   922  		expectedTypeName string
   923  		expectedType     reflect.Type
   924  		expectedGVK      *schema.GroupVersionKind
   925  	}{
   926  		"Nil type": {
   927  			expectedTypeName: defaultExpectedTypeName,
   928  		},
   929  		"Normal type": {
   930  			inputType:        &v1.Pod{},
   931  			expectedTypeName: "*v1.Pod",
   932  			expectedType:     reflect.TypeOf(&v1.Pod{}),
   933  		},
   934  		"Unstructured type without GVK": {
   935  			inputType:        &unstructured.Unstructured{},
   936  			expectedTypeName: "*unstructured.Unstructured",
   937  			expectedType:     reflect.TypeOf(&unstructured.Unstructured{}),
   938  		},
   939  		"Unstructured type with GVK": {
   940  			inputType:        obj,
   941  			expectedTypeName: gvk.String(),
   942  			expectedType:     reflect.TypeOf(&unstructured.Unstructured{}),
   943  			expectedGVK:      &gvk,
   944  		},
   945  	}
   946  	for testName, tc := range testCases {
   947  		t.Run(testName, func(t *testing.T) {
   948  			r := &Reflector{}
   949  			r.setExpectedType(tc.inputType)
   950  			if tc.expectedType != r.expectedType {
   951  				t.Fatalf("Expected expectedType %v, got %v", tc.expectedType, r.expectedType)
   952  			}
   953  			if tc.expectedTypeName != r.expectedTypeName {
   954  				t.Fatalf("Expected expectedTypeName %v, got %v", tc.expectedTypeName, r.expectedTypeName)
   955  			}
   956  			gvkNotEqual := (tc.expectedGVK == nil) != (r.expectedGVK == nil)
   957  			if tc.expectedGVK != nil && r.expectedGVK != nil {
   958  				gvkNotEqual = *tc.expectedGVK != *r.expectedGVK
   959  			}
   960  			if gvkNotEqual {
   961  				t.Fatalf("Expected expectedGVK %v, got %v", tc.expectedGVK, r.expectedGVK)
   962  			}
   963  		})
   964  	}
   965  }
   966  
   967  type storeWithRV struct {
   968  	Store
   969  
   970  	// resourceVersions tracks values passed by UpdateResourceVersion
   971  	resourceVersions []string
   972  }
   973  
   974  func (s *storeWithRV) UpdateResourceVersion(resourceVersion string) {
   975  	s.resourceVersions = append(s.resourceVersions, resourceVersion)
   976  }
   977  
   978  func newStoreWithRV() *storeWithRV {
   979  	return &storeWithRV{
   980  		Store: NewStore(MetaNamespaceKeyFunc),
   981  	}
   982  }
   983  
   984  func TestReflectorResourceVersionUpdate(t *testing.T) {
   985  	s := newStoreWithRV()
   986  
   987  	stopCh := make(chan struct{})
   988  	fw := watch.NewFake()
   989  
   990  	lw := &testLW{
   991  		WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   992  			return fw, nil
   993  		},
   994  		ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   995  			return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}}, nil
   996  		},
   997  	}
   998  	r := NewReflector(lw, &v1.Pod{}, s, 0)
   999  
  1000  	makePod := func(rv string) *v1.Pod {
  1001  		return &v1.Pod{ObjectMeta: metav1.ObjectMeta{ResourceVersion: rv}}
  1002  	}
  1003  
  1004  	go func() {
  1005  		fw.Action(watch.Added, makePod("10"))
  1006  		fw.Action(watch.Modified, makePod("20"))
  1007  		fw.Action(watch.Bookmark, makePod("30"))
  1008  		fw.Action(watch.Deleted, makePod("40"))
  1009  		close(stopCh)
  1010  	}()
  1011  
  1012  	// Initial list should use RV=0
  1013  	if err := r.ListAndWatch(stopCh); err != nil {
  1014  		t.Fatal(err)
  1015  	}
  1016  
  1017  	expectedRVs := []string{"10", "20", "30", "40"}
  1018  	if !reflect.DeepEqual(s.resourceVersions, expectedRVs) {
  1019  		t.Errorf("Expected series of resource version updates of %#v but got: %#v", expectedRVs, s.resourceVersions)
  1020  	}
  1021  }