github.com/cilium/statedb@v0.3.2/reconciler/builder.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package reconciler 5 6 import ( 7 "time" 8 9 "github.com/cilium/hive/job" 10 "github.com/cilium/statedb" 11 "github.com/cilium/statedb/index" 12 "golang.org/x/time/rate" 13 ) 14 15 // Register creates a new reconciler and registers it to the application lifecycle. 16 // 17 // The setStatus etc. functions are passed in as arguments rather than requiring 18 // the object to implement them via interface as this allows constructing multiple 19 // reconcilers for a single object by having multiple status fields and different 20 // functions for manipulating them. 21 func Register[Obj comparable]( 22 // General dependencies of the reconciler. 23 params Params, 24 // The table to reconcile 25 table statedb.RWTable[Obj], 26 27 // Function for cloning the object. 28 clone func(Obj) Obj, 29 30 // Function for setting the status. 31 setStatus func(Obj, Status) Obj, 32 33 // Function for getting the status. 34 getStatus func(Obj) Status, 35 36 // Reconciliation operations 37 ops Operations[Obj], 38 39 // (Optional) batch operations. Set to nil if not available. 40 batchOps BatchOperations[Obj], 41 42 // zero or more options to override defaults. 43 options ...Option, 44 ) (Reconciler[Obj], error) { 45 cfg := config[Obj]{ 46 Table: table, 47 GetObjectStatus: getStatus, 48 SetObjectStatus: setStatus, 49 CloneObject: clone, 50 Operations: ops, 51 BatchOperations: batchOps, 52 options: defaultOptions(), 53 } 54 for _, opt := range options { 55 opt(&cfg.options) 56 } 57 58 if cfg.Metrics == nil { 59 if params.DefaultMetrics == nil { 60 cfg.Metrics = NewUnpublishedExpVarMetrics() 61 } else { 62 cfg.Metrics = params.DefaultMetrics 63 } 64 } 65 66 if err := cfg.validate(); err != nil { 67 return nil, err 68 } 69 70 idx := cfg.Table.PrimaryIndexer() 71 objectToKey := func(o any) index.Key { 72 return idx.ObjectToKey(o.(Obj)) 73 } 74 r := &reconciler[Obj]{ 75 Params: params, 76 config: cfg, 77 retries: newRetries(cfg.RetryBackoffMinDuration, cfg.RetryBackoffMaxDuration, objectToKey), 78 externalPruneTrigger: make(chan struct{}, 1), 79 primaryIndexer: idx, 80 } 81 82 g := params.Jobs.NewGroup(params.Health) 83 84 g.Add(job.OneShot("reconcile", r.reconcileLoop)) 85 if r.config.RefreshInterval > 0 { 86 g.Add(job.OneShot("refresh", r.refreshLoop)) 87 } 88 params.Lifecycle.Append(g) 89 90 return r, nil 91 } 92 93 // Option for the reconciler 94 type Option func(opts *options) 95 96 // WithMetrics sets the [Metrics] instance to use with this reconciler. 97 // The metrics capture the duration of operations during incremental and 98 // full reconcilation and the errors that occur during either. 99 // 100 // If this option is not used, then the default metrics instance is used. 101 func WithMetrics(m Metrics) Option { 102 return func(opts *options) { 103 opts.Metrics = m 104 } 105 } 106 107 // WithPruning enables periodic pruning (calls to Prune() operation) 108 // [interval] is the interval at which Prune() is called to prune 109 // unexpected objects in the target system. 110 // Prune() will not be called before the table has been fully initialized 111 // (Initialized() returns true). 112 // A single Prune() can be forced via the [Reconciler.Prune] method regardless 113 // if pruning has been enabled. 114 // 115 // Pruning is enabled by default. See [config.go] for the default interval. 116 func WithPruning(interval time.Duration) Option { 117 return func(opts *options) { 118 opts.PruneInterval = interval 119 } 120 } 121 122 // WithoutPruning disabled periodic pruning. 123 func WithoutPruning() Option { 124 return func(opts *options) { 125 opts.PruneInterval = 0 126 } 127 } 128 129 // WithRefreshing enables periodic refreshes of objects. 130 // [interval] is the interval at which the objects are refreshed, 131 // e.g. how often Update() should be called to refresh an object even 132 // when it has not changed. This is implemented by periodically setting 133 // all objects that have not been updated for [RefreshInterval] or longer 134 // as pending. 135 // [limiter] is the rate-limiter for controlling the rate at which the 136 // objects are marked pending. 137 // 138 // Refreshing is disabled by default. 139 func WithRefreshing(interval time.Duration, limiter *rate.Limiter) Option { 140 return func(opts *options) { 141 opts.RefreshInterval = interval 142 opts.RefreshRateLimiter = limiter 143 } 144 } 145 146 // WithRetry sets the minimum and maximum amount of time to wait before 147 // retrying a failed Update() or Delete() operation on an object. 148 // The retry wait time for an object will increase exponentially on 149 // subsequent failures until [maxBackoff] is reached. 150 func WithRetry(minBackoff, maxBackoff time.Duration) Option { 151 return func(opts *options) { 152 opts.RetryBackoffMinDuration = minBackoff 153 opts.RetryBackoffMaxDuration = maxBackoff 154 } 155 } 156 157 // WithRoundLimits sets the reconciliation round size and rate limit. 158 // [numObjects] limits how many objects are reconciled per round before 159 // updating their status. A high number will delay status updates and increase 160 // latency for those watching the object reconciliation status. A low value 161 // increases the overhead of the status committing and reduces effectiveness 162 // of the batch operations (smaller batch sizes). 163 // [limiter] is used to limit the number of rounds per second to allow a larger 164 // batch to build up and to avoid reconciliation of intermediate object states. 165 func WithRoundLimits(numObjects int, limiter *rate.Limiter) Option { 166 return func(opts *options) { 167 opts.IncrementalRoundSize = numObjects 168 opts.RateLimiter = limiter 169 } 170 }