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 }