Skip to content

Commit

Permalink
feat: configure max reconciliation interval via property
Browse files Browse the repository at this point in the history
Fixes #893

Signed-off-by: Chris Laprun <claprun@redhat.com>
  • Loading branch information
metacosm committed Jul 2, 2024
1 parent 78114d1 commit 9747c38
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public Supplier<QuarkusConfigurationService> configurationServiceSupplier(Versio
if (extConfig != null) {
extConfig.finalizer.ifPresent(c::setFinalizer);
extConfig.selector.ifPresent(c::setLabelSelector);
extConfig.maxReconciliationInterval.ifPresent(c::setMaxReconciliationInterval);
c.setRetryConfiguration(RetryConfigurationResolver.resolve(extConfig.retry));
setNamespacesFromRuntime(c, extConfig.namespaces);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
@SuppressWarnings("rawtypes")
public class QuarkusControllerConfiguration<R extends HasMetadata> implements ControllerConfiguration<R>,
DependentResourceConfigurationProvider {

// we need to create this class because Quarkus cannot reference the default implementation that
// JOSDK provides as it doesn't like lambdas at build time. The class also needs to be public
// because otherwise Quarkus isn't able to access it… :(
Expand Down Expand Up @@ -71,7 +72,6 @@ public DefaultRateLimiter(Duration refreshPeriod, int limitForPeriod) {
private final Class<R> resourceClass;
private final Optional<Long> informerListLimit;
private final ResourceEventFilter<R> eventFilter;
private final Optional<Duration> maxReconciliationInterval;
private final Optional<OnAddFilter<? super R>> onAddFilter;
private final Optional<OnUpdateFilter<? super R>> onUpdateFilter;
private final Optional<GenericFilter<? super R>> genericFilter;
Expand All @@ -83,6 +83,7 @@ public DefaultRateLimiter(Duration refreshPeriod, int limitForPeriod) {
private Class<? extends Retry> retryClass;
private Class<? extends Annotation> rateLimiterConfigurationClass;
private Class<? extends RateLimiter> rateLimiterClass;
private Optional<Duration> maxReconciliationInterval;
private String finalizer;
private Set<String> namespaces;
private boolean wereNamespacesSet;
Expand Down Expand Up @@ -225,8 +226,7 @@ void setNamespaces(Collection<String> namespaces) {
// propagate namespace changes to the dependents' config if needed
this.dependentsMetadata.forEach((name, spec) -> {
final var config = spec.getDependentResourceConfig();
if (config instanceof QuarkusKubernetesDependentResourceConfig) {
final var qConfig = (QuarkusKubernetesDependentResourceConfig) config;
if (config instanceof QuarkusKubernetesDependentResourceConfig qConfig) {
qConfig.setNamespaces(this.namespaces);
}
});
Expand Down Expand Up @@ -262,7 +262,7 @@ public String getLabelSelector() {
return labelSelector;
}

public void setLabelSelector(String labelSelector) {
void setLabelSelector(String labelSelector) {
this.labelSelector = labelSelector;
}

Expand Down Expand Up @@ -321,6 +321,10 @@ public Duration getMaxReconciliationInterval() {
return maxReconciliationInterval.orElseThrow();
}

void setMaxReconciliationInterval(Duration duration) {
maxReconciliationInterval = Optional.of(duration);
}

// for Quarkus' RecordableConstructor
@SuppressWarnings("unused")
public OnAddFilter<? super R> getOnAddFilter() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import static io.quarkiverse.operatorsdk.runtime.Constants.QOSDK_USE_BUILDTIME_NAMESPACES;

import java.time.Duration;
import java.util.List;
import java.util.Optional;

import io.javaoperatorsdk.operator.api.reconciler.MaxReconciliationInterval;
import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;

Expand Down Expand Up @@ -35,8 +37,17 @@ public class RunTimeControllerConfiguration {

/**
* An optional list of comma-separated label selectors that Custom Resources must match to trigger the controller.
* See https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more details on selectors.
* See <a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/">...</a> for more details on
* selectors.
*/
@ConfigItem
public Optional<String> selector;

/**
* An optional {@link Duration} to specify the maximum time that is allowed to elapse before a reconciliation will happen
* regardless of the presence of events. See {@link MaxReconciliationInterval#interval()} for more details.
* Value is specified according to the rules defined at {@link Duration#parse(CharSequence)}.
*/
@ConfigItem
public Optional<Duration> maxReconciliationInterval;
}
20 changes: 19 additions & 1 deletion docs/modules/ROOT/pages/includes/quarkus-operator-sdk.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,7 @@ a| [[quarkus-operator-sdk_quarkus-operator-sdk-controllers-controllers-selector]

[.description]
--
An optional list of comma-separated label selectors that Custom Resources must match to trigger the controller. See https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more details on selectors.
An optional list of comma-separated label selectors that Custom Resources must match to trigger the controller. See link:https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/[...] for more details on selectors.

ifdef::add-copy-button-to-env-var[]
Environment variable: env_var_with_copy_button:+++QUARKUS_OPERATOR_SDK_CONTROLLERS__CONTROLLERS__SELECTOR+++[]
Expand All @@ -587,6 +587,24 @@ endif::add-copy-button-to-env-var[]
--|string
|


a| [[quarkus-operator-sdk_quarkus-operator-sdk-controllers-controllers-max-reconciliation-interval]]`link:#quarkus-operator-sdk_quarkus-operator-sdk-controllers-controllers-max-reconciliation-interval[quarkus.operator-sdk.controllers."controllers".max-reconciliation-interval]`


[.description]
--
An optional `Duration` to specify the maximum time that is allowed to elapse before a reconciliation will happen regardless of the presence of events. See `MaxReconciliationInterval++#++interval()` for more details. Value is specified according to the rules defined at `Duration++#++parse(CharSequence)`.

ifdef::add-copy-button-to-env-var[]
Environment variable: env_var_with_copy_button:+++QUARKUS_OPERATOR_SDK_CONTROLLERS__CONTROLLERS__MAX_RECONCILIATION_INTERVAL+++[]
endif::add-copy-button-to-env-var[]
ifndef::add-copy-button-to-env-var[]
Environment variable: `+++QUARKUS_OPERATOR_SDK_CONTROLLERS__CONTROLLERS__MAX_RECONCILIATION_INTERVAL+++`
endif::add-copy-button-to-env-var[]
--|link:https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html[Duration]
link:#duration-note-anchor-{summaryTableId}[icon:question-circle[title=More information about the Duration format]]
|

|===
ifndef::no-duration-note[]
[NOTE]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
import io.fabric8.kubernetes.api.model.Secret;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.MaxReconciliationInterval;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;

@ControllerConfiguration
@ControllerConfiguration(name = SecretReconciler.NAME, maxReconciliationInterval = @MaxReconciliationInterval(interval = 2))
public class SecretReconciler implements Reconciler<Secret> {
public static final String NAME = "secret";

@Override
public UpdateControl<Secret> reconcile(Secret secret, Context context) {
Expand Down
3 changes: 3 additions & 0 deletions integration-tests/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@ quarkus.operator-sdk.controllers.annotation.selector=environment=production,tier
quarkus.operator-sdk.controllers.ApplicationScoped.namespaces=default
quarkus.operator-sdk.controllers.variablens.namespaces=${VARIABLE_NS_ENV}
quarkus.operator-sdk.controllers.name\ with\ space.namespaces=name-with-space
quarkus.operator-sdk.controllers.secret.max-reconciliation-interval=PT15M

quarkus.operator-sdk.concurrent-reconciliation-threads=10
quarkus.operator-sdk.termination-timeout-seconds=20
quarkus.operator-sdk.crd.validate=false
quarkus.operator-sdk.crd.versions=v1beta1
quarkus.operator-sdk.activate-leader-election-for-profiles=prod,test,dev

## activate to prevent the operator to start when debugging tests (note that some tests might fail because of this)
quarkus.operator-sdk.start-operator=false
quarkus.operator-sdk.enable-ssa=false
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;

import java.time.Duration;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -98,14 +100,14 @@ void allControllersShouldHaveAssociatedConfiguration() {
assertThat(names, arrayContainingInAnyOrder(ApplicationScopedReconciler.NAME,
ConfiguredReconciler.NAME,
TestReconciler.NAME,
ReconcilerUtils.getDefaultNameFor(SecretReconciler.class),
ReconcilerUtils.getDefaultNameFor(GatewayReconciler.class),
DependentDefiningReconciler.NAME, NamespaceFromEnvReconciler.NAME,
EmptyReconciler.NAME, VariableNSReconciler.NAME,
AnnotatedDependentReconciler.NAME,
ReconcilerUtils.getDefaultNameFor(KeycloakController.class),
NameWithSpaceReconciler.NAME,
CustomRateLimiterReconciler.NAME));
CustomRateLimiterReconciler.NAME,
SecretReconciler.NAME));
}

@Test
Expand Down Expand Up @@ -286,4 +288,25 @@ void customRateLimiterConfiguredViaCustomAnnotationShouldWork() {
"rateLimiter.value", equalTo(42),
"itemStore.name", equalTo(NullItemStore.NAME));
}

@Test
void shouldHaveDefaultMaxReconciliationInterval() {
given()
.when()
.get("/operator/" + EmptyReconciler.NAME + "/config")
.then()
.statusCode(200)
.body("maxReconciliationIntervalSeconds", equalTo(Long.valueOf(Duration.ofHours(10).getSeconds()).intValue()));
}

@Test
void shouldUseMaxReconciliationIntervalFromPropertyIfProvided() {
given()
.when()
.get("/operator/" + SecretReconciler.NAME + "/config")
.then()
.statusCode(200)
.body("maxReconciliationIntervalSeconds",
equalTo(Long.valueOf(Duration.ofMinutes(15).getSeconds()).intValue()));
}
}

0 comments on commit 9747c38

Please sign in to comment.