k8s.io/client-go@v0.22.2/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 "sync" 21 "time" 22 23 "k8s.io/apimachinery/pkg/runtime" 24 "k8s.io/apimachinery/pkg/util/clock" 25 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 26 "k8s.io/apimachinery/pkg/util/wait" 27 ) 28 29 // This file implements a low-level controller that is used in 30 // sharedIndexInformer, which is an implementation of 31 // SharedIndexInformer. Such informers, in turn, are key components 32 // in the high level controllers that form the backbone of the 33 // Kubernetes control plane. Look at those for examples, or the 34 // example in 35 // https://github.com/kubernetes/client-go/tree/master/examples/workqueue 36 // . 37 38 // Config contains all the settings for one of these low-level controllers. 39 type Config struct { 40 // The queue for your objects - has to be a DeltaFIFO due to 41 // assumptions in the implementation. Your Process() function 42 // should accept the output of this Queue's Pop() method. 43 Queue 44 45 // Something that can list and watch your objects. 46 ListerWatcher 47 48 // Something that can process a popped Deltas. 49 Process ProcessFunc 50 51 // ObjectType is an example object of the type this controller is 52 // expected to handle. Only the type needs to be right, except 53 // that when that is `unstructured.Unstructured` the object's 54 // `"apiVersion"` and `"kind"` must also be right. 55 ObjectType runtime.Object 56 57 // FullResyncPeriod is the period at which ShouldResync is considered. 58 FullResyncPeriod time.Duration 59 60 // ShouldResync is periodically used by the reflector to determine 61 // whether to Resync the Queue. If ShouldResync is `nil` or 62 // returns true, it means the reflector should proceed with the 63 // resync. 64 ShouldResync ShouldResyncFunc 65 66 // If true, when Process() returns an error, re-enqueue the object. 67 // TODO: add interface to let you inject a delay/backoff or drop 68 // the object completely if desired. Pass the object in 69 // question to this interface as a parameter. This is probably moot 70 // now that this functionality appears at a higher level. 71 RetryOnError bool 72 73 // Called whenever the ListAndWatch drops the connection with an error. 74 WatchErrorHandler WatchErrorHandler 75 76 // WatchListPageSize is the requested chunk size of initial and relist watch lists. 77 WatchListPageSize int64 78 } 79 80 // ShouldResyncFunc is a type of function that indicates if a reflector should perform a 81 // resync or not. It can be used by a shared informer to support multiple event handlers with custom 82 // resync periods. 83 type ShouldResyncFunc func() bool 84 85 // ProcessFunc processes a single object. 86 type ProcessFunc func(obj interface{}) error 87 88 // `*controller` implements Controller 89 type controller struct { 90 config Config 91 reflector *Reflector 92 reflectorMutex sync.RWMutex 93 clock clock.Clock 94 } 95 96 // Controller is a low-level controller that is parameterized by a 97 // Config and used in sharedIndexInformer. 98 type Controller interface { 99 // Run does two things. One is to construct and run a Reflector 100 // to pump objects/notifications from the Config's ListerWatcher 101 // to the Config's Queue and possibly invoke the occasional Resync 102 // on that Queue. The other is to repeatedly Pop from the Queue 103 // and process with the Config's ProcessFunc. Both of these 104 // continue until `stopCh` is closed. 105 Run(stopCh <-chan struct{}) 106 107 // HasSynced delegates to the Config's Queue 108 HasSynced() bool 109 110 // LastSyncResourceVersion delegates to the Reflector when there 111 // is one, otherwise returns the empty string 112 LastSyncResourceVersion() string 113 } 114 115 // New makes a new Controller from the given Config. 116 func New(c *Config) Controller { 117 ctlr := &controller{ 118 config: *c, 119 clock: &clock.RealClock{}, 120 } 121 return ctlr 122 } 123 124 // Run begins processing items, and will continue until a value is sent down stopCh or it is closed. 125 // It's an error to call Run more than once. 126 // Run blocks; call via go. 127 func (c *controller) Run(stopCh <-chan struct{}) { 128 defer utilruntime.HandleCrash() 129 go func() { 130 <-stopCh 131 c.config.Queue.Close() 132 }() 133 r := NewReflector( 134 c.config.ListerWatcher, 135 c.config.ObjectType, 136 c.config.Queue, 137 c.config.FullResyncPeriod, 138 ) 139 r.ShouldResync = c.config.ShouldResync 140 r.WatchListPageSize = c.config.WatchListPageSize 141 r.clock = c.clock 142 if c.config.WatchErrorHandler != nil { 143 r.watchErrorHandler = c.config.WatchErrorHandler 144 } 145 146 c.reflectorMutex.Lock() 147 c.reflector = r 148 c.reflectorMutex.Unlock() 149 150 var wg wait.Group 151 152 wg.StartWithChannel(stopCh, r.Run) 153 154 wait.Until(c.processLoop, time.Second, stopCh) 155 wg.Wait() 156 } 157 158 // Returns true once this controller has completed an initial resource listing 159 func (c *controller) HasSynced() bool { 160 return c.config.Queue.HasSynced() 161 } 162 163 func (c *controller) LastSyncResourceVersion() string { 164 c.reflectorMutex.RLock() 165 defer c.reflectorMutex.RUnlock() 166 if c.reflector == nil { 167 return "" 168 } 169 return c.reflector.LastSyncResourceVersion() 170 } 171 172 // processLoop drains the work queue. 173 // TODO: Consider doing the processing in parallel. This will require a little thought 174 // to make sure that we don't end up processing the same object multiple times 175 // concurrently. 176 // 177 // TODO: Plumb through the stopCh here (and down to the queue) so that this can 178 // actually exit when the controller is stopped. Or just give up on this stuff 179 // ever being stoppable. Converting this whole package to use Context would 180 // also be helpful. 181 func (c *controller) processLoop() { 182 for { 183 obj, err := c.config.Queue.Pop(PopProcessFunc(c.config.Process)) 184 if err != nil { 185 if err == ErrFIFOClosed { 186 return 187 } 188 if c.config.RetryOnError { 189 // This is the safe way to re-enqueue. 190 c.config.Queue.AddIfNotPresent(obj) 191 } 192 } 193 } 194 } 195 196 // ResourceEventHandler can handle notifications for events that 197 // happen to a resource. The events are informational only, so you 198 // can't return an error. The handlers MUST NOT modify the objects 199 // received; this concerns not only the top level of structure but all 200 // the data structures reachable from it. 201 // * OnAdd is called when an object is added. 202 // * OnUpdate is called when an object is modified. Note that oldObj is the 203 // last known state of the object-- it is possible that several changes 204 // were combined together, so you can't use this to see every single 205 // change. OnUpdate is also called when a re-list happens, and it will 206 // get called even if nothing changed. This is useful for periodically 207 // evaluating or syncing something. 208 // * OnDelete will get the final state of the item if it is known, otherwise 209 // it will get an object of type DeletedFinalStateUnknown. This can 210 // happen if the watch is closed and misses the delete event and we don't 211 // notice the deletion until the subsequent re-list. 212 type ResourceEventHandler interface { 213 OnAdd(obj interface{}) 214 OnUpdate(oldObj, newObj interface{}) 215 OnDelete(obj interface{}) 216 } 217 218 // ResourceEventHandlerFuncs is an adaptor to let you easily specify as many or 219 // as few of the notification functions as you want while still implementing 220 // ResourceEventHandler. This adapter does not remove the prohibition against 221 // modifying the objects. 222 type ResourceEventHandlerFuncs struct { 223 AddFunc func(obj interface{}) 224 UpdateFunc func(oldObj, newObj interface{}) 225 DeleteFunc func(obj interface{}) 226 } 227 228 // OnAdd calls AddFunc if it's not nil. 229 func (r ResourceEventHandlerFuncs) OnAdd(obj interface{}) { 230 if r.AddFunc != nil { 231 r.AddFunc(obj) 232 } 233 } 234 235 // OnUpdate calls UpdateFunc if it's not nil. 236 func (r ResourceEventHandlerFuncs) OnUpdate(oldObj, newObj interface{}) { 237 if r.UpdateFunc != nil { 238 r.UpdateFunc(oldObj, newObj) 239 } 240 } 241 242 // OnDelete calls DeleteFunc if it's not nil. 243 func (r ResourceEventHandlerFuncs) OnDelete(obj interface{}) { 244 if r.DeleteFunc != nil { 245 r.DeleteFunc(obj) 246 } 247 } 248 249 // FilteringResourceEventHandler applies the provided filter to all events coming 250 // in, ensuring the appropriate nested handler method is invoked. An object 251 // that starts passing the filter after an update is considered an add, and an 252 // object that stops passing the filter after an update is considered a delete. 253 // Like the handlers, the filter MUST NOT modify the objects it is given. 254 type FilteringResourceEventHandler struct { 255 FilterFunc func(obj interface{}) bool 256 Handler ResourceEventHandler 257 } 258 259 // OnAdd calls the nested handler only if the filter succeeds 260 func (r FilteringResourceEventHandler) OnAdd(obj interface{}) { 261 if !r.FilterFunc(obj) { 262 return 263 } 264 r.Handler.OnAdd(obj) 265 } 266 267 // OnUpdate ensures the proper handler is called depending on whether the filter matches 268 func (r FilteringResourceEventHandler) OnUpdate(oldObj, newObj interface{}) { 269 newer := r.FilterFunc(newObj) 270 older := r.FilterFunc(oldObj) 271 switch { 272 case newer && older: 273 r.Handler.OnUpdate(oldObj, newObj) 274 case newer && !older: 275 r.Handler.OnAdd(newObj) 276 case !newer && older: 277 r.Handler.OnDelete(oldObj) 278 default: 279 // do nothing 280 } 281 } 282 283 // OnDelete calls the nested handler only if the filter succeeds 284 func (r FilteringResourceEventHandler) OnDelete(obj interface{}) { 285 if !r.FilterFunc(obj) { 286 return 287 } 288 r.Handler.OnDelete(obj) 289 } 290 291 // DeletionHandlingMetaNamespaceKeyFunc checks for 292 // DeletedFinalStateUnknown objects before calling 293 // MetaNamespaceKeyFunc. 294 func DeletionHandlingMetaNamespaceKeyFunc(obj interface{}) (string, error) { 295 if d, ok := obj.(DeletedFinalStateUnknown); ok { 296 return d.Key, nil 297 } 298 return MetaNamespaceKeyFunc(obj) 299 } 300 301 // NewInformer returns a Store and a controller for populating the store 302 // while also providing event notifications. You should only used the returned 303 // Store for Get/List operations; Add/Modify/Deletes will cause the event 304 // notifications to be faulty. 305 // 306 // Parameters: 307 // * lw is list and watch functions for the source of the resource you want to 308 // be informed of. 309 // * objType is an object of the type that you expect to receive. 310 // * resyncPeriod: if non-zero, will re-list this often (you will get OnUpdate 311 // calls, even if nothing changed). Otherwise, re-list will be delayed as 312 // long as possible (until the upstream source closes the watch or times out, 313 // or you stop the controller). 314 // * h is the object you want notifications sent to. 315 // 316 func NewInformer( 317 lw ListerWatcher, 318 objType runtime.Object, 319 resyncPeriod time.Duration, 320 h ResourceEventHandler, 321 ) (Store, Controller) { 322 // This will hold the client state, as we know it. 323 clientState := NewStore(DeletionHandlingMetaNamespaceKeyFunc) 324 325 return clientState, newInformer(lw, objType, resyncPeriod, h, clientState) 326 } 327 328 // NewIndexerInformer returns an Indexer and a Controller for populating the index 329 // while also providing event notifications. You should only used the returned 330 // Index for Get/List operations; Add/Modify/Deletes will cause the event 331 // notifications to be faulty. 332 // 333 // Parameters: 334 // * lw is list and watch functions for the source of the resource you want to 335 // be informed of. 336 // * objType is an object of the type that you expect to receive. 337 // * resyncPeriod: if non-zero, will re-list this often (you will get OnUpdate 338 // calls, even if nothing changed). Otherwise, re-list will be delayed as 339 // long as possible (until the upstream source closes the watch or times out, 340 // or you stop the controller). 341 // * h is the object you want notifications sent to. 342 // * indexers is the indexer for the received object type. 343 // 344 func NewIndexerInformer( 345 lw ListerWatcher, 346 objType runtime.Object, 347 resyncPeriod time.Duration, 348 h ResourceEventHandler, 349 indexers Indexers, 350 ) (Indexer, Controller) { 351 // This will hold the client state, as we know it. 352 clientState := NewIndexer(DeletionHandlingMetaNamespaceKeyFunc, indexers) 353 354 return clientState, newInformer(lw, objType, resyncPeriod, h, clientState) 355 } 356 357 // newInformer returns a controller for populating the store while also 358 // providing event notifications. 359 // 360 // Parameters 361 // * lw is list and watch functions for the source of the resource you want to 362 // be informed of. 363 // * objType is an object of the type that you expect to receive. 364 // * resyncPeriod: if non-zero, will re-list this often (you will get OnUpdate 365 // calls, even if nothing changed). Otherwise, re-list will be delayed as 366 // long as possible (until the upstream source closes the watch or times out, 367 // or you stop the controller). 368 // * h is the object you want notifications sent to. 369 // * clientState is the store you want to populate 370 // 371 func newInformer( 372 lw ListerWatcher, 373 objType runtime.Object, 374 resyncPeriod time.Duration, 375 h ResourceEventHandler, 376 clientState Store, 377 ) Controller { 378 // This will hold incoming changes. Note how we pass clientState in as a 379 // KeyLister, that way resync operations will result in the correct set 380 // of update/delete deltas. 381 fifo := NewDeltaFIFOWithOptions(DeltaFIFOOptions{ 382 KnownObjects: clientState, 383 EmitDeltaTypeReplaced: true, 384 }) 385 386 cfg := &Config{ 387 Queue: fifo, 388 ListerWatcher: lw, 389 ObjectType: objType, 390 FullResyncPeriod: resyncPeriod, 391 RetryOnError: false, 392 393 Process: func(obj interface{}) error { 394 // from oldest to newest 395 for _, d := range obj.(Deltas) { 396 switch d.Type { 397 case Sync, Replaced, Added, Updated: 398 if old, exists, err := clientState.Get(d.Object); err == nil && exists { 399 if err := clientState.Update(d.Object); err != nil { 400 return err 401 } 402 h.OnUpdate(old, d.Object) 403 } else { 404 if err := clientState.Add(d.Object); err != nil { 405 return err 406 } 407 h.OnAdd(d.Object) 408 } 409 case Deleted: 410 if err := clientState.Delete(d.Object); err != nil { 411 return err 412 } 413 h.OnDelete(d.Object) 414 } 415 } 416 return nil 417 }, 418 } 419 return New(cfg) 420 }