sigs.k8s.io/cluster-api-provider-azure@v1.14.3/pkg/coalescing/reconciler.go (about) 1 /* 2 Copyright 2021 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 coalescing 18 19 import ( 20 "context" 21 "time" 22 23 "github.com/go-logr/logr" 24 "github.com/pkg/errors" 25 "sigs.k8s.io/cluster-api-provider-azure/util/cache/ttllru" 26 "sigs.k8s.io/cluster-api-provider-azure/util/tele" 27 "sigs.k8s.io/controller-runtime/pkg/reconcile" 28 ) 29 30 type ( 31 // ReconcileCache uses and underlying time to live last recently used cache to track high frequency requests. 32 // A reconciler should call ShouldProcess to determine if the key has expired. If the key has expired, a zero value 33 // time.Time and true is returned. If the key has not expired, the expiration and false is returned. Upon successful 34 // reconciliation a reconciler should call Reconciled to update the cache expiry. 35 ReconcileCache struct { 36 lastSuccessfulReconciliationCache ttllru.PeekingCacher 37 } 38 39 // ReconcileCacher describes an interface for determining if a request should be reconciled through a call to 40 // ShouldProcess and if ok, reset the cool down through a call to Reconciled. 41 ReconcileCacher interface { 42 ShouldProcess(key string) (expiration time.Time, ok bool) 43 Reconciled(key string) 44 } 45 46 // reconciler is the caching reconciler middleware that uses the cache. 47 reconciler struct { 48 upstream reconcile.Reconciler 49 cache ReconcileCacher 50 log logr.Logger 51 } 52 ) 53 54 // NewRequestCache creates a new instance of a ReconcileCache given a specified window of expiration. 55 func NewRequestCache(window time.Duration) (*ReconcileCache, error) { 56 cache, err := ttllru.New(1024, window) 57 if err != nil { 58 return nil, errors.Wrap(err, "failed to build ttllru cache") 59 } 60 61 return &ReconcileCache{ 62 lastSuccessfulReconciliationCache: cache, 63 }, nil 64 } 65 66 // ShouldProcess determines if the key has expired. If the key has expired, a zero value 67 // time.Time and true is returned. If the key has not expired, the expiration and false is returned. 68 func (cache *ReconcileCache) ShouldProcess(key string) (time.Time, bool) { 69 _, expiration, ok := cache.lastSuccessfulReconciliationCache.Peek(key) 70 return expiration, !ok 71 } 72 73 // Reconciled updates the cache expiry for a given key. 74 func (cache *ReconcileCache) Reconciled(key string) { 75 cache.lastSuccessfulReconciliationCache.Add(key, nil) 76 } 77 78 // NewReconciler returns a reconcile wrapper that will delay new reconcile.Requests 79 // after the cache expiry of the request string key. 80 // A successful reconciliation is defined as one where no error is returned. 81 func NewReconciler(upstream reconcile.Reconciler, cache ReconcileCacher, log logr.Logger) reconcile.Reconciler { 82 return &reconciler{ 83 upstream: upstream, 84 cache: cache, 85 log: log.WithName("CoalescingReconciler"), 86 } 87 } 88 89 // Reconcile sends a request to the upstream reconciler if the request is outside of the debounce window. 90 func (rc *reconciler) Reconcile(ctx context.Context, r reconcile.Request) (reconcile.Result, error) { 91 ctx, log, done := tele.StartSpanWithLogger(ctx, "controllers.reconciler.Reconcile", 92 tele.KVP("namespace", r.Namespace), 93 tele.KVP("name", r.Name), 94 ) 95 defer done() 96 97 log = log.WithValues("request", r.String()) 98 99 if expiration, ok := rc.cache.ShouldProcess(r.String()); !ok { 100 log.V(4).Info("not processing", "expiration", expiration, "timeUntil", time.Until(expiration)) 101 var requeueAfter = time.Until(expiration) 102 if requeueAfter < 1*time.Second { 103 requeueAfter = 1 * time.Second 104 } 105 return reconcile.Result{RequeueAfter: requeueAfter}, nil 106 } 107 108 log.V(4).Info("processing") 109 result, err := rc.upstream.Reconcile(ctx, r) 110 if err != nil { 111 log.V(4).Info("not successful") 112 return result, err 113 } 114 115 log.V(4).Info("successful") 116 rc.cache.Reconciled(r.String()) 117 return result, nil 118 }