From 6d84d0808aafcfcf5f605e6704590ad75fa8aa12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Mon, 11 Aug 2025 10:58:18 +0200 Subject: [PATCH 1/5] feat: all-event mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- notes.txt | 1 + .../operator/api/config/ControllerConfiguration.java | 4 ++++ .../operator/api/config/ControllerMode.java | 6 ++++++ .../operator/api/reconciler/ControllerConfiguration.java | 3 +++ .../operator/processing/event/EventProcessor.java | 9 +++++++-- .../operator/processing/event/ResourceState.java | 9 ++++++++- 6 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 notes.txt create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerMode.java diff --git a/notes.txt b/notes.txt new file mode 100644 index 0000000000..253d135578 --- /dev/null +++ b/notes.txt @@ -0,0 +1 @@ +- check that Cleaner interface is not present 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 2c18fa55d3..ca66ffc56c 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 @@ -92,4 +92,8 @@ default String fieldManager() { } C getConfigurationFor(DependentResourceSpec spec); + + default ControllerMode getMode() { + return ControllerMode.DEFAULT; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerMode.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerMode.java new file mode 100644 index 0000000000..330313015b --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerMode.java @@ -0,0 +1,6 @@ +package io.javaoperatorsdk.operator.api.config; + +public enum ControllerMode { + DEFAULT, + ALL_EVENT_MODE +} 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 d407ed0fc6..bf64009997 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 @@ -6,6 +6,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import io.javaoperatorsdk.operator.api.config.ControllerMode; import io.javaoperatorsdk.operator.api.config.informer.Informer; import io.javaoperatorsdk.operator.processing.event.rate.LinearRateLimiter; import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter; @@ -77,4 +78,6 @@ MaxReconciliationInterval maxReconciliationInterval() default * @return the name used as field manager for SSA operations */ String fieldManager() default CONTROLLER_NAME_AS_FIELD_MANAGER; + + ControllerMode allEventMode() default ControllerMode.DEFAULT; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java index bdaf575814..3ee3547639 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java @@ -15,6 +15,7 @@ import io.javaoperatorsdk.operator.OperatorException; import io.javaoperatorsdk.operator.api.config.ConfigurationService; import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; +import io.javaoperatorsdk.operator.api.config.ControllerMode; import io.javaoperatorsdk.operator.api.monitoring.Metrics; import io.javaoperatorsdk.operator.api.reconciler.Constants; import io.javaoperatorsdk.operator.processing.LifecycleAware; @@ -122,7 +123,7 @@ public synchronized void handleEvent(Event event) { } private void handleMarkedEventForResource(ResourceState state) { - if (state.deleteEventPresent()) { + if (state.deleteEventPresent() && !isAllEventMode()) { cleanupForDeletedEvent(state.getId()); } else if (!state.processedMarkForDeletionPresent()) { submitReconciliationExecution(state); @@ -179,7 +180,7 @@ private void handleEventMarking(Event event, ResourceState state) { if (event instanceof ResourceEvent resourceEvent) { if (resourceEvent.getAction() == ResourceAction.DELETED) { log.debug("Marking delete event received for: {}", relatedCustomResourceID); - state.markDeleteEventReceived(); + state.markDeleteEventReceived(resourceEvent.getResource().orElseThrow()); } else { if (state.processedMarkForDeletionPresent() && isResourceMarkedForDeletion(resourceEvent)) { log.debug( @@ -501,4 +502,8 @@ public synchronized boolean isUnderProcessing(ResourceID resourceID) { public synchronized boolean isRunning() { return running; } + + private boolean isAllEventMode() { + return controllerConfiguration.getMode() == ControllerMode.ALL_EVENT_MODE; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceState.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceState.java index 5d4e74d681..59ad479c0d 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceState.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceState.java @@ -1,5 +1,6 @@ package io.javaoperatorsdk.operator.processing.event; +import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter.RateLimitState; import io.javaoperatorsdk.operator.processing.retry.RetryExecution; @@ -29,6 +30,7 @@ private enum EventingState { private RetryExecution retry; private EventingState eventing; private RateLimitState rateLimit; + private HasMetadata lastKnownResource; public ResourceState(ResourceID id) { this.id = id; @@ -63,8 +65,9 @@ public void setUnderProcessing(boolean underProcessing) { this.underProcessing = underProcessing; } - public void markDeleteEventReceived() { + public void markDeleteEventReceived(HasMetadata lastKnownResource) { eventing = EventingState.DELETE_EVENT_PRESENT; + this.lastKnownResource = lastKnownResource; } public boolean deleteEventPresent() { @@ -94,6 +97,10 @@ public boolean noEventPresent() { return eventing == EventingState.NO_EVENT_PRESENT; } + public HasMetadata getLastKnownResource() { + return lastKnownResource; + } + public void unMarkEventReceived() { switch (eventing) { case EVENT_PRESENT: From 825cd2eca7acfa8ef41facdf3bb9e9d472f41de2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Mon, 11 Aug 2025 13:39:21 +0200 Subject: [PATCH 2/5] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../operator/api/config/ControllerMode.java | 2 +- .../processing/event/EventProcessor.java | 9 +++++- .../event/ResourceStateManager.java | 5 ++++ .../controller/ControllerEventSource.java | 23 ++++++++++---- .../controller/ResourceDeleteEvent.java | 18 +++++++++++ .../event/ResourceStateManagerTest.java | 10 ++++--- .../controller/ControllerEventSourceTest.java | 30 +++++++++---------- 7 files changed, 70 insertions(+), 27 deletions(-) create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ResourceDeleteEvent.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerMode.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerMode.java index 330313015b..536cefc7cf 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerMode.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerMode.java @@ -2,5 +2,5 @@ public enum ControllerMode { DEFAULT, - ALL_EVENT_MODE + RECONCILE_ALL_EVENT } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java index 3ee3547639..804222218a 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java @@ -465,6 +465,13 @@ public void run() { try { var actualResource = cache.get(resourceID); if (actualResource.isEmpty()) { + if (isAllEventMode()) { + var state = resourceStateManager.get(resourceID); + actualResource = + (Optional

) + state.filter(s -> s.deleteEventPresent()).map(s -> s.getLastKnownResource()); + } + log.debug("Skipping execution; primary resource missing from cache: {}", resourceID); return; } @@ -504,6 +511,6 @@ public synchronized boolean isRunning() { } private boolean isAllEventMode() { - return controllerConfiguration.getMode() == ControllerMode.ALL_EVENT_MODE; + return controllerConfiguration.getMode() == ControllerMode.RECONCILE_ALL_EVENT; } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManager.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManager.java index 6932e1ca5e..5b11d41dda 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManager.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManager.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @@ -15,6 +16,10 @@ public ResourceState getOrCreate(ResourceID resourceID) { return states.computeIfAbsent(resourceID, ResourceState::new); } + public Optional get(ResourceID resourceID) { + return Optional.ofNullable(states.get(resourceID)); + } + public ResourceState remove(ResourceID resourceID) { return states.remove(resourceID); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSource.java index eb9f65eafc..a505a97702 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSource.java @@ -62,7 +62,8 @@ public synchronized void start() { } } - public void eventReceived(ResourceAction action, T resource, T oldResource) { + public void eventReceived( + ResourceAction action, T resource, T oldResource, Boolean deletedFinalStateUnknown) { try { if (log.isDebugEnabled()) { log.debug( @@ -76,8 +77,18 @@ public void eventReceived(ResourceAction action, T resource, T oldResource) { MDCUtils.addResourceInfo(resource); controller.getEventSourceManager().broadcastOnResourceEvent(action, resource, oldResource); if (isAcceptedByFilters(action, resource, oldResource)) { - getEventHandler() - .handleEvent(new ResourceEvent(action, ResourceID.fromResource(resource), resource)); + if (deletedFinalStateUnknown != null) { + getEventHandler() + .handleEvent( + new ResourceDeleteEvent( + action, + ResourceID.fromResource(resource), + resource, + deletedFinalStateUnknown)); + } else { + getEventHandler() + .handleEvent(new ResourceEvent(action, ResourceID.fromResource(resource), resource)); + } } else { log.debug("Skipping event handling resource {}", ResourceID.fromResource(resource)); } @@ -103,19 +114,19 @@ private boolean isAcceptedByFilters(ResourceAction action, T resource, T oldReso @Override public void onAdd(T resource) { super.onAdd(resource); - eventReceived(ResourceAction.ADDED, resource, null); + eventReceived(ResourceAction.ADDED, resource, null, null); } @Override public void onUpdate(T oldCustomResource, T newCustomResource) { super.onUpdate(oldCustomResource, newCustomResource); - eventReceived(ResourceAction.UPDATED, newCustomResource, oldCustomResource); + eventReceived(ResourceAction.UPDATED, newCustomResource, oldCustomResource, null); } @Override public void onDelete(T resource, boolean b) { super.onDelete(resource, b); - eventReceived(ResourceAction.DELETED, resource, null); + eventReceived(ResourceAction.DELETED, resource, null, b); } @Override diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ResourceDeleteEvent.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ResourceDeleteEvent.java new file mode 100644 index 0000000000..83cee7fc77 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ResourceDeleteEvent.java @@ -0,0 +1,18 @@ +package io.javaoperatorsdk.operator.processing.event.source.controller; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.processing.event.ResourceID; + +public class ResourceDeleteEvent extends ResourceEvent { + + private final boolean deletedFinalStateUnknown; + + public ResourceDeleteEvent( + ResourceAction action, + ResourceID resourceID, + HasMetadata resource, + boolean deletedFinalStateUnknown) { + super(action, resourceID, resource); + this.deletedFinalStateUnknown = deletedFinalStateUnknown; + } +} diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManagerTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManagerTest.java index 2c4d9fa4f3..fa0cf5f9a7 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManagerTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManagerTest.java @@ -4,6 +4,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import io.javaoperatorsdk.operator.TestUtils; + import static org.assertj.core.api.Assertions.assertThat; class ResourceStateManagerTest { @@ -38,7 +40,7 @@ public void marksEvent() { @Test public void marksDeleteEvent() { - state.markDeleteEventReceived(); + state.markDeleteEventReceived(TestUtils.testCustomResource()); assertThat(state.deleteEventPresent()).isTrue(); assertThat(state.eventPresent()).isFalse(); @@ -48,7 +50,7 @@ public void marksDeleteEvent() { public void afterDeleteEventMarkEventIsNotRelevant() { state.markEventReceived(); - state.markDeleteEventReceived(); + state.markDeleteEventReceived(TestUtils.testCustomResource()); assertThat(state.deleteEventPresent()).isTrue(); assertThat(state.eventPresent()).isFalse(); @@ -57,7 +59,7 @@ public void afterDeleteEventMarkEventIsNotRelevant() { @Test public void cleansUp() { state.markEventReceived(); - state.markDeleteEventReceived(); + state.markDeleteEventReceived(TestUtils.testCustomResource()); manager.remove(sampleResourceID); @@ -71,7 +73,7 @@ public void cannotMarkEventAfterDeleteEventReceived() { Assertions.assertThrows( IllegalStateException.class, () -> { - state.markDeleteEventReceived(); + state.markDeleteEventReceived(TestUtils.testCustomResource()); state.markEventReceived(); }); } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSourceTest.java index 6548bbddc7..257af38e0c 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSourceTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSourceTest.java @@ -53,10 +53,10 @@ void skipsEventHandlingIfGenerationNotIncreased() { TestCustomResource oldCustomResource = TestUtils.testCustomResource(); oldCustomResource.getMetadata().setFinalizers(List.of(FINALIZER)); - source.eventReceived(ResourceAction.UPDATED, customResource, oldCustomResource); + source.eventReceived(ResourceAction.UPDATED, customResource, oldCustomResource, null); verify(eventHandler, times(1)).handleEvent(any()); - source.eventReceived(ResourceAction.UPDATED, customResource, customResource); + source.eventReceived(ResourceAction.UPDATED, customResource, customResource, null); verify(eventHandler, times(1)).handleEvent(any()); } @@ -64,12 +64,12 @@ void skipsEventHandlingIfGenerationNotIncreased() { void dontSkipEventHandlingIfMarkedForDeletion() { TestCustomResource customResource1 = TestUtils.testCustomResource(); - source.eventReceived(ResourceAction.UPDATED, customResource1, customResource1); + source.eventReceived(ResourceAction.UPDATED, customResource1, customResource1, null); verify(eventHandler, times(1)).handleEvent(any()); // mark for deletion customResource1.getMetadata().setDeletionTimestamp(LocalDateTime.now().toString()); - source.eventReceived(ResourceAction.UPDATED, customResource1, customResource1); + source.eventReceived(ResourceAction.UPDATED, customResource1, customResource1, null); verify(eventHandler, times(2)).handleEvent(any()); } @@ -77,11 +77,11 @@ void dontSkipEventHandlingIfMarkedForDeletion() { void normalExecutionIfGenerationChanges() { TestCustomResource customResource1 = TestUtils.testCustomResource(); - source.eventReceived(ResourceAction.UPDATED, customResource1, customResource1); + source.eventReceived(ResourceAction.UPDATED, customResource1, customResource1, null); verify(eventHandler, times(1)).handleEvent(any()); customResource1.getMetadata().setGeneration(2L); - source.eventReceived(ResourceAction.UPDATED, customResource1, customResource1); + source.eventReceived(ResourceAction.UPDATED, customResource1, customResource1, null); verify(eventHandler, times(2)).handleEvent(any()); } @@ -92,10 +92,10 @@ void handlesAllEventIfNotGenerationAware() { TestCustomResource customResource1 = TestUtils.testCustomResource(); - source.eventReceived(ResourceAction.UPDATED, customResource1, customResource1); + source.eventReceived(ResourceAction.UPDATED, customResource1, customResource1, null); verify(eventHandler, times(1)).handleEvent(any()); - source.eventReceived(ResourceAction.UPDATED, customResource1, customResource1); + source.eventReceived(ResourceAction.UPDATED, customResource1, customResource1, null); verify(eventHandler, times(2)).handleEvent(any()); } @@ -103,7 +103,7 @@ void handlesAllEventIfNotGenerationAware() { void eventWithNoGenerationProcessedIfNoFinalizer() { TestCustomResource customResource1 = TestUtils.testCustomResource(); - source.eventReceived(ResourceAction.UPDATED, customResource1, customResource1); + source.eventReceived(ResourceAction.UPDATED, customResource1, customResource1, null); verify(eventHandler, times(1)).handleEvent(any()); } @@ -112,7 +112,7 @@ void eventWithNoGenerationProcessedIfNoFinalizer() { void callsBroadcastsOnResourceEvents() { TestCustomResource customResource1 = TestUtils.testCustomResource(); - source.eventReceived(ResourceAction.UPDATED, customResource1, customResource1); + source.eventReceived(ResourceAction.UPDATED, customResource1, customResource1, null); verify(testController.getEventSourceManager(), times(1)) .broadcastOnResourceEvent( @@ -128,8 +128,8 @@ void filtersOutEventsOnAddAndUpdate() { source = new ControllerEventSource<>(new TestController(onAddFilter, onUpdatePredicate, null)); setUpSource(source, true, controllerConfig); - source.eventReceived(ResourceAction.ADDED, cr, null); - source.eventReceived(ResourceAction.UPDATED, cr, cr); + source.eventReceived(ResourceAction.ADDED, cr, null, null); + source.eventReceived(ResourceAction.UPDATED, cr, cr, null); verify(eventHandler, never()).handleEvent(any()); } @@ -141,9 +141,9 @@ void genericFilterFiltersOutAddUpdateAndDeleteEvents() { source = new ControllerEventSource<>(new TestController(null, null, res -> false)); setUpSource(source, true, controllerConfig); - source.eventReceived(ResourceAction.ADDED, cr, null); - source.eventReceived(ResourceAction.UPDATED, cr, cr); - source.eventReceived(ResourceAction.DELETED, cr, cr); + source.eventReceived(ResourceAction.ADDED, cr, null, null); + source.eventReceived(ResourceAction.UPDATED, cr, cr, null); + source.eventReceived(ResourceAction.DELETED, cr, cr, true); verify(eventHandler, never()).handleEvent(any()); } From b068d1db837535f6ec21907747f5998a9188d2d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 15 Aug 2025 10:49:35 +0200 Subject: [PATCH 3/5] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../api/config/BaseConfigurationService.java | 7 +++- .../api/config/ControllerConfiguration.java | 6 ++- .../ControllerConfigurationOverrider.java | 8 ++++ .../operator/api/config/ControllerMode.java | 2 +- .../ResolvedControllerConfiguration.java | 18 +++++++-- .../operator/api/reconciler/Context.java | 4 ++ .../reconciler/ControllerConfiguration.java | 2 +- .../api/reconciler/DefaultContext.java | 23 +++++++++++- .../processing/event/EventProcessor.java | 37 ++++++++++++------- .../processing/event/ExecutionScope.java | 18 +++++++++ .../event/ReconciliationDispatcher.java | 16 ++++++-- .../processing/event/ResourceState.java | 9 ++++- .../controller/ResourceDeleteEvent.java | 4 ++ .../api/reconciler/DefaultContextTest.java | 3 +- .../operator/processing/ControllerTest.java | 3 +- .../processing/event/EventProcessorTest.java | 6 ++- .../event/ResourceStateManagerTest.java | 8 ++-- .../controller/ControllerEventSourceTest.java | 3 +- 18 files changed, 141 insertions(+), 36 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java index 438f7d91a9..4cd88df891 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java @@ -296,12 +296,14 @@ private

ResolvedControllerConfiguration

controllerCon final var dependentFieldManager = fieldManager.equals(CONTROLLER_NAME_AS_FIELD_MANAGER) ? name : fieldManager; + var controllerMode = annotation == null ? ControllerMode.DEFAULT : annotation.controllerMode(); + InformerConfiguration

informerConfig = InformerConfiguration.builder(resourceClass) .initFromAnnotation(annotation != null ? annotation.informer() : null, context) .buildForController(); - return new ResolvedControllerConfiguration

( + return new ResolvedControllerConfiguration<>( name, generationAware, associatedReconcilerClass, @@ -315,7 +317,8 @@ private

ResolvedControllerConfiguration

controllerCon null, dependentFieldManager, this, - informerConfig); + informerConfig, + controllerMode); } protected boolean createIfNeeded() { 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 ca66ffc56c..29ecd418fa 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 @@ -93,7 +93,11 @@ default String fieldManager() { C getConfigurationFor(DependentResourceSpec spec); - default ControllerMode getMode() { + default ControllerMode getControllerMode() { return ControllerMode.DEFAULT; } + + default boolean isAllEventReconcileMode() { + return getControllerMode() == ControllerMode.ALL_EVENT_RECONCILE; + } } 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 d2e37a397d..5138c666a9 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 @@ -30,6 +30,7 @@ public class ControllerConfigurationOverrider { private Duration reconciliationMaxInterval; private Map configurations; private final InformerConfiguration.Builder config; + private ControllerMode controllerMode; private ControllerConfigurationOverrider(ControllerConfiguration original) { this.finalizer = original.getFinalizerName(); @@ -42,6 +43,7 @@ private ControllerConfigurationOverrider(ControllerConfiguration original) { this.rateLimiter = original.getRateLimiter(); this.name = original.getName(); this.fieldManager = original.fieldManager(); + this.controllerMode = original.getControllerMode(); } public ControllerConfigurationOverrider withFinalizer(String finalizer) { @@ -154,6 +156,11 @@ public ControllerConfigurationOverrider withFieldManager(String dependentFiel return this; } + public ControllerConfigurationOverrider withControllerMode(ControllerMode controllerMode) { + this.controllerMode = controllerMode; + return this; + } + /** * Sets a max page size limit when starting the informer. This will result in pagination while * populating the cache. This means that longer lists will take multiple requests to fetch. See @@ -198,6 +205,7 @@ public ControllerConfiguration build() { fieldManager, original.getConfigurationService(), config.buildForController(), + controllerMode, original.getWorkflowSpec().orElse(null)); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerMode.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerMode.java index 536cefc7cf..2e73b28c15 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerMode.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerMode.java @@ -2,5 +2,5 @@ public enum ControllerMode { DEFAULT, - RECONCILE_ALL_EVENT + ALL_EVENT_RECONCILE } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResolvedControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResolvedControllerConfiguration.java index 3c26659ed2..4880eee48c 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResolvedControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResolvedControllerConfiguration.java @@ -30,6 +30,7 @@ public class ResolvedControllerConfiguration

private final ConfigurationService configurationService; private final String fieldManager; private WorkflowSpec workflowSpec; + private ControllerMode controllerMode; public ResolvedControllerConfiguration(ControllerConfiguration

other) { this( @@ -44,6 +45,7 @@ public ResolvedControllerConfiguration(ControllerConfiguration

other) { other.fieldManager(), other.getConfigurationService(), other.getInformerConfig(), + other.getControllerMode(), other.getWorkflowSpec().orElse(null)); } @@ -59,6 +61,7 @@ public ResolvedControllerConfiguration( String fieldManager, ConfigurationService configurationService, InformerConfiguration

informerConfig, + ControllerMode controllerMode, WorkflowSpec workflowSpec) { this( name, @@ -71,7 +74,8 @@ public ResolvedControllerConfiguration( configurations, fieldManager, configurationService, - informerConfig); + informerConfig, + controllerMode); setWorkflowSpec(workflowSpec); } @@ -86,7 +90,8 @@ protected ResolvedControllerConfiguration( Map configurations, String fieldManager, ConfigurationService configurationService, - InformerConfiguration

informerConfig) { + InformerConfiguration

informerConfig, + ControllerMode controllerMode) { this.informerConfig = informerConfig; this.configurationService = configurationService; this.name = ControllerConfiguration.ensureValidName(name, associatedReconcilerClassName); @@ -99,6 +104,7 @@ protected ResolvedControllerConfiguration( this.finalizer = ControllerConfiguration.ensureValidFinalizerName(finalizer, getResourceTypeName()); this.fieldManager = fieldManager; + this.controllerMode = controllerMode; } protected ResolvedControllerConfiguration( @@ -117,7 +123,8 @@ protected ResolvedControllerConfiguration( null, null, configurationService, - InformerConfiguration.builder(resourceClass).buildForController()); + InformerConfiguration.builder(resourceClass).buildForController(), + null); } @Override @@ -207,4 +214,9 @@ public C getConfigurationFor(DependentResourceSpec spec) { public String fieldManager() { return fieldManager; } + + @Override + public ControllerMode getControllerMode() { + return controllerMode; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java index f47deb9734..b063dfedaf 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java @@ -72,4 +72,8 @@ default Stream getSecondaryResourcesAsStream(Class expectedType) { * @return {@code true} is another reconciliation is already scheduled, {@code false} otherwise */ boolean isNextReconciliationImminent(); + + boolean isDeleteEventPresent(); + + boolean isDeleteFinalStateUnknown(); } 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 bf64009997..d235124463 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 @@ -79,5 +79,5 @@ MaxReconciliationInterval maxReconciliationInterval() default */ String fieldManager() default CONTROLLER_NAME_AS_FIELD_MANAGER; - ControllerMode allEventMode() default ControllerMode.DEFAULT; + ControllerMode controllerMode() default ControllerMode.DEFAULT; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java index 2acf8d13ca..d6d454bd3f 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java @@ -24,12 +24,21 @@ public class DefaultContext

implements Context

{ private final ControllerConfiguration

controllerConfiguration; private final DefaultManagedWorkflowAndDependentResourceContext

defaultManagedDependentResourceContext; - - public DefaultContext(RetryInfo retryInfo, Controller

controller, P primaryResource) { + private final boolean isDeleteEventPresent; + private final boolean isDeleteFinalStateUnknown; + + public DefaultContext( + RetryInfo retryInfo, + Controller

controller, + P primaryResource, + boolean isDeleteEventPresent, + boolean isDeleteFinalStateUnknown) { this.retryInfo = retryInfo; this.controller = controller; this.primaryResource = primaryResource; this.controllerConfiguration = controller.getConfiguration(); + this.isDeleteEventPresent = isDeleteEventPresent; + this.isDeleteFinalStateUnknown = isDeleteFinalStateUnknown; this.defaultManagedDependentResourceContext = new DefaultManagedWorkflowAndDependentResourceContext<>(controller, primaryResource, this); } @@ -119,6 +128,16 @@ public boolean isNextReconciliationImminent() { .isNextReconciliationImminent(ResourceID.fromResource(primaryResource)); } + @Override + public boolean isDeleteEventPresent() { + return isDeleteEventPresent; + } + + @Override + public boolean isDeleteFinalStateUnknown() { + return isDeleteFinalStateUnknown; + } + public DefaultContext

setRetryInfo(RetryInfo retryInfo) { this.retryInfo = retryInfo; return this; diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java index 804222218a..80e0ccf8fe 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java @@ -15,7 +15,6 @@ import io.javaoperatorsdk.operator.OperatorException; import io.javaoperatorsdk.operator.api.config.ConfigurationService; import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; -import io.javaoperatorsdk.operator.api.config.ControllerMode; import io.javaoperatorsdk.operator.api.monitoring.Metrics; import io.javaoperatorsdk.operator.api.reconciler.Constants; import io.javaoperatorsdk.operator.processing.LifecycleAware; @@ -24,6 +23,7 @@ import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter.RateLimitState; import io.javaoperatorsdk.operator.processing.event.source.Cache; import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceAction; +import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceDeleteEvent; import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEvent; import io.javaoperatorsdk.operator.processing.event.source.timer.TimerEventSource; import io.javaoperatorsdk.operator.processing.retry.Retry; @@ -123,7 +123,7 @@ public synchronized void handleEvent(Event event) { } private void handleMarkedEventForResource(ResourceState state) { - if (state.deleteEventPresent() && !isAllEventMode()) { + if (state.deleteEventPresent() && !controllerConfiguration.isAllEventReconcileMode()) { cleanupForDeletedEvent(state.getId()); } else if (!state.processedMarkForDeletionPresent()) { submitReconciliationExecution(state); @@ -180,7 +180,9 @@ private void handleEventMarking(Event event, ResourceState state) { if (event instanceof ResourceEvent resourceEvent) { if (resourceEvent.getAction() == ResourceAction.DELETED) { log.debug("Marking delete event received for: {}", relatedCustomResourceID); - state.markDeleteEventReceived(resourceEvent.getResource().orElseThrow()); + state.markDeleteEventReceived( + resourceEvent.getResource().orElseThrow(), + ((ResourceDeleteEvent) resourceEvent).isDeletedFinalStateUnknown()); } else { if (state.processedMarkForDeletionPresent() && isResourceMarkedForDeletion(resourceEvent)) { log.debug( @@ -252,7 +254,8 @@ synchronized void eventProcessingFinished( } cleanupOnSuccessfulExecution(executionScope); metrics.finishedReconciliation(executionScope.getResource(), metricsMetadata); - if (state.deleteEventPresent()) { + if ((controllerConfiguration.isAllEventReconcileMode() && executionScope.isDeleteEvent()) + || (!controllerConfiguration.isAllEventReconcileMode() && state.deleteEventPresent())) { cleanupForDeletedEvent(executionScope.getResourceID()); } else if (postExecutionControl.isFinalizerRemoved()) { state.markProcessedMarkForDeletion(); @@ -451,6 +454,7 @@ private ReconcilerExecutor(ResourceID resourceID, ExecutionScope

executionSco } @Override + @SuppressWarnings("unchecked") public void run() { if (!running) { // this is needed for the case when controller stopped, but there is a graceful shutdown @@ -465,15 +469,26 @@ public void run() { try { var actualResource = cache.get(resourceID); if (actualResource.isEmpty()) { - if (isAllEventMode()) { + if (controllerConfiguration.isAllEventReconcileMode()) { + log.debug( + "Resource not found in the cache, checking for delete event resource: {}", + resourceID); var state = resourceStateManager.get(resourceID); actualResource = (Optional

) - state.filter(s -> s.deleteEventPresent()).map(s -> s.getLastKnownResource()); + state + .filter(ResourceState::deleteEventPresent) + .map(ResourceState::getLastKnownResource); + if (actualResource.isEmpty()) { + log.debug( + "Skipping execution; delete event resource not found in state: {}", resourceID); + return; + } + executionScope.setDeleteEvent(true); + } else { + log.debug("Skipping execution; primary resource missing from cache: {}", resourceID); + return; } - - log.debug("Skipping execution; primary resource missing from cache: {}", resourceID); - return; } actualResource.ifPresent(executionScope::setResource); MDCUtils.addResourceInfo(executionScope.getResource()); @@ -509,8 +524,4 @@ public synchronized boolean isUnderProcessing(ResourceID resourceID) { public synchronized boolean isRunning() { return running; } - - private boolean isAllEventMode() { - return controllerConfiguration.getMode() == ControllerMode.RECONCILE_ALL_EVENT; - } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ExecutionScope.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ExecutionScope.java index 90899a6e1a..b7a253faa1 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ExecutionScope.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ExecutionScope.java @@ -8,6 +8,8 @@ class ExecutionScope { // the latest custom resource from cache private R resource; private final RetryInfo retryInfo; + private boolean deleteEvent = false; + private boolean isDeleteFinalStateUnknown = false; ExecutionScope(RetryInfo retryInfo) { this.retryInfo = retryInfo; @@ -26,6 +28,22 @@ public ResourceID getResourceID() { return ResourceID.fromResource(resource); } + public boolean isDeleteEvent() { + return deleteEvent; + } + + public void setDeleteEvent(boolean deleteEvent) { + this.deleteEvent = deleteEvent; + } + + public boolean isDeleteFinalStateUnknown() { + return isDeleteFinalStateUnknown; + } + + public void setDeleteFinalStateUnknown(boolean deleteFinalStateUnknown) { + isDeleteFinalStateUnknown = deleteFinalStateUnknown; + } + @Override public String toString() { if (resource == null) { 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 c4b161ef27..f4e1d0f343 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 @@ -81,7 +81,9 @@ private PostExecutionControl

handleDispatch(ExecutionScope

executionScope) originalResource.getMetadata().getNamespace()); final var markedForDeletion = originalResource.isMarkedForDeletion(); - if (markedForDeletion && shouldNotDispatchToCleanupWhenMarkedForDeletion(originalResource)) { + if (!configuration().isAllEventReconcileMode() + && markedForDeletion + && shouldNotDispatchToCleanupWhenMarkedForDeletion(originalResource)) { log.debug( "Skipping cleanup of resource {} because finalizer(s) {} don't allow processing yet", getName(originalResource), @@ -90,8 +92,13 @@ private PostExecutionControl

handleDispatch(ExecutionScope

executionScope) } Context

context = - new DefaultContext<>(executionScope.getRetryInfo(), controller, resourceForExecution); - if (markedForDeletion) { + new DefaultContext<>( + executionScope.getRetryInfo(), + controller, + resourceForExecution, + executionScope.isDeleteEvent(), + executionScope.isDeleteFinalStateUnknown()); + if (markedForDeletion && !configuration().isAllEventReconcileMode()) { return handleCleanup(resourceForExecution, originalResource, context); } else { return handleReconcile(executionScope, resourceForExecution, originalResource, context); @@ -110,7 +117,8 @@ private PostExecutionControl

handleReconcile( P originalResource, Context

context) throws Exception { - if (controller.useFinalizer() + if (!configuration().isAllEventReconcileMode() + && controller.useFinalizer() && !originalResource.hasFinalizer(configuration().getFinalizerName())) { /* * We always add the finalizer if missing and the controller is configured to use a finalizer. diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceState.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceState.java index 59ad479c0d..3ed12d963b 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceState.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceState.java @@ -31,6 +31,7 @@ private enum EventingState { private EventingState eventing; private RateLimitState rateLimit; private HasMetadata lastKnownResource; + private boolean isDeleteFinalStateUnknown; public ResourceState(ResourceID id) { this.id = id; @@ -65,9 +66,11 @@ public void setUnderProcessing(boolean underProcessing) { this.underProcessing = underProcessing; } - public void markDeleteEventReceived(HasMetadata lastKnownResource) { + public void markDeleteEventReceived( + HasMetadata lastKnownResource, boolean isDeleteFinalStateUnknown) { eventing = EventingState.DELETE_EVENT_PRESENT; this.lastKnownResource = lastKnownResource; + this.isDeleteFinalStateUnknown = isDeleteFinalStateUnknown; } public boolean deleteEventPresent() { @@ -97,6 +100,10 @@ public boolean noEventPresent() { return eventing == EventingState.NO_EVENT_PRESENT; } + public boolean isDeleteFinalStateUnknown() { + return isDeleteFinalStateUnknown; + } + public HasMetadata getLastKnownResource() { return lastKnownResource; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ResourceDeleteEvent.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ResourceDeleteEvent.java index 83cee7fc77..73d856e922 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ResourceDeleteEvent.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ResourceDeleteEvent.java @@ -15,4 +15,8 @@ public ResourceDeleteEvent( super(action, resourceID, resource); this.deletedFinalStateUnknown = deletedFinalStateUnknown; } + + public boolean isDeletedFinalStateUnknown() { + return deletedFinalStateUnknown; + } } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContextTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContextTest.java index b289d68b22..1f59b8912c 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContextTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContextTest.java @@ -18,7 +18,8 @@ class DefaultContextTest { private final Secret primary = new Secret(); private final Controller mockController = mock(); - private final DefaultContext context = new DefaultContext<>(null, mockController, primary); + private final DefaultContext context = + new DefaultContext<>(null, mockController, primary, false, false); @Test @SuppressWarnings("unchecked") diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/ControllerTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/ControllerTest.java index 82ecdb111a..ca14fbc76b 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/ControllerTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/ControllerTest.java @@ -122,7 +122,8 @@ void callsCleanupOnWorkflowWhenHasCleanerAndReconcilerIsNotCleaner( new Controller( reconciler, configuration, MockKubernetesClient.client(Secret.class)); - controller.cleanup(new Secret(), new DefaultContext<>(null, controller, new Secret())); + controller.cleanup( + new Secret(), new DefaultContext<>(null, controller, new Secret(), false, false)); verify(managedWorkflowMock, times(workflowCleanerExecuted ? 1 : 0)).cleanup(any(), any()); } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/EventProcessorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/EventProcessorTest.java index fe2e6e9514..dfacbbfd0f 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/EventProcessorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/EventProcessorTest.java @@ -15,6 +15,7 @@ import org.slf4j.LoggerFactory; import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.TestUtils; import io.javaoperatorsdk.operator.api.config.BaseConfigurationService; import io.javaoperatorsdk.operator.api.config.ConfigurationService; import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; @@ -24,6 +25,7 @@ import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter.RateLimitState; import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerEventSource; import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceAction; +import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceDeleteEvent; import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEvent; import io.javaoperatorsdk.operator.processing.event.source.timer.TimerEventSource; import io.javaoperatorsdk.operator.processing.retry.GenericRetry; @@ -458,7 +460,9 @@ void cleansUpForDeleteEventEvenIfProcessorNotStarted() { null)); eventProcessor.handleEvent(prepareCREvent(resourceID)); - eventProcessor.handleEvent(new ResourceEvent(ResourceAction.DELETED, resourceID, null)); + eventProcessor.handleEvent( + new ResourceDeleteEvent( + ResourceAction.DELETED, resourceID, TestUtils.testCustomResource(), true)); eventProcessor.handleEvent(prepareCREvent(resourceID)); // no exception thrown } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManagerTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManagerTest.java index fa0cf5f9a7..2f8df0c6c4 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManagerTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManagerTest.java @@ -40,7 +40,7 @@ public void marksEvent() { @Test public void marksDeleteEvent() { - state.markDeleteEventReceived(TestUtils.testCustomResource()); + state.markDeleteEventReceived(TestUtils.testCustomResource(), true); assertThat(state.deleteEventPresent()).isTrue(); assertThat(state.eventPresent()).isFalse(); @@ -50,7 +50,7 @@ public void marksDeleteEvent() { public void afterDeleteEventMarkEventIsNotRelevant() { state.markEventReceived(); - state.markDeleteEventReceived(TestUtils.testCustomResource()); + state.markDeleteEventReceived(TestUtils.testCustomResource(), true); assertThat(state.deleteEventPresent()).isTrue(); assertThat(state.eventPresent()).isFalse(); @@ -59,7 +59,7 @@ public void afterDeleteEventMarkEventIsNotRelevant() { @Test public void cleansUp() { state.markEventReceived(); - state.markDeleteEventReceived(TestUtils.testCustomResource()); + state.markDeleteEventReceived(TestUtils.testCustomResource(), true); manager.remove(sampleResourceID); @@ -73,7 +73,7 @@ public void cannotMarkEventAfterDeleteEventReceived() { Assertions.assertThrows( IllegalStateException.class, () -> { - state.markDeleteEventReceived(TestUtils.testCustomResource()); + state.markDeleteEventReceived(TestUtils.testCustomResource(), true); state.markEventReceived(); }); } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSourceTest.java index 257af38e0c..e4891d0456 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSourceTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSourceTest.java @@ -208,7 +208,8 @@ public TestConfiguration( .withOnAddFilter(onAddFilter) .withOnUpdateFilter(onUpdateFilter) .withGenericFilter(genericFilter) - .buildForController()); + .buildForController(), + null); } } } From 05fa9756654fb85e5fa5fb8e3c514234e036b239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 15 Aug 2025 10:57:25 +0200 Subject: [PATCH 4/5] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../operator/processing/event/EventProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java index 80e0ccf8fe..4ef6b2737c 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java @@ -247,7 +247,7 @@ synchronized void eventProcessingFinished( // Either way we don't want to retry. if (isRetryConfigured() && postExecutionControl.exceptionDuringExecution() - && !state.deleteEventPresent()) { + && (!state.deleteEventPresent() || controllerConfiguration.isAllEventReconcileMode())) { handleRetryOnException( executionScope, postExecutionControl.getRuntimeException().orElseThrow()); return; From 6de07679b52446e5e25f7f0c45aa5e1dd93a28da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 15 Aug 2025 11:33:32 +0200 Subject: [PATCH 5/5] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../operator/processing/event/ResourceState.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceState.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceState.java index 3ed12d963b..99cc783dc0 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceState.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceState.java @@ -22,6 +22,8 @@ private enum EventingState { PROCESSED_MARK_FOR_DELETION, /** Delete event present, from this point other events are not relevant */ DELETE_EVENT_PRESENT, + // todo we probably need an additional state for the case when procesing delete event + // that fails we want to retry the delete event but meanwhile additional event is received } private final ResourceID id;