Skip to content

feat: decouple configuration of rate limiting and retry from controller #1317

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Jul 11, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.javaoperatorsdk.operator.api.config;

import java.lang.annotation.Annotation;

public interface AnnotationConfigurable<C extends Annotation> {
void initFrom(C configuration);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.javaoperatorsdk.operator.api.config;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.time.Duration;
import java.util.Arrays;
Expand Down Expand Up @@ -27,14 +28,14 @@
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig;
import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition;
import io.javaoperatorsdk.operator.processing.event.rate.PeriodRateLimiter;
import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilters;
import io.javaoperatorsdk.operator.processing.event.source.filter.VoidGenericFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.VoidOnAddFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.VoidOnDeleteFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.VoidOnUpdateFilter;
import io.javaoperatorsdk.operator.processing.retry.Retry;

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

Expand Down Expand Up @@ -154,12 +155,33 @@ public Optional<Duration> reconciliationMaxInterval() {

@Override
public RateLimiter getRateLimiter() {
if (annotation.rateLimit() != null) {
return new PeriodRateLimiter(Duration.of(annotation.rateLimit().refreshPeriod(),
annotation.rateLimit().refreshPeriodTimeUnit().toChronoUnit()),
annotation.rateLimit().limitForPeriod());
} else {
return io.javaoperatorsdk.operator.api.config.ControllerConfiguration.super.getRateLimiter();
final Class<? extends RateLimiter> rateLimiterClass = annotation.rateLimiter();
return instantiateAndConfigureIfNeeded(rateLimiterClass);
}

@Override
public Retry getRetry() {
final Class<? extends Retry> retryClass = annotation.retry();
return instantiateAndConfigureIfNeeded(retryClass);
}

private <T> T instantiateAndConfigureIfNeeded(Class<? extends T> targetClass) {
try {
final var instance = targetClass.getConstructor().newInstance();
if (instance instanceof AnnotationConfigurable) {
AnnotationConfigurable configurable = (AnnotationConfigurable) instance;
final Class<? extends Annotation> configurationClass =
(Class<? extends Annotation>) Utils.getFirstTypeArgumentFromSuperClassOrInterface(
targetClass, AnnotationConfigurable.class);
final var configAnnotation = reconciler.getClass().getAnnotation(configurationClass);
if (configAnnotation != null) {
configurable.initFrom(configAnnotation);
}
}
return instance;
} catch (InstantiationException | IllegalAccessException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
public interface ControllerConfiguration<R extends HasMetadata> extends ResourceConfiguration<R> {

RateLimiter DEFAULT_RATE_LIMITER = new PeriodRateLimiter();
Retry DEFAULT_RETRY = new GenericRetry();

default String getName() {
return ReconcilerUtils.getDefaultReconcilerName(getAssociatedReconcilerClassName());
Expand All @@ -34,17 +35,7 @@ default boolean isGenerationAware() {
String getAssociatedReconcilerClassName();

default Retry getRetry() {
return GenericRetry.fromConfiguration(getRetryConfiguration()); // NOSONAR
}

/**
* Use getRetry instead.
*
* @return configuration for retry.
*/
@Deprecated
default RetryConfiguration getRetryConfiguration() {
return RetryConfiguration.DEFAULT;
return DEFAULT_RETRY;
}

default RateLimiter getRateLimiter() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig;
import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
import io.javaoperatorsdk.operator.processing.retry.GenericRetry;
import io.javaoperatorsdk.operator.processing.retry.Retry;

import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_NAMESPACES_SET;
Expand Down Expand Up @@ -111,12 +110,6 @@ public ControllerConfigurationOverrider<R> withRetry(Retry retry) {
return this;
}

@Deprecated
public ControllerConfigurationOverrider<R> withRetry(RetryConfiguration retry) {
this.retry = GenericRetry.fromConfiguration(retry);
return this;
}

public ControllerConfigurationOverrider<R> withRateLimiter(RateLimiter rateLimiter) {
this.rateLimiter = rateLimiter;
return this;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,26 +1,23 @@
package io.javaoperatorsdk.operator.api.config;

public interface RetryConfiguration {
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

RetryConfiguration DEFAULT = new DefaultRetryConfiguration();
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RetryConfiguration {

int DEFAULT_MAX_ATTEMPTS = 5;
long DEFAULT_INITIAL_INTERVAL = 2000L;
double DEFAULT_MULTIPLIER = 1.5D;

default int getMaxAttempts() {
return DEFAULT_MAX_ATTEMPTS;
}
int maxAttempts() default DEFAULT_MAX_ATTEMPTS;

default long getInitialInterval() {
return DEFAULT_INITIAL_INTERVAL;
}
long initialInterval() default DEFAULT_INITIAL_INTERVAL;

default double getIntervalMultiplier() {
return DEFAULT_MULTIPLIER;
}
double intervalMultiplier() default DEFAULT_MULTIPLIER;

default long getMaxInterval() {
return (long) (DEFAULT_INITIAL_INTERVAL * Math.pow(DEFAULT_MULTIPLIER, DEFAULT_MAX_ATTEMPTS));
}
long maxInterval() default -1;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
import io.javaoperatorsdk.operator.processing.event.rate.PeriodRateLimiter;
import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.VoidGenericFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.VoidOnAddFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.VoidOnUpdateFilter;
import io.javaoperatorsdk.operator.processing.retry.GenericRetry;
import io.javaoperatorsdk.operator.processing.retry.Retry;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
Expand Down Expand Up @@ -92,13 +96,15 @@ ReconciliationMaxInterval reconciliationMaxInterval() default @ReconciliationMax
interval = 10);


RateLimit rateLimit() default @RateLimit;

/**
* Optional list of {@link Dependent} configurations which associate a resource type to a
* {@link io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource} implementation
*
* @return the list of {@link Dependent} configurations
*/
Dependent[] dependents() default {};

Class<? extends Retry> retry() default GenericRetry.class;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe have some javadoc here? link how to configur it.


Class<? extends RateLimiter> rateLimiter() default PeriodRateLimiter.class;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
import java.util.Map;
import java.util.Optional;

import io.javaoperatorsdk.operator.api.config.AnnotationConfigurable;
import io.javaoperatorsdk.operator.api.reconciler.RateLimit;
import io.javaoperatorsdk.operator.processing.event.ResourceID;

/**
* A Simple rate limiter that limits the number of permission for a time interval.
*/
public class PeriodRateLimiter implements RateLimiter {
public class PeriodRateLimiter implements RateLimiter, AnnotationConfigurable<RateLimit> {

public static final int DEFAULT_REFRESH_PERIOD_SECONDS = 10;
public static final int DEFAULT_LIMIT_FOR_PERIOD = 3;
Expand All @@ -22,9 +24,9 @@ public class PeriodRateLimiter implements RateLimiter {
public static final int NO_LIMIT_PERIOD = -1;

private Duration refreshPeriod;
private int limitForPeriod;
protected int limitForPeriod;

private Map<ResourceID, RateState> limitData = new HashMap<>();
private final Map<ResourceID, RateState> limitData = new HashMap<>();

public PeriodRateLimiter() {
this(DEFAULT_REFRESH_PERIOD, DEFAULT_LIMIT_FOR_PERIOD);
Expand Down Expand Up @@ -58,4 +60,11 @@ public Optional<Duration> acquirePermission(ResourceID resourceID) {
public void clear(ResourceID resourceID) {
limitData.remove(resourceID);
}

@Override
public void initFrom(RateLimit configuration) {
this.refreshPeriod = Duration.of(configuration.refreshPeriod(),
configuration.refreshPeriodTimeUnit().toChronoUnit());
this.limitForPeriod = configuration.limitForPeriod();
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package io.javaoperatorsdk.operator.processing.retry;

import io.javaoperatorsdk.operator.api.config.AnnotationConfigurable;
import io.javaoperatorsdk.operator.api.config.RetryConfiguration;

public class GenericRetry implements Retry {
public class GenericRetry implements Retry, AnnotationConfigurable<RetryConfiguration> {
private int maxAttempts = RetryConfiguration.DEFAULT_MAX_ATTEMPTS;
private long initialInterval = RetryConfiguration.DEFAULT_INITIAL_INTERVAL;
private double intervalMultiplier = RetryConfiguration.DEFAULT_MULTIPLIER;
Expand All @@ -20,15 +21,6 @@ public static GenericRetry every10second10TimesRetry() {
return new GenericRetry().withLinearRetry().setMaxAttempts(10).setInitialInterval(10000);
}

public static Retry fromConfiguration(RetryConfiguration configuration) {
return configuration == null ? defaultLimitedExponentialRetry()
: new GenericRetry()
.setInitialInterval(configuration.getInitialInterval())
.setMaxAttempts(configuration.getMaxAttempts())
.setIntervalMultiplier(configuration.getIntervalMultiplier())
.setMaxInterval(configuration.getMaxInterval());
}

@Override
public GenericRetryExecution initExecution() {
return new GenericRetryExecution(this);
Expand Down Expand Up @@ -83,4 +75,14 @@ public GenericRetry withLinearRetry() {
this.intervalMultiplier = 1;
return this;
}

@Override
public void initFrom(RetryConfiguration configuration) {
this.initialInterval = configuration.initialInterval();
this.maxAttempts = configuration.maxAttempts();
this.intervalMultiplier = configuration.intervalMultiplier();
this.maxInterval = configuration.maxInterval() < 0
? (long) (initialInterval * Math.pow(intervalMultiplier, maxAttempts))
: configuration.maxInterval();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.javaoperatorsdk.operator.processing.retry;

@FunctionalInterface
public interface Retry {

RetryExecution initExecution();
Expand Down
Loading