k8s.io/apiserver@v0.31.1/pkg/admission/plugin/policy/internal/generic/controller.go (about) 1 /* 2 Copyright 2022 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 generic 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "sync" 24 "sync/atomic" 25 "time" 26 27 kerrors "k8s.io/apimachinery/pkg/api/errors" 28 "k8s.io/apimachinery/pkg/api/meta" 29 "k8s.io/apimachinery/pkg/runtime" 30 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 31 32 "k8s.io/apimachinery/pkg/util/wait" 33 "k8s.io/client-go/tools/cache" 34 "k8s.io/client-go/tools/cache/synctrack" 35 "k8s.io/client-go/util/workqueue" 36 "k8s.io/klog/v2" 37 ) 38 39 var _ Controller[runtime.Object] = &controller[runtime.Object]{} 40 41 type controller[T runtime.Object] struct { 42 informer Informer[T] 43 queue workqueue.TypedRateLimitingInterface[string] 44 45 // Returns an error if there was a transient error during reconciliation 46 // and the object should be tried again later. 47 reconciler func(namespace, name string, newObj T) error 48 49 options ControllerOptions 50 51 // must hold a func() bool or nil 52 notificationsDelivered atomic.Value 53 54 hasProcessed synctrack.AsyncTracker[string] 55 } 56 57 type ControllerOptions struct { 58 Name string 59 Workers uint 60 } 61 62 func (c *controller[T]) Informer() Informer[T] { 63 return c.informer 64 } 65 66 func NewController[T runtime.Object]( 67 informer Informer[T], 68 reconciler func(namepace, name string, newObj T) error, 69 options ControllerOptions, 70 ) Controller[T] { 71 if options.Workers == 0 { 72 options.Workers = 2 73 } 74 75 if len(options.Name) == 0 { 76 options.Name = fmt.Sprintf("%T-controller", *new(T)) 77 } 78 79 c := &controller[T]{ 80 options: options, 81 informer: informer, 82 reconciler: reconciler, 83 queue: nil, 84 } 85 c.hasProcessed.UpstreamHasSynced = func() bool { 86 f := c.notificationsDelivered.Load() 87 if f == nil { 88 return false 89 } 90 return f.(func() bool)() 91 } 92 return c 93 } 94 95 // Runs the controller and returns an error explaining why running was stopped. 96 // Reconciliation ends as soon as the context completes. If there are events 97 // waiting to be processed at that itme, they will be dropped. 98 func (c *controller[T]) Run(ctx context.Context) error { 99 klog.Infof("starting %s", c.options.Name) 100 defer klog.Infof("stopping %s", c.options.Name) 101 102 c.queue = workqueue.NewTypedRateLimitingQueueWithConfig( 103 workqueue.DefaultTypedControllerRateLimiter[string](), 104 workqueue.TypedRateLimitingQueueConfig[string]{Name: c.options.Name}, 105 ) 106 107 // Forcefully shutdown workqueue. Drop any enqueued items. 108 // Important to do this in a `defer` at the start of `Run`. 109 // Otherwise, if there are any early returns without calling this, we 110 // would never shut down the workqueue 111 defer c.queue.ShutDown() 112 113 enqueue := func(obj interface{}, isInInitialList bool) { 114 var key string 115 var err error 116 if key, err = cache.DeletionHandlingMetaNamespaceKeyFunc(obj); err != nil { 117 utilruntime.HandleError(err) 118 return 119 } 120 if isInInitialList { 121 c.hasProcessed.Start(key) 122 } 123 124 c.queue.Add(key) 125 } 126 127 registration, err := c.informer.AddEventHandler(cache.ResourceEventHandlerDetailedFuncs{ 128 AddFunc: enqueue, 129 UpdateFunc: func(oldObj, newObj interface{}) { 130 oldMeta, err1 := meta.Accessor(oldObj) 131 newMeta, err2 := meta.Accessor(newObj) 132 133 if err1 != nil || err2 != nil { 134 if err1 != nil { 135 utilruntime.HandleError(err1) 136 } 137 138 if err2 != nil { 139 utilruntime.HandleError(err2) 140 } 141 return 142 } else if oldMeta.GetResourceVersion() == newMeta.GetResourceVersion() { 143 if len(oldMeta.GetResourceVersion()) == 0 { 144 klog.Warningf("%v throwing out update with empty RV. this is likely to happen if a test did not supply a resource version on an updated object", c.options.Name) 145 } 146 return 147 } 148 149 enqueue(newObj, false) 150 }, 151 DeleteFunc: func(obj interface{}) { 152 // Enqueue 153 enqueue(obj, false) 154 }, 155 }) 156 157 // Error might be raised if informer was started and stopped already 158 if err != nil { 159 return err 160 } 161 162 c.notificationsDelivered.Store(registration.HasSynced) 163 164 // Make sure event handler is removed from informer in case return early from 165 // an error 166 defer func() { 167 c.notificationsDelivered.Store(func() bool { return false }) 168 // Remove event handler and Handle Error here. Error should only be raised 169 // for improper usage of event handler API. 170 if err := c.informer.RemoveEventHandler(registration); err != nil { 171 utilruntime.HandleError(err) 172 } 173 }() 174 175 // Wait for initial cache list to complete before beginning to reconcile 176 // objects. 177 if !cache.WaitForNamedCacheSync(c.options.Name, ctx.Done(), c.informer.HasSynced) { 178 // ctx cancelled during cache sync. return early 179 err := ctx.Err() 180 if err == nil { 181 // if context wasnt cancelled then the sync failed for another reason 182 err = errors.New("cache sync failed") 183 } 184 return err 185 } 186 187 waitGroup := sync.WaitGroup{} 188 189 for i := uint(0); i < c.options.Workers; i++ { 190 waitGroup.Add(1) 191 go func() { 192 defer waitGroup.Done() 193 wait.Until(c.runWorker, time.Second, ctx.Done()) 194 }() 195 } 196 197 klog.Infof("Started %v workers for %v", c.options.Workers, c.options.Name) 198 199 // Wait for context cancel. 200 <-ctx.Done() 201 202 // Forcefully shutdown workqueue. Drop any enqueued items. 203 c.queue.ShutDown() 204 205 // Workqueue shutdown signals for workers to stop. Wait for all workers to 206 // clean up 207 waitGroup.Wait() 208 209 // Only way for workers to ever stop is for caller to cancel the context 210 return ctx.Err() 211 } 212 213 func (c *controller[T]) HasSynced() bool { 214 return c.hasProcessed.HasSynced() 215 } 216 217 func (c *controller[T]) runWorker() { 218 for { 219 key, shutdown := c.queue.Get() 220 if shutdown { 221 return 222 } 223 224 // We wrap this block in a func so we can defer c.workqueue.Done. 225 err := func(obj string) error { 226 // We call Done here so the workqueue knows we have finished 227 // processing this item. We also must remember to call Forget if we 228 // do not want this work item being re-queued. For example, we do 229 // not call Forget if a transient error occurs, instead the item is 230 // put back on the workqueue and attempted again after a back-off 231 // period. 232 defer c.queue.Done(obj) 233 defer c.hasProcessed.Finished(key) 234 235 if err := c.reconcile(key); err != nil { 236 // Put the item back on the workqueue to handle any transient errors. 237 c.queue.AddRateLimited(key) 238 return fmt.Errorf("error syncing '%s': %s, requeuing", key, err.Error()) 239 } 240 // Finally, if no error occurs we Forget this item so it is allowed 241 // to be re-enqueued without a long rate limit 242 c.queue.Forget(obj) 243 klog.V(4).Infof("syncAdmissionPolicy(%q)", key) 244 return nil 245 }(key) 246 247 if err != nil { 248 utilruntime.HandleError(err) 249 } 250 } 251 } 252 253 func (c *controller[T]) reconcile(key string) error { 254 var newObj T 255 var err error 256 var namespace string 257 var name string 258 var lister NamespacedLister[T] 259 260 // Convert the namespace/name string into a distinct namespace and name 261 namespace, name, err = cache.SplitMetaNamespaceKey(key) 262 if err != nil { 263 utilruntime.HandleError(fmt.Errorf("invalid resource key: %s", key)) 264 return nil 265 } 266 267 if len(namespace) > 0 { 268 lister = c.informer.Namespaced(namespace) 269 } else { 270 lister = c.informer 271 } 272 273 newObj, err = lister.Get(name) 274 if err != nil { 275 if !kerrors.IsNotFound(err) { 276 return err 277 } 278 279 // Deleted object. Inform reconciler with empty 280 } 281 282 return c.reconciler(namespace, name, newObj) 283 }