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

     1  /*
     2  Copyright 2015 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  	"fmt"
    21  	"math/rand"
    22  	"sync"
    23  	"testing"
    24  	"time"
    25  
    26  	"k8s.io/api/core/v1"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/apimachinery/pkg/util/sets"
    30  	"k8s.io/apimachinery/pkg/util/wait"
    31  	"k8s.io/apimachinery/pkg/watch"
    32  	fcache "k8s.io/client-go/tools/cache/testing"
    33  
    34  	"github.com/google/gofuzz"
    35  )
    36  
    37  func Example() {
    38  	// source simulates an apiserver object endpoint.
    39  	source := fcache.NewFakeControllerSource()
    40  
    41  	// This will hold the downstream state, as we know it.
    42  	downstream := NewStore(DeletionHandlingMetaNamespaceKeyFunc)
    43  
    44  	// This will hold incoming changes. Note how we pass downstream in as a
    45  	// KeyLister, that way resync operations will result in the correct set
    46  	// of update/delete deltas.
    47  	fifo := NewDeltaFIFOWithOptions(DeltaFIFOOptions{
    48  		KeyFunction:  MetaNamespaceKeyFunc,
    49  		KnownObjects: downstream,
    50  	})
    51  
    52  	// Let's do threadsafe output to get predictable test results.
    53  	deletionCounter := make(chan string, 1000)
    54  
    55  	cfg := &Config{
    56  		Queue:            fifo,
    57  		ListerWatcher:    source,
    58  		ObjectType:       &v1.Pod{},
    59  		FullResyncPeriod: time.Millisecond * 100,
    60  		RetryOnError:     false,
    61  
    62  		// Let's implement a simple controller that just deletes
    63  		// everything that comes in.
    64  		Process: func(obj interface{}) error {
    65  			// Obj is from the Pop method of the Queue we make above.
    66  			newest := obj.(Deltas).Newest()
    67  
    68  			if newest.Type != Deleted {
    69  				// Update our downstream store.
    70  				err := downstream.Add(newest.Object)
    71  				if err != nil {
    72  					return err
    73  				}
    74  
    75  				// Delete this object.
    76  				source.Delete(newest.Object.(runtime.Object))
    77  			} else {
    78  				// Update our downstream store.
    79  				err := downstream.Delete(newest.Object)
    80  				if err != nil {
    81  					return err
    82  				}
    83  
    84  				// fifo's KeyOf is easiest, because it handles
    85  				// DeletedFinalStateUnknown markers.
    86  				key, err := fifo.KeyOf(newest.Object)
    87  				if err != nil {
    88  					return err
    89  				}
    90  
    91  				// Report this deletion.
    92  				deletionCounter <- key
    93  			}
    94  			return nil
    95  		},
    96  	}
    97  
    98  	// Create the controller and run it until we close stop.
    99  	stop := make(chan struct{})
   100  	defer close(stop)
   101  	go New(cfg).Run(stop)
   102  
   103  	// Let's add a few objects to the source.
   104  	testIDs := []string{"a-hello", "b-controller", "c-framework"}
   105  	for _, name := range testIDs {
   106  		// Note that these pods are not valid-- the fake source doesn't
   107  		// call validation or anything.
   108  		source.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: name}})
   109  	}
   110  
   111  	// Let's wait for the controller to process the things we just added.
   112  	outputSet := sets.String{}
   113  	for i := 0; i < len(testIDs); i++ {
   114  		outputSet.Insert(<-deletionCounter)
   115  	}
   116  
   117  	for _, key := range outputSet.List() {
   118  		fmt.Println(key)
   119  	}
   120  	// Output:
   121  	// a-hello
   122  	// b-controller
   123  	// c-framework
   124  }
   125  
   126  func ExampleNewInformer() {
   127  	// source simulates an apiserver object endpoint.
   128  	source := fcache.NewFakeControllerSource()
   129  
   130  	// Let's do threadsafe output to get predictable test results.
   131  	deletionCounter := make(chan string, 1000)
   132  
   133  	// Make a controller that immediately deletes anything added to it, and
   134  	// logs anything deleted.
   135  	_, controller := NewInformer(
   136  		source,
   137  		&v1.Pod{},
   138  		time.Millisecond*100,
   139  		ResourceEventHandlerFuncs{
   140  			AddFunc: func(obj interface{}) {
   141  				source.Delete(obj.(runtime.Object))
   142  			},
   143  			DeleteFunc: func(obj interface{}) {
   144  				key, err := DeletionHandlingMetaNamespaceKeyFunc(obj)
   145  				if err != nil {
   146  					key = "oops something went wrong with the key"
   147  				}
   148  
   149  				// Report this deletion.
   150  				deletionCounter <- key
   151  			},
   152  		},
   153  	)
   154  
   155  	// Run the controller and run it until we close stop.
   156  	stop := make(chan struct{})
   157  	defer close(stop)
   158  	go controller.Run(stop)
   159  
   160  	// Let's add a few objects to the source.
   161  	testIDs := []string{"a-hello", "b-controller", "c-framework"}
   162  	for _, name := range testIDs {
   163  		// Note that these pods are not valid-- the fake source doesn't
   164  		// call validation or anything.
   165  		source.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: name}})
   166  	}
   167  
   168  	// Let's wait for the controller to process the things we just added.
   169  	outputSet := sets.String{}
   170  	for i := 0; i < len(testIDs); i++ {
   171  		outputSet.Insert(<-deletionCounter)
   172  	}
   173  
   174  	for _, key := range outputSet.List() {
   175  		fmt.Println(key)
   176  	}
   177  	// Output:
   178  	// a-hello
   179  	// b-controller
   180  	// c-framework
   181  }
   182  
   183  func TestHammerController(t *testing.T) {
   184  	// This test executes a bunch of requests through the fake source and
   185  	// controller framework to make sure there's no locking/threading
   186  	// errors. If an error happens, it should hang forever or trigger the
   187  	// race detector.
   188  
   189  	// source simulates an apiserver object endpoint.
   190  	source := fcache.NewFakeControllerSource()
   191  
   192  	// Let's do threadsafe output to get predictable test results.
   193  	outputSetLock := sync.Mutex{}
   194  	// map of key to operations done on the key
   195  	outputSet := map[string][]string{}
   196  
   197  	recordFunc := func(eventType string, obj interface{}) {
   198  		key, err := DeletionHandlingMetaNamespaceKeyFunc(obj)
   199  		if err != nil {
   200  			t.Errorf("something wrong with key: %v", err)
   201  			key = "oops something went wrong with the key"
   202  		}
   203  
   204  		// Record some output when items are deleted.
   205  		outputSetLock.Lock()
   206  		defer outputSetLock.Unlock()
   207  		outputSet[key] = append(outputSet[key], eventType)
   208  	}
   209  
   210  	// Make a controller which just logs all the changes it gets.
   211  	_, controller := NewInformer(
   212  		source,
   213  		&v1.Pod{},
   214  		time.Millisecond*100,
   215  		ResourceEventHandlerFuncs{
   216  			AddFunc:    func(obj interface{}) { recordFunc("add", obj) },
   217  			UpdateFunc: func(oldObj, newObj interface{}) { recordFunc("update", newObj) },
   218  			DeleteFunc: func(obj interface{}) { recordFunc("delete", obj) },
   219  		},
   220  	)
   221  
   222  	if controller.HasSynced() {
   223  		t.Errorf("Expected HasSynced() to return false before we started the controller")
   224  	}
   225  
   226  	// Run the controller and run it until we close stop.
   227  	stop := make(chan struct{})
   228  	go controller.Run(stop)
   229  
   230  	// Let's wait for the controller to do its initial sync
   231  	wait.Poll(100*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) {
   232  		return controller.HasSynced(), nil
   233  	})
   234  	if !controller.HasSynced() {
   235  		t.Errorf("Expected HasSynced() to return true after the initial sync")
   236  	}
   237  
   238  	wg := sync.WaitGroup{}
   239  	const threads = 3
   240  	wg.Add(threads)
   241  	for i := 0; i < threads; i++ {
   242  		go func() {
   243  			defer wg.Done()
   244  			// Let's add a few objects to the source.
   245  			currentNames := sets.String{}
   246  			rs := rand.NewSource(rand.Int63())
   247  			f := fuzz.New().NilChance(.5).NumElements(0, 2).RandSource(rs)
   248  			for i := 0; i < 100; i++ {
   249  				var name string
   250  				var isNew bool
   251  				if currentNames.Len() == 0 || rand.Intn(3) == 1 {
   252  					f.Fuzz(&name)
   253  					isNew = true
   254  				} else {
   255  					l := currentNames.List()
   256  					name = l[rand.Intn(len(l))]
   257  				}
   258  
   259  				pod := &v1.Pod{}
   260  				f.Fuzz(pod)
   261  				pod.ObjectMeta.Name = name
   262  				pod.ObjectMeta.Namespace = "default"
   263  				// Add, update, or delete randomly.
   264  				// Note that these pods are not valid-- the fake source doesn't
   265  				// call validation or perform any other checking.
   266  				if isNew {
   267  					currentNames.Insert(name)
   268  					source.Add(pod)
   269  					continue
   270  				}
   271  				switch rand.Intn(2) {
   272  				case 0:
   273  					currentNames.Insert(name)
   274  					source.Modify(pod)
   275  				case 1:
   276  					currentNames.Delete(name)
   277  					source.Delete(pod)
   278  				}
   279  			}
   280  		}()
   281  	}
   282  	wg.Wait()
   283  
   284  	// Let's wait for the controller to finish processing the things we just added.
   285  	// TODO: look in the queue to see how many items need to be processed.
   286  	time.Sleep(100 * time.Millisecond)
   287  	close(stop)
   288  
   289  	// TODO: Verify that no goroutines were leaked here and that everything shut
   290  	// down cleanly.
   291  
   292  	outputSetLock.Lock()
   293  	t.Logf("got: %#v", outputSet)
   294  }
   295  
   296  func TestUpdate(t *testing.T) {
   297  	// This test is going to exercise the various paths that result in a
   298  	// call to update.
   299  
   300  	// source simulates an apiserver object endpoint.
   301  	source := fcache.NewFakeControllerSource()
   302  
   303  	const (
   304  		FROM = "from"
   305  		TO   = "to"
   306  	)
   307  
   308  	// These are the transitions we expect to see; because this is
   309  	// asynchronous, there are a lot of valid possibilities.
   310  	type pair struct{ from, to string }
   311  	allowedTransitions := map[pair]bool{
   312  		{FROM, TO}: true,
   313  
   314  		// Because a resync can happen when we've already observed one
   315  		// of the above but before the item is deleted.
   316  		{TO, TO}: true,
   317  		// Because a resync could happen before we observe an update.
   318  		{FROM, FROM}: true,
   319  	}
   320  
   321  	pod := func(name, check string, final bool) *v1.Pod {
   322  		p := &v1.Pod{
   323  			ObjectMeta: metav1.ObjectMeta{
   324  				Name:   name,
   325  				Labels: map[string]string{"check": check},
   326  			},
   327  		}
   328  		if final {
   329  			p.Labels["final"] = "true"
   330  		}
   331  		return p
   332  	}
   333  	deletePod := func(p *v1.Pod) bool {
   334  		return p.Labels["final"] == "true"
   335  	}
   336  
   337  	tests := []func(string){
   338  		func(name string) {
   339  			name = "a-" + name
   340  			source.Add(pod(name, FROM, false))
   341  			source.Modify(pod(name, TO, true))
   342  		},
   343  	}
   344  
   345  	const threads = 3
   346  
   347  	var testDoneWG sync.WaitGroup
   348  	testDoneWG.Add(threads * len(tests))
   349  
   350  	// Make a controller that deletes things once it observes an update.
   351  	// It calls Done() on the wait group on deletions so we can tell when
   352  	// everything we've added has been deleted.
   353  	watchCh := make(chan struct{})
   354  	_, controller := NewInformer(
   355  		&testLW{
   356  			WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   357  				watch, err := source.Watch(options)
   358  				close(watchCh)
   359  				return watch, err
   360  			},
   361  			ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   362  				return source.List(options)
   363  			},
   364  		},
   365  		&v1.Pod{},
   366  		0,
   367  		ResourceEventHandlerFuncs{
   368  			UpdateFunc: func(oldObj, newObj interface{}) {
   369  				o, n := oldObj.(*v1.Pod), newObj.(*v1.Pod)
   370  				from, to := o.Labels["check"], n.Labels["check"]
   371  				if !allowedTransitions[pair{from, to}] {
   372  					t.Errorf("observed transition %q -> %q for %v", from, to, n.Name)
   373  				}
   374  				if deletePod(n) {
   375  					source.Delete(n)
   376  				}
   377  			},
   378  			DeleteFunc: func(obj interface{}) {
   379  				testDoneWG.Done()
   380  			},
   381  		},
   382  	)
   383  
   384  	// Run the controller and run it until we close stop.
   385  	// Once Run() is called, calls to testDoneWG.Done() might start, so
   386  	// all testDoneWG.Add() calls must happen before this point
   387  	stop := make(chan struct{})
   388  	go controller.Run(stop)
   389  	<-watchCh
   390  
   391  	// run every test a few times, in parallel
   392  	var wg sync.WaitGroup
   393  	wg.Add(threads * len(tests))
   394  	for i := 0; i < threads; i++ {
   395  		for j, f := range tests {
   396  			go func(name string, f func(string)) {
   397  				defer wg.Done()
   398  				f(name)
   399  			}(fmt.Sprintf("%v-%v", i, j), f)
   400  		}
   401  	}
   402  	wg.Wait()
   403  
   404  	// Let's wait for the controller to process the things we just added.
   405  	testDoneWG.Wait()
   406  	close(stop)
   407  }
   408  
   409  func TestPanicPropagated(t *testing.T) {
   410  	// source simulates an apiserver object endpoint.
   411  	source := fcache.NewFakeControllerSource()
   412  
   413  	// Make a controller that just panic if the AddFunc is called.
   414  	_, controller := NewInformer(
   415  		source,
   416  		&v1.Pod{},
   417  		time.Millisecond*100,
   418  		ResourceEventHandlerFuncs{
   419  			AddFunc: func(obj interface{}) {
   420  				// Create a panic.
   421  				panic("Just panic.")
   422  			},
   423  		},
   424  	)
   425  
   426  	// Run the controller and run it until we close stop.
   427  	stop := make(chan struct{})
   428  	defer close(stop)
   429  
   430  	propagated := make(chan interface{})
   431  	go func() {
   432  		defer func() {
   433  			if r := recover(); r != nil {
   434  				propagated <- r
   435  			}
   436  		}()
   437  		controller.Run(stop)
   438  	}()
   439  	// Let's add a object to the source. It will trigger a panic.
   440  	source.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "test"}})
   441  
   442  	// Check if the panic propagated up.
   443  	select {
   444  	case p := <-propagated:
   445  		if p == "Just panic." {
   446  			t.Logf("Test Passed")
   447  		} else {
   448  			t.Errorf("unrecognized panic in controller run: %v", p)
   449  		}
   450  	case <-time.After(wait.ForeverTestTimeout):
   451  		t.Errorf("timeout: the panic failed to propagate from the controller run method!")
   452  	}
   453  }