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  }