k8s.io/client-go@v0.31.1/tools/watch/informerwatcher_test.go (about)

     1  /*
     2  Copyright 2017 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 watch
    18  
    19  import (
    20  	"context"
    21  	"reflect"
    22  	goruntime "runtime"
    23  	"sort"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/google/go-cmp/cmp"
    28  
    29  	corev1 "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/runtime"
    33  	"k8s.io/apimachinery/pkg/runtime/schema"
    34  	"k8s.io/apimachinery/pkg/util/dump"
    35  	"k8s.io/apimachinery/pkg/util/wait"
    36  	"k8s.io/apimachinery/pkg/watch"
    37  	clientfeatures "k8s.io/client-go/features"
    38  	clientfeaturestesting "k8s.io/client-go/features/testing"
    39  	fakeclientset "k8s.io/client-go/kubernetes/fake"
    40  	testcore "k8s.io/client-go/testing"
    41  	"k8s.io/client-go/tools/cache"
    42  )
    43  
    44  // TestEventProcessorExit is expected to timeout if the event processor fails
    45  // to exit when stopped.
    46  func TestEventProcessorExit(t *testing.T) {
    47  	event := watch.Event{}
    48  
    49  	tests := []struct {
    50  		name  string
    51  		write func(e *eventProcessor)
    52  	}{
    53  		{
    54  			name: "exit on blocked read",
    55  			write: func(e *eventProcessor) {
    56  				e.push(event)
    57  			},
    58  		},
    59  		{
    60  			name: "exit on blocked write",
    61  			write: func(e *eventProcessor) {
    62  				e.push(event)
    63  				e.push(event)
    64  			},
    65  		},
    66  	}
    67  	for _, test := range tests {
    68  		t.Run(test.name, func(t *testing.T) {
    69  			out := make(chan watch.Event)
    70  			e := newEventProcessor(out)
    71  
    72  			test.write(e)
    73  
    74  			exited := make(chan struct{})
    75  			go func() {
    76  				e.run()
    77  				close(exited)
    78  			}()
    79  
    80  			<-out
    81  			e.stop()
    82  			goruntime.Gosched()
    83  			<-exited
    84  		})
    85  	}
    86  }
    87  
    88  type apiInt int
    89  
    90  func (apiInt) GetObjectKind() schema.ObjectKind { return nil }
    91  func (apiInt) DeepCopyObject() runtime.Object   { return nil }
    92  
    93  func TestEventProcessorOrdersEvents(t *testing.T) {
    94  	out := make(chan watch.Event)
    95  	e := newEventProcessor(out)
    96  	go e.run()
    97  
    98  	numProcessed := 0
    99  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   100  	go func() {
   101  		for i := 0; i < 1000; i++ {
   102  			e := <-out
   103  			if got, want := int(e.Object.(apiInt)), i; got != want {
   104  				t.Errorf("unexpected event: got=%d, want=%d", got, want)
   105  			}
   106  			numProcessed++
   107  		}
   108  		cancel()
   109  	}()
   110  
   111  	for i := 0; i < 1000; i++ {
   112  		e.push(watch.Event{Object: apiInt(i)})
   113  	}
   114  
   115  	<-ctx.Done()
   116  	e.stop()
   117  
   118  	if numProcessed != 1000 {
   119  		t.Errorf("unexpected number of events processed: %d", numProcessed)
   120  	}
   121  
   122  }
   123  
   124  type byEventTypeAndName []watch.Event
   125  
   126  func (a byEventTypeAndName) Len() int      { return len(a) }
   127  func (a byEventTypeAndName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
   128  func (a byEventTypeAndName) Less(i, j int) bool {
   129  	if a[i].Type < a[j].Type {
   130  		return true
   131  	}
   132  
   133  	if a[i].Type > a[j].Type {
   134  		return false
   135  	}
   136  
   137  	return a[i].Object.(*corev1.Secret).Name < a[j].Object.(*corev1.Secret).Name
   138  }
   139  
   140  func newTestSecret(name, key, value string) *corev1.Secret {
   141  	return &corev1.Secret{
   142  		ObjectMeta: metav1.ObjectMeta{
   143  			Name: name,
   144  		},
   145  		StringData: map[string]string{
   146  			key: value,
   147  		},
   148  	}
   149  }
   150  
   151  func TestNewInformerWatcher(t *testing.T) {
   152  	// Make sure there are no 2 same types of events on a secret with the same name or that might be flaky.
   153  	tt := []struct {
   154  		name                    string
   155  		watchListFeatureEnabled bool
   156  		objects                 []runtime.Object
   157  		inputEvents             []watch.Event
   158  		outputEvents            []watch.Event
   159  	}{
   160  		{
   161  			name:                    "WatchListClient feature disabled",
   162  			watchListFeatureEnabled: false,
   163  			objects: []runtime.Object{
   164  				newTestSecret("pod-1", "foo-1", "initial"),
   165  				newTestSecret("pod-2", "foo-2", "initial"),
   166  				newTestSecret("pod-3", "foo-3", "initial"),
   167  			},
   168  			inputEvents: []watch.Event{
   169  				{
   170  					Type:   watch.Added,
   171  					Object: newTestSecret("pod-4", "foo-4", "initial"),
   172  				},
   173  				{
   174  					Type:   watch.Modified,
   175  					Object: newTestSecret("pod-2", "foo-2", "new"),
   176  				},
   177  				{
   178  					Type:   watch.Deleted,
   179  					Object: newTestSecret("pod-3", "foo-3", "initial"),
   180  				},
   181  			},
   182  			outputEvents: []watch.Event{
   183  				// When WatchListClient is disabled, ListAndWatch creates fake
   184  				// ADDED events for each object listed.
   185  				{
   186  					Type:   watch.Added,
   187  					Object: newTestSecret("pod-1", "foo-1", "initial"),
   188  				},
   189  				{
   190  					Type:   watch.Added,
   191  					Object: newTestSecret("pod-2", "foo-2", "initial"),
   192  				},
   193  				{
   194  					Type:   watch.Added,
   195  					Object: newTestSecret("pod-3", "foo-3", "initial"),
   196  				},
   197  				// Normal events follow.
   198  				{
   199  					Type:   watch.Added,
   200  					Object: newTestSecret("pod-4", "foo-4", "initial"),
   201  				},
   202  				{
   203  					Type:   watch.Modified,
   204  					Object: newTestSecret("pod-2", "foo-2", "new"),
   205  				},
   206  				{
   207  					Type:   watch.Deleted,
   208  					Object: newTestSecret("pod-3", "foo-3", "initial"),
   209  				},
   210  			},
   211  		},
   212  		{
   213  			name:                    "WatchListClient feature enabled",
   214  			watchListFeatureEnabled: true,
   215  			objects: []runtime.Object{
   216  				newTestSecret("pod-1", "foo-1", "initial"),
   217  				newTestSecret("pod-2", "foo-2", "initial"),
   218  				newTestSecret("pod-3", "foo-3", "initial"),
   219  			},
   220  			inputEvents: []watch.Event{
   221  				{
   222  					Type:   watch.Added,
   223  					Object: newTestSecret("pod-1", "foo-1", "initial"),
   224  				},
   225  				{
   226  					Type:   watch.Added,
   227  					Object: newTestSecret("pod-2", "foo-2", "initial"),
   228  				},
   229  				{
   230  					Type:   watch.Added,
   231  					Object: newTestSecret("pod-3", "foo-3", "initial"),
   232  				},
   233  				// ListWatch bookmark indicates that initial listing is done
   234  				{
   235  					Type: watch.Bookmark,
   236  					Object: &corev1.Secret{
   237  						ObjectMeta: metav1.ObjectMeta{
   238  							Annotations: map[string]string{
   239  								metav1.InitialEventsAnnotationKey: "true",
   240  							},
   241  						},
   242  					},
   243  				},
   244  				{
   245  					Type:   watch.Added,
   246  					Object: newTestSecret("pod-4", "foo-4", "initial"),
   247  				},
   248  				{
   249  					Type:   watch.Modified,
   250  					Object: newTestSecret("pod-2", "foo-2", "new"),
   251  				},
   252  				{
   253  					Type:   watch.Deleted,
   254  					Object: newTestSecret("pod-3", "foo-3", "initial"),
   255  				},
   256  			},
   257  			outputEvents: []watch.Event{
   258  				// When WatchListClient is enabled, WatchList receives
   259  				// ADDED events from the server for each existing object.
   260  				{
   261  					Type:   watch.Added,
   262  					Object: newTestSecret("pod-1", "foo-1", "initial"),
   263  				},
   264  				{
   265  					Type:   watch.Added,
   266  					Object: newTestSecret("pod-2", "foo-2", "initial"),
   267  				},
   268  				{
   269  					Type:   watch.Added,
   270  					Object: newTestSecret("pod-3", "foo-3", "initial"),
   271  				},
   272  				// Bookmark event at the end of listing is not sent to the client.
   273  				// Normal events follow.
   274  				{
   275  					Type:   watch.Added,
   276  					Object: newTestSecret("pod-4", "foo-4", "initial"),
   277  				},
   278  				{
   279  					Type:   watch.Modified,
   280  					Object: newTestSecret("pod-2", "foo-2", "new"),
   281  				},
   282  				{
   283  					Type:   watch.Deleted,
   284  					Object: newTestSecret("pod-3", "foo-3", "initial"),
   285  				},
   286  			},
   287  		},
   288  	}
   289  
   290  	for _, tc := range tt {
   291  		t.Run(tc.name, func(t *testing.T) {
   292  			clientfeaturestesting.SetFeatureDuringTest(t, clientfeatures.WatchListClient, tc.watchListFeatureEnabled)
   293  
   294  			fake := fakeclientset.NewSimpleClientset(tc.objects...)
   295  			inputCh := make(chan watch.Event)
   296  			inputWatcher := watch.NewProxyWatcher(inputCh)
   297  			// Indexer should stop the input watcher when the output watcher is stopped.
   298  			// But stop it at the end of the test, just in case.
   299  			defer inputWatcher.Stop()
   300  			inputStopCh := inputWatcher.StopChan()
   301  			fake.PrependWatchReactor("secrets", testcore.DefaultWatchReactor(inputWatcher, nil))
   302  			// Send events and then close the done channel
   303  			inputDoneCh := make(chan struct{})
   304  			go func() {
   305  				defer close(inputDoneCh)
   306  				for _, e := range tc.inputEvents {
   307  					select {
   308  					case inputCh <- e:
   309  					case <-inputStopCh:
   310  						return
   311  					}
   312  				}
   313  			}()
   314  
   315  			lw := &cache.ListWatch{
   316  				ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   317  					return fake.CoreV1().Secrets("").List(context.TODO(), options)
   318  				},
   319  				WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   320  					return fake.CoreV1().Secrets("").Watch(context.TODO(), options)
   321  				},
   322  			}
   323  			_, _, outputWatcher, informerDoneCh := NewIndexerInformerWatcher(lw, &corev1.Secret{})
   324  			outputCh := outputWatcher.ResultChan()
   325  			timeoutCh := time.After(wait.ForeverTestTimeout)
   326  			var result []watch.Event
   327  		loop:
   328  			for {
   329  				select {
   330  				case event, ok := <-outputCh:
   331  					if !ok {
   332  						t.Errorf("Output result channel closed prematurely")
   333  						break loop
   334  					}
   335  					result = append(result, *event.DeepCopy())
   336  					if len(result) >= len(tc.outputEvents) {
   337  						break loop
   338  					}
   339  				case <-timeoutCh:
   340  					t.Error("Timed out waiting for events")
   341  					break loop
   342  				}
   343  			}
   344  
   345  			// Informers don't guarantee event order so we need to sort these arrays to compare them.
   346  			sort.Sort(byEventTypeAndName(tc.outputEvents))
   347  			sort.Sort(byEventTypeAndName(result))
   348  
   349  			if !reflect.DeepEqual(tc.outputEvents, result) {
   350  				t.Errorf("\nexpected: %s,\ngot:      %s,\ndiff: %s", dump.Pretty(tc.outputEvents), dump.Pretty(result), cmp.Diff(tc.outputEvents, result))
   351  				return
   352  			}
   353  
   354  			// Send some more events, but don't read them.
   355  			// Stop producing events when the consumer stops the watcher.
   356  			go func() {
   357  				defer close(inputCh)
   358  				for _, e := range tc.inputEvents {
   359  					select {
   360  					case inputCh <- e:
   361  					case <-inputStopCh:
   362  						return
   363  					}
   364  				}
   365  			}()
   366  
   367  			// Stop before reading all the data to make sure the informer can deal with closed channel
   368  			outputWatcher.Stop()
   369  
   370  			select {
   371  			case <-informerDoneCh:
   372  			case <-time.After(wait.ForeverTestTimeout):
   373  				t.Error("Timed out waiting for informer to cleanup")
   374  			}
   375  		})
   376  	}
   377  
   378  }
   379  
   380  // TestInformerWatcherDeletedFinalStateUnknown tests the code path when `DeleteFunc`
   381  // in `NewIndexerInformerWatcher` receives a `cache.DeletedFinalStateUnknown`
   382  // object from the underlying `DeltaFIFO`. The triggering condition is described
   383  // at https://github.com/kubernetes/kubernetes/blob/dc39ab2417bfddcec37be4011131c59921fdbe98/staging/src/k8s.io/client-go/tools/cache/delta_fifo.go#L736-L739.
   384  //
   385  // Code from @liggitt
   386  func TestInformerWatcherDeletedFinalStateUnknown(t *testing.T) {
   387  	listCalls := 0
   388  	watchCalls := 0
   389  	lw := &cache.ListWatch{
   390  		ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   391  			retval := &corev1.SecretList{}
   392  			if listCalls == 0 {
   393  				// Return a list with items in it
   394  				retval.ResourceVersion = "1"
   395  				retval.Items = []corev1.Secret{{ObjectMeta: metav1.ObjectMeta{Name: "secret1", Namespace: "ns1", ResourceVersion: "123"}}}
   396  			} else {
   397  				// Return empty lists after the first call
   398  				retval.ResourceVersion = "2"
   399  			}
   400  			listCalls++
   401  			return retval, nil
   402  		},
   403  		WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   404  			w := watch.NewRaceFreeFake()
   405  			if options.ResourceVersion == "1" {
   406  				go func() {
   407  					// Close with a "Gone" error when trying to start a watch from the first list
   408  					w.Error(&apierrors.NewGone("gone").ErrStatus)
   409  					w.Stop()
   410  				}()
   411  			}
   412  			watchCalls++
   413  			return w, nil
   414  		},
   415  	}
   416  	_, _, w, done := NewIndexerInformerWatcher(lw, &corev1.Secret{})
   417  	defer w.Stop()
   418  
   419  	// Expect secret add
   420  	select {
   421  	case event, ok := <-w.ResultChan():
   422  		if !ok {
   423  			t.Fatal("unexpected close")
   424  		}
   425  		if event.Type != watch.Added {
   426  			t.Fatalf("expected Added event, got %#v", event)
   427  		}
   428  		if event.Object.(*corev1.Secret).ResourceVersion != "123" {
   429  			t.Fatalf("expected added Secret with rv=123, got %#v", event.Object)
   430  		}
   431  	case <-time.After(time.Second * 10):
   432  		t.Fatal("timeout")
   433  	}
   434  
   435  	// Expect secret delete because the relist was missing the secret
   436  	select {
   437  	case event, ok := <-w.ResultChan():
   438  		if !ok {
   439  			t.Fatal("unexpected close")
   440  		}
   441  		if event.Type != watch.Deleted {
   442  			t.Fatalf("expected Deleted event, got %#v", event)
   443  		}
   444  		if event.Object.(*corev1.Secret).ResourceVersion != "123" {
   445  			t.Fatalf("expected deleted Secret with rv=123, got %#v", event.Object)
   446  		}
   447  	case <-time.After(time.Second * 10):
   448  		t.Fatal("timeout")
   449  	}
   450  
   451  	w.Stop()
   452  	select {
   453  	case <-done:
   454  	case <-time.After(time.Second * 10):
   455  		t.Fatal("timeout")
   456  	}
   457  
   458  	if listCalls < 2 {
   459  		t.Fatalf("expected at least 2 list calls, got %d", listCalls)
   460  	}
   461  	if watchCalls < 1 {
   462  		t.Fatalf("expected at least 1 watch call, got %d", watchCalls)
   463  	}
   464  }