@@ -61,6 +61,8 @@ const (
61
61
// MessageResourceSynced is the message used for an Event fired when a Foo
62
62
// is synced successfully
63
63
MessageResourceSynced = "Foo synced successfully"
64
+ // FieldManager distinguishes this controller from other things writing to API objects
65
+ FieldManager = controllerAgentName
64
66
)
65
67
66
68
// Controller is the controller implementation for Foo resources
@@ -80,7 +82,7 @@ type Controller struct {
80
82
// means we can ensure we only process a fixed amount of resources at a
81
83
// time, and makes it easy to ensure we are never processing the same item
82
84
// simultaneously in two different workers.
83
- workqueue workqueue.TypedRateLimitingInterface [string ]
85
+ workqueue workqueue.TypedRateLimitingInterface [cache. ObjectName ]
84
86
// recorder is an event recorder for recording Event resources to the
85
87
// Kubernetes API.
86
88
recorder record.EventRecorder
@@ -106,8 +108,8 @@ func NewController(
106
108
eventBroadcaster .StartRecordingToSink (& typedcorev1.EventSinkImpl {Interface : kubeclientset .CoreV1 ().Events ("" )})
107
109
recorder := eventBroadcaster .NewRecorder (scheme .Scheme , corev1.EventSource {Component : controllerAgentName })
108
110
ratelimiter := workqueue .NewTypedMaxOfRateLimiter (
109
- workqueue .NewTypedItemExponentialFailureRateLimiter [string ](5 * time .Millisecond , 1000 * time .Second ),
110
- & workqueue.TypedBucketRateLimiter [string ]{Limiter : rate .NewLimiter (rate .Limit (50 ), 300 )},
111
+ workqueue .NewTypedItemExponentialFailureRateLimiter [cache. ObjectName ](5 * time .Millisecond , 1000 * time .Second ),
112
+ & workqueue.TypedBucketRateLimiter [cache. ObjectName ]{Limiter : rate .NewLimiter (rate .Limit (50 ), 300 )},
111
113
)
112
114
113
115
controller := & Controller {
@@ -196,64 +198,56 @@ func (c *Controller) runWorker(ctx context.Context) {
196
198
// processNextWorkItem will read a single work item off the workqueue and
197
199
// attempt to process it, by calling the syncHandler.
198
200
func (c * Controller ) processNextWorkItem (ctx context.Context ) bool {
199
- obj , shutdown := c .workqueue .Get ()
201
+ objRef , shutdown := c .workqueue .Get ()
200
202
logger := klog .FromContext (ctx )
201
203
202
204
if shutdown {
203
205
return false
204
206
}
205
207
206
- // We wrap this block in a func so we can defer c.workqueue.Done.
207
- err := func () error {
208
- // We call Done here so the workqueue knows we have finished
209
- // processing this item. We also must remember to call Forget if we
210
- // do not want this work item being re-queued. For example, we do
211
- // not call Forget if a transient error occurs, instead the item is
212
- // put back on the workqueue and attempted again after a back-off
213
- // period.
214
- defer c .workqueue .Done (obj )
215
- // Run the syncHandler, passing it the namespace/name string of the
216
- // Foo resource to be synced.
217
- if err := c .syncHandler (ctx , obj ); err != nil {
218
- // Put the item back on the workqueue to handle any transient errors.
219
- c .workqueue .AddRateLimited (obj )
220
- return fmt .Errorf ("error syncing '%s': %s, requeuing" , obj , err .Error ())
221
- }
222
- // Finally, if no error occurs we Forget this item so it does not
208
+ // We call Done at the end of this func so the workqueue knows we have
209
+ // finished processing this item. We also must remember to call Forget
210
+ // if we do not want this work item being re-queued. For example, we do
211
+ // not call Forget if a transient error occurs, instead the item is
212
+ // put back on the workqueue and attempted again after a back-off
213
+ // period.
214
+ defer c .workqueue .Done (objRef )
215
+
216
+ // Run the syncHandler, passing it the structured reference to the object to be synced.
217
+ err := c .syncHandler (ctx , objRef )
218
+ if err == nil {
219
+ // If no error occurs then we Forget this item so it does not
223
220
// get queued again until another change happens.
224
- c .workqueue .Forget (obj )
225
- logger .Info ("Successfully synced" , "resourceName" , obj )
226
- return nil
227
- }()
228
-
229
- if err != nil {
230
- utilruntime .HandleError (err )
221
+ c .workqueue .Forget (objRef )
222
+ logger .Info ("Successfully synced" , "objectName" , objRef )
231
223
return true
232
224
}
233
-
225
+ // there was a failure so be sure to report it. This method allows for
226
+ // pluggable error handling which can be used for things like
227
+ // cluster-monitoring.
228
+ utilruntime .HandleErrorWithContext (ctx , err , "Error syncing; requeuing for later retry" , "objectReference" , objRef )
229
+ // since we failed, we should requeue the item to work on later. This
230
+ // method will add a backoff to avoid hotlooping on particular items
231
+ // (they're probably still not going to work right away) and overall
232
+ // controller protection (everything I've done is broken, this controller
233
+ // needs to calm down or it can starve other useful work) cases.
234
+ c .workqueue .AddRateLimited (objRef )
234
235
return true
235
236
}
236
237
237
238
// syncHandler compares the actual state with the desired, and attempts to
238
239
// converge the two. It then updates the Status block of the Foo resource
239
240
// with the current status of the resource.
240
- func (c * Controller ) syncHandler (ctx context.Context , key string ) error {
241
- // Convert the namespace/name string into a distinct namespace and name
242
- logger := klog .LoggerWithValues (klog .FromContext (ctx ), "resourceName" , key )
243
-
244
- namespace , name , err := cache .SplitMetaNamespaceKey (key )
245
- if err != nil {
246
- utilruntime .HandleError (fmt .Errorf ("invalid resource key: %s" , key ))
247
- return nil
248
- }
241
+ func (c * Controller ) syncHandler (ctx context.Context , objectRef cache.ObjectName ) error {
242
+ logger := klog .LoggerWithValues (klog .FromContext (ctx ), "objectRef" , objectRef )
249
243
250
244
// Get the Foo resource with this namespace/name
251
- foo , err := c .foosLister .Foos (namespace ).Get (name )
245
+ foo , err := c .foosLister .Foos (objectRef . Namespace ).Get (objectRef . Name )
252
246
if err != nil {
253
247
// The Foo resource may no longer exist, in which case we stop
254
248
// processing.
255
249
if errors .IsNotFound (err ) {
256
- utilruntime .HandleError ( fmt . Errorf ( "foo '%s' in work queue no longer exists" , key ) )
250
+ utilruntime .HandleErrorWithContext ( ctx , err , "Foo referenced by item in work queue no longer exists" , "objectReference" , objectRef )
257
251
return nil
258
252
}
259
253
@@ -265,15 +259,15 @@ func (c *Controller) syncHandler(ctx context.Context, key string) error {
265
259
// We choose to absorb the error here as the worker would requeue the
266
260
// resource otherwise. Instead, the next time the resource is updated
267
261
// the resource will be queued again.
268
- utilruntime .HandleError ( fmt . Errorf ( "%s: deployment name must be specified " , key ) )
262
+ utilruntime .HandleErrorWithContext ( ctx , nil , "Deployment name missing from object reference " , "objectReference" , objectRef )
269
263
return nil
270
264
}
271
265
272
266
// Get the deployment with the name specified in Foo.spec
273
267
deployment , err := c .deploymentsLister .Deployments (foo .Namespace ).Get (deploymentName )
274
268
// If the resource doesn't exist, we'll create it
275
269
if errors .IsNotFound (err ) {
276
- deployment , err = c .kubeclientset .AppsV1 ().Deployments (foo .Namespace ).Create (context .TODO (), newDeployment (foo ), metav1.CreateOptions {})
270
+ deployment , err = c .kubeclientset .AppsV1 ().Deployments (foo .Namespace ).Create (context .TODO (), newDeployment (foo ), metav1.CreateOptions {FieldManager : FieldManager })
277
271
}
278
272
279
273
// If an error occurs during Get/Create, we'll requeue the item so we can
@@ -296,7 +290,7 @@ func (c *Controller) syncHandler(ctx context.Context, key string) error {
296
290
// should update the Deployment resource.
297
291
if foo .Spec .Replicas != nil && * foo .Spec .Replicas != * deployment .Spec .Replicas {
298
292
logger .V (4 ).Info ("Update deployment resource" , "currentReplicas" , * foo .Spec .Replicas , "desiredReplicas" , * deployment .Spec .Replicas )
299
- deployment , err = c .kubeclientset .AppsV1 ().Deployments (foo .Namespace ).Update (context .TODO (), newDeployment (foo ), metav1.UpdateOptions {})
293
+ deployment , err = c .kubeclientset .AppsV1 ().Deployments (foo .Namespace ).Update (context .TODO (), newDeployment (foo ), metav1.UpdateOptions {FieldManager : FieldManager })
300
294
}
301
295
302
296
// If an error occurs during Update, we'll requeue the item so we can
@@ -327,21 +321,20 @@ func (c *Controller) updateFooStatus(foo *samplev1alpha1.Foo, deployment *appsv1
327
321
// we must use Update instead of UpdateStatus to update the Status block of the Foo resource.
328
322
// UpdateStatus will not allow changes to the Spec of the resource,
329
323
// which is ideal for ensuring nothing other than resource status has been updated.
330
- _ , err := c .sampleclientset .SamplecontrollerV1alpha1 ().Foos (foo .Namespace ).UpdateStatus (context .TODO (), fooCopy , metav1.UpdateOptions {})
324
+ _ , err := c .sampleclientset .SamplecontrollerV1alpha1 ().Foos (foo .Namespace ).UpdateStatus (context .TODO (), fooCopy , metav1.UpdateOptions {FieldManager : FieldManager })
331
325
return err
332
326
}
333
327
334
328
// enqueueFoo takes a Foo resource and converts it into a namespace/name
335
329
// string which is then put onto the work queue. This method should *not* be
336
330
// passed resources of any type other than Foo.
337
331
func (c * Controller ) enqueueFoo (obj interface {}) {
338
- var key string
339
- var err error
340
- if key , err = cache .MetaNamespaceKeyFunc (obj ); err != nil {
332
+ if objectRef , err := cache .ObjectToName (obj ); err != nil {
341
333
utilruntime .HandleError (err )
342
334
return
335
+ } else {
336
+ c .workqueue .Add (objectRef )
343
337
}
344
- c .workqueue .Add (key )
345
338
}
346
339
347
340
// handleObject will take any resource implementing metav1.Object and attempt
@@ -356,12 +349,16 @@ func (c *Controller) handleObject(obj interface{}) {
356
349
if object , ok = obj .(metav1.Object ); ! ok {
357
350
tombstone , ok := obj .(cache.DeletedFinalStateUnknown )
358
351
if ! ok {
359
- utilruntime .HandleError (fmt .Errorf ("error decoding object, invalid type" ))
352
+ // If the object value is not too big and does not contain sensitive information then
353
+ // it may be useful to include it.
354
+ utilruntime .HandleErrorWithContext (context .Background (), nil , "Error decoding object, invalid type" , "type" , fmt .Sprintf ("%T" , obj ))
360
355
return
361
356
}
362
357
object , ok = tombstone .Obj .(metav1.Object )
363
358
if ! ok {
364
- utilruntime .HandleError (fmt .Errorf ("error decoding object tombstone, invalid type" ))
359
+ // If the object value is not too big and does not contain sensitive information then
360
+ // it may be useful to include it.
361
+ utilruntime .HandleErrorWithContext (context .Background (), nil , "Error decoding object tombstone, invalid type" , "type" , fmt .Sprintf ("%T" , tombstone .Obj ))
365
362
return
366
363
}
367
364
logger .V (4 ).Info ("Recovered deleted object" , "resourceName" , object .GetName ())
0 commit comments