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..536cefc7cf --- /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, + RECONCILE_ALL_EVENT +} 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..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 @@ -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( @@ -464,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; } @@ -501,4 +509,8 @@ 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/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: 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()); }