k8s.io/client-go@v0.22.2/tools/cache/reflector.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  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"io"
    24  	"math/rand"
    25  	"reflect"
    26  	"sync"
    27  	"time"
    28  
    29  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    30  	"k8s.io/apimachinery/pkg/api/meta"
    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/naming"
    37  	utilnet "k8s.io/apimachinery/pkg/util/net"
    38  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    39  	"k8s.io/apimachinery/pkg/util/wait"
    40  	"k8s.io/apimachinery/pkg/watch"
    41  	"k8s.io/client-go/tools/pager"
    42  	"k8s.io/klog/v2"
    43  	"k8s.io/utils/trace"
    44  )
    45  
    46  const defaultExpectedTypeName = "<unspecified>"
    47  
    48  // Reflector watches a specified resource and causes all changes to be reflected in the given store.
    49  type Reflector struct {
    50  	// name identifies this reflector. By default it will be a file:line if possible.
    51  	name string
    52  
    53  	// The name of the type we expect to place in the store. The name
    54  	// will be the stringification of expectedGVK if provided, and the
    55  	// stringification of expectedType otherwise. It is for display
    56  	// only, and should not be used for parsing or comparison.
    57  	expectedTypeName string
    58  	// An example object of the type we expect to place in the store.
    59  	// Only the type needs to be right, except that when that is
    60  	// `unstructured.Unstructured` the object's `"apiVersion"` and
    61  	// `"kind"` must also be right.
    62  	expectedType reflect.Type
    63  	// The GVK of the object we expect to place in the store if unstructured.
    64  	expectedGVK *schema.GroupVersionKind
    65  	// The destination to sync up with the watch source
    66  	store Store
    67  	// listerWatcher is used to perform lists and watches.
    68  	listerWatcher ListerWatcher
    69  
    70  	// backoff manages backoff of ListWatch
    71  	backoffManager wait.BackoffManager
    72  	// initConnBackoffManager manages backoff the initial connection with the Watch calll of ListAndWatch.
    73  	initConnBackoffManager wait.BackoffManager
    74  
    75  	resyncPeriod time.Duration
    76  	// ShouldResync is invoked periodically and whenever it returns `true` the Store's Resync operation is invoked
    77  	ShouldResync func() bool
    78  	// clock allows tests to manipulate time
    79  	clock clock.Clock
    80  	// paginatedResult defines whether pagination should be forced for list calls.
    81  	// It is set based on the result of the initial list call.
    82  	paginatedResult bool
    83  	// lastSyncResourceVersion is the resource version token last
    84  	// observed when doing a sync with the underlying store
    85  	// it is thread safe, but not synchronized with the underlying store
    86  	lastSyncResourceVersion string
    87  	// isLastSyncResourceVersionUnavailable is true if the previous list or watch request with
    88  	// lastSyncResourceVersion failed with an "expired" or "too large resource version" error.
    89  	isLastSyncResourceVersionUnavailable bool
    90  	// lastSyncResourceVersionMutex guards read/write access to lastSyncResourceVersion
    91  	lastSyncResourceVersionMutex sync.RWMutex
    92  	// WatchListPageSize is the requested chunk size of initial and resync watch lists.
    93  	// If unset, for consistent reads (RV="") or reads that opt-into arbitrarily old data
    94  	// (RV="0") it will default to pager.PageSize, for the rest (RV != "" && RV != "0")
    95  	// it will turn off pagination to allow serving them from watch cache.
    96  	// NOTE: It should be used carefully as paginated lists are always served directly from
    97  	// etcd, which is significantly less efficient and may lead to serious performance and
    98  	// scalability problems.
    99  	WatchListPageSize int64
   100  	// Called whenever the ListAndWatch drops the connection with an error.
   101  	watchErrorHandler WatchErrorHandler
   102  }
   103  
   104  // ResourceVersionUpdater is an interface that allows store implementation to
   105  // track the current resource version of the reflector. This is especially
   106  // important if storage bookmarks are enabled.
   107  type ResourceVersionUpdater interface {
   108  	// UpdateResourceVersion is called each time current resource version of the reflector
   109  	// is updated.
   110  	UpdateResourceVersion(resourceVersion string)
   111  }
   112  
   113  // The WatchErrorHandler is called whenever ListAndWatch drops the
   114  // connection with an error. After calling this handler, the informer
   115  // will backoff and retry.
   116  //
   117  // The default implementation looks at the error type and tries to log
   118  // the error message at an appropriate level.
   119  //
   120  // Implementations of this handler may display the error message in other
   121  // ways. Implementations should return quickly - any expensive processing
   122  // should be offloaded.
   123  type WatchErrorHandler func(r *Reflector, err error)
   124  
   125  // DefaultWatchErrorHandler is the default implementation of WatchErrorHandler
   126  func DefaultWatchErrorHandler(r *Reflector, err error) {
   127  	switch {
   128  	case isExpiredError(err):
   129  		// Don't set LastSyncResourceVersionUnavailable - LIST call with ResourceVersion=RV already
   130  		// has a semantic that it returns data at least as fresh as provided RV.
   131  		// So first try to LIST with setting RV to resource version of last observed object.
   132  		klog.V(4).Infof("%s: watch of %v closed with: %v", r.name, r.expectedTypeName, err)
   133  	case err == io.EOF:
   134  		// watch closed normally
   135  	case err == io.ErrUnexpectedEOF:
   136  		klog.V(1).Infof("%s: Watch for %v closed with unexpected EOF: %v", r.name, r.expectedTypeName, err)
   137  	default:
   138  		utilruntime.HandleError(fmt.Errorf("%s: Failed to watch %v: %v", r.name, r.expectedTypeName, err))
   139  	}
   140  }
   141  
   142  var (
   143  	// We try to spread the load on apiserver by setting timeouts for
   144  	// watch requests - it is random in [minWatchTimeout, 2*minWatchTimeout].
   145  	minWatchTimeout = 5 * time.Minute
   146  )
   147  
   148  // NewNamespaceKeyedIndexerAndReflector creates an Indexer and a Reflector
   149  // The indexer is configured to key on namespace
   150  func NewNamespaceKeyedIndexerAndReflector(lw ListerWatcher, expectedType interface{}, resyncPeriod time.Duration) (indexer Indexer, reflector *Reflector) {
   151  	indexer = NewIndexer(MetaNamespaceKeyFunc, Indexers{NamespaceIndex: MetaNamespaceIndexFunc})
   152  	reflector = NewReflector(lw, expectedType, indexer, resyncPeriod)
   153  	return indexer, reflector
   154  }
   155  
   156  // NewReflector creates a new Reflector object which will keep the
   157  // given store up to date with the server's contents for the given
   158  // resource. Reflector promises to only put things in the store that
   159  // have the type of expectedType, unless expectedType is nil. If
   160  // resyncPeriod is non-zero, then the reflector will periodically
   161  // consult its ShouldResync function to determine whether to invoke
   162  // the Store's Resync operation; `ShouldResync==nil` means always
   163  // "yes".  This enables you to use reflectors to periodically process
   164  // everything as well as incrementally processing the things that
   165  // change.
   166  func NewReflector(lw ListerWatcher, expectedType interface{}, store Store, resyncPeriod time.Duration) *Reflector {
   167  	return NewNamedReflector(naming.GetNameFromCallsite(internalPackages...), lw, expectedType, store, resyncPeriod)
   168  }
   169  
   170  // NewNamedReflector same as NewReflector, but with a specified name for logging
   171  func NewNamedReflector(name string, lw ListerWatcher, expectedType interface{}, store Store, resyncPeriod time.Duration) *Reflector {
   172  	realClock := &clock.RealClock{}
   173  	r := &Reflector{
   174  		name:          name,
   175  		listerWatcher: lw,
   176  		store:         store,
   177  		// We used to make the call every 1sec (1 QPS), the goal here is to achieve ~98% traffic reduction when
   178  		// API server is not healthy. With these parameters, backoff will stop at [30,60) sec interval which is
   179  		// 0.22 QPS. If we don't backoff for 2min, assume API server is healthy and we reset the backoff.
   180  		backoffManager:         wait.NewExponentialBackoffManager(800*time.Millisecond, 30*time.Second, 2*time.Minute, 2.0, 1.0, realClock),
   181  		initConnBackoffManager: wait.NewExponentialBackoffManager(800*time.Millisecond, 30*time.Second, 2*time.Minute, 2.0, 1.0, realClock),
   182  		resyncPeriod:           resyncPeriod,
   183  		clock:                  realClock,
   184  		watchErrorHandler:      WatchErrorHandler(DefaultWatchErrorHandler),
   185  	}
   186  	r.setExpectedType(expectedType)
   187  	return r
   188  }
   189  
   190  func (r *Reflector) setExpectedType(expectedType interface{}) {
   191  	r.expectedType = reflect.TypeOf(expectedType)
   192  	if r.expectedType == nil {
   193  		r.expectedTypeName = defaultExpectedTypeName
   194  		return
   195  	}
   196  
   197  	r.expectedTypeName = r.expectedType.String()
   198  
   199  	if obj, ok := expectedType.(*unstructured.Unstructured); ok {
   200  		// Use gvk to check that watch event objects are of the desired type.
   201  		gvk := obj.GroupVersionKind()
   202  		if gvk.Empty() {
   203  			klog.V(4).Infof("Reflector from %s configured with expectedType of *unstructured.Unstructured with empty GroupVersionKind.", r.name)
   204  			return
   205  		}
   206  		r.expectedGVK = &gvk
   207  		r.expectedTypeName = gvk.String()
   208  	}
   209  }
   210  
   211  // internalPackages are packages that ignored when creating a default reflector name. These packages are in the common
   212  // call chains to NewReflector, so they'd be low entropy names for reflectors
   213  var internalPackages = []string{"client-go/tools/cache/"}
   214  
   215  // Run repeatedly uses the reflector's ListAndWatch to fetch all the
   216  // objects and subsequent deltas.
   217  // Run will exit when stopCh is closed.
   218  func (r *Reflector) Run(stopCh <-chan struct{}) {
   219  	klog.V(3).Infof("Starting reflector %s (%s) from %s", r.expectedTypeName, r.resyncPeriod, r.name)
   220  	wait.BackoffUntil(func() {
   221  		if err := r.ListAndWatch(stopCh); err != nil {
   222  			r.watchErrorHandler(r, err)
   223  		}
   224  	}, r.backoffManager, true, stopCh)
   225  	klog.V(3).Infof("Stopping reflector %s (%s) from %s", r.expectedTypeName, r.resyncPeriod, r.name)
   226  }
   227  
   228  var (
   229  	// nothing will ever be sent down this channel
   230  	neverExitWatch <-chan time.Time = make(chan time.Time)
   231  
   232  	// Used to indicate that watching stopped because of a signal from the stop
   233  	// channel passed in from a client of the reflector.
   234  	errorStopRequested = errors.New("Stop requested")
   235  )
   236  
   237  // resyncChan returns a channel which will receive something when a resync is
   238  // required, and a cleanup function.
   239  func (r *Reflector) resyncChan() (<-chan time.Time, func() bool) {
   240  	if r.resyncPeriod == 0 {
   241  		return neverExitWatch, func() bool { return false }
   242  	}
   243  	// The cleanup function is required: imagine the scenario where watches
   244  	// always fail so we end up listing frequently. Then, if we don't
   245  	// manually stop the timer, we could end up with many timers active
   246  	// concurrently.
   247  	t := r.clock.NewTimer(r.resyncPeriod)
   248  	return t.C(), t.Stop
   249  }
   250  
   251  // ListAndWatch first lists all items and get the resource version at the moment of call,
   252  // and then use the resource version to watch.
   253  // It returns error if ListAndWatch didn't even try to initialize watch.
   254  func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
   255  	klog.V(3).Infof("Listing and watching %v from %s", r.expectedTypeName, r.name)
   256  	var resourceVersion string
   257  
   258  	options := metav1.ListOptions{ResourceVersion: r.relistResourceVersion()}
   259  
   260  	if err := func() error {
   261  		initTrace := trace.New("Reflector ListAndWatch", trace.Field{"name", r.name})
   262  		defer initTrace.LogIfLong(10 * time.Second)
   263  		var list runtime.Object
   264  		var paginatedResult bool
   265  		var err error
   266  		listCh := make(chan struct{}, 1)
   267  		panicCh := make(chan interface{}, 1)
   268  		go func() {
   269  			defer func() {
   270  				if r := recover(); r != nil {
   271  					panicCh <- r
   272  				}
   273  			}()
   274  			// Attempt to gather list in chunks, if supported by listerWatcher, if not, the first
   275  			// list request will return the full response.
   276  			pager := pager.New(pager.SimplePageFunc(func(opts metav1.ListOptions) (runtime.Object, error) {
   277  				return r.listerWatcher.List(opts)
   278  			}))
   279  			switch {
   280  			case r.WatchListPageSize != 0:
   281  				pager.PageSize = r.WatchListPageSize
   282  			case r.paginatedResult:
   283  				// We got a paginated result initially. Assume this resource and server honor
   284  				// paging requests (i.e. watch cache is probably disabled) and leave the default
   285  				// pager size set.
   286  			case options.ResourceVersion != "" && options.ResourceVersion != "0":
   287  				// User didn't explicitly request pagination.
   288  				//
   289  				// With ResourceVersion != "", we have a possibility to list from watch cache,
   290  				// but we do that (for ResourceVersion != "0") only if Limit is unset.
   291  				// To avoid thundering herd on etcd (e.g. on master upgrades), we explicitly
   292  				// switch off pagination to force listing from watch cache (if enabled).
   293  				// With the existing semantic of RV (result is at least as fresh as provided RV),
   294  				// this is correct and doesn't lead to going back in time.
   295  				//
   296  				// We also don't turn off pagination for ResourceVersion="0", since watch cache
   297  				// is ignoring Limit in that case anyway, and if watch cache is not enabled
   298  				// we don't introduce regression.
   299  				pager.PageSize = 0
   300  			}
   301  
   302  			list, paginatedResult, err = pager.List(context.Background(), options)
   303  			if isExpiredError(err) || isTooLargeResourceVersionError(err) {
   304  				r.setIsLastSyncResourceVersionUnavailable(true)
   305  				// Retry immediately if the resource version used to list is unavailable.
   306  				// The pager already falls back to full list if paginated list calls fail due to an "Expired" error on
   307  				// continuation pages, but the pager might not be enabled, the full list might fail because the
   308  				// resource version it is listing at is expired or the cache may not yet be synced to the provided
   309  				// resource version. So we need to fallback to resourceVersion="" in all to recover and ensure
   310  				// the reflector makes forward progress.
   311  				list, paginatedResult, err = pager.List(context.Background(), metav1.ListOptions{ResourceVersion: r.relistResourceVersion()})
   312  			}
   313  			close(listCh)
   314  		}()
   315  		select {
   316  		case <-stopCh:
   317  			return nil
   318  		case r := <-panicCh:
   319  			panic(r)
   320  		case <-listCh:
   321  		}
   322  		if err != nil {
   323  			return fmt.Errorf("failed to list %v: %v", r.expectedTypeName, err)
   324  		}
   325  
   326  		// We check if the list was paginated and if so set the paginatedResult based on that.
   327  		// However, we want to do that only for the initial list (which is the only case
   328  		// when we set ResourceVersion="0"). The reasoning behind it is that later, in some
   329  		// situations we may force listing directly from etcd (by setting ResourceVersion="")
   330  		// which will return paginated result, even if watch cache is enabled. However, in
   331  		// that case, we still want to prefer sending requests to watch cache if possible.
   332  		//
   333  		// Paginated result returned for request with ResourceVersion="0" mean that watch
   334  		// cache is disabled and there are a lot of objects of a given type. In such case,
   335  		// there is no need to prefer listing from watch cache.
   336  		if options.ResourceVersion == "0" && paginatedResult {
   337  			r.paginatedResult = true
   338  		}
   339  
   340  		r.setIsLastSyncResourceVersionUnavailable(false) // list was successful
   341  		initTrace.Step("Objects listed")
   342  		listMetaInterface, err := meta.ListAccessor(list)
   343  		if err != nil {
   344  			return fmt.Errorf("unable to understand list result %#v: %v", list, err)
   345  		}
   346  		resourceVersion = listMetaInterface.GetResourceVersion()
   347  		initTrace.Step("Resource version extracted")
   348  		items, err := meta.ExtractList(list)
   349  		if err != nil {
   350  			return fmt.Errorf("unable to understand list result %#v (%v)", list, err)
   351  		}
   352  		initTrace.Step("Objects extracted")
   353  		if err := r.syncWith(items, resourceVersion); err != nil {
   354  			return fmt.Errorf("unable to sync list result: %v", err)
   355  		}
   356  		initTrace.Step("SyncWith done")
   357  		r.setLastSyncResourceVersion(resourceVersion)
   358  		initTrace.Step("Resource version updated")
   359  		return nil
   360  	}(); err != nil {
   361  		return err
   362  	}
   363  
   364  	resyncerrc := make(chan error, 1)
   365  	cancelCh := make(chan struct{})
   366  	defer close(cancelCh)
   367  	go func() {
   368  		resyncCh, cleanup := r.resyncChan()
   369  		defer func() {
   370  			cleanup() // Call the last one written into cleanup
   371  		}()
   372  		for {
   373  			select {
   374  			case <-resyncCh:
   375  			case <-stopCh:
   376  				return
   377  			case <-cancelCh:
   378  				return
   379  			}
   380  			if r.ShouldResync == nil || r.ShouldResync() {
   381  				klog.V(4).Infof("%s: forcing resync", r.name)
   382  				if err := r.store.Resync(); err != nil {
   383  					resyncerrc <- err
   384  					return
   385  				}
   386  			}
   387  			cleanup()
   388  			resyncCh, cleanup = r.resyncChan()
   389  		}
   390  	}()
   391  
   392  	for {
   393  		// give the stopCh a chance to stop the loop, even in case of continue statements further down on errors
   394  		select {
   395  		case <-stopCh:
   396  			return nil
   397  		default:
   398  		}
   399  
   400  		timeoutSeconds := int64(minWatchTimeout.Seconds() * (rand.Float64() + 1.0))
   401  		options = metav1.ListOptions{
   402  			ResourceVersion: resourceVersion,
   403  			// We want to avoid situations of hanging watchers. Stop any wachers that do not
   404  			// receive any events within the timeout window.
   405  			TimeoutSeconds: &timeoutSeconds,
   406  			// To reduce load on kube-apiserver on watch restarts, you may enable watch bookmarks.
   407  			// Reflector doesn't assume bookmarks are returned at all (if the server do not support
   408  			// watch bookmarks, it will ignore this field).
   409  			AllowWatchBookmarks: true,
   410  		}
   411  
   412  		// start the clock before sending the request, since some proxies won't flush headers until after the first watch event is sent
   413  		start := r.clock.Now()
   414  		w, err := r.listerWatcher.Watch(options)
   415  		if err != nil {
   416  			// If this is "connection refused" error, it means that most likely apiserver is not responsive.
   417  			// It doesn't make sense to re-list all objects because most likely we will be able to restart
   418  			// watch where we ended.
   419  			// If that's the case begin exponentially backing off and resend watch request.
   420  			// Do the same for "429" errors.
   421  			if utilnet.IsConnectionRefused(err) || apierrors.IsTooManyRequests(err) {
   422  				<-r.initConnBackoffManager.Backoff().C()
   423  				continue
   424  			}
   425  			return err
   426  		}
   427  
   428  		if err := r.watchHandler(start, w, &resourceVersion, resyncerrc, stopCh); err != nil {
   429  			if err != errorStopRequested {
   430  				switch {
   431  				case isExpiredError(err):
   432  					// Don't set LastSyncResourceVersionUnavailable - LIST call with ResourceVersion=RV already
   433  					// has a semantic that it returns data at least as fresh as provided RV.
   434  					// So first try to LIST with setting RV to resource version of last observed object.
   435  					klog.V(4).Infof("%s: watch of %v closed with: %v", r.name, r.expectedTypeName, err)
   436  				case apierrors.IsTooManyRequests(err):
   437  					klog.V(2).Infof("%s: watch of %v returned 429 - backing off", r.name, r.expectedTypeName)
   438  					<-r.initConnBackoffManager.Backoff().C()
   439  					continue
   440  				default:
   441  					klog.Warningf("%s: watch of %v ended with: %v", r.name, r.expectedTypeName, err)
   442  				}
   443  			}
   444  			return nil
   445  		}
   446  	}
   447  }
   448  
   449  // syncWith replaces the store's items with the given list.
   450  func (r *Reflector) syncWith(items []runtime.Object, resourceVersion string) error {
   451  	found := make([]interface{}, 0, len(items))
   452  	for _, item := range items {
   453  		found = append(found, item)
   454  	}
   455  	return r.store.Replace(found, resourceVersion)
   456  }
   457  
   458  // watchHandler watches w and keeps *resourceVersion up to date.
   459  func (r *Reflector) watchHandler(start time.Time, w watch.Interface, resourceVersion *string, errc chan error, stopCh <-chan struct{}) error {
   460  	eventCount := 0
   461  
   462  	// Stopping the watcher should be idempotent and if we return from this function there's no way
   463  	// we're coming back in with the same watch interface.
   464  	defer w.Stop()
   465  
   466  loop:
   467  	for {
   468  		select {
   469  		case <-stopCh:
   470  			return errorStopRequested
   471  		case err := <-errc:
   472  			return err
   473  		case event, ok := <-w.ResultChan():
   474  			if !ok {
   475  				break loop
   476  			}
   477  			if event.Type == watch.Error {
   478  				return apierrors.FromObject(event.Object)
   479  			}
   480  			if r.expectedType != nil {
   481  				if e, a := r.expectedType, reflect.TypeOf(event.Object); e != a {
   482  					utilruntime.HandleError(fmt.Errorf("%s: expected type %v, but watch event object had type %v", r.name, e, a))
   483  					continue
   484  				}
   485  			}
   486  			if r.expectedGVK != nil {
   487  				if e, a := *r.expectedGVK, event.Object.GetObjectKind().GroupVersionKind(); e != a {
   488  					utilruntime.HandleError(fmt.Errorf("%s: expected gvk %v, but watch event object had gvk %v", r.name, e, a))
   489  					continue
   490  				}
   491  			}
   492  			meta, err := meta.Accessor(event.Object)
   493  			if err != nil {
   494  				utilruntime.HandleError(fmt.Errorf("%s: unable to understand watch event %#v", r.name, event))
   495  				continue
   496  			}
   497  			newResourceVersion := meta.GetResourceVersion()
   498  			switch event.Type {
   499  			case watch.Added:
   500  				err := r.store.Add(event.Object)
   501  				if err != nil {
   502  					utilruntime.HandleError(fmt.Errorf("%s: unable to add watch event object (%#v) to store: %v", r.name, event.Object, err))
   503  				}
   504  			case watch.Modified:
   505  				err := r.store.Update(event.Object)
   506  				if err != nil {
   507  					utilruntime.HandleError(fmt.Errorf("%s: unable to update watch event object (%#v) to store: %v", r.name, event.Object, err))
   508  				}
   509  			case watch.Deleted:
   510  				// TODO: Will any consumers need access to the "last known
   511  				// state", which is passed in event.Object? If so, may need
   512  				// to change this.
   513  				err := r.store.Delete(event.Object)
   514  				if err != nil {
   515  					utilruntime.HandleError(fmt.Errorf("%s: unable to delete watch event object (%#v) from store: %v", r.name, event.Object, err))
   516  				}
   517  			case watch.Bookmark:
   518  				// A `Bookmark` means watch has synced here, just update the resourceVersion
   519  			default:
   520  				utilruntime.HandleError(fmt.Errorf("%s: unable to understand watch event %#v", r.name, event))
   521  			}
   522  			*resourceVersion = newResourceVersion
   523  			r.setLastSyncResourceVersion(newResourceVersion)
   524  			if rvu, ok := r.store.(ResourceVersionUpdater); ok {
   525  				rvu.UpdateResourceVersion(newResourceVersion)
   526  			}
   527  			eventCount++
   528  		}
   529  	}
   530  
   531  	watchDuration := r.clock.Since(start)
   532  	if watchDuration < 1*time.Second && eventCount == 0 {
   533  		return fmt.Errorf("very short watch: %s: Unexpected watch close - watch lasted less than a second and no items received", r.name)
   534  	}
   535  	klog.V(4).Infof("%s: Watch close - %v total %v items received", r.name, r.expectedTypeName, eventCount)
   536  	return nil
   537  }
   538  
   539  // LastSyncResourceVersion is the resource version observed when last sync with the underlying store
   540  // The value returned is not synchronized with access to the underlying store and is not thread-safe
   541  func (r *Reflector) LastSyncResourceVersion() string {
   542  	r.lastSyncResourceVersionMutex.RLock()
   543  	defer r.lastSyncResourceVersionMutex.RUnlock()
   544  	return r.lastSyncResourceVersion
   545  }
   546  
   547  func (r *Reflector) setLastSyncResourceVersion(v string) {
   548  	r.lastSyncResourceVersionMutex.Lock()
   549  	defer r.lastSyncResourceVersionMutex.Unlock()
   550  	r.lastSyncResourceVersion = v
   551  }
   552  
   553  // relistResourceVersion determines the resource version the reflector should list or relist from.
   554  // Returns either the lastSyncResourceVersion so that this reflector will relist with a resource
   555  // versions no older than has already been observed in relist results or watch events, or, if the last relist resulted
   556  // in an HTTP 410 (Gone) status code, returns "" so that the relist will use the latest resource version available in
   557  // etcd via a quorum read.
   558  func (r *Reflector) relistResourceVersion() string {
   559  	r.lastSyncResourceVersionMutex.RLock()
   560  	defer r.lastSyncResourceVersionMutex.RUnlock()
   561  
   562  	if r.isLastSyncResourceVersionUnavailable {
   563  		// Since this reflector makes paginated list requests, and all paginated list requests skip the watch cache
   564  		// if the lastSyncResourceVersion is unavailable, we set ResourceVersion="" and list again to re-establish reflector
   565  		// to the latest available ResourceVersion, using a consistent read from etcd.
   566  		return ""
   567  	}
   568  	if r.lastSyncResourceVersion == "" {
   569  		// For performance reasons, initial list performed by reflector uses "0" as resource version to allow it to
   570  		// be served from the watch cache if it is enabled.
   571  		return "0"
   572  	}
   573  	return r.lastSyncResourceVersion
   574  }
   575  
   576  // setIsLastSyncResourceVersionUnavailable sets if the last list or watch request with lastSyncResourceVersion returned
   577  // "expired" or "too large resource version" error.
   578  func (r *Reflector) setIsLastSyncResourceVersionUnavailable(isUnavailable bool) {
   579  	r.lastSyncResourceVersionMutex.Lock()
   580  	defer r.lastSyncResourceVersionMutex.Unlock()
   581  	r.isLastSyncResourceVersionUnavailable = isUnavailable
   582  }
   583  
   584  func isExpiredError(err error) bool {
   585  	// In Kubernetes 1.17 and earlier, the api server returns both apierrors.StatusReasonExpired and
   586  	// apierrors.StatusReasonGone for HTTP 410 (Gone) status code responses. In 1.18 the kube server is more consistent
   587  	// and always returns apierrors.StatusReasonExpired. For backward compatibility we can only remove the apierrors.IsGone
   588  	// check when we fully drop support for Kubernetes 1.17 servers from reflectors.
   589  	return apierrors.IsResourceExpired(err) || apierrors.IsGone(err)
   590  }
   591  
   592  func isTooLargeResourceVersionError(err error) bool {
   593  	if apierrors.HasStatusCause(err, metav1.CauseTypeResourceVersionTooLarge) {
   594  		return true
   595  	}
   596  	// In Kubernetes 1.17.0-1.18.5, the api server doesn't set the error status cause to
   597  	// metav1.CauseTypeResourceVersionTooLarge to indicate that the requested minimum resource
   598  	// version is larger than the largest currently available resource version. To ensure backward
   599  	// compatibility with these server versions we also need to detect the error based on the content
   600  	// of the error message field.
   601  	if !apierrors.IsTimeout(err) {
   602  		return false
   603  	}
   604  	apierr, ok := err.(apierrors.APIStatus)
   605  	if !ok || apierr == nil || apierr.Status().Details == nil {
   606  		return false
   607  	}
   608  	for _, cause := range apierr.Status().Details.Causes {
   609  		// Matches the message returned by api server 1.17.0-1.18.5 for this error condition
   610  		if cause.Message == "Too large resource version" {
   611  			return true
   612  		}
   613  	}
   614  	return false
   615  }