@@ -18,6 +18,7 @@ package main
1818
1919import (
2020 "context"
21+ "errors"
2122 "flag"
2223 "fmt"
2324 "os"
@@ -26,6 +27,7 @@ import (
2627 "github.com/go-logr/logr"
2728 "github.com/spf13/pflag"
2829 "go.uber.org/zap/zapcore"
30+ "golang.org/x/time/rate"
2931 "k8s.io/apimachinery/pkg/runtime"
3032 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
3133 "k8s.io/client-go/informers"
@@ -35,6 +37,7 @@ import (
3537 _ "k8s.io/client-go/plugin/pkg/client/auth"
3638 "k8s.io/client-go/rest"
3739 "k8s.io/client-go/tools/cache"
40+ "k8s.io/client-go/util/workqueue"
3841 capiv1 "sigs.k8s.io/cluster-api/api/v1beta1"
3942 bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1"
4043 capiflags "sigs.k8s.io/cluster-api/util/flags"
@@ -75,8 +78,54 @@ type managerConfig struct {
7578 concurrentReconcilesNutanixMachine int
7679 diagnosticsOptions capiflags.DiagnosticsOptions
7780
78- logger logr.Logger
79- restConfig * rest.Config
81+ logger logr.Logger
82+ restConfig * rest.Config
83+ rateLimiter workqueue.RateLimiter
84+ }
85+
86+ // compositeRateLimiter will build a limiter similar to the default from DefaultControllerRateLimiter but with custom values.
87+ func compositeRateLimiter (baseDelay , maxDelay time.Duration , bucketSize , qps int ) (workqueue.RateLimiter , error ) {
88+ // Validate the rate limiter configuration
89+ if err := validateRateLimiterConfig (baseDelay , maxDelay , bucketSize , qps ); err != nil {
90+ return nil , err
91+ }
92+ exponentialBackoffLimiter := workqueue .NewItemExponentialFailureRateLimiter (baseDelay , maxDelay )
93+ bucketLimiter := & workqueue.BucketRateLimiter {Limiter : rate .NewLimiter (rate .Limit (qps ), bucketSize )}
94+ return workqueue .NewMaxOfRateLimiter (exponentialBackoffLimiter , bucketLimiter ), nil
95+ }
96+
97+ // validateRateLimiterConfig validates the rate limiter configuration parameters
98+ func validateRateLimiterConfig (baseDelay , maxDelay time.Duration , bucketSize , qps int ) error {
99+ // Check if baseDelay is a non-negative value
100+ if baseDelay < 0 {
101+ return errors .New ("baseDelay cannot be negative" )
102+ }
103+
104+ // Check if maxDelay is non-negative and greater than or equal to baseDelay
105+ if maxDelay < 0 {
106+ return errors .New ("maxDelay cannot be negative" )
107+ }
108+
109+ if maxDelay < baseDelay {
110+ return errors .New ("maxDelay should be greater than or equal to baseDelay" )
111+ }
112+
113+ // Check if bucketSize is a positive number
114+ if bucketSize <= 0 {
115+ return errors .New ("bucketSize must be positive" )
116+ }
117+
118+ // Check if qps is a positive number
119+ if qps <= 0 {
120+ return errors .New ("minimum QPS must be positive" )
121+ }
122+
123+ // Check if bucketSize is at least as large as the QPS
124+ if bucketSize < qps {
125+ return errors .New ("bucketSize must be at least as large as the QPS to handle bursts effectively" )
126+ }
127+
128+ return nil
80129}
81130
82131func parseFlags (config * managerConfig ) {
@@ -88,6 +137,13 @@ func parseFlags(config *managerConfig) {
88137 pflag .IntVar (& maxConcurrentReconciles , "max-concurrent-reconciles" , defaultMaxConcurrentReconciles ,
89138 "The maximum number of allowed, concurrent reconciles." )
90139
140+ var baseDelay , maxDelay time.Duration
141+ var bucketSize , qps int
142+ pflag .DurationVar (& baseDelay , "rate-limiter-base-delay" , 500 * time .Millisecond , "The base delay for the rate limiter." )
143+ pflag .DurationVar (& maxDelay , "rate-limiter-max-delay" , 15 * time .Minute , "The maximum delay for the rate limiter." )
144+ pflag .IntVar (& bucketSize , "rate-limiter-bucket-size" , 100 , "The bucket size for the rate limiter." )
145+ pflag .IntVar (& qps , "rate-limiter-qps" , 10 , "The QPS for the rate limiter." )
146+
91147 opts := zap.Options {
92148 TimeEncoder : zapcore .RFC3339TimeEncoder ,
93149 }
@@ -100,6 +156,14 @@ func parseFlags(config *managerConfig) {
100156
101157 config .concurrentReconcilesNutanixCluster = maxConcurrentReconciles
102158 config .concurrentReconcilesNutanixMachine = maxConcurrentReconciles
159+
160+ rateLimiter , err := compositeRateLimiter (baseDelay , maxDelay , bucketSize , qps )
161+ if err != nil {
162+ config .logger .Error (err , "unable to create composite rate limiter" )
163+ os .Exit (1 )
164+ }
165+
166+ config .rateLimiter = rateLimiter
103167}
104168
105169func setupLogger () logr.Logger {
@@ -189,6 +253,7 @@ func runManager(ctx context.Context, mgr manager.Manager, config *managerConfig)
189253
190254 clusterControllerOpts := []controllers.ControllerConfigOpts {
191255 controllers .WithMaxConcurrentReconciles (config .concurrentReconcilesNutanixCluster ),
256+ controllers .WithRateLimiter (config .rateLimiter ),
192257 }
193258
194259 if err := setupNutanixClusterController (ctx , mgr , secretInformer , configMapInformer , clusterControllerOpts ... ); err != nil {
@@ -197,6 +262,7 @@ func runManager(ctx context.Context, mgr manager.Manager, config *managerConfig)
197262
198263 machineControllerOpts := []controllers.ControllerConfigOpts {
199264 controllers .WithMaxConcurrentReconciles (config .concurrentReconcilesNutanixMachine ),
265+ controllers .WithRateLimiter (config .rateLimiter ),
200266 }
201267
202268 if err := setupNutanixMachineController (ctx , mgr , secretInformer , configMapInformer , machineControllerOpts ... ); err != nil {
0 commit comments