Skip to content

Commit fae83c8

Browse files
metacosmcsviri
andcommitted
feat: decouple configuration of rate limiting and retry from controller (#1317)
Co-authored-by: csviri <[email protected]>
1 parent 91cfa11 commit fae83c8

File tree

21 files changed

+370
-92
lines changed

21 files changed

+370
-92
lines changed

docs/documentation/features.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -346,12 +346,18 @@ Users can override it by implementing their own
346346
[`RateLimiter`](https://github.com/java-operator-sdk/java-operator-sdk/blob/ce4d996ee073ebef5715737995fc3d33f4751275/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/rate/RateLimiter.java)
347347
.
348348

349-
To configure the default rate limiter use `@ControllerConfiguration` annotation. The following
350-
configuration limits
351-
each resource to reconcile at most twice within a 3 second interval:
349+
To configure the default rate limiter use the `@LimitingRateOverPeriod` annotation on your
350+
`Reconciler` class. The following configuration limits each resource to reconcile at most twice
351+
within a 3 second interval:
352352

353-
`@ControllerConfiguration(rateLimit = @RateLimit(limitForPeriod = 2,refreshPeriod = 3,refreshPeriodTimeUnit = TimeUnit.SECONDS))`
354-
.
353+
```java
354+
355+
@LimitingRateOverPeriod(maxReconciliations = 2, within = 3, unit = TimeUnit.SECONDS)
356+
@ControllerConfiguration
357+
public class MyReconciler implements Reconciler<MyCR> {
358+
359+
}
360+
```
355361

356362
Thus, if a given resource was reconciled twice in one second, no further reconciliation for this
357363
resource will happen before two seconds have elapsed. Note that, since rate is limited on a
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package io.javaoperatorsdk.operator.api.config;
2+
3+
import java.lang.annotation.Annotation;
4+
5+
public interface AnnotationConfigurable<C extends Annotation> {
6+
void initFrom(C configuration);
7+
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.javaoperatorsdk.operator.api.config;
22

3+
import java.lang.annotation.Annotation;
4+
import java.lang.reflect.Constructor;
35
import java.lang.reflect.InvocationTargetException;
46
import java.time.Duration;
57
import java.util.Arrays;
@@ -27,14 +29,14 @@
2729
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource;
2830
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig;
2931
import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition;
30-
import io.javaoperatorsdk.operator.processing.event.rate.PeriodRateLimiter;
3132
import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
3233
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
3334
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilters;
3435
import io.javaoperatorsdk.operator.processing.event.source.filter.VoidGenericFilter;
3536
import io.javaoperatorsdk.operator.processing.event.source.filter.VoidOnAddFilter;
3637
import io.javaoperatorsdk.operator.processing.event.source.filter.VoidOnDeleteFilter;
3738
import io.javaoperatorsdk.operator.processing.event.source.filter.VoidOnUpdateFilter;
39+
import io.javaoperatorsdk.operator.processing.retry.Retry;
3840

3941
import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_NAMESPACES_SET;
4042

@@ -154,12 +156,39 @@ public Optional<Duration> reconciliationMaxInterval() {
154156

155157
@Override
156158
public RateLimiter getRateLimiter() {
157-
if (annotation.rateLimit() != null) {
158-
return new PeriodRateLimiter(Duration.of(annotation.rateLimit().refreshPeriod(),
159-
annotation.rateLimit().refreshPeriodTimeUnit().toChronoUnit()),
160-
annotation.rateLimit().limitForPeriod());
161-
} else {
162-
return io.javaoperatorsdk.operator.api.config.ControllerConfiguration.super.getRateLimiter();
159+
final Class<? extends RateLimiter> rateLimiterClass = annotation.rateLimiter();
160+
return instantiateAndConfigureIfNeeded(rateLimiterClass, RateLimiter.class);
161+
}
162+
163+
@Override
164+
public Retry getRetry() {
165+
final Class<? extends Retry> retryClass = annotation.retry();
166+
return instantiateAndConfigureIfNeeded(retryClass, Retry.class);
167+
}
168+
169+
@SuppressWarnings("unchecked")
170+
private <T> T instantiateAndConfigureIfNeeded(Class<? extends T> targetClass,
171+
Class<T> expectedType) {
172+
try {
173+
final Constructor<? extends T> constructor = targetClass.getDeclaredConstructor();
174+
constructor.setAccessible(true);
175+
final var instance = constructor.newInstance();
176+
if (instance instanceof AnnotationConfigurable) {
177+
AnnotationConfigurable configurable = (AnnotationConfigurable) instance;
178+
final Class<? extends Annotation> configurationClass =
179+
(Class<? extends Annotation>) Utils.getFirstTypeArgumentFromSuperClassOrInterface(
180+
targetClass, AnnotationConfigurable.class);
181+
final var configAnnotation = reconciler.getClass().getAnnotation(configurationClass);
182+
if (configAnnotation != null) {
183+
configurable.initFrom(configAnnotation);
184+
}
185+
}
186+
return instance;
187+
} catch (InstantiationException | IllegalAccessException | InvocationTargetException
188+
| NoSuchMethodException e) {
189+
throw new OperatorException("Couldn't instantiate " + expectedType.getSimpleName() + " '"
190+
+ targetClass.getName() + "' for '" + getName()
191+
+ "' reconciler. You need to provide an accessible no-arg constructor.", e);
163192
}
164193
}
165194

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,17 @@
88
import io.fabric8.kubernetes.api.model.HasMetadata;
99
import io.javaoperatorsdk.operator.ReconcilerUtils;
1010
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
11-
import io.javaoperatorsdk.operator.processing.event.rate.PeriodRateLimiter;
11+
import io.javaoperatorsdk.operator.processing.event.rate.LinearRateLimiter;
1212
import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
1313
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
1414
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilters;
1515
import io.javaoperatorsdk.operator.processing.retry.GenericRetry;
16+
import io.javaoperatorsdk.operator.processing.retry.GradualRetry;
1617
import io.javaoperatorsdk.operator.processing.retry.Retry;
1718

1819
public interface ControllerConfiguration<R extends HasMetadata> extends ResourceConfiguration<R> {
1920

20-
RateLimiter DEFAULT_RATE_LIMITER = new PeriodRateLimiter();
21+
RateLimiter DEFAULT_RATE_LIMITER = LinearRateLimiter.deactivatedRateLimiter();
2122

2223
default String getName() {
2324
return ReconcilerUtils.getDefaultReconcilerName(getAssociatedReconcilerClassName());
@@ -34,15 +35,20 @@ default boolean isGenerationAware() {
3435
String getAssociatedReconcilerClassName();
3536

3637
default Retry getRetry() {
37-
return GenericRetry.fromConfiguration(getRetryConfiguration()); // NOSONAR
38+
final var configuration = getRetryConfiguration();
39+
return !RetryConfiguration.DEFAULT.equals(configuration)
40+
? GenericRetry.fromConfiguration(configuration)
41+
: GenericRetry.DEFAULT; // NOSONAR
3842
}
3943

4044
/**
41-
* Use getRetry instead.
45+
* Use {@link #getRetry()} instead.
4246
*
4347
* @return configuration for retry.
48+
* @deprecated provide your own {@link Retry} implementation or use the {@link GradualRetry}
49+
* annotation instead
4450
*/
45-
@Deprecated
51+
@Deprecated(forRemoval = true)
4652
default RetryConfiguration getRetryConfiguration() {
4753
return RetryConfiguration.DEFAULT;
4854
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,14 +106,17 @@ public ControllerConfigurationOverrider<R> watchingAllNamespaces() {
106106
return this;
107107
}
108108

109-
public ControllerConfigurationOverrider<R> withRetry(Retry retry) {
110-
this.retry = retry;
109+
/**
110+
* @deprecated Use {@link #withRetry(Retry)} instead
111+
*/
112+
@Deprecated(forRemoval = true)
113+
public ControllerConfigurationOverrider<R> withRetry(RetryConfiguration retry) {
114+
this.retry = GenericRetry.fromConfiguration(retry);
111115
return this;
112116
}
113117

114-
@Deprecated
115-
public ControllerConfigurationOverrider<R> withRetry(RetryConfiguration retry) {
116-
this.retry = GenericRetry.fromConfiguration(retry);
118+
public ControllerConfigurationOverrider<R> withRetry(Retry retry) {
119+
this.retry = retry;
117120
return this;
118121
}
119122

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import io.fabric8.kubernetes.api.model.HasMetadata;
1212
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
13+
import io.javaoperatorsdk.operator.processing.event.rate.LinearRateLimiter;
1314
import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
1415
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
1516
import io.javaoperatorsdk.operator.processing.retry.Retry;
@@ -60,7 +61,8 @@ public DefaultControllerConfiguration(
6061
? ControllerConfiguration.super.getRetry()
6162
: retry;
6263
this.resourceEventFilter = resourceEventFilter;
63-
this.rateLimiter = rateLimiter;
64+
this.rateLimiter =
65+
rateLimiter != null ? rateLimiter : LinearRateLimiter.deactivatedRateLimiter();
6466
this.dependents = dependents != null ? dependents : Collections.emptyList();
6567
}
6668

Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
package io.javaoperatorsdk.operator.api.config;
22

33
public class DefaultRetryConfiguration implements RetryConfiguration {
4+
45
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/RetryConfiguration.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
package io.javaoperatorsdk.operator.api.config;
22

3+
import io.javaoperatorsdk.operator.processing.retry.GradualRetry;
4+
5+
/**
6+
* @deprecated specify your own {@link io.javaoperatorsdk.operator.processing.retry.Retry}
7+
* implementation or use {@link GradualRetry} annotation instead
8+
*/
9+
@Deprecated(forRemoval = true)
310
public interface RetryConfiguration {
411

512
RetryConfiguration DEFAULT = new DefaultRetryConfiguration();

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,14 @@
99

1010
import io.fabric8.kubernetes.api.model.HasMetadata;
1111
import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
12+
import io.javaoperatorsdk.operator.processing.event.rate.LinearRateLimiter;
13+
import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
1214
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
1315
import io.javaoperatorsdk.operator.processing.event.source.filter.VoidGenericFilter;
1416
import io.javaoperatorsdk.operator.processing.event.source.filter.VoidOnAddFilter;
1517
import io.javaoperatorsdk.operator.processing.event.source.filter.VoidOnUpdateFilter;
18+
import io.javaoperatorsdk.operator.processing.retry.GenericRetry;
19+
import io.javaoperatorsdk.operator.processing.retry.Retry;
1620

1721
@Retention(RetentionPolicy.RUNTIME)
1822
@Target({ElementType.TYPE})
@@ -92,13 +96,27 @@ ReconciliationMaxInterval reconciliationMaxInterval() default @ReconciliationMax
9296
interval = 10);
9397

9498

95-
RateLimit rateLimit() default @RateLimit;
96-
9799
/**
98100
* Optional list of {@link Dependent} configurations which associate a resource type to a
99101
* {@link io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource} implementation
100102
*
101103
* @return the list of {@link Dependent} configurations
102104
*/
103105
Dependent[] dependents() default {};
106+
107+
/**
108+
* Optional {@link Retry} implementation for the associated controller to use.
109+
*
110+
* @return the class providing the {@link Retry} implementation to use, needs to provide an
111+
* accessible no-arg constructor.
112+
*/
113+
Class<? extends Retry> retry() default GenericRetry.class;
114+
115+
/**
116+
* Optional {@link RateLimiter} implementation for the associated controller to use.
117+
*
118+
* @return the class providing the {@link RateLimiter} implementation to use, needs to provide an
119+
* accessible no-arg constructor.
120+
*/
121+
Class<? extends RateLimiter> rateLimiter() default LinearRateLimiter.class;
104122
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/RateLimit.java

Lines changed: 0 additions & 23 deletions
This file was deleted.

0 commit comments

Comments
 (0)