diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java index 86efb457c5..53bfc75df9 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java @@ -364,4 +364,31 @@ default Set> defaultNonSSAResource() { return Set.of(ConfigMap.class, Secret.class); } + /** + * If a javaoperatorsdk.io/previous annotation should be used so that the operator sdk can detect + * events from its own updates of dependent resources and then filter them. + *

+ * Disable this if you want to react to your own dependent resource updates + * + * @since 4.5.0 + */ + default boolean previousAnnotationForDependentResourcesEventFiltering() { + return true; + } + + /** + * If the event logic should parse the resourceVersion to determine the ordering of dependent + * resource events. This is typically not needed. + *

+ * Disabled by default as Kubernetes does not support, and discourages, this interpretation of + * resourceVersions. Enable only if your api server event processing seems to lag the operator + * logic and you want to further minimize the the amount of work done / updates issued by the + * operator. + * + * @since 4.5.0 + */ + default boolean parseResourceVersionsForEventFilteringAndCaching() { + return false; + } + } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverrider.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverrider.java index 15418aed5e..2a2f6964c1 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverrider.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverrider.java @@ -37,6 +37,8 @@ public class ConfigurationServiceOverrider { private ResourceClassResolver resourceClassResolver; private Boolean ssaBasedCreateUpdateMatchForDependentResources; private Set> defaultNonSSAResource; + private Boolean previousAnnotationForDependentResources; + private Boolean parseResourceVersions; ConfigurationServiceOverrider(ConfigurationService original) { this.original = original; @@ -158,6 +160,18 @@ public ConfigurationServiceOverrider withDefaultNonSSAResource( return this; } + public ConfigurationServiceOverrider withPreviousAnnotationForDependentResources( + boolean value) { + this.previousAnnotationForDependentResources = value; + return this; + } + + public ConfigurationServiceOverrider wihtParseResourceVersions( + boolean value) { + this.parseResourceVersions = value; + return this; + } + public ConfigurationService build() { return new BaseConfigurationService(original.getVersion(), cloner, client) { @Override @@ -270,6 +284,20 @@ public Set> defaultNonSSAResource() { return defaultNonSSAResource != null ? defaultNonSSAResource : super.defaultNonSSAResource(); } + + @Override + public boolean previousAnnotationForDependentResourcesEventFiltering() { + return previousAnnotationForDependentResources != null + ? previousAnnotationForDependentResources + : super.previousAnnotationForDependentResourcesEventFiltering(); + } + + @Override + public boolean parseResourceVersionsForEventFilteringAndCaching() { + return parseResourceVersions != null + ? parseResourceVersions + : super.parseResourceVersionsForEventFilteringAndCaching(); + } }; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerConfiguration.java index 1f40677ee7..4b0007c96e 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerConfiguration.java @@ -62,6 +62,7 @@ public SecondaryToPrimaryMapper getSecondaryToPrimaryMapper() { return secondaryToPrimaryMapper; } + @Override public Optional> onDeleteFilter() { return Optional.ofNullable(onDeleteFilter); } @@ -95,12 +96,15 @@ public

PrimaryToSecondaryMapper

getPrimaryToSecondary */ SecondaryToPrimaryMapper getSecondaryToPrimaryMapper(); + @Override Optional> onAddFilter(); + @Override Optional> onUpdateFilter(); Optional> onDeleteFilter(); + @Override Optional> genericFilter();

PrimaryToSecondaryMapper

getPrimaryToSecondaryMapper(); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java index 22ec5a48f9..f81f98f12c 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java @@ -113,7 +113,7 @@ public R create(R desired, P primary, Context

context) { desired.getMetadata().setResourceVersion("1"); } } - addMetadata(false, null, desired, primary); + addMetadata(false, null, desired, primary, context); sanitizeDesired(desired, null, primary, context); final var resource = prepare(desired, primary, "Creating"); return useSSA(context) @@ -130,7 +130,7 @@ public R update(R actual, R desired, P primary, Context

context) { actual.getMetadata().getResourceVersion()); } R updatedResource; - addMetadata(false, actual, desired, primary); + addMetadata(false, actual, desired, primary, context); sanitizeDesired(desired, actual, primary, context); if (useSSA(context)) { updatedResource = prepare(desired, primary, "Updating") @@ -163,7 +163,7 @@ public Result match(R actualResource, R desired, P primary, Context

contex public Result match(R actualResource, R desired, P primary, ResourceUpdaterMatcher matcher, Context

context) { final boolean matches; - addMetadata(true, actualResource, desired, primary); + addMetadata(true, actualResource, desired, primary, context); if (useSSA(context)) { matches = SSABasedGenericKubernetesResourceMatcher.getInstance() .matches(actualResource, desired, context); @@ -173,8 +173,9 @@ public Result match(R actualResource, R desired, P primary, ResourceUpdaterMa return Result.computed(matches, desired); } - protected void addMetadata(boolean forMatch, R actualResource, final R target, P primary) { - if (forMatch) { // keep the current + protected void addMetadata(boolean forMatch, R actualResource, final R target, P primary, + Context

context) { + if (forMatch) { // keep the current previous annotation String actual = actualResource.getMetadata().getAnnotations() .get(InformerEventSource.PREVIOUS_ANNOTATION_KEY); Map annotations = target.getMetadata().getAnnotations(); @@ -183,7 +184,7 @@ protected void addMetadata(boolean forMatch, R actualResource, final R target, P } else { annotations.remove(InformerEventSource.PREVIOUS_ANNOTATION_KEY); } - } else { // set a new one + } else if (usePreviousAnnotation(context)) { // set a new one eventSource().orElseThrow().addPreviousAnnotation( Optional.ofNullable(actualResource).map(r -> r.getMetadata().getResourceVersion()) .orElse(null), @@ -208,6 +209,11 @@ protected boolean useSSA(Context

context) { .ssaBasedCreateUpdateMatchForDependentResources()); } + private boolean usePreviousAnnotation(Context

context) { + return context.getControllerConfiguration().getConfigurationService() + .previousAnnotationForDependentResourcesEventFiltering(); + } + @Override protected void handleDelete(P primary, R secondary, Context

context) { if (secondary != null) { diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSource.java index da2e517376..f7b51bd572 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSource.java @@ -32,12 +32,12 @@ public class ControllerResourceEventSource @SuppressWarnings({"unchecked", "rawtypes"}) public ControllerResourceEventSource(Controller controller) { - super(controller.getCRClient(), controller.getConfiguration()); + super(controller.getCRClient(), controller.getConfiguration(), false); this.controller = controller; final var config = controller.getConfiguration(); OnUpdateFilter internalOnUpdateFilter = - (OnUpdateFilter) onUpdateFinalizerNeededAndApplied(controller.useFinalizer(), + onUpdateFinalizerNeededAndApplied(controller.useFinalizer(), config.getFinalizerName()) .or(onUpdateGenerationAware(config.isGenerationAware())) .or(onUpdateMarkedForDeletion()); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSource.java index d154ad201b..968ccc27b9 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSource.java @@ -81,12 +81,18 @@ public class InformerEventSource public InformerEventSource( InformerConfiguration configuration, EventSourceContext

context) { - this(configuration, context.getClient()); + this(configuration, context.getClient(), + context.getControllerConfiguration().getConfigurationService() + .parseResourceVersionsForEventFilteringAndCaching()); } public InformerEventSource(InformerConfiguration configuration, KubernetesClient client) { - super(client.resources(configuration.getResourceClass()), configuration); + this(configuration, client, false); + } + public InformerEventSource(InformerConfiguration configuration, KubernetesClient client, + boolean parseResourceVersions) { + super(client.resources(configuration.getResourceClass()), configuration, parseResourceVersions); // If there is a primary to secondary mapper there is no need for primary to secondary index. primaryToSecondaryMapper = configuration.getPrimaryToSecondaryMapper(); if (primaryToSecondaryMapper == null) { @@ -169,6 +175,9 @@ private synchronized void onAddOrUpdate(Operation operation, R newObject, R oldO } private boolean canSkipEvent(R newObject, R oldObject, ResourceID resourceID) { + if (temporaryResourceCache.isKnownResourceVersion(newObject)) { + return true; + } var res = temporaryResourceCache.getResourceFromCache(resourceID); if (res.isEmpty()) { return isEventKnownFromAnnotation(newObject, oldObject); @@ -275,7 +284,6 @@ public boolean allowsNamespaceChanges() { return configuration().followControllerNamespaceChanges(); } - private boolean eventAcceptedByFilter(Operation operation, R newObject, R oldObject) { if (genericFilter != null && !genericFilter.accept(newObject)) { return false; diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/ManagedInformerEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/ManagedInformerEventSource.java index 6ec6cd7f6e..5dff2be51b 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/ManagedInformerEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/ManagedInformerEventSource.java @@ -42,26 +42,27 @@ public abstract class ManagedInformerEventSource, Resource> client; protected ManagedInformerEventSource( - MixedOperation, Resource> client, C configuration) { + MixedOperation, Resource> client, C configuration, + boolean parseResourceVersions) { super(configuration.getResourceClass()); this.client = client; - temporaryResourceCache = new TemporaryResourceCache<>(this); + temporaryResourceCache = new TemporaryResourceCache<>(this, parseResourceVersions); this.cache = new InformerManager<>(client, configuration, this); } @Override public void onAdd(R resource) { - temporaryResourceCache.removeResourceFromCache(resource); + temporaryResourceCache.onEvent(resource, false); } @Override public void onUpdate(R oldObj, R newObj) { - temporaryResourceCache.removeResourceFromCache(newObj); + temporaryResourceCache.onEvent(newObj, false); } @Override public void onDelete(R obj, boolean deletedFinalStateUnknown) { - temporaryResourceCache.removeResourceFromCache(obj); + temporaryResourceCache.onEvent(obj, deletedFinalStateUnknown); } protected InformerManager manager() { @@ -127,6 +128,7 @@ void setTemporalResourceCache(TemporaryResourceCache temporaryResourceCache) this.temporaryResourceCache = temporaryResourceCache; } + @Override public void addIndexers(Map>> indexers) { cache.addIndexers(indexers); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/TemporaryResourceCache.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/TemporaryResourceCache.java index 233b409f3f..fd9a8ad565 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/TemporaryResourceCache.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/TemporaryResourceCache.java @@ -1,13 +1,17 @@ package io.javaoperatorsdk.operator.processing.event.source.informer; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; import io.javaoperatorsdk.operator.processing.event.ResourceID; @@ -33,16 +37,33 @@ public class TemporaryResourceCache { private static final Logger log = LoggerFactory.getLogger(TemporaryResourceCache.class); + private static final int MAX_RESOURCE_VERSIONS = 256; private final Map cache = new ConcurrentHashMap<>(); private final ManagedInformerEventSource managedInformerEventSource; + private final boolean parseResourceVersions; + private final Set knownResourceVersions; - public TemporaryResourceCache(ManagedInformerEventSource managedInformerEventSource) { + public TemporaryResourceCache(ManagedInformerEventSource managedInformerEventSource, + boolean parseResourceVersions) { this.managedInformerEventSource = managedInformerEventSource; + this.parseResourceVersions = parseResourceVersions; + if (parseResourceVersions) { + knownResourceVersions = Collections.newSetFromMap(new LinkedHashMap() { + @Override + protected boolean removeEldestEntry(java.util.Map.Entry eldest) { + return size() >= MAX_RESOURCE_VERSIONS; + } + }); + } else { + knownResourceVersions = null; + } } - public synchronized Optional removeResourceFromCache(T resource) { - return Optional.ofNullable(cache.remove(ResourceID.fromResource(resource))); + public synchronized void onEvent(T resource, boolean unknownState) { + cache.computeIfPresent(ResourceID.fromResource(resource), + (id, cached) -> (unknownState || !isLaterResourceVersion(id, cached, resource)) ? null + : cached); } public synchronized void putAddedResource(T newResource) { @@ -56,23 +77,50 @@ public synchronized void putAddedResource(T newResource) { * @param previousResourceVersion null indicates an add */ public synchronized void putResource(T newResource, String previousResourceVersion) { + if (knownResourceVersions != null) { + knownResourceVersions.add(newResource.getMetadata().getResourceVersion()); + } var resourceId = ResourceID.fromResource(newResource); var cachedResource = getResourceFromCache(resourceId) .orElse(managedInformerEventSource.get(resourceId).orElse(null)); if ((previousResourceVersion == null && cachedResource == null) - || (cachedResource != null && previousResourceVersion != null - && cachedResource.getMetadata().getResourceVersion() - .equals(previousResourceVersion))) { + || (cachedResource != null + && (cachedResource.getMetadata().getResourceVersion().equals(previousResourceVersion)) + || isLaterResourceVersion(resourceId, newResource, cachedResource))) { log.debug( "Temporarily moving ahead to target version {} for resource id: {}", newResource.getMetadata().getResourceVersion(), resourceId); putToCache(newResource, resourceId); - } else { - if (cache.remove(resourceId) != null) { - log.debug("Removed an obsolete resource from cache for id: {}", resourceId); + } else if (cache.remove(resourceId) != null) { + log.debug("Removed an obsolete resource from cache for id: {}", resourceId); + } + } + + public boolean isKnownResourceVersion(T resource) { + return knownResourceVersions != null + && knownResourceVersions.contains(resource.getMetadata().getResourceVersion()); + } + + /** + * @return true if {@link InformerConfiguration#parseResourceVersions()} is enabled and the + * resourceVersion of newResource is numerically greater than cachedResource, otherwise + * false + */ + private boolean isLaterResourceVersion(ResourceID resourceId, T newResource, T cachedResource) { + try { + if (parseResourceVersions + && Long.parseLong(newResource.getMetadata().getResourceVersion()) > Long + .parseLong(cachedResource.getMetadata().getResourceVersion())) { + return true; } + } catch (NumberFormatException e) { + log.debug( + "Could not compare resourceVersions {} and {} for {}", + newResource.getMetadata().getResourceVersion(), + cachedResource.getMetadata().getResourceVersion(), resourceId); } + return false; } private void putToCache(T resource, ResourceID resourceID) { diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java index 7acecc7099..0881cccaf2 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java @@ -119,7 +119,7 @@ void propagateEventAndRemoveResourceFromTempCacheIfResourceVersionMismatch() { informerEventSource.onUpdate(cachedDeployment, testDeployment()); verify(eventHandlerMock, times(1)).handleEvent(any()); - verify(temporaryResourceCacheMock, times(1)).removeResourceFromCache(any()); + verify(temporaryResourceCacheMock, times(1)).onEvent(testDeployment(), false); } @Test diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/TemporaryResourceCacheTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/TemporaryResourceCacheTest.java index 4d5bdf0dfd..d641736739 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/TemporaryResourceCacheTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/TemporaryResourceCacheTest.java @@ -3,9 +3,11 @@ import java.util.Map; import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.javaoperatorsdk.operator.processing.event.ResourceID; @@ -16,12 +18,16 @@ class TemporaryResourceCacheTest { - public static final String RESOURCE_VERSION = "1"; + public static final String RESOURCE_VERSION = "2"; @SuppressWarnings("unchecked") - private final InformerEventSource informerEventSource = - mock(InformerEventSource.class); - private final TemporaryResourceCache temporaryResourceCache = - new TemporaryResourceCache<>(informerEventSource); + private InformerEventSource informerEventSource; + private TemporaryResourceCache temporaryResourceCache; + + @BeforeEach + void setup() { + informerEventSource = mock(InformerEventSource.class); + temporaryResourceCache = new TemporaryResourceCache<>(informerEventSource, false); + } @Test void updateAddsTheResourceIntoCacheIfTheInformerHasThePreviousResourceVersion() { @@ -75,7 +81,30 @@ void addOperationNotAddsTheResourceIfInformerCacheNotEmpty() { void removesResourceFromCache() { ConfigMap testResource = propagateTestResourceToCache(); - temporaryResourceCache.removeResourceFromCache(testResource()); + temporaryResourceCache.onEvent(testResource(), false); + + assertThat(temporaryResourceCache.getResourceFromCache(ResourceID.fromResource(testResource))) + .isNotPresent(); + } + + @Test + void resourceVersionParsing() { + this.temporaryResourceCache = new TemporaryResourceCache<>(informerEventSource, true); + + assertThat(temporaryResourceCache.isKnownResourceVersion(testResource())).isFalse(); + + ConfigMap testResource = propagateTestResourceToCache(); + + // an event with a newer version will not remove + temporaryResourceCache.onEvent(new ConfigMapBuilder(testResource).editMetadata() + .withResourceVersion("1").endMetadata().build(), false); + + assertThat(temporaryResourceCache.isKnownResourceVersion(testResource)).isTrue(); + assertThat(temporaryResourceCache.getResourceFromCache(ResourceID.fromResource(testResource))) + .isPresent(); + + // anything else will remove + temporaryResourceCache.onEvent(testResource(), false); assertThat(temporaryResourceCache.getResourceFromCache(ResourceID.fromResource(testResource))) .isNotPresent(); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/CreateUpdateInformerEventSourceEventFilterIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/CreateUpdateInformerEventSourceEventFilterIT.java index f319509c47..8a68c9a2b3 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/CreateUpdateInformerEventSourceEventFilterIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/CreateUpdateInformerEventSourceEventFilterIT.java @@ -26,28 +26,12 @@ class CreateUpdateInformerEventSourceEventFilterIT { @Test void updateEventNotReceivedAfterCreateOrUpdate() { - CreateUpdateEventFilterTestCustomResource resource = prepareTestResource(); + CreateUpdateEventFilterTestCustomResource resource = + CreateUpdateInformerEventSourceEventFilterIT.prepareTestResource(); var createdResource = operator.create(resource); - await() - .atMost(Duration.ofSeconds(1)) - .until(() -> { - var cm = operator.get(ConfigMap.class, createdResource.getMetadata().getName()); - if (cm == null) { - return false; - } - return cm.getData() - .get(CONFIG_MAP_TEST_DATA_KEY) - .equals(createdResource.getSpec().getValue()); - }); - - assertThat( - ((CreateUpdateEventFilterTestReconciler) operator.getFirstReconciler()) - .getNumberOfExecutions()) - .isEqualTo(1); // this should be 1 usually but sometimes event is received - // faster than added resource added to the cache - + assertData(operator, createdResource, 1, 1); CreateUpdateEventFilterTestCustomResource actualCreatedResource = operator.get(CreateUpdateEventFilterTestCustomResource.class, @@ -55,26 +39,30 @@ void updateEventNotReceivedAfterCreateOrUpdate() { actualCreatedResource.getSpec().setValue("2"); operator.replace(actualCreatedResource); + assertData(operator, actualCreatedResource, 2, 2); + } - await().atMost(Duration.ofSeconds(1)) + static void assertData(LocallyRunOperatorExtension operator, + CreateUpdateEventFilterTestCustomResource resource, int minExecutions, int maxExecutions) { + await() + .atMost(Duration.ofSeconds(1)) .until(() -> { - var cm = operator.get(ConfigMap.class, createdResource.getMetadata().getName()); + var cm = operator.get(ConfigMap.class, resource.getMetadata().getName()); if (cm == null) { return false; } return cm.getData() .get(CONFIG_MAP_TEST_DATA_KEY) - .equals(actualCreatedResource.getSpec().getValue()); + .equals(resource.getSpec().getValue()); }); - assertThat( - ((CreateUpdateEventFilterTestReconciler) operator.getFirstReconciler()) - .getNumberOfExecutions()) - // same as for previous assert (usually this should be 2) - .isEqualTo(2); + int numberOfExecutions = ((CreateUpdateEventFilterTestReconciler) operator.getFirstReconciler()) + .getNumberOfExecutions(); + assertThat(numberOfExecutions).isGreaterThanOrEqualTo(minExecutions); + assertThat(numberOfExecutions).isLessThanOrEqualTo(maxExecutions); } - private CreateUpdateEventFilterTestCustomResource prepareTestResource() { + static CreateUpdateEventFilterTestCustomResource prepareTestResource() { CreateUpdateEventFilterTestCustomResource resource = new CreateUpdateEventFilterTestCustomResource(); resource.setMetadata(new ObjectMeta()); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/PreviousAnnotationDisabledIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/PreviousAnnotationDisabledIT.java new file mode 100644 index 0000000000..c636737d0a --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/PreviousAnnotationDisabledIT.java @@ -0,0 +1,38 @@ +package io.javaoperatorsdk.operator; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; +import io.javaoperatorsdk.operator.sample.createupdateeventfilter.CreateUpdateEventFilterTestCustomResource; +import io.javaoperatorsdk.operator.sample.createupdateeventfilter.CreateUpdateEventFilterTestReconciler; + +class PreviousAnnotationDisabledIT { + + @RegisterExtension + LocallyRunOperatorExtension operator = + LocallyRunOperatorExtension.builder() + .withReconciler(new CreateUpdateEventFilterTestReconciler()) + .withConfigurationService( + overrider -> overrider.withPreviousAnnotationForDependentResources(false)) + .build(); + + @Test + void updateEventReceivedAfterCreateOrUpdate() { + CreateUpdateEventFilterTestCustomResource resource = + CreateUpdateInformerEventSourceEventFilterIT.prepareTestResource(); + var createdResource = + operator.create(resource); + + CreateUpdateInformerEventSourceEventFilterIT.assertData(operator, createdResource, 1, 2); + + CreateUpdateEventFilterTestCustomResource actualCreatedResource = + operator.get(CreateUpdateEventFilterTestCustomResource.class, + resource.getMetadata().getName()); + actualCreatedResource.getSpec().setValue("2"); + operator.replace(actualCreatedResource); + + CreateUpdateInformerEventSourceEventFilterIT.assertData(operator, actualCreatedResource, 2, 4); + } + +}