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..ea1df583a0 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 @@ -102,8 +102,16 @@ public synchronized void handleEvent(Event event) { try { log.debug("Received event: {}", event); + final var optionalState = resourceStateManager.getOrCreateOnResourceEvent(event); + if (optionalState.isEmpty()) { + log.debug( + "Skipping event, since no state present and it is not a resource event. Resource ID:" + + " {}", + event.getRelatedCustomResourceID()); + return; + } + var state = optionalState.orElseThrow(); final var resourceID = event.getRelatedCustomResourceID(); - final var state = resourceStateManager.getOrCreate(event.getRelatedCustomResourceID()); MDCUtils.addResourceIDInfo(resourceID); metrics.receivedEvent(event, metricsMetadata); handleEventMarking(event, state); @@ -231,7 +239,7 @@ synchronized void eventProcessingFinished( return; } ResourceID resourceID = executionScope.getResourceID(); - final var state = resourceStateManager.getOrCreate(resourceID); + final var state = resourceStateManager.getOrThrow(resourceID); log.debug( "Event processing finished. Scope: {}, PostExecutionControl: {}", executionScope, @@ -378,13 +386,13 @@ private void cleanupOnSuccessfulExecution(ExecutionScope

executionScope) { log.debug( "Cleanup for successful execution for resource: {}", getName(executionScope.getResource())); if (isRetryConfigured()) { - resourceStateManager.getOrCreate(executionScope.getResourceID()).setRetry(null); + resourceStateManager.getOrThrow(executionScope.getResourceID()).setRetry(null); } retryEventSource().cancelOnceSchedule(executionScope.getResourceID()); } private ResourceState getOrInitRetryExecution(ExecutionScope

executionScope) { - final var state = resourceStateManager.getOrCreate(executionScope.getResourceID()); + final var state = resourceStateManager.getOrThrow(executionScope.getResourceID()); RetryExecution retryExecution = state.getRetry(); if (retryExecution == null) { retryExecution = retry.initExecution(); @@ -404,7 +412,7 @@ private boolean isControllerUnderExecution(ResourceState state) { } private void unsetUnderExecution(ResourceID resourceID) { - resourceStateManager.getOrCreate(resourceID).setUnderProcessing(false); + resourceStateManager.getOrThrow(resourceID).setUnderProcessing(false); } private boolean isRetryConfigured() { @@ -430,7 +438,7 @@ public synchronized void start() throws OperatorException { } public boolean isNextReconciliationImminent(ResourceID resourceID) { - return resourceStateManager.getOrCreate(resourceID).eventPresent(); + return resourceStateManager.getOrThrow(resourceID).eventPresent(); } private void handleAlreadyMarkedEvents() { @@ -495,7 +503,7 @@ private String controllerName() { } public synchronized boolean isUnderProcessing(ResourceID resourceID) { - return isControllerUnderExecution(resourceStateManager.getOrCreate(resourceID)); + return isControllerUnderExecution(resourceStateManager.getOrThrow(resourceID)); } public synchronized boolean isRunning() { 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..f45e317d94 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,17 +2,39 @@ import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; +import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEvent; + class ResourceStateManager { // maybe we should have a way for users to specify a hint on the amount of CRs their reconciler // will process to avoid under- or over-sizing the state maps and avoid too many resizing that // take time and memory? private final Map states = new ConcurrentHashMap<>(100); - public ResourceState getOrCreate(ResourceID resourceID) { - return states.computeIfAbsent(resourceID, ResourceState::new); + public Optional getOrCreateOnResourceEvent(Event event) { + var resourceId = event.getRelatedCustomResourceID(); + var state = states.get(event.getRelatedCustomResourceID()); + if (state != null) { + return Optional.of(state); + } + if (event instanceof ResourceEvent) { + state = new ResourceState(resourceId); + states.put(resourceId, state); + return Optional.of(state); + } else { + return Optional.empty(); + } + } + + public ResourceState getOrThrow(ResourceID resourceID) { + var state = states.get(resourceID); + if (state == null) { + throw new IllegalStateException("No state for resource id: " + resourceID); + } + return state; } public ResourceState remove(ResourceID resourceID) { 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..9819eb7ee9 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 @@ -276,6 +276,30 @@ void cancelScheduleOnceEventsOnSuccessfulExecution() { verify(retryTimerEventSourceMock, times(1)).cancelOnceSchedule(eq(crID)); } + @Test + void skipsGenericEventIfNoResourceEventReceivedBefore() { + var crID = new ResourceID("test-cr", TEST_NAMESPACE); + eventProcessor = + spy( + new EventProcessor( + controllerConfiguration(null, LinearRateLimiter.deactivatedRateLimiter()), + reconciliationDispatcherMock, + eventSourceManagerMock, + metricsMock)); + + verify(reconciliationDispatcherMock, timeout(100).times(0)).handleExecution(any()); + + eventProcessor.start(); + eventProcessor.handleEvent(new Event(crID)); + + await() + .pollDelay(Duration.ofMillis(100)) + .untilAsserted( + () -> { + verify(reconciliationDispatcherMock, never()).handleExecution(any()); + }); + } + @Test void startProcessedMarkedEventReceivedBefore() { var crID = new ResourceID("test-cr", TEST_NAMESPACE); @@ -287,7 +311,7 @@ void startProcessedMarkedEventReceivedBefore() { eventSourceManagerMock, metricsMock)); when(controllerEventSourceMock.get(eq(crID))).thenReturn(Optional.of(testCustomResource())); - eventProcessor.handleEvent(new Event(crID)); + eventProcessor.handleEvent(new ResourceEvent(ResourceAction.ADDED, crID, testCustomResource())); verify(reconciliationDispatcherMock, timeout(100).times(0)).handleExecution(any()); 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..0d5c2868a7 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,10 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import io.javaoperatorsdk.operator.TestUtils; +import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceAction; +import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEvent; + import static org.assertj.core.api.Assertions.assertThat; class ResourceStateManagerTest { @@ -19,8 +23,8 @@ void init() { manager.remove(sampleResourceID); manager.remove(sampleResourceID2); - state = manager.getOrCreate(sampleResourceID); - state2 = manager.getOrCreate(sampleResourceID2); + state = manager.getOrThrow(sampleResourceID); + state2 = manager.getOrThrow(sampleResourceID2); } @Test @@ -61,7 +65,7 @@ public void cleansUp() { manager.remove(sampleResourceID); - state = manager.getOrCreate(sampleResourceID); + state = manager.getOrThrow(sampleResourceID); assertThat(state.deleteEventPresent()).isFalse(); assertThat(state.eventPresent()).isFalse(); } @@ -87,4 +91,26 @@ public void listsResourceIDSWithEventsPresent() { assertThat(res).hasSize(1); assertThat(res.get(0).getId()).isEqualTo(sampleResourceID2); } + + @Test + void createStateOnlyOnResourceEvent() { + var state = manager.getOrCreateOnResourceEvent(new Event(new ResourceID("newEvent"))); + + assertThat(state).isEmpty(); + + state = + manager.getOrCreateOnResourceEvent( + new ResourceEvent( + ResourceAction.ADDED, new ResourceID("newEvent"), TestUtils.testCustomResource())); + + assertThat(state).isNotNull(); + } + + @Test + void createsOnlyResourceEventReturnsPreviouslyCreatedState() { + manager.getOrThrow(new ResourceID("newEvent")); + + var res = manager.getOrCreateOnResourceEvent(new Event(new ResourceID("newEvent"))); + assertThat(res).isNotNull(); + } }