From 7699d5cb1e98a5b8fa2557aa335c9787eba6cc3c Mon Sep 17 00:00:00 2001 From: csviri Date: Wed, 26 Jan 2022 11:27:40 +0100 Subject: [PATCH 01/13] feature: controller reconciliation max delay --- .../api/config/ControllerConfiguration.java | 9 +++++ .../operator/api/reconciler/Constants.java | 1 + .../reconciler/ControllerConfiguration.java | 15 +++++++ .../event/ReconciliationDispatcher.java | 9 ++++- .../event/ReconciliationDispatcherTest.java | 39 ++++++++++++++++++- .../runtime/AnnotationConfiguration.java | 13 +++++++ 6 files changed, 83 insertions(+), 3 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java index b85df49eba..d4376d10d9 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java @@ -3,6 +3,7 @@ import java.lang.reflect.ParameterizedType; import java.util.Collections; import java.util.Set; +import java.util.concurrent.TimeUnit; import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.ReconcilerUtils; @@ -114,4 +115,12 @@ default boolean useFinalizer() { default ResourceEventFilter getEventFilter() { return ResourceEventFilters.passthrough(); } + + default long reconciliationMaxDelay() { + return 10L; + } + + default TimeUnit reconciliationTimeUnit() { + return TimeUnit.HOURS; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Constants.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Constants.java index 075b4e79a3..dcf9440fbc 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Constants.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Constants.java @@ -5,6 +5,7 @@ public final class Constants { public static final String EMPTY_STRING = ""; public static final String WATCH_CURRENT_NAMESPACE = "JOSDK_WATCH_CURRENT"; public static final String NO_FINALIZER = "JOSDK_NO_FINALIZER"; + public static final long NO_RECONCILIATION_MAX_DELAY = -1L; private Constants() {} } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java index fad4bc8573..f3c93beb6f 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java @@ -4,6 +4,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.concurrent.TimeUnit; import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter; @@ -56,4 +57,18 @@ */ @SuppressWarnings("rawtypes") Class[] eventFilters() default {}; + + /** + * A max delay between two reconciliations. Having this value larger than zero, will ensure that a + * reconciliation is scheduled with a target interval after the last reconciliation. Note that + * this not applies in case of retries, thus the reconciliation is not scheduled. + * + * @return max delay between reconciliations + **/ + long reconciliationMaxDelay() default 10; + + /** + * @return time unit for max delay between reconciliations + */ + TimeUnit reconciliationTimeUnit() default TimeUnit.HOURS; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java index 52374c5645..3b96eda89b 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java @@ -245,7 +245,14 @@ private PostExecutionControl createPostExecutionControl(R updatedCustomResour private void updatePostExecutionControlWithReschedule( PostExecutionControl postExecutionControl, BaseControl baseControl) { - baseControl.getScheduleDelay().ifPresent(postExecutionControl::withReSchedule); + baseControl.getScheduleDelay().ifPresentOrElse(postExecutionControl::withReSchedule, + () -> { + var maxDelay = controller.getConfiguration().reconciliationMaxDelay(); + if (maxDelay > 0) { + postExecutionControl.withReSchedule(controller.getConfiguration() + .reconciliationTimeUnit().toMillis(maxDelay)); + } + }); } private PostExecutionControl handleCleanup(R resource, Context context) { diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java index 9da51337d3..7aa89064c7 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java @@ -47,6 +47,7 @@ class ReconciliationDispatcherTest { private static final String DEFAULT_FINALIZER = "javaoperatorsdk.io/finalizer"; public static final String ERROR_MESSAGE = "ErrorMessage"; + public static final long RECONCILIATION_MAX_DELAY = 10L; private TestCustomResource testCustomResource; private ReconciliationDispatcher reconciliationDispatcher; private final Reconciler reconciler = mock(Reconciler.class, @@ -54,26 +55,32 @@ class ReconciliationDispatcherTest { private final ConfigurationService configService = mock(ConfigurationService.class); private final CustomResourceFacade customResourceFacade = mock(ReconciliationDispatcher.CustomResourceFacade.class); + private ControllerConfiguration configuration = mock(ControllerConfiguration.class); @BeforeEach void setup() { testCustomResource = TestUtils.testCustomResource(); reconciliationDispatcher = - init(testCustomResource, reconciler, null, customResourceFacade, true); + init(testCustomResource, reconciler,null, customResourceFacade, true); } private ReconciliationDispatcher init(R customResource, - Reconciler reconciler, ControllerConfiguration configuration, + Reconciler reconciler,ControllerConfiguration configuration , CustomResourceFacade customResourceFacade, boolean useFinalizer) { + configuration = configuration == null ? mock(ControllerConfiguration.class) : configuration; + ReconciliationDispatcherTest.this.configuration = configuration; final var finalizer = useFinalizer ? DEFAULT_FINALIZER : Constants.NO_FINALIZER; when(configuration.getFinalizer()).thenReturn(finalizer); when(configuration.useFinalizer()).thenCallRealMethod(); when(configuration.getName()).thenReturn("EventDispatcherTestController"); when(configuration.getResourceClass()).thenReturn((Class) customResource.getClass()); when(configuration.getRetryConfiguration()).thenReturn(RetryConfiguration.DEFAULT); + when(configuration.reconciliationMaxDelay()).thenReturn(RECONCILIATION_MAX_DELAY); + when(configuration.reconciliationTimeUnit()).thenReturn(TimeUnit.HOURS); when(configuration.getConfigurationService()).thenReturn(configService); + /* * We need this for mock reconcilers to properly generate the expected UpdateControl: without * this, calls such as `when(reconciler.reconcile(eq(testCustomResource), @@ -429,6 +436,34 @@ void callErrorStatusHandlerEvenOnFirstError() { any(), any()); } + @Test + void schedulesReconciliationIfMaxDelayIsSet() { + testCustomResource.addFinalizer(DEFAULT_FINALIZER); + + when(reconciler.reconcile(eq(testCustomResource), any())) + .thenReturn(UpdateControl.noUpdate()); + + PostExecutionControl control = + reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource)); + + assertThat(control.getReScheduleDelay()).isPresent() + .hasValue(TimeUnit.HOURS.toMillis(RECONCILIATION_MAX_DELAY)); + } + + @Test + void canSkipSchedulingMaxDelayIf() { + testCustomResource.addFinalizer(DEFAULT_FINALIZER); + + when(reconciler.reconcile(eq(testCustomResource), any())) + .thenReturn(UpdateControl.noUpdate()); + when(configuration.reconciliationMaxDelay()).thenReturn(Constants.NO_RECONCILIATION_MAX_DELAY); + + PostExecutionControl control = + reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource)); + + assertThat(control.getReScheduleDelay()).isNotPresent(); + } + private ObservedGenCustomResource createObservedGenCustomResource() { ObservedGenCustomResource observedGenCustomResource = new ObservedGenCustomResource(); observedGenCustomResource.setMetadata(new ObjectMeta()); diff --git a/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationConfiguration.java b/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationConfiguration.java index 1000cb61c4..0a55e18424 100644 --- a/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationConfiguration.java +++ b/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationConfiguration.java @@ -1,6 +1,7 @@ package io.javaoperatorsdk.operator.config.runtime; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.function.Function; import io.fabric8.kubernetes.api.model.HasMetadata; @@ -108,6 +109,18 @@ public ResourceEventFilter getEventFilter() { : ResourceEventFilters.passthrough(); } + @Override + public long reconciliationMaxDelay() { + return valueOrDefault(annotation, ControllerConfiguration::reconciliationMaxDelay, + io.javaoperatorsdk.operator.api.config.ControllerConfiguration.super.reconciliationMaxDelay()); + } + + @Override + public TimeUnit reconciliationTimeUnit() { + return valueOrDefault(annotation, ControllerConfiguration::reconciliationTimeUnit, + io.javaoperatorsdk.operator.api.config.ControllerConfiguration.super.reconciliationTimeUnit()); + } + public static T valueOrDefault(ControllerConfiguration controllerConfiguration, Function mapper, T defaultValue) { From 4b3a8bec8489ba5c0ce36df7013ea95c2c8b08ed Mon Sep 17 00:00:00 2001 From: csviri Date: Wed, 26 Jan 2022 13:36:03 +0100 Subject: [PATCH 02/13] fix: format --- .../processing/event/ReconciliationDispatcherTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java index 7aa89064c7..c53afbbdc8 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java @@ -61,11 +61,11 @@ class ReconciliationDispatcherTest { void setup() { testCustomResource = TestUtils.testCustomResource(); reconciliationDispatcher = - init(testCustomResource, reconciler,null, customResourceFacade, true); + init(testCustomResource, reconciler, null, customResourceFacade, true); } private ReconciliationDispatcher init(R customResource, - Reconciler reconciler,ControllerConfiguration configuration , + Reconciler reconciler, ControllerConfiguration configuration, CustomResourceFacade customResourceFacade, boolean useFinalizer) { configuration = configuration == null ? mock(ControllerConfiguration.class) : configuration; @@ -455,11 +455,11 @@ void canSkipSchedulingMaxDelayIf() { testCustomResource.addFinalizer(DEFAULT_FINALIZER); when(reconciler.reconcile(eq(testCustomResource), any())) - .thenReturn(UpdateControl.noUpdate()); + .thenReturn(UpdateControl.noUpdate()); when(configuration.reconciliationMaxDelay()).thenReturn(Constants.NO_RECONCILIATION_MAX_DELAY); PostExecutionControl control = - reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource)); + reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource)); assertThat(control.getReScheduleDelay()).isNotPresent(); } From 4ccf36876bf6ffb8145fe5ba460078396f7e4edf Mon Sep 17 00:00:00 2001 From: csviri Date: Wed, 26 Jan 2022 14:56:51 +0100 Subject: [PATCH 03/13] fix: integration tests, code improvements --- .../api/config/ControllerConfiguration.java | 4 +- .../reconciler/ControllerConfiguration.java | 12 +++-- .../event/ReconciliationDispatcher.java | 4 +- .../event/ReconciliationDispatcherTest.java | 11 ++--- .../operator/junit/OperatorExtension.java | 6 +++ .../runtime/AnnotationConfiguration.java | 12 ++--- .../operator/ConcurrencyIT.java | 4 +- .../operator/ControllerExecutionIT.java | 6 +-- .../operator/CustomResourceFilterIT.java | 4 +- .../operator/ErrorStatusHandlerIT.java | 4 +- .../operator/EventSourceIT.java | 4 +- .../operator/InformerEventSourceIT.java | 2 +- .../KubernetesResourceStatusUpdateIT.java | 4 +- .../operator/MaxIntervalIT.java | 45 +++++++++++++++++++ .../ObservedGenerationHandlingIT.java | 4 +- .../io/javaoperatorsdk/operator/RetryIT.java | 4 +- .../operator/RetryMaxAttemptIT.java | 4 +- .../operator/SubResourceUpdateIT.java | 10 ++--- .../operator/UpdatingResAndSubResIT.java | 4 +- .../MaxIntervalTestCustomResource.java | 17 +++++++ .../MaxIntervalTestCustomResourceStatus.java | 5 +++ .../MaxIntervalTestReconciler.java | 29 ++++++++++++ 22 files changed, 153 insertions(+), 46 deletions(-) create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/MaxIntervalIT.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/maxinterval/MaxIntervalTestCustomResource.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/maxinterval/MaxIntervalTestCustomResourceStatus.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/maxinterval/MaxIntervalTestReconciler.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java index d4376d10d9..dfa9c79697 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java @@ -116,11 +116,11 @@ default ResourceEventFilter getEventFilter() { return ResourceEventFilters.passthrough(); } - default long reconciliationMaxDelay() { + default long reconciliationMaxInterval() { return 10L; } - default TimeUnit reconciliationTimeUnit() { + default TimeUnit reconciliationMaxIntervalTimeUnit() { return TimeUnit.HOURS; } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java index f3c93beb6f..549edc4dbf 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java @@ -61,14 +61,18 @@ /** * A max delay between two reconciliations. Having this value larger than zero, will ensure that a * reconciliation is scheduled with a target interval after the last reconciliation. Note that - * this not applies in case of retries, thus the reconciliation is not scheduled. - * + * this not applies for retries, in case of an exception reconciliation is not scheduled. This is + * not a fixed rate, in other words a new reconciliation is scheduled after each reconciliation. + * + * If an interval is specified by {@link UpdateControl} or {@link DeleteControl}, those take + * precedence. + * * @return max delay between reconciliations **/ - long reconciliationMaxDelay() default 10; + long reconciliationMaxInterval() default 10; /** * @return time unit for max delay between reconciliations */ - TimeUnit reconciliationTimeUnit() default TimeUnit.HOURS; + TimeUnit reconciliationMaxIntervalTimeUnit() default TimeUnit.HOURS; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java index 3b96eda89b..dd44c778da 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java @@ -247,10 +247,10 @@ private void updatePostExecutionControlWithReschedule( BaseControl baseControl) { baseControl.getScheduleDelay().ifPresentOrElse(postExecutionControl::withReSchedule, () -> { - var maxDelay = controller.getConfiguration().reconciliationMaxDelay(); + var maxDelay = controller.getConfiguration().reconciliationMaxInterval(); if (maxDelay > 0) { postExecutionControl.withReSchedule(controller.getConfiguration() - .reconciliationTimeUnit().toMillis(maxDelay)); + .reconciliationMaxIntervalTimeUnit().toMillis(maxDelay)); } }); } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java index c53afbbdc8..2c97bc9db3 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java @@ -47,7 +47,7 @@ class ReconciliationDispatcherTest { private static final String DEFAULT_FINALIZER = "javaoperatorsdk.io/finalizer"; public static final String ERROR_MESSAGE = "ErrorMessage"; - public static final long RECONCILIATION_MAX_DELAY = 10L; + public static final long RECONCILIATION_MAX_INTERVAL = 10L; private TestCustomResource testCustomResource; private ReconciliationDispatcher reconciliationDispatcher; private final Reconciler reconciler = mock(Reconciler.class, @@ -76,8 +76,8 @@ private ReconciliationDispatcher init(R customResourc when(configuration.getName()).thenReturn("EventDispatcherTestController"); when(configuration.getResourceClass()).thenReturn((Class) customResource.getClass()); when(configuration.getRetryConfiguration()).thenReturn(RetryConfiguration.DEFAULT); - when(configuration.reconciliationMaxDelay()).thenReturn(RECONCILIATION_MAX_DELAY); - when(configuration.reconciliationTimeUnit()).thenReturn(TimeUnit.HOURS); + when(configuration.reconciliationMaxInterval()).thenReturn(RECONCILIATION_MAX_INTERVAL); + when(configuration.reconciliationMaxIntervalTimeUnit()).thenReturn(TimeUnit.HOURS); when(configuration.getConfigurationService()).thenReturn(configService); @@ -447,7 +447,7 @@ void schedulesReconciliationIfMaxDelayIsSet() { reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource)); assertThat(control.getReScheduleDelay()).isPresent() - .hasValue(TimeUnit.HOURS.toMillis(RECONCILIATION_MAX_DELAY)); + .hasValue(TimeUnit.HOURS.toMillis(RECONCILIATION_MAX_INTERVAL)); } @Test @@ -456,7 +456,8 @@ void canSkipSchedulingMaxDelayIf() { when(reconciler.reconcile(eq(testCustomResource), any())) .thenReturn(UpdateControl.noUpdate()); - when(configuration.reconciliationMaxDelay()).thenReturn(Constants.NO_RECONCILIATION_MAX_DELAY); + when(configuration.reconciliationMaxInterval()) + .thenReturn(Constants.NO_RECONCILIATION_MAX_DELAY); PostExecutionControl control = reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource)); diff --git a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/OperatorExtension.java b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/OperatorExtension.java index 5941975a71..3393de7a55 100644 --- a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/OperatorExtension.java +++ b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/OperatorExtension.java @@ -112,6 +112,12 @@ public List getReconcilers() { .collect(Collectors.toUnmodifiableList()); } + public Reconciler getFirstReconciler() { + return operator.getControllers().stream() + .map(Controller::getReconciler) + .findFirst().orElseThrow(); + } + @SuppressWarnings({"rawtypes"}) public T getControllerOfType(Class type) { return operator.getControllers().stream() diff --git a/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationConfiguration.java b/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationConfiguration.java index 0a55e18424..0528419578 100644 --- a/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationConfiguration.java +++ b/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationConfiguration.java @@ -110,15 +110,15 @@ public ResourceEventFilter getEventFilter() { } @Override - public long reconciliationMaxDelay() { - return valueOrDefault(annotation, ControllerConfiguration::reconciliationMaxDelay, - io.javaoperatorsdk.operator.api.config.ControllerConfiguration.super.reconciliationMaxDelay()); + public long reconciliationMaxInterval() { + return valueOrDefault(annotation, ControllerConfiguration::reconciliationMaxInterval, + io.javaoperatorsdk.operator.api.config.ControllerConfiguration.super.reconciliationMaxInterval()); } @Override - public TimeUnit reconciliationTimeUnit() { - return valueOrDefault(annotation, ControllerConfiguration::reconciliationTimeUnit, - io.javaoperatorsdk.operator.api.config.ControllerConfiguration.super.reconciliationTimeUnit()); + public TimeUnit reconciliationMaxIntervalTimeUnit() { + return valueOrDefault(annotation, ControllerConfiguration::reconciliationMaxIntervalTimeUnit, + io.javaoperatorsdk.operator.api.config.ControllerConfiguration.super.reconciliationMaxIntervalTimeUnit()); } public static T valueOrDefault(ControllerConfiguration controllerConfiguration, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/ConcurrencyIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/ConcurrencyIT.java index 458e2d12eb..12cf3ca4d8 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/ConcurrencyIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/ConcurrencyIT.java @@ -19,7 +19,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; -public class ConcurrencyIT { +class ConcurrencyIT { public static final int NUMBER_OF_RESOURCES_CREATED = 50; public static final int NUMBER_OF_RESOURCES_DELETED = 30; public static final int NUMBER_OF_RESOURCES_UPDATED = 20; @@ -34,7 +34,7 @@ public class ConcurrencyIT { .build(); @Test - public void manyResourcesGetCreatedUpdatedAndDeleted() throws InterruptedException { + void manyResourcesGetCreatedUpdatedAndDeleted() throws InterruptedException { log.info("Creating {} new resources", NUMBER_OF_RESOURCES_CREATED); for (int i = 0; i < NUMBER_OF_RESOURCES_CREATED; i++) { TestCustomResource tcr = TestUtils.testCustomResourceWithPrefix(String.valueOf(i)); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/ControllerExecutionIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/ControllerExecutionIT.java index b1f783b958..dbd015587e 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/ControllerExecutionIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/ControllerExecutionIT.java @@ -15,7 +15,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; -public class ControllerExecutionIT { +class ControllerExecutionIT { @RegisterExtension OperatorExtension operator = OperatorExtension.builder() @@ -24,7 +24,7 @@ public class ControllerExecutionIT { .build(); @Test - public void configMapGetsCreatedForTestCustomResource() { + void configMapGetsCreatedForTestCustomResource() { operator.getControllerOfType(TestReconciler.class).setUpdateStatus(true); TestCustomResource resource = TestUtils.testCustomResource(); @@ -36,7 +36,7 @@ public void configMapGetsCreatedForTestCustomResource() { } @Test - public void eventIsSkippedChangedOnMetadataOnlyUpdate() { + void eventIsSkippedChangedOnMetadataOnlyUpdate() { operator.getControllerOfType(TestReconciler.class).setUpdateStatus(false); TestCustomResource resource = TestUtils.testCustomResource(); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/CustomResourceFilterIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/CustomResourceFilterIT.java index 876710ff36..70a3f04777 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/CustomResourceFilterIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/CustomResourceFilterIT.java @@ -12,7 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat; -public class CustomResourceFilterIT { +class CustomResourceFilterIT { @RegisterExtension OperatorExtension operator = @@ -22,7 +22,7 @@ public class CustomResourceFilterIT { .build(); @Test - public void doesCustomFiltering() throws InterruptedException { + void doesCustomFiltering() throws InterruptedException { var filtered1 = createTestResource("filtered1", true, false); var filtered2 = createTestResource("filtered2", false, true); var notFiltered = createTestResource("notfiltered", true, true); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/ErrorStatusHandlerIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/ErrorStatusHandlerIT.java index 4a1b3293e7..fbf54a1ad1 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/ErrorStatusHandlerIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/ErrorStatusHandlerIT.java @@ -15,7 +15,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; -public class ErrorStatusHandlerIT { +class ErrorStatusHandlerIT { public static final int MAX_RETRY_ATTEMPTS = 3; ErrorStatusHandlerTestReconciler reconciler = new ErrorStatusHandlerTestReconciler(); @@ -29,7 +29,7 @@ public class ErrorStatusHandlerIT { .build(); @Test - public void testErrorMessageSetEventually() { + void testErrorMessageSetEventually() { ErrorStatusHandlerTestCustomResource resource = operator.create(ErrorStatusHandlerTestCustomResource.class, createCustomResource()); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/EventSourceIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/EventSourceIT.java index 033bbf2b8b..c91ebaad43 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/EventSourceIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/EventSourceIT.java @@ -16,7 +16,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; -public class EventSourceIT { +class EventSourceIT { @RegisterExtension OperatorExtension operator = OperatorExtension.builder() @@ -25,7 +25,7 @@ public class EventSourceIT { .build(); @Test - public void receivingPeriodicEvents() { + void receivingPeriodicEvents() { EventSourceTestCustomResource resource = createTestCustomResource("1"); operator.create(EventSourceTestCustomResource.class, resource); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/InformerEventSourceIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/InformerEventSourceIT.java index 4885e7d356..7ff36bd375 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/InformerEventSourceIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/InformerEventSourceIT.java @@ -18,7 +18,7 @@ import static org.assertj.core.api.Assertions.fail; import static org.awaitility.Awaitility.await; -public class InformerEventSourceIT { +class InformerEventSourceIT { public static final String RESOURCE_NAME = "informertestcr"; public static final String INITIAL_STATUS_MESSAGE = "Initial Status"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/KubernetesResourceStatusUpdateIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/KubernetesResourceStatusUpdateIT.java index 2c5ea1cba1..a2c7378ff5 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/KubernetesResourceStatusUpdateIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/KubernetesResourceStatusUpdateIT.java @@ -19,7 +19,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; -public class KubernetesResourceStatusUpdateIT { +class KubernetesResourceStatusUpdateIT { @RegisterExtension OperatorExtension operator = @@ -29,7 +29,7 @@ public class KubernetesResourceStatusUpdateIT { .build(); @Test - public void testReconciliationOfNonCustomResourceAndStatusUpdate() { + void testReconciliationOfNonCustomResourceAndStatusUpdate() { var deployment = operator.create(Deployment.class, testDeployment()); await().atMost(120, TimeUnit.SECONDS).untilAsserted(() -> { var d = operator.get(Deployment.class, deployment.getMetadata().getName()); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/MaxIntervalIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/MaxIntervalIT.java new file mode 100644 index 0000000000..f284dbf407 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/MaxIntervalIT.java @@ -0,0 +1,45 @@ +package io.javaoperatorsdk.operator; + +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.javaoperatorsdk.operator.config.runtime.DefaultConfigurationService; +import io.javaoperatorsdk.operator.junit.OperatorExtension; +import io.javaoperatorsdk.operator.sample.maxinterval.MaxIntervalTestCustomResource; +import io.javaoperatorsdk.operator.sample.maxinterval.MaxIntervalTestReconciler; + +import static org.awaitility.Awaitility.await; + +class MaxIntervalIT { + + @RegisterExtension + OperatorExtension operator = + OperatorExtension.builder() + .withConfigurationService(DefaultConfigurationService.instance()) + .withReconciler(new MaxIntervalTestReconciler()) + .build(); + + @Test + void reconciliationTriggeredBasedOnMaxInterval() { + MaxIntervalTestCustomResource cr = createTestResource(); + + operator.create(MaxIntervalTestCustomResource.class, cr); + + await() + .pollInterval(50, TimeUnit.MILLISECONDS) + .atMost(500, TimeUnit.MILLISECONDS) + .until( + () -> ((MaxIntervalTestReconciler) operator.getFirstReconciler()) + .getNumberOfExecutions() > 3); + } + + private MaxIntervalTestCustomResource createTestResource() { + MaxIntervalTestCustomResource cr = new MaxIntervalTestCustomResource(); + cr.setMetadata(new ObjectMeta()); + cr.getMetadata().setName("maxintervaltest1"); + return cr; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/ObservedGenerationHandlingIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/ObservedGenerationHandlingIT.java index 9eac198f35..5f86336a1c 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/ObservedGenerationHandlingIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/ObservedGenerationHandlingIT.java @@ -14,7 +14,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; -public class ObservedGenerationHandlingIT { +class ObservedGenerationHandlingIT { @RegisterExtension OperatorExtension operator = OperatorExtension.builder() @@ -23,7 +23,7 @@ public class ObservedGenerationHandlingIT { .build(); @Test - public void testReconciliationOfNonCustomResourceAndStatusUpdate() { + void testReconciliationOfNonCustomResourceAndStatusUpdate() { var resource = new ObservedGenerationTestCustomResource(); resource.setMetadata(new ObjectMeta()); resource.getMetadata().setName("observed-gen1"); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/RetryIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/RetryIT.java index 4ab63c7513..c4ab025ac5 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/RetryIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/RetryIT.java @@ -18,7 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; -public class RetryIT { +class RetryIT { public static final int RETRY_INTERVAL = 150; public static final int MAX_RETRY_ATTEMPTS = 5; @@ -36,7 +36,7 @@ public class RetryIT { @Test - public void retryFailedExecution() { + void retryFailedExecution() { RetryTestCustomResource resource = createTestCustomResource("1"); operator.create(RetryTestCustomResource.class, resource); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/RetryMaxAttemptIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/RetryMaxAttemptIT.java index e855b016fd..5ebb05eb0c 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/RetryMaxAttemptIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/RetryMaxAttemptIT.java @@ -12,7 +12,7 @@ import static io.javaoperatorsdk.operator.RetryIT.createTestCustomResource; import static org.assertj.core.api.Assertions.assertThat; -public class RetryMaxAttemptIT { +class RetryMaxAttemptIT { public static final int MAX_RETRY_ATTEMPTS = 3; public static final int RETRY_INTERVAL = 100; @@ -31,7 +31,7 @@ public class RetryMaxAttemptIT { @Test - public void retryFailedExecution() throws InterruptedException { + void retryFailedExecution() throws InterruptedException { RetryTestCustomResource resource = createTestCustomResource("max-retry"); operator.create(RetryTestCustomResource.class, resource); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/SubResourceUpdateIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/SubResourceUpdateIT.java index 5998502f32..75597643bf 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/SubResourceUpdateIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/SubResourceUpdateIT.java @@ -18,7 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; -public class SubResourceUpdateIT { +class SubResourceUpdateIT { @RegisterExtension OperatorExtension operator = @@ -28,7 +28,7 @@ public class SubResourceUpdateIT { .build(); @Test - public void updatesSubResourceStatus() { + void updatesSubResourceStatus() { SubResourceTestCustomResource resource = createTestCustomResource("1"); operator.create(SubResourceTestCustomResource.class, resource); @@ -41,7 +41,7 @@ public void updatesSubResourceStatus() { } @Test - public void updatesSubResourceStatusNoFinalizer() { + void updatesSubResourceStatusNoFinalizer() { SubResourceTestCustomResource resource = createTestCustomResource("1"); resource.getMetadata().setFinalizers(Collections.emptyList()); @@ -57,7 +57,7 @@ public void updatesSubResourceStatusNoFinalizer() { /** Note that we check on controller impl if there is finalizer on execution. */ @Test - public void ifNoFinalizerPresentFirstAddsTheFinalizerThenExecutesControllerAgain() { + void ifNoFinalizerPresentFirstAddsTheFinalizerThenExecutesControllerAgain() { SubResourceTestCustomResource resource = createTestCustomResource("1"); resource.getMetadata().getFinalizers().clear(); operator.create(SubResourceTestCustomResource.class, resource); @@ -77,7 +77,7 @@ public void ifNoFinalizerPresentFirstAddsTheFinalizerThenExecutesControllerAgain * fail since its resource version is outdated already. */ @Test - public void updateCustomResourceAfterSubResourceChange() { + void updateCustomResourceAfterSubResourceChange() { SubResourceTestCustomResource resource = createTestCustomResource("1"); operator.create(SubResourceTestCustomResource.class, resource); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/UpdatingResAndSubResIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/UpdatingResAndSubResIT.java index 2a479d47df..6bad954400 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/UpdatingResAndSubResIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/UpdatingResAndSubResIT.java @@ -17,7 +17,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; -public class UpdatingResAndSubResIT { +class UpdatingResAndSubResIT { @RegisterExtension OperatorExtension operator = OperatorExtension.builder() @@ -26,7 +26,7 @@ public class UpdatingResAndSubResIT { .build(); @Test - public void updatesSubResourceStatus() { + void updatesSubResourceStatus() { DoubleUpdateTestCustomResource resource = createTestCustomResource("1"); operator.create(DoubleUpdateTestCustomResource.class, resource); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/maxinterval/MaxIntervalTestCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/maxinterval/MaxIntervalTestCustomResource.java new file mode 100644 index 0000000000..1c6cf81453 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/maxinterval/MaxIntervalTestCustomResource.java @@ -0,0 +1,17 @@ +package io.javaoperatorsdk.operator.sample.maxinterval; + +import io.fabric8.kubernetes.api.model.Namespaced; +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Kind; +import io.fabric8.kubernetes.model.annotation.ShortNames; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("sample.javaoperatorsdk") +@Version("v1") +@Kind("MaxIntervalTestCustomResource") +@ShortNames("mit") +public class MaxIntervalTestCustomResource + extends CustomResource + implements Namespaced { +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/maxinterval/MaxIntervalTestCustomResourceStatus.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/maxinterval/MaxIntervalTestCustomResourceStatus.java new file mode 100644 index 0000000000..5c403febfb --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/maxinterval/MaxIntervalTestCustomResourceStatus.java @@ -0,0 +1,5 @@ +package io.javaoperatorsdk.operator.sample.maxinterval; + +public class MaxIntervalTestCustomResourceStatus { + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/maxinterval/MaxIntervalTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/maxinterval/MaxIntervalTestReconciler.java new file mode 100644 index 0000000000..c0e59fd7ef --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/maxinterval/MaxIntervalTestReconciler.java @@ -0,0 +1,29 @@ +package io.javaoperatorsdk.operator.sample.maxinterval; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; + +import static io.javaoperatorsdk.operator.api.reconciler.Constants.NO_FINALIZER; + +@ControllerConfiguration(finalizerName = NO_FINALIZER, reconciliationMaxInterval = 50, + reconciliationMaxIntervalTimeUnit = TimeUnit.MILLISECONDS) +public class MaxIntervalTestReconciler + implements Reconciler, TestExecutionInfoProvider { + + private final AtomicInteger numberOfExecutions = new AtomicInteger(0); + + @Override + public UpdateControl reconcile( + MaxIntervalTestCustomResource resource, Context context) { + numberOfExecutions.addAndGet(1); + return UpdateControl.noUpdate(); + } + + public int getNumberOfExecutions() { + return numberOfExecutions.get(); + } + +} From 442e7d8718e992a36880e42bd0a5c01affaec9df Mon Sep 17 00:00:00 2001 From: csviri Date: Wed, 26 Jan 2022 15:37:14 +0100 Subject: [PATCH 04/13] fix: impl fix with config --- docs/documentation/features.md | 13 +++++++----- .../ControllerConfigurationOverrider.java | 21 ++++++++++++++++++- .../DefaultControllerConfiguration.java | 18 +++++++++++++++- .../operator/api/reconciler/Constants.java | 2 +- .../reconciler/ControllerConfiguration.java | 7 ++++++- .../operator/ControllerManagerTest.java | 3 ++- .../event/ReconciliationDispatcherTest.java | 2 +- .../event/source/ResourceEventFilterTest.java | 2 +- .../ControllerResourceEventSourceTest.java | 3 ++- 9 files changed, 58 insertions(+), 13 deletions(-) diff --git a/docs/documentation/features.md b/docs/documentation/features.md index 8edfec98e5..9a974e8eac 100644 --- a/docs/documentation/features.md +++ b/docs/documentation/features.md @@ -160,9 +160,7 @@ larger than the `.observedGeneration` field on status. In order to have this fea . If these conditions are fulfilled and generation awareness not turned off, the observed generation is automatically set -by the framework after the `reconcile` method is called. There is just one exception, when the reconciler returns -with `UpdateControl.updateResource`, in this case the status is not updated, just the custom resource - however, this -update will lead to a new reconciliation. Note that the observed generation is updated also +by the framework after the `reconcile` method is called. Note that the observed generation is updated also when `UpdateControl.noUpdate()` is returned from the reconciler. See this feature working in the [WebPage example](https://github.com/java-operator-sdk/java-operator-sdk/blob/b91221bb54af19761a617bf18eef381e8ceb3b4c/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStatus.java#L5) . @@ -198,8 +196,9 @@ in status) to handle generation filtering in memory. Thus, if an event is receiv resource is compared with the resource in the cache. Note that the **first approach has significant benefits** in the situation when the operator is restarted and there is -no cached resource anymore. In case two this leads to a reconciliation of every resource, event if the resource is not -changed while the operator was not running. +no cached resource anymore. In case two this leads to a reconciliation of every resource in all cases, +event if the resource is not changed while the operator was not running. However, in case informers are used +the reconciliation from startup will happen anyway, since events will be propagated by the informer. ## Support for Well Known (non-custom) Kubernetes Resources @@ -223,6 +222,10 @@ public class DeploymentReconciler } ``` +## Max Interval Between Reconciliations + + + ## Automatic Retries on Error When an exception is thrown from a controller, the framework will schedule an automatic retry of the reconciliation. The diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java index e8e2ef1162..e0c0634ec8 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java @@ -3,6 +3,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.TimeUnit; import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter; @@ -16,6 +17,8 @@ public class ControllerConfigurationOverrider { private String labelSelector; private ResourceEventFilter customResourcePredicate; private final ControllerConfiguration original; + private long reconciliationMaxInterval; + private TimeUnit reconciliationMaxIntervalTimeUnit; private ControllerConfigurationOverrider(ControllerConfiguration original) { finalizer = original.getFinalizer(); @@ -24,7 +27,10 @@ private ControllerConfigurationOverrider(ControllerConfiguration original) { retry = original.getRetryConfiguration(); labelSelector = original.getLabelSelector(); customResourcePredicate = original.getEventFilter(); + reconciliationMaxInterval = original.reconciliationMaxInterval(); + reconciliationMaxIntervalTimeUnit = original.reconciliationMaxIntervalTimeUnit(); this.original = original; + } public ControllerConfigurationOverrider withFinalizer(String finalizer) { @@ -74,6 +80,18 @@ public ControllerConfigurationOverrider withCustomResourcePredicate( return this; } + public ControllerConfigurationOverrider withReconciliationMaxInterval( + long reconciliationMaxInterval) { + this.reconciliationMaxInterval = reconciliationMaxInterval; + return this; + } + + public ControllerConfigurationOverrider withReconciliationMaxIntervalTimeUnit( + TimeUnit reconciliationMaxIntervalTimeUnit) { + this.reconciliationMaxIntervalTimeUnit = reconciliationMaxIntervalTimeUnit; + return this; + } + public ControllerConfiguration build() { return new DefaultControllerConfiguration<>( original.getAssociatedReconcilerClassName(), @@ -86,7 +104,8 @@ public ControllerConfiguration build() { labelSelector, customResourcePredicate, original.getResourceClass(), - original.getConfigurationService()); + reconciliationMaxInterval, + reconciliationMaxIntervalTimeUnit, original.getConfigurationService()); } public static ControllerConfigurationOverrider override( diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java index 860152745b..bc7f122a83 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java @@ -2,6 +2,7 @@ import java.util.Collections; import java.util.Set; +import java.util.concurrent.TimeUnit; import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter; @@ -20,6 +21,8 @@ public class DefaultControllerConfiguration private final String labelSelector; private final ResourceEventFilter resourceEventFilter; private final Class resourceClass; + private final long reconciliationMaxInterval; + private final TimeUnit reconciliationMaxIntervalTimeUnit; private ConfigurationService service; public DefaultControllerConfiguration( @@ -33,7 +36,8 @@ public DefaultControllerConfiguration( String labelSelector, ResourceEventFilter resourceEventFilter, Class resourceClass, - ConfigurationService service) { + long reconciliationMaxInterval, + TimeUnit reconciliationMaxIntervalTimeUnit, ConfigurationService service) { this.associatedControllerClassName = associatedControllerClassName; this.name = name; this.crdName = crdName; @@ -41,6 +45,8 @@ public DefaultControllerConfiguration( this.generationAware = generationAware; this.namespaces = namespaces != null ? Collections.unmodifiableSet(namespaces) : Collections.emptySet(); + this.reconciliationMaxIntervalTimeUnit = reconciliationMaxIntervalTimeUnit; + this.reconciliationMaxInterval = reconciliationMaxInterval; this.watchAllNamespaces = this.namespaces.isEmpty(); this.retryConfiguration = retryConfiguration == null @@ -122,4 +128,14 @@ public Class getResourceClass() { public ResourceEventFilter getEventFilter() { return resourceEventFilter; } + + @Override + public long reconciliationMaxInterval() { + return reconciliationMaxInterval; + } + + @Override + public TimeUnit reconciliationMaxIntervalTimeUnit() { + return reconciliationMaxIntervalTimeUnit; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Constants.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Constants.java index dcf9440fbc..85b3a00807 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Constants.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Constants.java @@ -5,7 +5,7 @@ public final class Constants { public static final String EMPTY_STRING = ""; public static final String WATCH_CURRENT_NAMESPACE = "JOSDK_WATCH_CURRENT"; public static final String NO_FINALIZER = "JOSDK_NO_FINALIZER"; - public static final long NO_RECONCILIATION_MAX_DELAY = -1L; + public static final long NO_RECONCILIATION_MAX_INTERVAL = -1L; private Constants() {} } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java index 549edc4dbf..2ec5e36ca6 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java @@ -63,9 +63,14 @@ * reconciliation is scheduled with a target interval after the last reconciliation. Note that * this not applies for retries, in case of an exception reconciliation is not scheduled. This is * not a fixed rate, in other words a new reconciliation is scheduled after each reconciliation. - * + *

* If an interval is specified by {@link UpdateControl} or {@link DeleteControl}, those take * precedence. + *

+ * This is a fail-safe feature, in the sense that if informers are in place and the reconciler + * implementation is correct, this feature can be turned off. + *

+ * Use NO_RECONCILIATION_MAX_INTERVAL in {@link Constants} to turn off this feature. * * @return max delay between reconciliations **/ diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ControllerManagerTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ControllerManagerTest.java index a1cfa3e82d..15a688c96a 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ControllerManagerTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ControllerManagerTest.java @@ -60,7 +60,8 @@ private static class TestControllerConfiguration public TestControllerConfiguration(Reconciler controller, Class crClass) { super(null, getControllerName(controller), - CustomResource.getCRDName(crClass), null, false, null, null, null, null, crClass, null); + CustomResource.getCRDName(crClass), null, false, null, null, null, null, crClass, -1L, + null, null); this.controller = controller; } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java index 2c97bc9db3..7d80c454e9 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java @@ -457,7 +457,7 @@ void canSkipSchedulingMaxDelayIf() { when(reconciler.reconcile(eq(testCustomResource), any())) .thenReturn(UpdateControl.noUpdate()); when(configuration.reconciliationMaxInterval()) - .thenReturn(Constants.NO_RECONCILIATION_MAX_DELAY); + .thenReturn(Constants.NO_RECONCILIATION_MAX_INTERVAL); PostExecutionControl control = reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource)); diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventFilterTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventFilterTest.java index 17168876a5..76f241f951 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventFilterTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventFilterTest.java @@ -171,7 +171,7 @@ public ControllerConfig(String finalizer, boolean generationAware, null, eventFilter, customResourceClass, - null); + -1L, null, null); } } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java index 5cdc85c553..45e3aec221 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java @@ -170,7 +170,8 @@ public TestConfiguration(boolean generationAware) { null, null, TestCustomResource.class, - null); + -1L, + null, null); } } } From 2e8ceb51f32ce71821b9a3a437a1aade2ed5cb53 Mon Sep 17 00:00:00 2001 From: csviri Date: Wed, 26 Jan 2022 16:11:20 +0100 Subject: [PATCH 05/13] docs: description of the feature --- docs/documentation/features.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/documentation/features.md b/docs/documentation/features.md index 9a974e8eac..2254681fae 100644 --- a/docs/documentation/features.md +++ b/docs/documentation/features.md @@ -224,7 +224,24 @@ public class DeploymentReconciler ## Max Interval Between Reconciliations +In case informers are all in place and reconciler is implemented correctly the reconciliation is triggered when needed, +and reconciliation happening as expected. However, it's a [common practice](https://github.com/java-operator-sdk/java-operator-sdk/issues/848#issuecomment-1016419966) having a failsafe periodic trigger in place, +just to make sure the resources are reconciled after certain time. This functionality is in place by default, there +is quite high interval (currently 10 hours) while the reconciliation is triggered. See how to override this using +the standard annotation: +```java +@ControllerConfiguration(finalizerName = NO_FINALIZER, reconciliationMaxInterval = 2L, + reconciliationMaxIntervalTimeUnit = TimeUnit.HOURS) +``` + +The event is not propagated in a fixed rate, rather it's scheduled after each reconciliation. So the +next reconciliation will after at most within the specified interval after last reconciliation. + +This feature can be turned off by setting `reconciliationMaxInterval` to [`Constants.NO_RECONCILIATION_MAX_INTERVAL`](https://github.com/java-operator-sdk/java-operator-sdk/blob/442e7d8718e992a36880e42bd0a5c01affaec9df/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Constants.java#L8-L8) +or any non-positive number. + +The automatic retries are not affected by this feature, in case of an error no schedule is set by this feature. ## Automatic Retries on Error From 608aa9fdfeed7aa9197e6d1d927958965df58010 Mon Sep 17 00:00:00 2001 From: csviri Date: Thu, 27 Jan 2022 10:54:06 +0100 Subject: [PATCH 06/13] docs: improved wording --- docs/documentation/features.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/documentation/features.md b/docs/documentation/features.md index 2254681fae..b7e6515485 100644 --- a/docs/documentation/features.md +++ b/docs/documentation/features.md @@ -224,8 +224,9 @@ public class DeploymentReconciler ## Max Interval Between Reconciliations -In case informers are all in place and reconciler is implemented correctly the reconciliation is triggered when needed, -and reconciliation happening as expected. However, it's a [common practice](https://github.com/java-operator-sdk/java-operator-sdk/issues/848#issuecomment-1016419966) having a failsafe periodic trigger in place, +In case informers are all in place and reconciler is implemented correctly, there is no need for additional triggers. +However, it's a [common practice](https://github.com/java-operator-sdk/java-operator-sdk/issues/848#issuecomment-1016419966) +having a failsafe periodic trigger in place, just to make sure the resources are reconciled after certain time. This functionality is in place by default, there is quite high interval (currently 10 hours) while the reconciliation is triggered. See how to override this using the standard annotation: From 3f1e796252a4088d5edd790c247a0e2e7b0b99d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 28 Jan 2022 10:34:19 +0100 Subject: [PATCH 07/13] Update docs/documentation/features.md Co-authored-by: Chris Laprun --- docs/documentation/features.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/documentation/features.md b/docs/documentation/features.md index b7e6515485..496ed80d7e 100644 --- a/docs/documentation/features.md +++ b/docs/documentation/features.md @@ -226,7 +226,7 @@ public class DeploymentReconciler In case informers are all in place and reconciler is implemented correctly, there is no need for additional triggers. However, it's a [common practice](https://github.com/java-operator-sdk/java-operator-sdk/issues/848#issuecomment-1016419966) -having a failsafe periodic trigger in place, +to have a failsafe periodic trigger in place, just to make sure the resources are reconciled after certain time. This functionality is in place by default, there is quite high interval (currently 10 hours) while the reconciliation is triggered. See how to override this using the standard annotation: From bdcd99aca1271cb9b411fd66c898b2e13d5f3d01 Mon Sep 17 00:00:00 2001 From: csviri Date: Fri, 28 Jan 2022 10:44:37 +0100 Subject: [PATCH 08/13] docs: revert in relevant part --- docs/documentation/features.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/documentation/features.md b/docs/documentation/features.md index 496ed80d7e..517b54e71e 100644 --- a/docs/documentation/features.md +++ b/docs/documentation/features.md @@ -196,9 +196,8 @@ in status) to handle generation filtering in memory. Thus, if an event is receiv resource is compared with the resource in the cache. Note that the **first approach has significant benefits** in the situation when the operator is restarted and there is -no cached resource anymore. In case two this leads to a reconciliation of every resource in all cases, -event if the resource is not changed while the operator was not running. However, in case informers are used -the reconciliation from startup will happen anyway, since events will be propagated by the informer. +no cached resource anymore. In case two this leads to a reconciliation of every resource, event if the resource is not +changed while the operator was not running. ## Support for Well Known (non-custom) Kubernetes Resources From eb86d66e8a37753458b5c4db2cd9c7c94a27c0cd Mon Sep 17 00:00:00 2001 From: csviri Date: Fri, 28 Jan 2022 15:45:49 +0100 Subject: [PATCH 09/13] fix: using nested annotation --- .../reconciler/ControllerConfiguration.java | 23 ++---------- .../reconciler/ReconciliationMaxInterval.java | 36 +++++++++++++++++++ .../runtime/AnnotationConfiguration.java | 14 +++++--- .../MaxIntervalTestReconciler.java | 5 +-- 4 files changed, 51 insertions(+), 27 deletions(-) create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ReconciliationMaxInterval.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java index 2ec5e36ca6..bd8863cad1 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java @@ -58,26 +58,7 @@ @SuppressWarnings("rawtypes") Class[] eventFilters() default {}; - /** - * A max delay between two reconciliations. Having this value larger than zero, will ensure that a - * reconciliation is scheduled with a target interval after the last reconciliation. Note that - * this not applies for retries, in case of an exception reconciliation is not scheduled. This is - * not a fixed rate, in other words a new reconciliation is scheduled after each reconciliation. - *

- * If an interval is specified by {@link UpdateControl} or {@link DeleteControl}, those take - * precedence. - *

- * This is a fail-safe feature, in the sense that if informers are in place and the reconciler - * implementation is correct, this feature can be turned off. - *

- * Use NO_RECONCILIATION_MAX_INTERVAL in {@link Constants} to turn off this feature. - * - * @return max delay between reconciliations - **/ - long reconciliationMaxInterval() default 10; + ReconciliationMaxInterval reconciliationMaxInterval() default @ReconciliationMaxInterval( + interval = 10, timeUnit = TimeUnit.HOURS); - /** - * @return time unit for max delay between reconciliations - */ - TimeUnit reconciliationMaxIntervalTimeUnit() default TimeUnit.HOURS; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ReconciliationMaxInterval.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ReconciliationMaxInterval.java new file mode 100644 index 0000000000..bef7131daf --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ReconciliationMaxInterval.java @@ -0,0 +1,36 @@ +package io.javaoperatorsdk.operator.api.reconciler; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.concurrent.TimeUnit; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface ReconciliationMaxInterval { + + /** + * A max delay between two reconciliations. Having this value larger than zero, will ensure that a + * reconciliation is scheduled with a target interval after the last reconciliation. Note that + * this not applies for retries, in case of an exception reconciliation is not scheduled. This is + * not a fixed rate, in other words a new reconciliation is scheduled after each reconciliation. + *

+ * If an interval is specified by {@link UpdateControl} or {@link DeleteControl}, those take + * precedence. + *

+ * This is a fail-safe feature, in the sense that if informers are in place and the reconciler + * implementation is correct, this feature can be turned off. + *

+ * Use NO_RECONCILIATION_MAX_INTERVAL in {@link Constants} to turn off this feature. + * + * @return max delay between reconciliations + **/ + long interval(); + + /** + * @return time unit for max delay between reconciliations + */ + TimeUnit timeUnit(); + +} diff --git a/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationConfiguration.java b/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationConfiguration.java index 0528419578..dc5686ca05 100644 --- a/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationConfiguration.java +++ b/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationConfiguration.java @@ -111,14 +111,20 @@ public ResourceEventFilter getEventFilter() { @Override public long reconciliationMaxInterval() { - return valueOrDefault(annotation, ControllerConfiguration::reconciliationMaxInterval, - io.javaoperatorsdk.operator.api.config.ControllerConfiguration.super.reconciliationMaxInterval()); + if (annotation.reconciliationMaxInterval() != null) { + return annotation.reconciliationMaxInterval().interval(); + } else { + return io.javaoperatorsdk.operator.api.config.ControllerConfiguration.super.reconciliationMaxInterval(); + } } @Override public TimeUnit reconciliationMaxIntervalTimeUnit() { - return valueOrDefault(annotation, ControllerConfiguration::reconciliationMaxIntervalTimeUnit, - io.javaoperatorsdk.operator.api.config.ControllerConfiguration.super.reconciliationMaxIntervalTimeUnit()); + if (annotation.reconciliationMaxInterval() != null) { + return annotation.reconciliationMaxInterval().timeUnit(); + } else { + return io.javaoperatorsdk.operator.api.config.ControllerConfiguration.super.reconciliationMaxIntervalTimeUnit(); + } } public static T valueOrDefault(ControllerConfiguration controllerConfiguration, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/maxinterval/MaxIntervalTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/maxinterval/MaxIntervalTestReconciler.java index c0e59fd7ef..a5343c27a4 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/maxinterval/MaxIntervalTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/maxinterval/MaxIntervalTestReconciler.java @@ -8,8 +8,9 @@ import static io.javaoperatorsdk.operator.api.reconciler.Constants.NO_FINALIZER; -@ControllerConfiguration(finalizerName = NO_FINALIZER, reconciliationMaxInterval = 50, - reconciliationMaxIntervalTimeUnit = TimeUnit.MILLISECONDS) +@ControllerConfiguration(finalizerName = NO_FINALIZER, + reconciliationMaxInterval = @ReconciliationMaxInterval(interval = 50, + timeUnit = TimeUnit.MILLISECONDS)) public class MaxIntervalTestReconciler implements Reconciler, TestExecutionInfoProvider { From 0b6532c0cbe014e7832323c8cc8a0bfb30c26390 Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 31 Jan 2022 13:49:12 +0100 Subject: [PATCH 10/13] fix: changed controller config servuce api to use Duration --- .../api/config/ControllerConfiguration.java | 11 ++++------ .../ControllerConfigurationOverrider.java | 18 +++++---------- .../DefaultControllerConfiguration.java | 20 ++++++----------- .../event/ReconciliationDispatcher.java | 10 +++------ .../operator/ControllerManagerTest.java | 2 +- .../event/ReconciliationDispatcherTest.java | 8 ++++--- .../event/source/ResourceEventFilterTest.java | 2 +- .../ControllerResourceEventSourceTest.java | 4 ++-- .../runtime/AnnotationConfiguration.java | 22 +++++++++---------- 9 files changed, 38 insertions(+), 59 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java index dfa9c79697..f4909398a4 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java @@ -1,9 +1,10 @@ package io.javaoperatorsdk.operator.api.config; import java.lang.reflect.ParameterizedType; +import java.time.Duration; import java.util.Collections; +import java.util.Optional; import java.util.Set; -import java.util.concurrent.TimeUnit; import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.ReconcilerUtils; @@ -116,11 +117,7 @@ default ResourceEventFilter getEventFilter() { return ResourceEventFilters.passthrough(); } - default long reconciliationMaxInterval() { - return 10L; - } - - default TimeUnit reconciliationMaxIntervalTimeUnit() { - return TimeUnit.HOURS; + default Optional reconciliationMaxInterval() { + return Optional.of(Duration.ofMillis(10L)); } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java index e0c0634ec8..c018c369f0 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java @@ -1,9 +1,9 @@ package io.javaoperatorsdk.operator.api.config; +import java.time.Duration; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.concurrent.TimeUnit; import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter; @@ -17,8 +17,7 @@ public class ControllerConfigurationOverrider { private String labelSelector; private ResourceEventFilter customResourcePredicate; private final ControllerConfiguration original; - private long reconciliationMaxInterval; - private TimeUnit reconciliationMaxIntervalTimeUnit; + private Duration reconciliationMaxInterval; private ControllerConfigurationOverrider(ControllerConfiguration original) { finalizer = original.getFinalizer(); @@ -27,8 +26,7 @@ private ControllerConfigurationOverrider(ControllerConfiguration original) { retry = original.getRetryConfiguration(); labelSelector = original.getLabelSelector(); customResourcePredicate = original.getEventFilter(); - reconciliationMaxInterval = original.reconciliationMaxInterval(); - reconciliationMaxIntervalTimeUnit = original.reconciliationMaxIntervalTimeUnit(); + reconciliationMaxInterval = original.reconciliationMaxInterval().orElse(null); this.original = original; } @@ -81,17 +79,11 @@ public ControllerConfigurationOverrider withCustomResourcePredicate( } public ControllerConfigurationOverrider withReconciliationMaxInterval( - long reconciliationMaxInterval) { + Duration reconciliationMaxInterval) { this.reconciliationMaxInterval = reconciliationMaxInterval; return this; } - public ControllerConfigurationOverrider withReconciliationMaxIntervalTimeUnit( - TimeUnit reconciliationMaxIntervalTimeUnit) { - this.reconciliationMaxIntervalTimeUnit = reconciliationMaxIntervalTimeUnit; - return this; - } - public ControllerConfiguration build() { return new DefaultControllerConfiguration<>( original.getAssociatedReconcilerClassName(), @@ -105,7 +97,7 @@ public ControllerConfiguration build() { customResourcePredicate, original.getResourceClass(), reconciliationMaxInterval, - reconciliationMaxIntervalTimeUnit, original.getConfigurationService()); + original.getConfigurationService()); } public static ControllerConfigurationOverrider override( diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java index bc7f122a83..33ff56899e 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java @@ -1,8 +1,9 @@ package io.javaoperatorsdk.operator.api.config; +import java.time.Duration; import java.util.Collections; +import java.util.Optional; import java.util.Set; -import java.util.concurrent.TimeUnit; import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter; @@ -21,8 +22,7 @@ public class DefaultControllerConfiguration private final String labelSelector; private final ResourceEventFilter resourceEventFilter; private final Class resourceClass; - private final long reconciliationMaxInterval; - private final TimeUnit reconciliationMaxIntervalTimeUnit; + private final Duration reconciliationMaxInterval; private ConfigurationService service; public DefaultControllerConfiguration( @@ -36,8 +36,8 @@ public DefaultControllerConfiguration( String labelSelector, ResourceEventFilter resourceEventFilter, Class resourceClass, - long reconciliationMaxInterval, - TimeUnit reconciliationMaxIntervalTimeUnit, ConfigurationService service) { + Duration reconciliationMaxInterval, + ConfigurationService service) { this.associatedControllerClassName = associatedControllerClassName; this.name = name; this.crdName = crdName; @@ -45,7 +45,6 @@ public DefaultControllerConfiguration( this.generationAware = generationAware; this.namespaces = namespaces != null ? Collections.unmodifiableSet(namespaces) : Collections.emptySet(); - this.reconciliationMaxIntervalTimeUnit = reconciliationMaxIntervalTimeUnit; this.reconciliationMaxInterval = reconciliationMaxInterval; this.watchAllNamespaces = this.namespaces.isEmpty(); this.retryConfiguration = @@ -130,12 +129,7 @@ public ResourceEventFilter getEventFilter() { } @Override - public long reconciliationMaxInterval() { - return reconciliationMaxInterval; - } - - @Override - public TimeUnit reconciliationMaxIntervalTimeUnit() { - return reconciliationMaxIntervalTimeUnit; + public Optional reconciliationMaxInterval() { + return Optional.ofNullable(reconciliationMaxInterval); } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java index dd44c778da..02c8f8cbb3 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java @@ -246,15 +246,11 @@ private void updatePostExecutionControlWithReschedule( PostExecutionControl postExecutionControl, BaseControl baseControl) { baseControl.getScheduleDelay().ifPresentOrElse(postExecutionControl::withReSchedule, - () -> { - var maxDelay = controller.getConfiguration().reconciliationMaxInterval(); - if (maxDelay > 0) { - postExecutionControl.withReSchedule(controller.getConfiguration() - .reconciliationMaxIntervalTimeUnit().toMillis(maxDelay)); - } - }); + () -> controller.getConfiguration().reconciliationMaxInterval() + .ifPresent(m -> postExecutionControl.withReSchedule(m.toMillis()))); } + private PostExecutionControl handleCleanup(R resource, Context context) { log.debug( "Executing delete for resource: {} with version: {}", diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ControllerManagerTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ControllerManagerTest.java index 15a688c96a..19f65a9441 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ControllerManagerTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ControllerManagerTest.java @@ -60,7 +60,7 @@ private static class TestControllerConfiguration public TestControllerConfiguration(Reconciler controller, Class crClass) { super(null, getControllerName(controller), - CustomResource.getCRDName(crClass), null, false, null, null, null, null, crClass, -1L, + CustomResource.getCRDName(crClass), null, false, null, null, null, null, crClass, null, null); this.controller = controller; } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java index 7d80c454e9..8e4be5e6dc 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java @@ -1,5 +1,6 @@ package io.javaoperatorsdk.operator.processing.event; +import java.time.Duration; import java.util.ArrayList; import java.util.Optional; import java.util.concurrent.TimeUnit; @@ -76,8 +77,9 @@ private ReconciliationDispatcher init(R customResourc when(configuration.getName()).thenReturn("EventDispatcherTestController"); when(configuration.getResourceClass()).thenReturn((Class) customResource.getClass()); when(configuration.getRetryConfiguration()).thenReturn(RetryConfiguration.DEFAULT); - when(configuration.reconciliationMaxInterval()).thenReturn(RECONCILIATION_MAX_INTERVAL); - when(configuration.reconciliationMaxIntervalTimeUnit()).thenReturn(TimeUnit.HOURS); + when(configuration.reconciliationMaxInterval()) + .thenReturn(Optional.of(Duration.ofHours(RECONCILIATION_MAX_INTERVAL))); + when(configuration.getConfigurationService()).thenReturn(configService); @@ -457,7 +459,7 @@ void canSkipSchedulingMaxDelayIf() { when(reconciler.reconcile(eq(testCustomResource), any())) .thenReturn(UpdateControl.noUpdate()); when(configuration.reconciliationMaxInterval()) - .thenReturn(Constants.NO_RECONCILIATION_MAX_INTERVAL); + .thenReturn(Optional.empty()); PostExecutionControl control = reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource)); diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventFilterTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventFilterTest.java index 76f241f951..433614fe17 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventFilterTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventFilterTest.java @@ -171,7 +171,7 @@ public ControllerConfig(String finalizer, boolean generationAware, null, eventFilter, customResourceClass, - -1L, null, null); + null, null); } } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java index 45e3aec221..5859115ee2 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java @@ -170,8 +170,8 @@ public TestConfiguration(boolean generationAware) { null, null, TestCustomResource.class, - -1L, - null, null); + null, + null); } } } diff --git a/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationConfiguration.java b/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationConfiguration.java index dc5686ca05..8977bf3ff8 100644 --- a/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationConfiguration.java +++ b/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationConfiguration.java @@ -1,7 +1,8 @@ package io.javaoperatorsdk.operator.config.runtime; +import java.time.Duration; +import java.util.Optional; import java.util.Set; -import java.util.concurrent.TimeUnit; import java.util.function.Function; import io.fabric8.kubernetes.api.model.HasMetadata; @@ -109,21 +110,18 @@ public ResourceEventFilter getEventFilter() { : ResourceEventFilters.passthrough(); } - @Override - public long reconciliationMaxInterval() { - if (annotation.reconciliationMaxInterval() != null) { - return annotation.reconciliationMaxInterval().interval(); - } else { - return io.javaoperatorsdk.operator.api.config.ControllerConfiguration.super.reconciliationMaxInterval(); - } - } + @Override - public TimeUnit reconciliationMaxIntervalTimeUnit() { + public Optional reconciliationMaxInterval() { if (annotation.reconciliationMaxInterval() != null) { - return annotation.reconciliationMaxInterval().timeUnit(); + if (annotation.reconciliationMaxInterval().interval() <= 0) { + return Optional.empty(); + } + return Optional.of(Duration.of(annotation.reconciliationMaxInterval().interval(), + annotation.reconciliationMaxInterval().timeUnit().toChronoUnit())); } else { - return io.javaoperatorsdk.operator.api.config.ControllerConfiguration.super.reconciliationMaxIntervalTimeUnit(); + return io.javaoperatorsdk.operator.api.config.ControllerConfiguration.super.reconciliationMaxInterval(); } } From 11b7f5e26f23d0b8b2257fa5dbba5a6758a21bbb Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 31 Jan 2022 14:19:25 +0100 Subject: [PATCH 11/13] fix: wrong default --- .../operator/api/config/ControllerConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java index f4909398a4..eb247752aa 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java @@ -118,6 +118,6 @@ default ResourceEventFilter getEventFilter() { } default Optional reconciliationMaxInterval() { - return Optional.of(Duration.ofMillis(10L)); + return Optional.of(Duration.ofHours(10L)); } } From 053bd2c506ed60607fed1c18136a617b3d9b47e7 Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 31 Jan 2022 14:23:21 +0100 Subject: [PATCH 12/13] docs: updated --- docs/documentation/features.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/documentation/features.md b/docs/documentation/features.md index 517b54e71e..bcd657eca3 100644 --- a/docs/documentation/features.md +++ b/docs/documentation/features.md @@ -231,8 +231,10 @@ is quite high interval (currently 10 hours) while the reconciliation is triggere the standard annotation: ```java -@ControllerConfiguration(finalizerName = NO_FINALIZER, reconciliationMaxInterval = 2L, - reconciliationMaxIntervalTimeUnit = TimeUnit.HOURS) +@ControllerConfiguration(finalizerName = NO_FINALIZER, + reconciliationMaxInterval = @ReconciliationMaxInterval( + interval = 50, + timeUnit = TimeUnit.MILLISECONDS)) ``` The event is not propagated in a fixed rate, rather it's scheduled after each reconciliation. So the From b2338c79a8a8c7d8f333ad0086c346ccb2b4d39f Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 31 Jan 2022 14:26:53 +0100 Subject: [PATCH 13/13] fix: added default time interval --- .../operator/api/reconciler/ReconciliationMaxInterval.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ReconciliationMaxInterval.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ReconciliationMaxInterval.java index bef7131daf..05459e7123 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ReconciliationMaxInterval.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ReconciliationMaxInterval.java @@ -31,6 +31,6 @@ /** * @return time unit for max delay between reconciliations */ - TimeUnit timeUnit(); + TimeUnit timeUnit() default TimeUnit.HOURS; }