k8s.io/client-go@v0.31.1/tools/cache/controller.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 "errors" 21 "sync" 22 "time" 23 24 "k8s.io/apimachinery/pkg/runtime" 25 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 26 "k8s.io/apimachinery/pkg/util/wait" 27 "k8s.io/utils/clock" 28 ) 29 30 // This file implements a low-level controller that is used in 31 // sharedIndexInformer, which is an implementation of 32 // SharedIndexInformer. Such informers, in turn, are key components 33 // in the high level controllers that form the backbone of the 34 // Kubernetes control plane. Look at those for examples, or the 35 // example in 36 // https://github.com/kubernetes/client-go/tree/master/examples/workqueue 37 // . 38 39 // Config contains all the settings for one of these low-level controllers. 40 type Config struct { 41 // The queue for your objects - has to be a DeltaFIFO due to 42 // assumptions in the implementation. Your Process() function 43 // should accept the output of this Queue's Pop() method. 44 Queue 45 46 // Something that can list and watch your objects. 47 ListerWatcher 48 49 // Something that can process a popped Deltas. 50 Process ProcessFunc 51 52 // ObjectType is an example object of the type this controller is 53 // expected to handle. 54 ObjectType runtime.Object 55 56 // ObjectDescription is the description to use when logging type-specific information about this controller. 57 ObjectDescription string 58 59 // FullResyncPeriod is the period at which ShouldResync is considered. 60 FullResyncPeriod time.Duration 61 62 // MinWatchTimeout, if set, will define the minimum timeout for watch requests send 63 // to kube-apiserver. However, values lower than 5m will not be honored to avoid 64 // negative performance impact on controlplane. 65 // Optional - if unset a default value of 5m will be used. 66 MinWatchTimeout time.Duration 67 68 // ShouldResync is periodically used by the reflector to determine 69 // whether to Resync the Queue. If ShouldResync is `nil` or 70 // returns true, it means the reflector should proceed with the 71 // resync. 72 ShouldResync ShouldResyncFunc 73 74 // If true, when Process() returns an error, re-enqueue the object. 75 // TODO: add interface to let you inject a delay/backoff or drop 76 // the object completely if desired. Pass the object in 77 // question to this interface as a parameter. This is probably moot 78 // now that this functionality appears at a higher level. 79 RetryOnError bool 80 81 // Called whenever the ListAndWatch drops the connection with an error. 82 WatchErrorHandler WatchErrorHandler 83 84 // WatchListPageSize is the requested chunk size of initial and relist watch lists. 85 WatchListPageSize int64 86 } 87 88 // ShouldResyncFunc is a type of function that indicates if a reflector should perform a 89 // resync or not. It can be used by a shared informer to support multiple event handlers with custom 90 // resync periods. 91 type ShouldResyncFunc func() bool 92 93 // ProcessFunc processes a single object. 94 type ProcessFunc func(obj interface{}, isInInitialList bool) error 95 96 // `*controller` implements Controller 97 type controller struct { 98 config Config 99 reflector *Reflector 100 reflectorMutex sync.RWMutex 101 clock clock.Clock 102 } 103 104 // Controller is a low-level controller that is parameterized by a 105 // Config and used in sharedIndexInformer. 106 type Controller interface { 107 // Run does two things. One is to construct and run a Reflector 108 // to pump objects/notifications from the Config's ListerWatcher 109 // to the Config's Queue and possibly invoke the occasional Resync 110 // on that Queue. The other is to repeatedly Pop from the Queue 111 // and process with the Config's ProcessFunc. Both of these 112 // continue until `stopCh` is closed. 113 Run(stopCh <-chan struct{}) 114 115 // HasSynced delegates to the Config's Queue 116 HasSynced() bool 117 118 // LastSyncResourceVersion delegates to the Reflector when there 119 // is one, otherwise returns the empty string 120 LastSyncResourceVersion() string 121 } 122 123 // New makes a new Controller from the given Config. 124 func New(c *Config) Controller { 125 ctlr := &controller{ 126 config: *c, 127 clock: &clock.RealClock{}, 128 } 129 return ctlr 130 } 131 132 // Run begins processing items, and will continue until a value is sent down stopCh or it is closed. 133 // It's an error to call Run more than once. 134 // Run blocks; call via go. 135 func (c *controller) Run(stopCh <-chan struct{}) { 136 defer utilruntime.HandleCrash() 137 go func() { 138 <-stopCh 139 c.config.Queue.Close() 140 }() 141 r := NewReflectorWithOptions( 142 c.config.ListerWatcher, 143 c.config.ObjectType, 144 c.config.Queue, 145 ReflectorOptions{ 146 ResyncPeriod: c.config.FullResyncPeriod, 147 MinWatchTimeout: c.config.MinWatchTimeout, 148 TypeDescription: c.config.ObjectDescription, 149 Clock: c.clock, 150 }, 151 ) 152 r.ShouldResync = c.config.ShouldResync 153 r.WatchListPageSize = c.config.WatchListPageSize 154 if c.config.WatchErrorHandler != nil { 155 r.watchErrorHandler = c.config.WatchErrorHandler 156 } 157 158 c.reflectorMutex.Lock() 159 c.reflector = r 160 c.reflectorMutex.Unlock() 161 162 var wg wait.Group 163 164 wg.StartWithChannel(stopCh, r.Run) 165 166 wait.Until(c.processLoop, time.Second, stopCh) 167 wg.Wait() 168 } 169 170 // Returns true once this controller has completed an initial resource listing 171 func (c *controller) HasSynced() bool { 172 return c.config.Queue.HasSynced() 173 } 174 175 func (c *controller) LastSyncResourceVersion() string { 176 c.reflectorMutex.RLock() 177 defer c.reflectorMutex.RUnlock() 178 if c.reflector == nil { 179 return "" 180 } 181 return c.reflector.LastSyncResourceVersion() 182 } 183 184 // processLoop drains the work queue. 185 // TODO: Consider doing the processing in parallel. This will require a little thought 186 // to make sure that we don't end up processing the same object multiple times 187 // concurrently. 188 // 189 // TODO: Plumb through the stopCh here (and down to the queue) so that this can 190 // actually exit when the controller is stopped. Or just give up on this stuff 191 // ever being stoppable. Converting this whole package to use Context would 192 // also be helpful. 193 func (c *controller) processLoop() { 194 for { 195 obj, err := c.config.Queue.Pop(PopProcessFunc(c.config.Process)) 196 if err != nil { 197 if err == ErrFIFOClosed { 198 return 199 } 200 if c.config.RetryOnError { 201 // This is the safe way to re-enqueue. 202 c.config.Queue.AddIfNotPresent(obj) 203 } 204 } 205 } 206 } 207 208 // ResourceEventHandler can handle notifications for events that 209 // happen to a resource. The events are informational only, so you 210 // can't return an error. The handlers MUST NOT modify the objects 211 // received; this concerns not only the top level of structure but all 212 // the data structures reachable from it. 213 // - OnAdd is called when an object is added. 214 // - OnUpdate is called when an object is modified. Note that oldObj is the 215 // last known state of the object-- it is possible that several changes 216 // were combined together, so you can't use this to see every single 217 // change. OnUpdate is also called when a re-list happens, and it will 218 // get called even if nothing changed. This is useful for periodically 219 // evaluating or syncing something. 220 // - OnDelete will get the final state of the item if it is known, otherwise 221 // it will get an object of type DeletedFinalStateUnknown. This can 222 // happen if the watch is closed and misses the delete event and we don't 223 // notice the deletion until the subsequent re-list. 224 type ResourceEventHandler interface { 225 OnAdd(obj interface{}, isInInitialList bool) 226 OnUpdate(oldObj, newObj interface{}) 227 OnDelete(obj interface{}) 228 } 229 230 // ResourceEventHandlerFuncs is an adaptor to let you easily specify as many or 231 // as few of the notification functions as you want while still implementing 232 // ResourceEventHandler. This adapter does not remove the prohibition against 233 // modifying the objects. 234 // 235 // See ResourceEventHandlerDetailedFuncs if your use needs to propagate 236 // HasSynced. 237 type ResourceEventHandlerFuncs struct { 238 AddFunc func(obj interface{}) 239 UpdateFunc func(oldObj, newObj interface{}) 240 DeleteFunc func(obj interface{}) 241 } 242 243 // OnAdd calls AddFunc if it's not nil. 244 func (r ResourceEventHandlerFuncs) OnAdd(obj interface{}, isInInitialList bool) { 245 if r.AddFunc != nil { 246 r.AddFunc(obj) 247 } 248 } 249 250 // OnUpdate calls UpdateFunc if it's not nil. 251 func (r ResourceEventHandlerFuncs) OnUpdate(oldObj, newObj interface{}) { 252 if r.UpdateFunc != nil { 253 r.UpdateFunc(oldObj, newObj) 254 } 255 } 256 257 // OnDelete calls DeleteFunc if it's not nil. 258 func (r ResourceEventHandlerFuncs) OnDelete(obj interface{}) { 259 if r.DeleteFunc != nil { 260 r.DeleteFunc(obj) 261 } 262 } 263 264 // ResourceEventHandlerDetailedFuncs is exactly like ResourceEventHandlerFuncs 265 // except its AddFunc accepts the isInInitialList parameter, for propagating 266 // HasSynced. 267 type ResourceEventHandlerDetailedFuncs struct { 268 AddFunc func(obj interface{}, isInInitialList bool) 269 UpdateFunc func(oldObj, newObj interface{}) 270 DeleteFunc func(obj interface{}) 271 } 272 273 // OnAdd calls AddFunc if it's not nil. 274 func (r ResourceEventHandlerDetailedFuncs) OnAdd(obj interface{}, isInInitialList bool) { 275 if r.AddFunc != nil { 276 r.AddFunc(obj, isInInitialList) 277 } 278 } 279 280 // OnUpdate calls UpdateFunc if it's not nil. 281 func (r ResourceEventHandlerDetailedFuncs) OnUpdate(oldObj, newObj interface{}) { 282 if r.UpdateFunc != nil { 283 r.UpdateFunc(oldObj, newObj) 284 } 285 } 286 287 // OnDelete calls DeleteFunc if it's not nil. 288 func (r ResourceEventHandlerDetailedFuncs) OnDelete(obj interface{}) { 289 if r.DeleteFunc != nil { 290 r.DeleteFunc(obj) 291 } 292 } 293 294 // FilteringResourceEventHandler applies the provided filter to all events coming 295 // in, ensuring the appropriate nested handler method is invoked. An object 296 // that starts passing the filter after an update is considered an add, and an 297 // object that stops passing the filter after an update is considered a delete. 298 // Like the handlers, the filter MUST NOT modify the objects it is given. 299 type FilteringResourceEventHandler struct { 300 FilterFunc func(obj interface{}) bool 301 Handler ResourceEventHandler 302 } 303 304 // OnAdd calls the nested handler only if the filter succeeds 305 func (r FilteringResourceEventHandler) OnAdd(obj interface{}, isInInitialList bool) { 306 if !r.FilterFunc(obj) { 307 return 308 } 309 r.Handler.OnAdd(obj, isInInitialList) 310 } 311 312 // OnUpdate ensures the proper handler is called depending on whether the filter matches 313 func (r FilteringResourceEventHandler) OnUpdate(oldObj, newObj interface{}) { 314 newer := r.FilterFunc(newObj) 315 older := r.FilterFunc(oldObj) 316 switch { 317 case newer && older: 318 r.Handler.OnUpdate(oldObj, newObj) 319 case newer && !older: 320 r.Handler.OnAdd(newObj, false) 321 case !newer && older: 322 r.Handler.OnDelete(oldObj) 323 default: 324 // do nothing 325 } 326 } 327 328 // OnDelete calls the nested handler only if the filter succeeds 329 func (r FilteringResourceEventHandler) OnDelete(obj interface{}) { 330 if !r.FilterFunc(obj) { 331 return 332 } 333 r.Handler.OnDelete(obj) 334 } 335 336 // DeletionHandlingMetaNamespaceKeyFunc checks for 337 // DeletedFinalStateUnknown objects before calling 338 // MetaNamespaceKeyFunc. 339 func DeletionHandlingMetaNamespaceKeyFunc(obj interface{}) (string, error) { 340 if d, ok := obj.(DeletedFinalStateUnknown); ok { 341 return d.Key, nil 342 } 343 return MetaNamespaceKeyFunc(obj) 344 } 345 346 // DeletionHandlingObjectToName checks for 347 // DeletedFinalStateUnknown objects before calling 348 // ObjectToName. 349 func DeletionHandlingObjectToName(obj interface{}) (ObjectName, error) { 350 if d, ok := obj.(DeletedFinalStateUnknown); ok { 351 return ParseObjectName(d.Key) 352 } 353 return ObjectToName(obj) 354 } 355 356 // InformerOptions configure a Reflector. 357 type InformerOptions struct { 358 // ListerWatcher implements List and Watch functions for the source of the resource 359 // the informer will be informing about. 360 ListerWatcher ListerWatcher 361 362 // ObjectType is an object of the type that informer is expected to receive. 363 ObjectType runtime.Object 364 365 // Handler defines functions that should called on object mutations. 366 Handler ResourceEventHandler 367 368 // ResyncPeriod is the underlying Reflector's resync period. If non-zero, the store 369 // is re-synced with that frequency - Modify events are delivered even if objects 370 // didn't change. 371 // This is useful for synchronizing objects that configure external resources 372 // (e.g. configure cloud provider functionalities). 373 // Optional - if unset, store resyncing is not happening periodically. 374 ResyncPeriod time.Duration 375 376 // MinWatchTimeout, if set, will define the minimum timeout for watch requests send 377 // to kube-apiserver. However, values lower than 5m will not be honored to avoid 378 // negative performance impact on controlplane. 379 // Optional - if unset a default value of 5m will be used. 380 MinWatchTimeout time.Duration 381 382 // Indexers, if set, are the indexers for the received objects to optimize 383 // certain queries. 384 // Optional - if unset no indexes are maintained. 385 Indexers Indexers 386 387 // Transform function, if set, will be called on all objects before they will be 388 // put into the Store and corresponding Add/Modify/Delete handlers will be invoked 389 // for them. 390 // Optional - if unset no additional transforming is happening. 391 Transform TransformFunc 392 } 393 394 // NewInformerWithOptions returns a Store and a controller for populating the store 395 // while also providing event notifications. You should only used the returned 396 // Store for Get/List operations; Add/Modify/Deletes will cause the event 397 // notifications to be faulty. 398 func NewInformerWithOptions(options InformerOptions) (Store, Controller) { 399 var clientState Store 400 if options.Indexers == nil { 401 clientState = NewStore(DeletionHandlingMetaNamespaceKeyFunc) 402 } else { 403 clientState = NewIndexer(DeletionHandlingMetaNamespaceKeyFunc, options.Indexers) 404 } 405 return clientState, newInformer(clientState, options) 406 } 407 408 // NewInformer returns a Store and a controller for populating the store 409 // while also providing event notifications. You should only used the returned 410 // Store for Get/List operations; Add/Modify/Deletes will cause the event 411 // notifications to be faulty. 412 // 413 // Parameters: 414 // - lw is list and watch functions for the source of the resource you want to 415 // be informed of. 416 // - objType is an object of the type that you expect to receive. 417 // - resyncPeriod: if non-zero, will re-list this often (you will get OnUpdate 418 // calls, even if nothing changed). Otherwise, re-list will be delayed as 419 // long as possible (until the upstream source closes the watch or times out, 420 // or you stop the controller). 421 // - h is the object you want notifications sent to. 422 // 423 // Deprecated: Use NewInformerWithOptions instead. 424 func NewInformer( 425 lw ListerWatcher, 426 objType runtime.Object, 427 resyncPeriod time.Duration, 428 h ResourceEventHandler, 429 ) (Store, Controller) { 430 // This will hold the client state, as we know it. 431 clientState := NewStore(DeletionHandlingMetaNamespaceKeyFunc) 432 433 options := InformerOptions{ 434 ListerWatcher: lw, 435 ObjectType: objType, 436 Handler: h, 437 ResyncPeriod: resyncPeriod, 438 } 439 return clientState, newInformer(clientState, options) 440 } 441 442 // NewIndexerInformer returns an Indexer and a Controller for populating the index 443 // while also providing event notifications. You should only used the returned 444 // Index for Get/List operations; Add/Modify/Deletes will cause the event 445 // notifications to be faulty. 446 // 447 // Parameters: 448 // - lw is list and watch functions for the source of the resource you want to 449 // be informed of. 450 // - objType is an object of the type that you expect to receive. 451 // - resyncPeriod: if non-zero, will re-list this often (you will get OnUpdate 452 // calls, even if nothing changed). Otherwise, re-list will be delayed as 453 // long as possible (until the upstream source closes the watch or times out, 454 // or you stop the controller). 455 // - h is the object you want notifications sent to. 456 // - indexers is the indexer for the received object type. 457 // 458 // Deprecated: Use NewInformerWithOptions instead. 459 func NewIndexerInformer( 460 lw ListerWatcher, 461 objType runtime.Object, 462 resyncPeriod time.Duration, 463 h ResourceEventHandler, 464 indexers Indexers, 465 ) (Indexer, Controller) { 466 // This will hold the client state, as we know it. 467 clientState := NewIndexer(DeletionHandlingMetaNamespaceKeyFunc, indexers) 468 469 options := InformerOptions{ 470 ListerWatcher: lw, 471 ObjectType: objType, 472 Handler: h, 473 ResyncPeriod: resyncPeriod, 474 Indexers: indexers, 475 } 476 return clientState, newInformer(clientState, options) 477 } 478 479 // NewTransformingInformer returns a Store and a controller for populating 480 // the store while also providing event notifications. You should only used 481 // the returned Store for Get/List operations; Add/Modify/Deletes will cause 482 // the event notifications to be faulty. 483 // The given transform function will be called on all objects before they will 484 // put into the Store and corresponding Add/Modify/Delete handlers will 485 // be invoked for them. 486 // 487 // Deprecated: Use NewInformerWithOptions instead. 488 func NewTransformingInformer( 489 lw ListerWatcher, 490 objType runtime.Object, 491 resyncPeriod time.Duration, 492 h ResourceEventHandler, 493 transformer TransformFunc, 494 ) (Store, Controller) { 495 // This will hold the client state, as we know it. 496 clientState := NewStore(DeletionHandlingMetaNamespaceKeyFunc) 497 498 options := InformerOptions{ 499 ListerWatcher: lw, 500 ObjectType: objType, 501 Handler: h, 502 ResyncPeriod: resyncPeriod, 503 Transform: transformer, 504 } 505 return clientState, newInformer(clientState, options) 506 } 507 508 // NewTransformingIndexerInformer returns an Indexer and a controller for 509 // populating the index while also providing event notifications. You should 510 // only used the returned Index for Get/List operations; Add/Modify/Deletes 511 // will cause the event notifications to be faulty. 512 // The given transform function will be called on all objects before they will 513 // be put into the Index and corresponding Add/Modify/Delete handlers will 514 // be invoked for them. 515 // 516 // Deprecated: Use NewInformerWithOptions instead. 517 func NewTransformingIndexerInformer( 518 lw ListerWatcher, 519 objType runtime.Object, 520 resyncPeriod time.Duration, 521 h ResourceEventHandler, 522 indexers Indexers, 523 transformer TransformFunc, 524 ) (Indexer, Controller) { 525 // This will hold the client state, as we know it. 526 clientState := NewIndexer(DeletionHandlingMetaNamespaceKeyFunc, indexers) 527 528 options := InformerOptions{ 529 ListerWatcher: lw, 530 ObjectType: objType, 531 Handler: h, 532 ResyncPeriod: resyncPeriod, 533 Indexers: indexers, 534 Transform: transformer, 535 } 536 return clientState, newInformer(clientState, options) 537 } 538 539 // Multiplexes updates in the form of a list of Deltas into a Store, and informs 540 // a given handler of events OnUpdate, OnAdd, OnDelete 541 func processDeltas( 542 // Object which receives event notifications from the given deltas 543 handler ResourceEventHandler, 544 clientState Store, 545 deltas Deltas, 546 isInInitialList bool, 547 ) error { 548 // from oldest to newest 549 for _, d := range deltas { 550 obj := d.Object 551 552 switch d.Type { 553 case Sync, Replaced, Added, Updated: 554 if old, exists, err := clientState.Get(obj); err == nil && exists { 555 if err := clientState.Update(obj); err != nil { 556 return err 557 } 558 handler.OnUpdate(old, obj) 559 } else { 560 if err := clientState.Add(obj); err != nil { 561 return err 562 } 563 handler.OnAdd(obj, isInInitialList) 564 } 565 case Deleted: 566 if err := clientState.Delete(obj); err != nil { 567 return err 568 } 569 handler.OnDelete(obj) 570 } 571 } 572 return nil 573 } 574 575 // newInformer returns a controller for populating the store while also 576 // providing event notifications. 577 // 578 // Parameters 579 // - clientState is the store you want to populate 580 // - options contain the options to configure the controller 581 func newInformer(clientState Store, options InformerOptions) Controller { 582 // This will hold incoming changes. Note how we pass clientState in as a 583 // KeyLister, that way resync operations will result in the correct set 584 // of update/delete deltas. 585 fifo := NewDeltaFIFOWithOptions(DeltaFIFOOptions{ 586 KnownObjects: clientState, 587 EmitDeltaTypeReplaced: true, 588 Transformer: options.Transform, 589 }) 590 591 cfg := &Config{ 592 Queue: fifo, 593 ListerWatcher: options.ListerWatcher, 594 ObjectType: options.ObjectType, 595 FullResyncPeriod: options.ResyncPeriod, 596 MinWatchTimeout: options.MinWatchTimeout, 597 RetryOnError: false, 598 599 Process: func(obj interface{}, isInInitialList bool) error { 600 if deltas, ok := obj.(Deltas); ok { 601 return processDeltas(options.Handler, clientState, deltas, isInInitialList) 602 } 603 return errors.New("object given as Process argument is not Deltas") 604 }, 605 } 606 return New(cfg) 607 }