From 64c7330071941712292380aee2f0743165c36dc3 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Tue, 31 May 2022 15:46:02 +0200 Subject: [PATCH 01/33] refactor: rename JUnit extensions to be more explicit (#1254) Fixes #1215 --- .../operator/junit/LocallyRunOperatorExtension.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java index 74418d008d..f6d98a7b45 100644 --- a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java +++ b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java @@ -237,13 +237,13 @@ public Builder withPortForward(String namespace, String labelKey, String labelVa return this; } + public Builder withAdditionalCustomResourceDefinition( - Class customResource) { + Class customResource) { additionalCustomResourceDefinitions.add(customResource); return this; } - public LocallyRunOperatorExtension build() { return new LocallyRunOperatorExtension( configurationService, From 43d846fdff45f07854ceab064c72e0c4524ae728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Thu, 9 Jun 2022 15:14:46 +0200 Subject: [PATCH 02/33] feat: workflow Integration with API (dependent annotations, context) (#1257) --- .../java/io/javaoperatorsdk/operator/api/config/UtilsTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/UtilsTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/UtilsTest.java index 87e60b8aa6..fa43a350ee 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/UtilsTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/UtilsTest.java @@ -9,6 +9,8 @@ import io.javaoperatorsdk.operator.api.reconciler.Reconciler; import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; +import io.javaoperatorsdk.operator.processing.dependent.EmptyTestDependentResource; import io.javaoperatorsdk.operator.processing.dependent.EmptyTestDependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; From 97fecff6bcc7af5d4e64f0ad770e42ce8243cebc Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 20 Jun 2022 10:38:47 +0200 Subject: [PATCH 03/33] feat: add filters to event sources --- .../informer/InformerConfiguration.java | 17 ++++- .../event/source/CachingEventSource.java | 51 -------------- .../processing/event/source/EventFilter.java | 59 +++++++++++++++++ .../source/informer/InformerEventSource.java | 66 +++++++++++++------ .../informer/ManagedInformerEventSource.java | 31 +++++---- .../PerResourcePollingEventSource.java | 3 +- .../event/EventSourceManagerTest.java | 12 ++-- 7 files changed, 147 insertions(+), 92 deletions(-) delete mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/CachingEventSource.java create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/EventFilter.java 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 ddfae2919e..fef0bea589 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 @@ -8,6 +8,7 @@ import io.javaoperatorsdk.operator.api.config.ResourceConfiguration; import io.javaoperatorsdk.operator.api.config.Utils; import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; +import io.javaoperatorsdk.operator.processing.event.source.EventFilter; import io.javaoperatorsdk.operator.processing.event.source.PrimaryToSecondaryMapper; import io.javaoperatorsdk.operator.processing.event.source.SecondaryToPrimaryMapper; import io.javaoperatorsdk.operator.processing.event.source.informer.Mappers; @@ -23,15 +24,18 @@ class DefaultInformerConfiguration extends private final PrimaryToSecondaryMapper primaryToSecondaryMapper; private final SecondaryToPrimaryMapper secondaryToPrimaryMapper; private final boolean followControllerNamespaceChanges; + private final EventFilter eventFilter; protected DefaultInformerConfiguration(String labelSelector, Class resourceClass, PrimaryToSecondaryMapper primaryToSecondaryMapper, SecondaryToPrimaryMapper secondaryToPrimaryMapper, + EventFilter eventFilter, Set namespaces, boolean followControllerNamespaceChanges) { super(labelSelector, resourceClass, namespaces); this.followControllerNamespaceChanges = followControllerNamespaceChanges; this.primaryToSecondaryMapper = primaryToSecondaryMapper; + this.eventFilter = eventFilter; this.secondaryToPrimaryMapper = Objects.requireNonNullElse(secondaryToPrimaryMapper, Mappers.fromOwnerReference()); @@ -45,6 +49,9 @@ public SecondaryToPrimaryMapper getSecondaryToPrimaryMapper() { return secondaryToPrimaryMapper; } + public EventFilter getEventFilter() { + return eventFilter; + } @Override public

PrimaryToSecondaryMapper

getPrimaryToSecondaryMapper() { return (PrimaryToSecondaryMapper

) primaryToSecondaryMapper; @@ -61,6 +68,8 @@ public

PrimaryToSecondaryMapper

getPrimaryToSecondary SecondaryToPrimaryMapper getSecondaryToPrimaryMapper(); + EventFilter getEventFilter(); +

PrimaryToSecondaryMapper

getPrimaryToSecondaryMapper(); @SuppressWarnings("unused") @@ -70,6 +79,7 @@ class InformerConfigurationBuilder { private SecondaryToPrimaryMapper secondaryToPrimaryMapper; private Set namespaces; private String labelSelector; + private EventFilter eventFilter; private final Class resourceClass; private boolean inheritControllerNamespacesOnChange = false; @@ -151,10 +161,15 @@ public InformerConfigurationBuilder withLabelSelector(String labelSelector) { return this; } + public InformerConfigurationBuilder withEventFilter(EventFilter eventFilter) { + this.eventFilter = eventFilter; + return this; + } + public InformerConfiguration build() { return new DefaultInformerConfiguration<>(labelSelector, resourceClass, primaryToSecondaryMapper, - secondaryToPrimaryMapper, + secondaryToPrimaryMapper,eventFilter, namespaces, inheritControllerNamespacesOnChange); } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/CachingEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/CachingEventSource.java deleted file mode 100644 index 55bd1ab920..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/CachingEventSource.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.javaoperatorsdk.operator.processing.event.source; - -import java.util.Optional; -import java.util.function.Predicate; -import java.util.stream.Stream; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.processing.event.ResourceID; - -/** - * Base class for event sources with caching capabilities. - *

- * - * @param represents the type of resources (usually external non-kubernetes ones) being handled. - */ -public abstract class CachingEventSource - extends AbstractResourceEventSource implements Cache { - - protected UpdatableCache cache; - - protected CachingEventSource(Class resourceClass) { - super(resourceClass); - cache = initCache(); - } - - @Override - public Optional get(ResourceID resourceID) { - return cache.get(resourceID); - } - - @Override - public boolean contains(ResourceID resourceID) { - return cache.contains(resourceID); - } - - @Override - public Stream keys() { - return cache.keys(); - } - - @Override - public Stream list(Predicate predicate) { - return cache.list(predicate); - } - - public Optional getCachedValue(ResourceID resourceID) { - return cache.get(resourceID); - } - - protected abstract UpdatableCache initCache(); -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/EventFilter.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/EventFilter.java new file mode 100644 index 0000000000..bd8cf0a894 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/EventFilter.java @@ -0,0 +1,59 @@ +package io.javaoperatorsdk.operator.processing.event.source; + +public interface EventFilter { + + default boolean acceptAdd(T newResource) { + return true; + } + + default boolean acceptUpdate(T newResource, T oldResource) { + return true; + } + + default boolean acceptDelete(T resource, boolean deletedFinalStateUnknown) { + return true; + } + + default EventFilter or(EventFilter eventFilter) { + return new EventFilter<>() { + @Override + public boolean acceptAdd(T newResource) { + return EventFilter.this.acceptAdd(newResource) || eventFilter.acceptAdd(newResource); + } + + @Override + public boolean acceptUpdate(T newResource, T oldResource) { + return EventFilter.this.acceptUpdate(newResource, oldResource) || + eventFilter.acceptUpdate(newResource, oldResource); + } + + @Override + public boolean acceptDelete(T resource, boolean deletedFinalStateUnknown) { + return EventFilter.this.acceptDelete(resource, deletedFinalStateUnknown) || + eventFilter.acceptDelete(resource, deletedFinalStateUnknown); + } + }; + } + + default EventFilter and(EventFilter eventFilter) { + return new EventFilter<>() { + @Override + public boolean acceptAdd(T newResource) { + return EventFilter.this.acceptAdd(newResource) && eventFilter.acceptAdd(newResource); + } + + @Override + public boolean acceptUpdate(T newResource, T oldResource) { + return EventFilter.this.acceptUpdate(newResource, oldResource) && + eventFilter.acceptUpdate(newResource, oldResource); + } + + @Override + public boolean acceptDelete(T resource, boolean deletedFinalStateUnknown) { + return EventFilter.this.acceptDelete(resource, deletedFinalStateUnknown) && + eventFilter.acceptDelete(resource, deletedFinalStateUnknown); + } + }; + } + +} 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 c630b85a9c..c0d170f903 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 @@ -16,6 +16,7 @@ import io.javaoperatorsdk.operator.processing.event.Event; import io.javaoperatorsdk.operator.processing.event.EventHandler; import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.processing.event.source.EventFilter; import io.javaoperatorsdk.operator.processing.event.source.PrimaryToSecondaryMapper; /** @@ -75,6 +76,7 @@ public class InformerEventSource private final EventRecorder eventRecorder = new EventRecorder<>(); // we need direct control for the indexer to propagate the just update resource also to the index private final PrimaryToSecondaryIndex primaryToSecondaryIndex; + private final EventFilter eventFilter; private final PrimaryToSecondaryMapper

primaryToSecondaryMapper; public InformerEventSource( @@ -92,17 +94,19 @@ public InformerEventSource(InformerConfiguration configuration, KubernetesCli } else { primaryToSecondaryIndex = NOOPPrimaryToSecondaryIndex.getInstance(); } + this.eventFilter = configuration.getEventFilter(); } @Override - public void onAdd(R resource) { + public void onAdd(R newResource) { if (log.isDebugEnabled()) { log.debug("On add event received for resource id: {} type: {}", - ResourceID.fromResource(resource), + ResourceID.fromResource(newResource), resourceType().getSimpleName()); } - primaryToSecondaryIndex.onAddOrUpdate(resource); - onAddOrUpdate("add", resource, () -> InformerEventSource.super.onAdd(resource)); + primaryToSecondaryIndex.onAddOrUpdate(newResource); + onAddOrUpdate(Operation.ADD, newResource, null, + () -> InformerEventSource.super.onAdd(newResource)); } @Override @@ -113,7 +117,7 @@ public void onUpdate(R oldObject, R newObject) { resourceType().getSimpleName()); } primaryToSecondaryIndex.onAddOrUpdate(newObject); - onAddOrUpdate("update", newObject, + onAddOrUpdate(Operation.UPDATE, newObject, oldObject, () -> InformerEventSource.super.onUpdate(oldObject, newObject)); } @@ -126,10 +130,13 @@ public void onDelete(R resource, boolean b) { } primaryToSecondaryIndex.onDelete(resource); super.onDelete(resource, b); - propagateEvent(resource); + if (eventFilter == null || eventFilter.acceptDelete(resource, b)) { + propagateEvent(resource); + } } - private synchronized void onAddOrUpdate(String operation, R newObject, Runnable superOnOp) { + private synchronized void onAddOrUpdate(Operation operation, R newObject, R oldObject, + Runnable superOnOp) { var resourceID = ResourceID.fromResource(newObject); if (eventRecorder.isRecordingFor(resourceID)) { log.debug("Recording event for: {}", resourceID); @@ -148,7 +155,9 @@ private synchronized void onAddOrUpdate(String operation, R newObject, Runnable "Propagating event for {}, resource with same version not result of a reconciliation. Resource ID: {}", operation, resourceID); - propagateEvent(newObject); + if (eventAcceptedByFilter(operation, newObject, oldObject)) { + propagateEvent(newObject); + } } } @@ -205,20 +214,21 @@ public InformerConfiguration getConfiguration() { @Override public synchronized void handleRecentResourceUpdate(ResourceID resourceID, R resource, R previousVersionOfResource) { - handleRecentCreateOrUpdate(resource, + handleRecentCreateOrUpdate(Operation.UPDATE, resource, previousVersionOfResource, () -> super.handleRecentResourceUpdate(resourceID, resource, previousVersionOfResource)); } @Override public synchronized void handleRecentResourceCreate(ResourceID resourceID, R resource) { - handleRecentCreateOrUpdate(resource, + handleRecentCreateOrUpdate(Operation.ADD, resource, null, () -> super.handleRecentResourceCreate(resourceID, resource)); } - private void handleRecentCreateOrUpdate(R resource, Runnable runnable) { + private void handleRecentCreateOrUpdate(Operation operation, R resource, R oldResource, + Runnable runnable) { primaryToSecondaryIndex.onAddOrUpdate(resource); if (eventRecorder.isRecordingFor(ResourceID.fromResource(resource))) { - handleRecentResourceOperationAndStopEventRecording(resource); + handleRecentResourceOperationAndStopEventRecording(operation, resource, oldResource); } else { runnable.run(); } @@ -239,23 +249,26 @@ private void handleRecentCreateOrUpdate(R resource, Runnable runnable) { * an event needs to be propagated to compensate. * * - * @param resource just created or updated resource + * @param newResource just created or updated resource */ - private void handleRecentResourceOperationAndStopEventRecording(R resource) { - ResourceID resourceID = ResourceID.fromResource(resource); + private void handleRecentResourceOperationAndStopEventRecording(Operation operation, + R newResource, R oldResource) { + ResourceID resourceID = ResourceID.fromResource(newResource); try { if (!eventRecorder.containsEventWithResourceVersion( - resourceID, resource.getMetadata().getResourceVersion())) { + resourceID, newResource.getMetadata().getResourceVersion())) { log.debug( "Did not found event in buffer with target version and resource id: {}", resourceID); - temporaryResourceCache.unconditionallyCacheResource(resource); + temporaryResourceCache.unconditionallyCacheResource(newResource); } else if (eventRecorder.containsEventWithVersionButItsNotLastOne( - resourceID, resource.getMetadata().getResourceVersion())) { + resourceID, newResource.getMetadata().getResourceVersion())) { R lastEvent = eventRecorder.getLastEvent(resourceID); log.debug( "Found events in event buffer but the target event is not last for id: {}. Propagating event.", resourceID); - propagateEvent(lastEvent); + if (eventAcceptedByFilter(operation, newResource, oldResource)) { + propagateEvent(lastEvent); + } } } finally { eventRecorder.stopEventRecording(resourceID); @@ -289,4 +302,19 @@ public synchronized void cleanupOnCreateOrUpdateEventFiltering(ResourceID resour public boolean allowsNamespaceChanges() { return getConfiguration().followControllerNamespaceChanges(); } + + + private boolean eventAcceptedByFilter(Operation operation, R newObject, R oldObject) { + if (eventFilter == null) { + return true; + } else if (operation == Operation.ADD) { + return eventFilter.acceptAdd(newObject); + } else { + return eventFilter.acceptUpdate(newObject, oldObject); + } + } + + private enum Operation { + ADD, UPDATE + } } 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 98e445032c..6ebd63a7eb 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 @@ -20,18 +20,18 @@ import io.javaoperatorsdk.operator.api.config.ResourceConfiguration; import io.javaoperatorsdk.operator.api.reconciler.dependent.RecentOperationCacheFiller; import io.javaoperatorsdk.operator.processing.event.ResourceID; -import io.javaoperatorsdk.operator.processing.event.source.CachingEventSource; -import io.javaoperatorsdk.operator.processing.event.source.IndexerResourceCache; -import io.javaoperatorsdk.operator.processing.event.source.UpdatableCache; +import io.javaoperatorsdk.operator.processing.event.source.*; public abstract class ManagedInformerEventSource> - extends CachingEventSource - implements ResourceEventHandler, IndexerResourceCache, RecentOperationCacheFiller, + extends AbstractResourceEventSource + implements ResourceEventHandler, Cache, IndexerResourceCache, + RecentOperationCacheFiller, NamespaceChangeable { private static final Logger log = LoggerFactory.getLogger(ManagedInformerEventSource.class); protected TemporaryResourceCache temporaryResourceCache = new TemporaryResourceCache<>(this); + protected InformerManager cache = new InformerManager<>(); protected ManagedInformerEventSource( MixedOperation, Resource> client, C configuration) { @@ -54,13 +54,8 @@ public void onDelete(R obj, boolean deletedFinalStateUnknown) { temporaryResourceCache.removeResourceFromCache(obj); } - @Override - protected UpdatableCache initCache() { - return new InformerManager<>(); - } - protected InformerManager manager() { - return (InformerManager) cache; + return cache; } @Override @@ -103,11 +98,10 @@ public Optional get(ResourceID resourceID) { } else { log.debug("Resource not found in temporal cache reading it from informer cache," + " for Resource ID: {}", resourceID); - return super.get(resourceID); + return cache.get(resourceID); } } - @Override public Optional getCachedValue(ResourceID resourceID) { return get(resourceID); } @@ -128,4 +122,15 @@ public void addIndexers(Map>> indexers) { public List byIndex(String indexName, String indexKey) { return manager().byIndex(indexName, indexKey); } + + @Override + public Stream keys() { + return cache.keys(); + } + + @Override + public Stream list(Predicate predicate) { + return cache.list(predicate); + } + } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PerResourcePollingEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PerResourcePollingEventSource.java index f55e7dd05e..44bab7a624 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PerResourcePollingEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PerResourcePollingEventSource.java @@ -12,7 +12,6 @@ import io.javaoperatorsdk.operator.processing.event.ResourceID; import io.javaoperatorsdk.operator.processing.event.source.Cache; import io.javaoperatorsdk.operator.processing.event.source.CacheKeyMapper; -import io.javaoperatorsdk.operator.processing.event.source.CachingEventSource; import io.javaoperatorsdk.operator.processing.event.source.ExternalResourceCachingEventSource; import io.javaoperatorsdk.operator.processing.event.source.ResourceEventAware; @@ -22,7 +21,7 @@ * if there is no registerPredicate provided. If register predicate provided it is evaluated on * resource create and/or update to register polling for the event source. *

- * For other behavior see {@link CachingEventSource} + * For other behavior see {@link ExternalResourceCachingEventSource} * * @param the resource polled by the event source * @param

related custom resource diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/EventSourceManagerTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/EventSourceManagerTest.java index 821efbd16a..779fe032f9 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/EventSourceManagerTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/EventSourceManagerTest.java @@ -11,10 +11,10 @@ import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; import io.javaoperatorsdk.operator.api.reconciler.Reconciler; import io.javaoperatorsdk.operator.processing.Controller; -import io.javaoperatorsdk.operator.processing.event.source.CachingEventSource; import io.javaoperatorsdk.operator.processing.event.source.EventSource; import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerResourceEventSource; import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; +import io.javaoperatorsdk.operator.processing.event.source.informer.ManagedInformerEventSource; import io.javaoperatorsdk.operator.processing.event.source.timer.TimerEventSource; import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; @@ -79,7 +79,7 @@ void retrievingEventSourceForClassShouldWork() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> manager.getResourceEventSourceFor(HasMetadata.class, "unknown_name")); - CachingEventSource eventSource = mock(CachingEventSource.class); + ManagedInformerEventSource eventSource = mock(ManagedInformerEventSource.class); when(eventSource.resourceType()).thenReturn(String.class); manager.registerEventSource(eventSource); @@ -93,11 +93,11 @@ void shouldNotBePossibleToAddEventSourcesForSameTypeAndName() { EventSourceManager manager = initManager(); final var name = "name1"; - CachingEventSource eventSource = mock(CachingEventSource.class); + ManagedInformerEventSource eventSource = mock(ManagedInformerEventSource.class); when(eventSource.resourceType()).thenReturn(TestCustomResource.class); manager.registerEventSource(name, eventSource); - eventSource = mock(CachingEventSource.class); + eventSource = mock(ManagedInformerEventSource.class); when(eventSource.resourceType()).thenReturn(TestCustomResource.class); final var source = eventSource; @@ -114,11 +114,11 @@ void shouldNotBePossibleToAddEventSourcesForSameTypeAndName() { void retrievingAnEventSourceWhenMultipleAreRegisteredForATypeShouldRequireAQualifier() { EventSourceManager manager = initManager(); - CachingEventSource eventSource = mock(CachingEventSource.class); + ManagedInformerEventSource eventSource = mock(ManagedInformerEventSource.class); when(eventSource.resourceType()).thenReturn(TestCustomResource.class); manager.registerEventSource("name1", eventSource); - CachingEventSource eventSource2 = mock(CachingEventSource.class); + ManagedInformerEventSource eventSource2 = mock(ManagedInformerEventSource.class); when(eventSource2.resourceType()).thenReturn(TestCustomResource.class); manager.registerEventSource("name2", eventSource2); From b1ec50294afde41ef027b0bb4a004d6809c22a92 Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 20 Jun 2022 14:03:53 +0200 Subject: [PATCH 04/33] predicates as filters --- .../informer/InformerConfiguration.java | 65 +++++++++--- .../ExternalResourceCachingEventSource.java | 100 ++++++++++++++++-- .../source/informer/InformerEventSource.java | 21 ++-- 3 files changed, 160 insertions(+), 26 deletions(-) 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 fef0bea589..35e308761c 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 @@ -2,13 +2,14 @@ import java.util.Objects; import java.util.Set; +import java.util.function.BiPredicate; +import java.util.function.Predicate; import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.config.DefaultResourceConfiguration; import io.javaoperatorsdk.operator.api.config.ResourceConfiguration; import io.javaoperatorsdk.operator.api.config.Utils; import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; -import io.javaoperatorsdk.operator.processing.event.source.EventFilter; import io.javaoperatorsdk.operator.processing.event.source.PrimaryToSecondaryMapper; import io.javaoperatorsdk.operator.processing.event.source.SecondaryToPrimaryMapper; import io.javaoperatorsdk.operator.processing.event.source.informer.Mappers; @@ -24,33 +25,53 @@ class DefaultInformerConfiguration extends private final PrimaryToSecondaryMapper primaryToSecondaryMapper; private final SecondaryToPrimaryMapper secondaryToPrimaryMapper; private final boolean followControllerNamespaceChanges; - private final EventFilter eventFilter; + private final Predicate onAddFilter; + private final BiPredicate onUpdateFilter; + private final BiPredicate onDeleteFilter; protected DefaultInformerConfiguration(String labelSelector, Class resourceClass, PrimaryToSecondaryMapper primaryToSecondaryMapper, SecondaryToPrimaryMapper secondaryToPrimaryMapper, - EventFilter eventFilter, - Set namespaces, boolean followControllerNamespaceChanges) { + Set namespaces, boolean followControllerNamespaceChanges, + Predicate onAddFilter, + BiPredicate onUpdateFilter, + BiPredicate onDeleteFilter) { super(labelSelector, resourceClass, namespaces); this.followControllerNamespaceChanges = followControllerNamespaceChanges; + this.primaryToSecondaryMapper = primaryToSecondaryMapper; - this.eventFilter = eventFilter; this.secondaryToPrimaryMapper = Objects.requireNonNullElse(secondaryToPrimaryMapper, Mappers.fromOwnerReference()); + this.onAddFilter = onAddFilter; + this.onUpdateFilter = onUpdateFilter; + this.onDeleteFilter = onDeleteFilter; } + @Override public boolean followControllerNamespaceChanges() { return followControllerNamespaceChanges; } + @Override public SecondaryToPrimaryMapper getSecondaryToPrimaryMapper() { return secondaryToPrimaryMapper; } - public EventFilter getEventFilter() { - return eventFilter; + @Override + public Predicate getOnAddFilter() { + return onAddFilter; + } + + @Override + public BiPredicate getOnUpdateFilter() { + return onUpdateFilter; + } + + @Override + public BiPredicate getOnDeleteFilter() { + return onDeleteFilter; } @Override public

PrimaryToSecondaryMapper

getPrimaryToSecondaryMapper() { @@ -68,7 +89,11 @@ public

PrimaryToSecondaryMapper

getPrimaryToSecondary SecondaryToPrimaryMapper getSecondaryToPrimaryMapper(); - EventFilter getEventFilter(); + Predicate getOnAddFilter(); + + BiPredicate getOnUpdateFilter(); + + BiPredicate getOnDeleteFilter();

PrimaryToSecondaryMapper

getPrimaryToSecondaryMapper(); @@ -79,8 +104,10 @@ class InformerConfigurationBuilder { private SecondaryToPrimaryMapper secondaryToPrimaryMapper; private Set namespaces; private String labelSelector; - private EventFilter eventFilter; private final Class resourceClass; + private Predicate onAddFilter; + private BiPredicate onUpdateFilter; + private BiPredicate onDeleteFilter; private boolean inheritControllerNamespacesOnChange = false; private InformerConfigurationBuilder(Class resourceClass) { @@ -161,16 +188,28 @@ public InformerConfigurationBuilder withLabelSelector(String labelSelector) { return this; } - public InformerConfigurationBuilder withEventFilter(EventFilter eventFilter) { - this.eventFilter = eventFilter; + public InformerConfigurationBuilder withOnAddFilter(Predicate onAddFilter) { + this.onAddFilter = onAddFilter; + return this; + } + + public InformerConfigurationBuilder withOnUpdateFilter(BiPredicate onUpdateFilter) { + this.onUpdateFilter = onUpdateFilter; + return this; + } + + public InformerConfigurationBuilder withOnDeleteFilter( + BiPredicate onDeleteFilter) { + this.onDeleteFilter = onDeleteFilter; return this; } public InformerConfiguration build() { return new DefaultInformerConfiguration<>(labelSelector, resourceClass, primaryToSecondaryMapper, - secondaryToPrimaryMapper,eventFilter, - namespaces, inheritControllerNamespacesOnChange); + secondaryToPrimaryMapper, + namespaces, inheritControllerNamespacesOnChange, onAddFilter, onUpdateFilter, + onDeleteFilter); } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java index f8a0cafcd8..ac50f12fbe 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java @@ -2,7 +2,10 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiPredicate; +import java.util.function.Predicate; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,6 +43,10 @@ public abstract class ExternalResourceCachingEventSource> cache = new ConcurrentHashMap<>(); + protected Predicate onAddFilter; + protected BiPredicate onUpdateFilter; + protected BiPredicate onDeleteFilter; + protected ExternalResourceCachingEventSource(Class resourceClass, CacheKeyMapper cacheKeyMapper) { super(resourceClass); @@ -48,7 +55,7 @@ protected ExternalResourceCachingEventSource(Class resourceClass, protected synchronized void handleDelete(ResourceID primaryID) { var res = cache.remove(primaryID); - if (res != null) { + if (res != null && deleteAcceptedByFilter(res.values())) { getEventHandler().handleEvent(new Event(primaryID)); } } @@ -62,18 +69,18 @@ protected synchronized void handleDelete(ResourceID primaryID, R resource) { handleDelete(primaryID, Set.of(cacheKeyMapper.keyFor(resource))); } - protected synchronized void handleDelete(ResourceID primaryID, Set resourceID) { + protected synchronized void handleDelete(ResourceID primaryID, Set resourceIDs) { if (!isRunning()) { return; } var cachedValues = cache.get(primaryID); - var sizeBeforeRemove = cachedValues.size(); - resourceID.forEach(cachedValues::remove); + var removedResources = resourceIDs.stream() + .flatMap(id -> Stream.ofNullable(cachedValues.remove(id))).collect(Collectors.toList()); if (cachedValues.isEmpty()) { cache.remove(primaryID); } - if (sizeBeforeRemove > cachedValues.size()) { + if (!removedResources.isEmpty() && deleteAcceptedByFilter(removedResources)) { getEventHandler().handleEvent(new Event(primaryID)); } } @@ -90,7 +97,7 @@ protected synchronized void handleResources(Map> allNewResour var toDelete = cache.keySet().stream().filter(k -> !allNewResources.containsKey(k)) .collect(Collectors.toList()); toDelete.forEach(this::handleDelete); - allNewResources.forEach((primaryID, resources) -> handleResources(primaryID, resources)); + allNewResources.forEach(this::handleResources); } protected synchronized void handleResources(ResourceID primaryID, Set newResources, @@ -101,14 +108,65 @@ protected synchronized void handleResources(ResourceID primaryID, Set newReso return; } var cachedResources = cache.get(primaryID); + if (cachedResources == null) { + cachedResources = Collections.emptyMap(); + } var newResourcesMap = newResources.stream().collect(Collectors.toMap(cacheKeyMapper::keyFor, r -> r)); cache.put(primaryID, newResourcesMap); - if (propagateEvent && !newResourcesMap.equals(cachedResources)) { + if (propagateEvent && !newResourcesMap.equals(cachedResources) + && acceptedByFiler(cachedResources, newResourcesMap)) { getEventHandler().handleEvent(new Event(primaryID)); } } + private boolean acceptedByFiler(Map cachedResourceMap, + Map newResourcesMap) { + + var addedResources = new HashMap<>(newResourcesMap); + addedResources.keySet().removeAll(cachedResourceMap.keySet()); + if (onAddFilter != null) { + var anyAddAccepted = addedResources.values().stream().anyMatch(onAddFilter::test); + if (anyAddAccepted) { + return true; + } + } else if (!addedResources.isEmpty()) { + return true; + } + + var deletedResource = new HashMap<>(cachedResourceMap); + deletedResource.keySet().removeAll(newResourcesMap.keySet()); + if (onDeleteFilter != null) { + var anyDeleteAccepted = + deletedResource.values().stream().anyMatch(r -> onDeleteFilter.test(r, false)); + if (anyDeleteAccepted) { + return true; + } + } else if (!deletedResource.isEmpty()) { + return true; + } + + Map possibleUpdatedResources = new HashMap<>(cachedResourceMap); + possibleUpdatedResources.keySet().retainAll(newResourcesMap.keySet()); + possibleUpdatedResources = possibleUpdatedResources.entrySet().stream() + .filter(entry -> !newResourcesMap + .get(entry.getKey()).equals(entry.getValue())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + if (onUpdateFilter != null) { + var anyUpdated = possibleUpdatedResources.entrySet().stream() + .anyMatch( + entry -> onUpdateFilter.test(newResourcesMap.get(entry.getKey()), entry.getValue())); + if (anyUpdated) { + return true; + } + } else if (!possibleUpdatedResources.isEmpty()) { + return true; + } + + return false; + } + @Override public synchronized void handleRecentResourceCreate(ResourceID primaryID, R resource) { var actualValues = cache.get(primaryID); @@ -163,4 +221,32 @@ public Optional getSecondaryResource(ResourceID primaryID) { public Map> getCache() { return Collections.unmodifiableMap(cache); } + + protected boolean deleteAcceptedByFilter(Collection res) { + if (onDeleteFilter == null) { + return true; + } + // it is enough if at least one event is accepted + // Cannot be sure about the final state in general, mainly for polled resources. This might be + // fine-tuned for + // other event sources. (For now just by overriding this method.) + return res.stream().anyMatch(r -> onDeleteFilter.test(r, false)); + } + + public ExternalResourceCachingEventSource setOnAddFilter(Predicate onAddFilter) { + this.onAddFilter = onAddFilter; + return this; + } + + public ExternalResourceCachingEventSource setOnUpdateFilter( + BiPredicate onUpdateFilter) { + this.onUpdateFilter = onUpdateFilter; + return this; + } + + public ExternalResourceCachingEventSource setOnDeleteFilter( + BiPredicate onDeleteFilter) { + this.onDeleteFilter = onDeleteFilter; + return this; + } } 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 c0d170f903..7c78de4e6b 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 @@ -2,6 +2,8 @@ import java.util.Optional; import java.util.Set; +import java.util.function.BiPredicate; +import java.util.function.Predicate; import java.util.stream.Collectors; import org.slf4j.Logger; @@ -76,9 +78,15 @@ public class InformerEventSource private final EventRecorder eventRecorder = new EventRecorder<>(); // we need direct control for the indexer to propagate the just update resource also to the index private final PrimaryToSecondaryIndex primaryToSecondaryIndex; + private final EventFilter eventFilter; private final PrimaryToSecondaryMapper

primaryToSecondaryMapper; + protected final Predicate onAddFilter; + protected final BiPredicate onUpdateFilter; + protected final BiPredicate onDeleteFilter; + + public InformerEventSource( InformerConfiguration configuration, EventSourceContext

context) { this(configuration, context.getClient()); @@ -95,6 +103,9 @@ public InformerEventSource(InformerConfiguration configuration, KubernetesCli primaryToSecondaryIndex = NOOPPrimaryToSecondaryIndex.getInstance(); } this.eventFilter = configuration.getEventFilter(); + onAddFilter = configuration.getOnAddFilter(); + onUpdateFilter = configuration.getOnUpdateFilter(); + onDeleteFilter = configuration.getOnDeleteFilter(); } @Override @@ -130,7 +141,7 @@ public void onDelete(R resource, boolean b) { } primaryToSecondaryIndex.onDelete(resource); super.onDelete(resource, b); - if (eventFilter == null || eventFilter.acceptDelete(resource, b)) { + if (onDeleteFilter == null || onDeleteFilter.test(resource, b)) { propagateEvent(resource); } } @@ -305,12 +316,10 @@ public boolean allowsNamespaceChanges() { private boolean eventAcceptedByFilter(Operation operation, R newObject, R oldObject) { - if (eventFilter == null) { - return true; - } else if (operation == Operation.ADD) { - return eventFilter.acceptAdd(newObject); + if (operation == Operation.ADD) { + return onAddFilter == null || onAddFilter.test(newObject); } else { - return eventFilter.acceptUpdate(newObject, oldObject); + return onUpdateFilter == null || onUpdateFilter.test(newObject, oldObject); } } From 5d563cf1784ad73bddab357cd01adfaad68d0ab4 Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 20 Jun 2022 14:07:35 +0200 Subject: [PATCH 05/33] wip --- .../event/source/ExternalResourceCachingEventSource.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java index ac50f12fbe..7ce5098a09 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java @@ -164,7 +164,8 @@ private boolean acceptedByFiler(Map cachedResourceMap, return true; } - return false; + throw new IllegalStateException("Should not end up here. Cached map: " + cachedResourceMap + + ", new resource map: " + newResourcesMap); } @Override From 413a21ca54b37c8f0116aa3243c07e16feb550cf Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 20 Jun 2022 16:16:49 +0200 Subject: [PATCH 06/33] wip --- .../ControllerConfigurationOverrider.java | 20 +++++++++++++++ .../DefaultControllerConfiguration.java | 6 ++++- .../config/DefaultResourceConfiguration.java | 24 +++++++++++++++--- .../api/config/ResourceConfiguration.java | 10 ++++++++ .../informer/InformerConfiguration.java | 25 ++++--------------- .../reconciler/ControllerConfiguration.java | 13 ++++++++++ .../source/AbstractResourceEventSource.java | 21 ++++++++++++++++ .../ExternalResourceCachingEventSource.java | 23 ----------------- .../ControllerResourceEventSource.java | 21 +++++++++++++--- .../event/source/filter/VoidOnAddFilter.java | 12 +++++++++ .../source/filter/VoidOnUpdateFilter.java | 12 +++++++++ .../source/informer/InformerEventSource.java | 5 ---- .../operator/ControllerManagerTest.java | 2 +- .../source/CustomResourceSelectorTest.java | 2 +- .../event/source/ResourceEventFilterTest.java | 2 +- .../ControllerResourceEventSourceTest.java | 2 +- 16 files changed, 140 insertions(+), 60 deletions(-) create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/filter/VoidOnAddFilter.java create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/filter/VoidOnUpdateFilter.java 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 7e987d4700..064ebb767f 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 @@ -2,6 +2,7 @@ import java.time.Duration; import java.util.*; +import java.util.function.BiPredicate; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -27,6 +28,8 @@ public class ControllerConfigurationOverrider { private final ControllerConfiguration original; private Duration reconciliationMaxInterval; private final LinkedHashMap namedDependentResourceSpecs; + private Predicate onAddFilter; + private BiPredicate onUpdateFilter; private ControllerConfigurationOverrider(ControllerConfiguration original) { finalizer = original.getFinalizerName(); @@ -39,6 +42,8 @@ private ControllerConfigurationOverrider(ControllerConfiguration original) { // make the original specs modifiable final var dependentResources = original.getDependentResources(); namedDependentResourceSpecs = new LinkedHashMap<>(dependentResources.size()); + this.onAddFilter = original.onAddFilter(); + this.onUpdateFilter = original.onUpdateFilter(); dependentResources.forEach(drs -> namedDependentResourceSpecs.put(drs.getName(), drs)); this.original = original; } @@ -120,6 +125,19 @@ public ControllerConfigurationOverrider withReconciliationMaxInterval( return this; } + public ControllerConfigurationOverrider withOnAddFilter( + Predicate onAddFilter) { + this.onAddFilter = onAddFilter; + return this; + } + + public ControllerConfigurationOverrider withOnUpdateFilter( + BiPredicate onUpdateFilter) { + this.onUpdateFilter = onUpdateFilter; + return this; + } + + public ControllerConfigurationOverrider replacingNamedDependentResourceConfig(String name, Object dependentResourceConfig) { @@ -167,6 +185,8 @@ public ControllerConfiguration build() { customResourcePredicate, original.getResourceClass(), reconciliationMaxInterval, + onAddFilter, + onUpdateFilter, newDependentSpecs); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java index 7046af3d85..6fe14671d6 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java @@ -5,6 +5,8 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.BiPredicate; +import java.util.function.Predicate; import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec; @@ -39,8 +41,10 @@ public DefaultControllerConfiguration( ResourceEventFilter resourceEventFilter, Class resourceClass, Duration reconciliationMaxInterval, + Predicate onAddFilter, + BiPredicate onUpdateFilter, List dependents) { - super(labelSelector, resourceClass, namespaces); + super(labelSelector, resourceClass, onAddFilter, onUpdateFilter, namespaces); this.associatedControllerClassName = associatedControllerClassName; this.name = name; this.crdName = crdName; diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceConfiguration.java index 407c352be0..5d798a93a1 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceConfiguration.java @@ -1,6 +1,8 @@ package io.javaoperatorsdk.operator.api.config; import java.util.Set; +import java.util.function.BiPredicate; +import java.util.function.Predicate; import io.fabric8.kubernetes.api.model.HasMetadata; @@ -12,18 +14,24 @@ public class DefaultResourceConfiguration private final String labelSelector; private final Set namespaces; private final Class resourceClass; + private final Predicate onAddFilter; + private final BiPredicate onUpdateFilter; public DefaultResourceConfiguration(String labelSelector, Class resourceClass, - String... namespaces) { - this(labelSelector, resourceClass, + Predicate onAddFilter, + BiPredicate onUpdateFilter, String... namespaces) { + this(labelSelector, resourceClass, onAddFilter, onUpdateFilter, namespaces == null || namespaces.length == 0 ? DEFAULT_NAMESPACES_SET : Set.of(namespaces)); } public DefaultResourceConfiguration(String labelSelector, Class resourceClass, - Set namespaces) { + Predicate onAddFilter, + BiPredicate onUpdateFilter, Set namespaces) { this.labelSelector = labelSelector; this.resourceClass = resourceClass; + this.onAddFilter = onAddFilter; + this.onUpdateFilter = onUpdateFilter; this.namespaces = namespaces == null || namespaces.isEmpty() ? DEFAULT_NAMESPACES_SET : namespaces; @@ -48,4 +56,14 @@ public Set getNamespaces() { public Class getResourceClass() { return resourceClass; } + + @Override + public Predicate onAddFilter() { + return onAddFilter; + } + + @Override + public BiPredicate onUpdateFilter() { + return onUpdateFilter; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResourceConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResourceConfiguration.java index 70d2b765a5..dea583251c 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResourceConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResourceConfiguration.java @@ -2,6 +2,8 @@ import java.util.Collections; import java.util.Set; +import java.util.function.BiPredicate; +import java.util.function.Predicate; import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.OperatorException; @@ -17,6 +19,14 @@ default String getResourceTypeName() { return ReconcilerUtils.getResourceTypeName(getResourceClass()); } + default Predicate onAddFilter() { + return null; + } + + default BiPredicate onUpdateFilter() { + return null; + } + /** * Retrieves the label selector that is used to filter which resources are actually watched by the * associated event source. See the official documentation on the 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 35e308761c..53e81e9d9a 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 @@ -25,8 +25,6 @@ class DefaultInformerConfiguration extends private final PrimaryToSecondaryMapper primaryToSecondaryMapper; private final SecondaryToPrimaryMapper secondaryToPrimaryMapper; private final boolean followControllerNamespaceChanges; - private final Predicate onAddFilter; - private final BiPredicate onUpdateFilter; private final BiPredicate onDeleteFilter; protected DefaultInformerConfiguration(String labelSelector, @@ -37,15 +35,13 @@ protected DefaultInformerConfiguration(String labelSelector, Predicate onAddFilter, BiPredicate onUpdateFilter, BiPredicate onDeleteFilter) { - super(labelSelector, resourceClass, namespaces); + super(labelSelector, resourceClass, onAddFilter, onUpdateFilter, namespaces); this.followControllerNamespaceChanges = followControllerNamespaceChanges; this.primaryToSecondaryMapper = primaryToSecondaryMapper; this.secondaryToPrimaryMapper = Objects.requireNonNullElse(secondaryToPrimaryMapper, Mappers.fromOwnerReference()); - this.onAddFilter = onAddFilter; - this.onUpdateFilter = onUpdateFilter; this.onDeleteFilter = onDeleteFilter; } @@ -59,18 +55,7 @@ public SecondaryToPrimaryMapper getSecondaryToPrimaryMapper() { return secondaryToPrimaryMapper; } - @Override - public Predicate getOnAddFilter() { - return onAddFilter; - } - - @Override - public BiPredicate getOnUpdateFilter() { - return onUpdateFilter; - } - - @Override - public BiPredicate getOnDeleteFilter() { + public BiPredicate onDeleteFilter() { return onDeleteFilter; } @Override @@ -89,11 +74,11 @@ public

PrimaryToSecondaryMapper

getPrimaryToSecondary SecondaryToPrimaryMapper getSecondaryToPrimaryMapper(); - Predicate getOnAddFilter(); + Predicate onAddFilter(); - BiPredicate getOnUpdateFilter(); + BiPredicate onUpdateFilter(); - BiPredicate getOnDeleteFilter(); + BiPredicate onDeleteFilter();

PrimaryToSecondaryMapper

getPrimaryToSecondaryMapper(); 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 a5e1459cb7..1b1be4ec1f 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java @@ -4,9 +4,14 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.function.BiPredicate; +import java.util.function.Predicate; +import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter; +import io.javaoperatorsdk.operator.processing.event.source.filter.VoidOnAddFilter; +import io.javaoperatorsdk.operator.processing.event.source.filter.VoidOnUpdateFilter; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @@ -51,6 +56,8 @@ String labelSelector() default Constants.NO_VALUE_SET; /** + * Use onAddFilter, onUpdateFilter, onDeleteFilter instead. + * *

* Resource event filters only applies on events of the main custom resource. Not on events from * other event sources nor the periodic events. @@ -58,8 +65,14 @@ * * @return the list of event filters. */ + @Deprecated Class[] eventFilters() default {}; + // todo document missing delete filter + Class> onAddFilter() default VoidOnAddFilter.class; + + Class> onUpdateFilter() default VoidOnUpdateFilter.class; + /** * Optional configuration of the maximal interval the SDK will wait for a reconciliation request * to happen before one will be automatically triggered. diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/AbstractResourceEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/AbstractResourceEventSource.java index 051a75ff20..9b97529ce5 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/AbstractResourceEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/AbstractResourceEventSource.java @@ -1,5 +1,8 @@ package io.javaoperatorsdk.operator.processing.event.source; +import java.util.function.BiPredicate; +import java.util.function.Predicate; + import io.fabric8.kubernetes.api.model.HasMetadata; public abstract class AbstractResourceEventSource @@ -7,6 +10,10 @@ public abstract class AbstractResourceEventSource implements ResourceEventSource { private final Class resourceClass; + protected Predicate onAddFilter; + protected BiPredicate onUpdateFilter; + protected BiPredicate onDeleteFilter; + protected AbstractResourceEventSource(Class resourceClass) { this.resourceClass = resourceClass; } @@ -15,4 +22,18 @@ protected AbstractResourceEventSource(Class resourceClass) { public Class resourceType() { return resourceClass; } + + public void setOnAddFilter(Predicate onAddFilter) { + this.onAddFilter = onAddFilter; + } + + public void setOnUpdateFilter( + BiPredicate onUpdateFilter) { + this.onUpdateFilter = onUpdateFilter; + } + + public void setOnDeleteFilter( + BiPredicate onDeleteFilter) { + this.onDeleteFilter = onDeleteFilter; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java index 7ce5098a09..c4f041c656 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java @@ -2,8 +2,6 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; -import java.util.function.BiPredicate; -import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -43,10 +41,6 @@ public abstract class ExternalResourceCachingEventSource> cache = new ConcurrentHashMap<>(); - protected Predicate onAddFilter; - protected BiPredicate onUpdateFilter; - protected BiPredicate onDeleteFilter; - protected ExternalResourceCachingEventSource(Class resourceClass, CacheKeyMapper cacheKeyMapper) { super(resourceClass); @@ -233,21 +227,4 @@ protected boolean deleteAcceptedByFilter(Collection res) { // other event sources. (For now just by overriding this method.) return res.stream().anyMatch(r -> onDeleteFilter.test(r, false)); } - - public ExternalResourceCachingEventSource setOnAddFilter(Predicate onAddFilter) { - this.onAddFilter = onAddFilter; - return this; - } - - public ExternalResourceCachingEventSource setOnUpdateFilter( - BiPredicate onUpdateFilter) { - this.onUpdateFilter = onUpdateFilter; - return this; - } - - public ExternalResourceCachingEventSource setOnDeleteFilter( - BiPredicate onDeleteFilter) { - this.onDeleteFilter = onDeleteFilter; - return this; - } } 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 83afcc0be2..f47fd3d5ca 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 @@ -27,7 +27,7 @@ public class ControllerResourceEventSource private static final Logger log = LoggerFactory.getLogger(ControllerResourceEventSource.class); private final Controller controller; - private final ResourceEventFilter filter; + private final ResourceEventFilter legacyFilters; @SuppressWarnings("unchecked") public ControllerResourceEventSource(Controller controller) { @@ -39,9 +39,10 @@ public ControllerResourceEventSource(Controller controller) { ResourceEventFilters.generationAware(), }; if (controller.getConfiguration().getEventFilter() != null) { - filter = controller.getConfiguration().getEventFilter().and(ResourceEventFilters.or(filters)); + legacyFilters = + controller.getConfiguration().getEventFilter().and(ResourceEventFilters.or(filters)); } else { - filter = ResourceEventFilters.or(filters); + legacyFilters = ResourceEventFilters.or(filters); } } @@ -60,7 +61,8 @@ public void eventReceived(ResourceAction action, T resource, T oldResource) { log.debug("Event received for resource: {}", getName(resource)); MDCUtils.addResourceInfo(resource); controller.getEventSourceManager().broadcastOnResourceEvent(action, resource, oldResource); - if (filter.acceptChange(controller, oldResource, resource)) { + if (legacyFilters.acceptChange(controller, oldResource, resource) + && acceptFilters(action, resource, oldResource)) { getEventHandler().handleEvent( new ResourceEvent(action, ResourceID.fromResource(resource), resource)); } else { @@ -72,6 +74,17 @@ public void eventReceived(ResourceAction action, T resource, T oldResource) { } } + private boolean acceptFilters(ResourceAction action, T resource, T oldResource) { + // delete event not filtered, there is no reconciliation for delete anyways + switch (action) { + case ADDED: + return onAddFilter == null || onAddFilter.test(resource); + case UPDATED: + return onUpdateFilter == null || onUpdateFilter.test(resource, oldResource); + } + return true; + } + @Override public void onAdd(T resource) { super.onAdd(resource); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/filter/VoidOnAddFilter.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/filter/VoidOnAddFilter.java new file mode 100644 index 0000000000..11c233e14c --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/filter/VoidOnAddFilter.java @@ -0,0 +1,12 @@ +package io.javaoperatorsdk.operator.processing.event.source.filter; + +import java.util.function.Predicate; + +import io.fabric8.kubernetes.api.model.HasMetadata; + +public class VoidOnAddFilter implements Predicate { + @Override + public boolean test(HasMetadata hasMetadata) { + return true; + } +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/filter/VoidOnUpdateFilter.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/filter/VoidOnUpdateFilter.java new file mode 100644 index 0000000000..333bff2455 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/filter/VoidOnUpdateFilter.java @@ -0,0 +1,12 @@ +package io.javaoperatorsdk.operator.processing.event.source.filter; + +import java.util.function.BiPredicate; + +import io.fabric8.kubernetes.api.model.HasMetadata; + +public class VoidOnUpdateFilter implements BiPredicate { + @Override + public boolean test(HasMetadata hasMetadata, HasMetadata hasMetadata2) { + return true; + } +} 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 7c78de4e6b..a7dc8f4280 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 @@ -2,8 +2,6 @@ import java.util.Optional; import java.util.Set; -import java.util.function.BiPredicate; -import java.util.function.Predicate; import java.util.stream.Collectors; import org.slf4j.Logger; @@ -79,9 +77,6 @@ public class InformerEventSource // we need direct control for the indexer to propagate the just update resource also to the index private final PrimaryToSecondaryIndex primaryToSecondaryIndex; - private final EventFilter eventFilter; - private final PrimaryToSecondaryMapper

primaryToSecondaryMapper; - protected final Predicate onAddFilter; protected final BiPredicate onUpdateFilter; protected final BiPredicate onDeleteFilter; diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ControllerManagerTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ControllerManagerTest.java index ca02094d46..d008b92cf1 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ControllerManagerTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ControllerManagerTest.java @@ -63,7 +63,7 @@ private static class TestControllerConfiguration public TestControllerConfiguration(Reconciler controller, Class crClass) { super(null, getControllerName(controller), CustomResource.getCRDName(crClass), null, false, null, null, null, null, crClass, - null, null); + null, null, null, null); this.controller = controller; } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/CustomResourceSelectorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/CustomResourceSelectorTest.java index 3de6578515..cdca109628 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/CustomResourceSelectorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/CustomResourceSelectorTest.java @@ -136,7 +136,7 @@ public static class MyConfiguration extends DefaultControllerConfiguration Date: Tue, 21 Jun 2022 08:59:51 +0200 Subject: [PATCH 07/33] wip --- .../AnnotationControllerConfiguration.java | 53 ++++++++++++++++--- .../ControllerConfigurationOverrider.java | 4 +- .../config/DefaultResourceConfiguration.java | 9 ++-- .../api/config/ResourceConfiguration.java | 9 ++-- .../informer/InformerConfiguration.java | 11 ++-- .../source/informer/InformerEventSource.java | 10 ++-- 6 files changed, 69 insertions(+), 27 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java index c8d13b8688..fea30c226d 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java @@ -8,7 +8,9 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.BiPredicate; import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Collectors; import io.fabric8.kubernetes.api.model.HasMetadata; @@ -27,6 +29,8 @@ import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition; import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter; import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilters; +import io.javaoperatorsdk.operator.processing.event.source.filter.VoidOnAddFilter; +import io.javaoperatorsdk.operator.processing.event.source.filter.VoidOnUpdateFilter; import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_NAMESPACES_SET; @@ -144,14 +148,37 @@ public Optional reconciliationMaxInterval() { } } - public static T valueOrDefault( - ControllerConfiguration controllerConfiguration, - Function mapper, - T defaultValue) { - if (controllerConfiguration == null) { - return defaultValue; + @Override + @SuppressWarnings("unchecked") + public Optional> onAddFilter() { + var onAddFilter = annotation.onAddFilter(); + if (onAddFilter.equals(VoidOnAddFilter.class)) { + return Optional.empty(); } else { - return mapper.apply(controllerConfiguration); + try { + var instance = (Predicate) onAddFilter.getDeclaredConstructor().newInstance(); + return Optional.of(instance); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException + | NoSuchMethodException e) { + throw new OperatorException(e); + } + } + } + + @Override + @SuppressWarnings("unchecked") + public Optional> onUpdateFilter() { + var onUpdateFilter = annotation.onUpdateFilter(); + if (onUpdateFilter.equals(VoidOnUpdateFilter.class)) { + return Optional.empty(); + } else { + try { + var instance = (BiPredicate) onUpdateFilter.getDeclaredConstructor().newInstance(); + return Optional.of(instance); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException + | NoSuchMethodException e) { + throw new OperatorException(e); + } } } @@ -245,4 +272,16 @@ private Object createKubernetesResourceConfig(Class new KubernetesDependentResourceConfig(namespaces, labelSelector, configuredNS); return config; } + + public static T valueOrDefault( + ControllerConfiguration controllerConfiguration, + Function mapper, + T defaultValue) { + if (controllerConfiguration == null) { + return defaultValue; + } else { + return mapper.apply(controllerConfiguration); + } + } + } 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 064ebb767f..fe3bf26e1c 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 @@ -42,8 +42,8 @@ private ControllerConfigurationOverrider(ControllerConfiguration original) { // make the original specs modifiable final var dependentResources = original.getDependentResources(); namedDependentResourceSpecs = new LinkedHashMap<>(dependentResources.size()); - this.onAddFilter = original.onAddFilter(); - this.onUpdateFilter = original.onUpdateFilter(); + this.onAddFilter = original.onAddFilter().orElse(null); + this.onUpdateFilter = original.onUpdateFilter().orElse(null); dependentResources.forEach(drs -> namedDependentResourceSpecs.put(drs.getName(), drs)); this.original = original; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceConfiguration.java index 5d798a93a1..9793e43001 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceConfiguration.java @@ -1,5 +1,6 @@ package io.javaoperatorsdk.operator.api.config; +import java.util.Optional; import java.util.Set; import java.util.function.BiPredicate; import java.util.function.Predicate; @@ -58,12 +59,12 @@ public Class getResourceClass() { } @Override - public Predicate onAddFilter() { - return onAddFilter; + public Optional> onAddFilter() { + return Optional.ofNullable(onAddFilter); } @Override - public BiPredicate onUpdateFilter() { - return onUpdateFilter; + public Optional> onUpdateFilter() { + return Optional.ofNullable(onUpdateFilter); } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResourceConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResourceConfiguration.java index dea583251c..f918def476 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResourceConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResourceConfiguration.java @@ -1,6 +1,7 @@ package io.javaoperatorsdk.operator.api.config; import java.util.Collections; +import java.util.Optional; import java.util.Set; import java.util.function.BiPredicate; import java.util.function.Predicate; @@ -19,12 +20,12 @@ default String getResourceTypeName() { return ReconcilerUtils.getResourceTypeName(getResourceClass()); } - default Predicate onAddFilter() { - return null; + default Optional> onAddFilter() { + return Optional.empty(); } - default BiPredicate onUpdateFilter() { - return null; + default Optional> onUpdateFilter() { + return Optional.empty(); } /** 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 53e81e9d9a..b545cad3fb 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 @@ -1,6 +1,7 @@ package io.javaoperatorsdk.operator.api.config.informer; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.function.BiPredicate; import java.util.function.Predicate; @@ -55,8 +56,8 @@ public SecondaryToPrimaryMapper getSecondaryToPrimaryMapper() { return secondaryToPrimaryMapper; } - public BiPredicate onDeleteFilter() { - return onDeleteFilter; + public Optional> onDeleteFilter() { + return Optional.ofNullable(onDeleteFilter); } @Override public

PrimaryToSecondaryMapper

getPrimaryToSecondaryMapper() { @@ -74,11 +75,11 @@ public

PrimaryToSecondaryMapper

getPrimaryToSecondary SecondaryToPrimaryMapper getSecondaryToPrimaryMapper(); - Predicate onAddFilter(); + Optional> onAddFilter(); - BiPredicate onUpdateFilter(); + Optional> onUpdateFilter(); - BiPredicate onDeleteFilter(); + Optional> onDeleteFilter();

PrimaryToSecondaryMapper

getPrimaryToSecondaryMapper(); 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 a7dc8f4280..5e676cbf95 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,7 +81,6 @@ public class InformerEventSource protected final BiPredicate onUpdateFilter; protected final BiPredicate onDeleteFilter; - public InformerEventSource( InformerConfiguration configuration, EventSourceContext

context) { this(configuration, context.getClient()); @@ -90,6 +89,11 @@ public InformerEventSource( public InformerEventSource(InformerConfiguration configuration, KubernetesClient client) { super(client.resources(configuration.getResourceClass()), configuration); this.configuration = configuration; + primaryToSecondaryIndex = + new PrimaryToSecondaryIndex<>(configuration.getSecondaryToPrimaryMapper()); + onAddFilter = configuration.onAddFilter().orElse(null); + onUpdateFilter = configuration.onUpdateFilter().orElse(null); + onDeleteFilter = configuration.onDeleteFilter().orElse(null); primaryToSecondaryMapper = configuration.getPrimaryToSecondaryMapper(); if (primaryToSecondaryMapper == null) { primaryToSecondaryIndex = @@ -97,10 +101,6 @@ public InformerEventSource(InformerConfiguration configuration, KubernetesCli } else { primaryToSecondaryIndex = NOOPPrimaryToSecondaryIndex.getInstance(); } - this.eventFilter = configuration.getEventFilter(); - onAddFilter = configuration.getOnAddFilter(); - onUpdateFilter = configuration.getOnUpdateFilter(); - onDeleteFilter = configuration.getOnDeleteFilter(); } @Override From dc97d5a685c54e84e5f166abbc33f31a25c944e3 Mon Sep 17 00:00:00 2001 From: csviri Date: Tue, 21 Jun 2022 10:30:17 +0200 Subject: [PATCH 08/33] IT --- .../ControllerResourceEventSource.java | 2 + .../io/javaoperatorsdk/operator/FilterIT.java | 73 ++++++++++++++++++ .../filter/FilterTestCustomResource.java | 19 +++++ .../sample/filter/FilterTestReconciler.java | 77 +++++++++++++++++++ .../sample/filter/FilterTestResourceSpec.java | 15 ++++ .../filter/FilterTestResourceStatus.java | 5 ++ .../operator/sample/filter/UpdateFilter.java | 13 ++++ 7 files changed, 204 insertions(+) create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/FilterIT.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/filter/FilterTestCustomResource.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/filter/FilterTestReconciler.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/filter/FilterTestResourceSpec.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/filter/FilterTestResourceStatus.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/filter/UpdateFilter.java 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 f47fd3d5ca..8a567fd44d 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 @@ -44,6 +44,8 @@ public ControllerResourceEventSource(Controller controller) { } else { legacyFilters = ResourceEventFilters.or(filters); } + controller.getConfiguration().onAddFilter().ifPresent(this::setOnAddFilter); + controller.getConfiguration().onUpdateFilter().ifPresent(this::setOnUpdateFilter); } @Override diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/FilterIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/FilterIT.java new file mode 100644 index 0000000000..2dea399448 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/FilterIT.java @@ -0,0 +1,73 @@ +package io.javaoperatorsdk.operator; + +import java.time.Duration; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; +import io.javaoperatorsdk.operator.sample.filter.FilterTestCustomResource; +import io.javaoperatorsdk.operator.sample.filter.FilterTestReconciler; +import io.javaoperatorsdk.operator.sample.filter.FilterTestResourceSpec; + +import static io.javaoperatorsdk.operator.sample.filter.FilterTestReconciler.CONFIG_MAP_FILTER_VALUE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +class FilterIT { + + public static final String RESOURCE_NAME = "test1"; + public static final int POLL_DELAY = 150; + + @RegisterExtension + LocallyRunOperatorExtension operator = + LocallyRunOperatorExtension.builder().withReconciler(FilterTestReconciler.class) + .build(); + + @Test + void filtersControllerResourceUpdate() { + var res = operator.create(FilterTestCustomResource.class, createResource()); + // One for CR create event other for ConfigMap event + await().pollDelay(Duration.ofMillis(POLL_DELAY)) + .untilAsserted(() -> assertThat(operator.getReconcilerOfType(FilterTestReconciler.class) + .getNumberOfExecutions()).isEqualTo(2)); + + res.getSpec().setValue(FilterTestReconciler.CUSTOM_RESOURCE_FILTER_VALUE); + operator.replace(FilterTestCustomResource.class, res); + + // not more reconciliation with the filtered value + await().pollDelay(Duration.ofMillis(POLL_DELAY)) + .untilAsserted(() -> assertThat(operator.getReconcilerOfType(FilterTestReconciler.class) + .getNumberOfExecutions()).isEqualTo(2)); + } + + @Test + void filtersSecondaryResourceUpdate() { + var res = operator.create(FilterTestCustomResource.class, createResource()); + // One for CR create event other for ConfigMap event + await().pollDelay(Duration.ofMillis(POLL_DELAY)) + .untilAsserted(() -> assertThat(operator.getReconcilerOfType(FilterTestReconciler.class) + .getNumberOfExecutions()).isEqualTo(2)); + + res.getSpec().setValue(CONFIG_MAP_FILTER_VALUE); + operator.replace(FilterTestCustomResource.class, res); + + // the CM event filtered out + await().pollDelay(Duration.ofMillis(POLL_DELAY)) + .untilAsserted(() -> assertThat(operator.getReconcilerOfType(FilterTestReconciler.class) + .getNumberOfExecutions()).isEqualTo(3)); + } + + + FilterTestCustomResource createResource() { + FilterTestCustomResource resource = new FilterTestCustomResource(); + resource.setMetadata(new ObjectMetaBuilder() + .withName(RESOURCE_NAME) + .build()); + resource.setSpec(new FilterTestResourceSpec()); + resource.getSpec().setValue("value1"); + return resource; + } + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/filter/FilterTestCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/filter/FilterTestCustomResource.java new file mode 100644 index 0000000000..3314861ee5 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/filter/FilterTestCustomResource.java @@ -0,0 +1,19 @@ +package io.javaoperatorsdk.operator.sample.filter; + +import io.fabric8.kubernetes.api.model.Namespaced; +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.ShortNames; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("sample.javaoperatorsdk") +@Version("v1") +@ShortNames("ftc") +public class FilterTestCustomResource + extends CustomResource + implements Namespaced { + + public String getConfigMapName(int id) { + return "configmap" + id; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/filter/FilterTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/filter/FilterTestReconciler.java new file mode 100644 index 0000000000..7189b10c7f --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/filter/FilterTestReconciler.java @@ -0,0 +1,77 @@ +package io.javaoperatorsdk.operator.sample.filter; + +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.junit.KubernetesClientAware; +import io.javaoperatorsdk.operator.processing.event.source.EventSource; +import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; + +@ControllerConfiguration(onUpdateFilter = UpdateFilter.class) +public class FilterTestReconciler + implements Reconciler, + EventSourceInitializer, + KubernetesClientAware { + + public static final String CONFIG_MAP_FILTER_VALUE = "config_map_skip_this"; + public static final String CUSTOM_RESOURCE_FILTER_VALUE = "custom_resource_skip_this"; + + public static final String CM_VALUE_KEY = "value"; + private final AtomicInteger numberOfExecutions = new AtomicInteger(0); + private KubernetesClient client; + + @Override + public UpdateControl reconcile( + FilterTestCustomResource resource, + Context context) { + numberOfExecutions.addAndGet(1); + client.configMaps().inNamespace(resource.getMetadata().getNamespace()) + .createOrReplace(createConfigMap(resource)); + return UpdateControl.noUpdate(); + } + + private ConfigMap createConfigMap(FilterTestCustomResource resource) { + ConfigMap configMap = new ConfigMap(); + configMap.setMetadata(new ObjectMetaBuilder() + .withName(resource.getMetadata().getName()) + .withNamespace(resource.getMetadata().getNamespace()) + .build()); + configMap.addOwnerReference(resource); + configMap.setData(Map.of(CM_VALUE_KEY, resource.getSpec().getValue())); + return configMap; + } + + + public int getNumberOfExecutions() { + return numberOfExecutions.get(); + } + + @Override + public Map prepareEventSources( + EventSourceContext context) { + + InformerEventSource configMapES = + new InformerEventSource<>(InformerConfiguration + .from(ConfigMap.class, context) + .withOnUpdateFilter((newCM, oldCM) -> !newCM.getData().get(CM_VALUE_KEY) + .equals(CONFIG_MAP_FILTER_VALUE)) + .build(), context); + + return EventSourceInitializer.nameEventSources(configMapES); + } + + @Override + public void setKubernetesClient(KubernetesClient kubernetesClient) { + this.client = kubernetesClient; + } + + @Override + public KubernetesClient getKubernetesClient() { + return client; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/filter/FilterTestResourceSpec.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/filter/FilterTestResourceSpec.java new file mode 100644 index 0000000000..044b0ea883 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/filter/FilterTestResourceSpec.java @@ -0,0 +1,15 @@ +package io.javaoperatorsdk.operator.sample.filter; + +public class FilterTestResourceSpec { + + private String value; + + public String getValue() { + return value; + } + + public FilterTestResourceSpec setValue(String value) { + this.value = value; + return this; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/filter/FilterTestResourceStatus.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/filter/FilterTestResourceStatus.java new file mode 100644 index 0000000000..cf0d24aa2c --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/filter/FilterTestResourceStatus.java @@ -0,0 +1,5 @@ +package io.javaoperatorsdk.operator.sample.filter; + +public class FilterTestResourceStatus { + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/filter/UpdateFilter.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/filter/UpdateFilter.java new file mode 100644 index 0000000000..6b8a91f2c7 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/filter/UpdateFilter.java @@ -0,0 +1,13 @@ +package io.javaoperatorsdk.operator.sample.filter; + +import java.util.function.BiPredicate; + +import static io.javaoperatorsdk.operator.sample.filter.FilterTestReconciler.CUSTOM_RESOURCE_FILTER_VALUE; + +public class UpdateFilter + implements BiPredicate { + @Override + public boolean test(FilterTestCustomResource resource, FilterTestCustomResource oldResource) { + return !resource.getSpec().getValue().equals(CUSTOM_RESOURCE_FILTER_VALUE); + } +} From adb7888658c318c618288eb8240afc1bf8a2f77f Mon Sep 17 00:00:00 2001 From: csviri Date: Tue, 21 Jun 2022 11:32:34 +0200 Subject: [PATCH 09/33] unit test --- .../ControllerResourceEventSourceTest.java | 51 +++++++++++++++---- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java index aa1b9aa23b..4005744024 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java @@ -2,12 +2,16 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.Set; +import java.util.function.BiPredicate; +import java.util.function.Predicate; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import io.javaoperatorsdk.operator.MockKubernetesClient; import io.javaoperatorsdk.operator.TestUtils; +import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; import io.javaoperatorsdk.operator.api.config.DefaultControllerConfiguration; import io.javaoperatorsdk.operator.processing.Controller; import io.javaoperatorsdk.operator.processing.event.EventHandler; @@ -16,10 +20,7 @@ import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.*; class ControllerResourceEventSourceTest extends AbstractEventSourceTestBase, EventHandler> { @@ -90,7 +91,7 @@ void handlesAllEventIfNotGenerationAware() { } @Test - public void eventWithNoGenerationProcessedIfNoFinalizer() { + void eventWithNoGenerationProcessedIfNoFinalizer() { TestCustomResource customResource1 = TestUtils.testCustomResource(); source.eventReceived(ResourceAction.UPDATED, customResource1, customResource1); @@ -99,7 +100,7 @@ public void eventWithNoGenerationProcessedIfNoFinalizer() { } @Test - public void callsBroadcastsOnResourceEvents() { + void callsBroadcastsOnResourceEvents() { TestCustomResource customResource1 = TestUtils.testCustomResource(); source.eventReceived(ResourceAction.UPDATED, customResource1, customResource1); @@ -109,14 +110,45 @@ public void callsBroadcastsOnResourceEvents() { eq(customResource1)); } + @Test + void filtersOutEventsOnAddAndUpdate() { + TestCustomResource cr = TestUtils.testCustomResource(); + + Predicate onAddPredicate = (res) -> false; + BiPredicate onUpdatePredicate = (res, res2) -> false; + source = + new ControllerResourceEventSource<>(new TestController(onAddPredicate, onUpdatePredicate)); + setUpSource(source); + + source.eventReceived(ResourceAction.ADDED, cr, null); + source.eventReceived(ResourceAction.UPDATED, cr, cr); + + verify(eventHandler, never()).handleEvent(any()); + } + + private ControllerConfiguration mockConfiguration() { + var mockConfig = mock(ControllerConfiguration.class); + when(mockConfig.isGenerationAware()).thenReturn(true); + when(mockConfig.getFinalizerName()).thenReturn(FINALIZER); + when(mockConfig.getResourceClass()).thenReturn(TestCustomResource.class); + when(mockConfig.getNamespaces()).thenReturn(Set.of("namespace")); + return mockConfig; + } + @SuppressWarnings("unchecked") private static class TestController extends Controller { private final EventSourceManager eventSourceManager = mock(EventSourceManager.class); + public TestController(Predicate onAddFilter, + BiPredicate onUpdateFilter) { + super(null, new TestConfiguration(true, onAddFilter, onUpdateFilter), + MockKubernetesClient.client(TestCustomResource.class)); + } + public TestController(boolean generationAware) { - super(null, new TestConfiguration(generationAware), + super(null, new TestConfiguration(generationAware, null, null), MockKubernetesClient.client(TestCustomResource.class)); } @@ -134,7 +166,8 @@ public boolean useFinalizer() { private static class TestConfiguration extends DefaultControllerConfiguration { - public TestConfiguration(boolean generationAware) { + public TestConfiguration(boolean generationAware, Predicate onAddFilter, + BiPredicate onUpdateFilter) { super( null, null, @@ -147,7 +180,7 @@ public TestConfiguration(boolean generationAware) { null, TestCustomResource.class, null, - null, null, null); + onAddFilter, onUpdateFilter, null); } } } From 5252b1215310e5cc89864673f415d04ad53ee762 Mon Sep 17 00:00:00 2001 From: csviri Date: Tue, 21 Jun 2022 11:34:53 +0200 Subject: [PATCH 10/33] wip --- .../controller/ControllerResourceEventSourceTest.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java index 4005744024..9b7f02076c 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java @@ -2,7 +2,6 @@ import java.time.LocalDateTime; import java.util.List; -import java.util.Set; import java.util.function.BiPredicate; import java.util.function.Predicate; @@ -11,7 +10,6 @@ import io.javaoperatorsdk.operator.MockKubernetesClient; import io.javaoperatorsdk.operator.TestUtils; -import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; import io.javaoperatorsdk.operator.api.config.DefaultControllerConfiguration; import io.javaoperatorsdk.operator.processing.Controller; import io.javaoperatorsdk.operator.processing.event.EventHandler; @@ -126,15 +124,6 @@ void filtersOutEventsOnAddAndUpdate() { verify(eventHandler, never()).handleEvent(any()); } - private ControllerConfiguration mockConfiguration() { - var mockConfig = mock(ControllerConfiguration.class); - when(mockConfig.isGenerationAware()).thenReturn(true); - when(mockConfig.getFinalizerName()).thenReturn(FINALIZER); - when(mockConfig.getResourceClass()).thenReturn(TestCustomResource.class); - when(mockConfig.getNamespaces()).thenReturn(Set.of("namespace")); - return mockConfig; - } - @SuppressWarnings("unchecked") private static class TestController extends Controller { From 9ebaea0d28881ffabf8b26f0207fde68362ef478 Mon Sep 17 00:00:00 2001 From: csviri Date: Tue, 21 Jun 2022 14:41:22 +0200 Subject: [PATCH 11/33] unit tests --- .../ExternalResourceCachingEventSource.java | 10 ++-- ...xternalResourceCachingEventSourceTest.java | 60 ++++++++++++++++++- 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java index c4f041c656..4fe5f78cea 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java @@ -68,10 +68,11 @@ protected synchronized void handleDelete(ResourceID primaryID, Set resou return; } var cachedValues = cache.get(primaryID); - var removedResources = resourceIDs.stream() - .flatMap(id -> Stream.ofNullable(cachedValues.remove(id))).collect(Collectors.toList()); + List removedResources = cachedValues == null ? Collections.emptyList() + : resourceIDs.stream() + .flatMap(id -> Stream.ofNullable(cachedValues.remove(id))).collect(Collectors.toList()); - if (cachedValues.isEmpty()) { + if (cachedValues != null && cachedValues.isEmpty()) { cache.remove(primaryID); } if (!removedResources.isEmpty() && deleteAcceptedByFilter(removedResources)) { @@ -158,8 +159,7 @@ private boolean acceptedByFiler(Map cachedResourceMap, return true; } - throw new IllegalStateException("Should not end up here. Cached map: " + cachedResourceMap + - ", new resource map: " + newResourcesMap); + return false; } @Override diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSourceTest.java index 72a578421c..9529ba2111 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSourceTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSourceTest.java @@ -120,10 +120,68 @@ void handlesDeleteAllFromMultipleResources() { assertThat(source.getSecondaryResources(primaryID1())).isEmpty(); } + @Test + void canFilterOnDeleteEvents() { + TestExternalCachingEventSource delFilteringEventSource = new TestExternalCachingEventSource(); + delFilteringEventSource.setOnDeleteFilter((res, b) -> false); + setUpSource(delFilteringEventSource); + // try without any resources added + source.handleDeletes(primaryID1(), Set.of(testResource1(), testResource2())); + source.handleResources(primaryID1(), Set.of(testResource1(), testResource2())); + // handling the add event + verify(eventHandler, times(1)).handleEvent(any()); + + source.handleDeletes(primaryID1(), Set.of(testResource1(), testResource2())); + + // no more invocation + verify(eventHandler, times(1)).handleEvent(any()); + } + + @Test + void filtersAddOnAddEvents() { + TestExternalCachingEventSource delFilteringEventSource = new TestExternalCachingEventSource(); + delFilteringEventSource.setOnAddFilter((res) -> false); + setUpSource(delFilteringEventSource); + + source.handleResources(primaryID1(), Set.of(testResource1())); + verify(eventHandler, times(0)).handleEvent(any()); + + source.handleResources(primaryID1(), Set.of(testResource1(), testResource2())); + verify(eventHandler, times(0)).handleEvent(any()); + } + + @Test + void filtersAddOnUpdateEvents() { + TestExternalCachingEventSource delFilteringEventSource = new TestExternalCachingEventSource(); + delFilteringEventSource.setOnUpdateFilter((res, res2) -> false); + setUpSource(delFilteringEventSource); + source.handleResources(primaryID1(), Set.of(testResource1())); + verify(eventHandler, times(1)).handleEvent(any()); + + var resource = testResource1(); + resource.setValue("changed value"); + source.handleResources(primaryID1(), Set.of(resource)); + + verify(eventHandler, times(1)).handleEvent(any()); + } + + @Test + void filtersImplicitDeleteEvents() { + TestExternalCachingEventSource delFilteringEventSource = new TestExternalCachingEventSource(); + delFilteringEventSource.setOnDeleteFilter((res, b) -> false); + setUpSource(delFilteringEventSource); + + source.handleResources(primaryID1(), Set.of(testResource1(), testResource2())); + verify(eventHandler, times(1)).handleEvent(any()); + + source.handleResources(primaryID1(), Set.of(testResource1())); + verify(eventHandler, times(1)).handleEvent(any()); + } + public static class TestExternalCachingEventSource extends ExternalResourceCachingEventSource { public TestExternalCachingEventSource() { - super(SampleExternalResource.class, (r) -> r.getName() + "#" + r.getValue()); + super(SampleExternalResource.class, (r) -> r.getName()); } } From 36c96921f22954d2b44b62cd010770370985ccf3 Mon Sep 17 00:00:00 2001 From: csviri Date: Tue, 21 Jun 2022 14:58:46 +0200 Subject: [PATCH 12/33] wip --- .../processing/event/source/ResourceEventSource.java | 9 +++++++++ .../source/controller/ControllerResourceEventSource.java | 7 +++++++ .../sample/dependent/SchemaDependentResource.java | 1 + 3 files changed, 17 insertions(+) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventSource.java index d57a662d82..5e762b98ea 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventSource.java @@ -2,6 +2,8 @@ import java.util.Optional; import java.util.Set; +import java.util.function.BiPredicate; +import java.util.function.Predicate; import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.processing.ResourceOwner; @@ -22,4 +24,11 @@ default Optional getSecondaryResource(P primary) { } Set getSecondaryResources(P primary); + + void setOnAddFilter(Predicate onAddFilter); + + void setOnUpdateFilter(BiPredicate onUpdateFilter); + + void setOnDeleteFilter(BiPredicate onDeleteFilter); + } 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 8a567fd44d..04b6378f09 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 @@ -2,6 +2,7 @@ import java.util.Optional; import java.util.Set; +import java.util.function.BiPredicate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -114,4 +115,10 @@ public Optional getSecondaryResource(T primary) { public Set getSecondaryResources(T primary) { throw new IllegalStateException("This method should not be called here. Primary: " + primary); } + + @Override + public void setOnDeleteFilter(BiPredicate onDeleteFilter) { + throw new IllegalStateException( + "onAddFilter is not supported for controller resource event source"); + } } diff --git a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/dependent/SchemaDependentResource.java b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/dependent/SchemaDependentResource.java index 5bef4adb04..48e3f37abe 100644 --- a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/dependent/SchemaDependentResource.java +++ b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/dependent/SchemaDependentResource.java @@ -29,6 +29,7 @@ public class SchemaDependentResource implements EventSourceProvider, DependentResourceConfigurator, Creator, Deleter { + public static final String NAME = "schema"; private static final Logger log = LoggerFactory.getLogger(SchemaDependentResource.class); From 0241210d980e6c427b72d28d89064246b21a2fb7 Mon Sep 17 00:00:00 2001 From: csviri Date: Tue, 21 Jun 2022 15:56:40 +0200 Subject: [PATCH 13/33] filters for dependent resources --- .../AnnotationControllerConfiguration.java | 24 ++++++++++++- ...actEventSourceHolderDependentResource.java | 29 +++++++++++++++- .../kubernetes/KubernetesDependent.java | 12 +++++++ .../KubernetesDependentResource.java | 6 ++++ .../KubernetesDependentResourceConfig.java | 34 +++++++++++++++++-- .../source/filter/VoidOnDeleteFilter.java | 12 +++++++ 6 files changed, 112 insertions(+), 5 deletions(-) create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/filter/VoidOnDeleteFilter.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java index fea30c226d..69d897c0e2 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java @@ -251,6 +251,7 @@ private String getName(Dependent dependent, Class d } private Object createKubernetesResourceConfig(Class dependentType) { + Object config; final var kubeDependent = dependentType.getAnnotation(KubernetesDependent.class); @@ -267,9 +268,30 @@ private Object createKubernetesResourceConfig(Class final var fromAnnotation = kubeDependent.labelSelector(); labelSelector = Constants.NO_VALUE_SET.equals(fromAnnotation) ? null : fromAnnotation; } + Predicate onAddFilter = null; + BiPredicate onUpdateFilter = null; + BiPredicate onDeleteFilter = null; + if (kubeDependent != null) { + try { + onAddFilter = kubeDependent.onAddFilter() != VoidOnAddFilter.class + ? kubeDependent.onAddFilter().getConstructor().newInstance() + : null; + onUpdateFilter = kubeDependent.onAddFilter() != VoidOnAddFilter.class + ? kubeDependent.onUpdateFilter().getConstructor().newInstance() + : null; + onDeleteFilter = kubeDependent.onAddFilter() != VoidOnAddFilter.class + ? kubeDependent.onDeleteFilter().getConstructor().newInstance() + : null; + } catch (InstantiationException | IllegalAccessException | InvocationTargetException + | NoSuchMethodException e) { + throw new IllegalStateException(e); + } + } config = - new KubernetesDependentResourceConfig(namespaces, labelSelector, configuredNS); + new KubernetesDependentResourceConfig(namespaces, labelSelector, configuredNS, onAddFilter, + onUpdateFilter, onDeleteFilter); + return config; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractEventSourceHolderDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractEventSourceHolderDependentResource.java index 91d11309f8..4ecc9f2404 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractEventSourceHolderDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractEventSourceHolderDependentResource.java @@ -1,5 +1,8 @@ package io.javaoperatorsdk.operator.processing.dependent; +import java.util.function.BiPredicate; +import java.util.function.Predicate; + import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; import io.javaoperatorsdk.operator.api.reconciler.Ignore; @@ -16,13 +19,19 @@ public abstract class AbstractEventSourceHolderDependentResource onAddFilter; + protected BiPredicate onUpdateFilter; + protected BiPredicate onDeleteFilter; + public EventSource initEventSource(EventSourceContext

context) { // some sub-classes (e.g. KubernetesDependentResource) can have their event source created // before this method is called in the managed case, so only create the event source if it - // hasn't already been set + // hasn't already been set. + // The filters are applied automatically only if event source is created automatically. if (eventSource == null) { eventSource = createEventSource(context); + applyFilters(); } isCacheFillerEventSource = eventSource instanceof RecentOperationCacheFiller; @@ -35,6 +44,12 @@ protected void setEventSource(T eventSource) { this.eventSource = eventSource; } + protected void applyFilters() { + this.eventSource.setOnAddFilter(onAddFilter); + this.eventSource.setOnUpdateFilter(onUpdateFilter); + this.eventSource.setOnDeleteFilter(onDeleteFilter); + } + protected T eventSource() { return eventSource; } @@ -55,4 +70,16 @@ protected void onUpdated(ResourceID primaryResourceId, R updated, R actual) { private RecentOperationCacheFiller recentOperationCacheFiller() { return (RecentOperationCacheFiller) eventSource; } + + public void setOnAddFilter(Predicate onAddFilter) { + this.onAddFilter = onAddFilter; + } + + public void setOnUpdateFilter(BiPredicate onUpdateFilter) { + this.onUpdateFilter = onUpdateFilter; + } + + public void setOnDeleteFilter(BiPredicate onDeleteFilter) { + this.onDeleteFilter = onDeleteFilter; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependent.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependent.java index fc33975532..494d1d5f4d 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependent.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependent.java @@ -4,8 +4,14 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.function.BiPredicate; +import java.util.function.Predicate; +import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.Constants; +import io.javaoperatorsdk.operator.processing.event.source.filter.VoidOnAddFilter; +import io.javaoperatorsdk.operator.processing.event.source.filter.VoidOnDeleteFilter; +import io.javaoperatorsdk.operator.processing.event.source.filter.VoidOnUpdateFilter; import static io.javaoperatorsdk.operator.api.reconciler.Constants.NO_VALUE_SET; @@ -32,4 +38,10 @@ * @return the label selector */ String labelSelector() default NO_VALUE_SET; + + Class> onAddFilter() default VoidOnAddFilter.class; + + Class> onUpdateFilter() default VoidOnUpdateFilter.class; + + Class> onDeleteFilter() default VoidOnDeleteFilter.class; } 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 3f8e4f7fc6..a61c898359 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 @@ -159,8 +159,14 @@ protected NonNamespaceOperation, Resource> prepa } @Override + @SuppressWarnings("unchecked") protected InformerEventSource createEventSource(EventSourceContext

context) { if (kubernetesDependentResourceConfig != null) { + // sets the filters for the dependent resource, which are applied by parent class + setOnAddFilter(kubernetesDependentResourceConfig.onAddFilter()); + setOnUpdateFilter(kubernetesDependentResourceConfig.onUpdateFilter()); + setOnDeleteFilter(kubernetesDependentResourceConfig.onDeleteFilter()); + configureWith(kubernetesDependentResourceConfig.labelSelector(), kubernetesDependentResourceConfig.namespaces(), !kubernetesDependentResourceConfig.wereNamespacesConfigured(), context); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfig.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfig.java index a28668055a..eb05843586 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfig.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfig.java @@ -1,6 +1,8 @@ package io.javaoperatorsdk.operator.processing.dependent.kubernetes; import java.util.Set; +import java.util.function.BiPredicate; +import java.util.function.Predicate; import io.javaoperatorsdk.operator.api.reconciler.Constants; @@ -10,20 +12,32 @@ public class KubernetesDependentResourceConfig { private Set namespaces = Constants.SAME_AS_CONTROLLER_NAMESPACES_SET; private String labelSelector = NO_VALUE_SET; - private boolean namespacesWereConfigured = false; + @SuppressWarnings("rawtypes") + protected Predicate onAddFilter; + @SuppressWarnings("rawtypes") + protected BiPredicate onUpdateFilter; + @SuppressWarnings("rawtypes") + protected BiPredicate onDeleteFilter; + public KubernetesDependentResourceConfig() {} + @SuppressWarnings("rawtypes") public KubernetesDependentResourceConfig(Set namespaces, String labelSelector, - boolean configuredNS) { + boolean configuredNS, Predicate onAddFilter, + BiPredicate onUpdateFilter, + BiPredicate onDeleteFilter) { this.namespaces = namespaces; this.labelSelector = labelSelector; this.namespacesWereConfigured = configuredNS; + this.onAddFilter = onAddFilter; + this.onUpdateFilter = onUpdateFilter; + this.onDeleteFilter = onDeleteFilter; } public KubernetesDependentResourceConfig(Set namespaces, String labelSelector) { - this(namespaces, labelSelector, true); + this(namespaces, labelSelector, true, null, null, null); } public KubernetesDependentResourceConfig setNamespaces(Set namespaces) { @@ -49,4 +63,18 @@ public boolean wereNamespacesConfigured() { return namespacesWereConfigured; } + @SuppressWarnings("rawtypes") + public Predicate onAddFilter() { + return onAddFilter; + } + + @SuppressWarnings("rawtypes") + public BiPredicate onUpdateFilter() { + return onUpdateFilter; + } + + @SuppressWarnings("rawtypes") + public BiPredicate onDeleteFilter() { + return onDeleteFilter; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/filter/VoidOnDeleteFilter.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/filter/VoidOnDeleteFilter.java new file mode 100644 index 0000000000..7dba4566b4 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/filter/VoidOnDeleteFilter.java @@ -0,0 +1,12 @@ +package io.javaoperatorsdk.operator.processing.event.source.filter; + +import java.util.function.BiPredicate; + +import io.fabric8.kubernetes.api.model.HasMetadata; + +public class VoidOnDeleteFilter implements BiPredicate { + @Override + public boolean test(HasMetadata hasMetadata, Boolean aBoolean) { + return true; + } +} From bcf67add4af4e760eb55dc9ae314a2cdae5fdad8 Mon Sep 17 00:00:00 2001 From: csviri Date: Wed, 22 Jun 2022 08:54:19 +0200 Subject: [PATCH 14/33] IT for dependent --- .../AnnotationControllerConfiguration.java | 5 +- .../reconciler/ControllerConfiguration.java | 1 - .../source/informer/InformerEventSource.java | 10 ++-- .../operator/DependentFilterIT.java | 60 +++++++++++++++++++ .../DependentFilterTestCustomResource.java | 19 ++++++ .../DependentFilterTestReconciler.java | 29 +++++++++ .../DependentFilterTestResourceSpec.java | 15 +++++ .../DependentFilterTestResourceStatus.java | 5 ++ .../FilteredDependentConfigMap.java | 32 ++++++++++ .../sample/dependentfilter/UpdateFilter.java | 16 +++++ 10 files changed, 185 insertions(+), 7 deletions(-) create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/DependentFilterIT.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentfilter/DependentFilterTestCustomResource.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentfilter/DependentFilterTestReconciler.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentfilter/DependentFilterTestResourceSpec.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentfilter/DependentFilterTestResourceStatus.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentfilter/FilteredDependentConfigMap.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentfilter/UpdateFilter.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java index 69d897c0e2..597ab4307c 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java @@ -30,6 +30,7 @@ import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter; import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilters; import io.javaoperatorsdk.operator.processing.event.source.filter.VoidOnAddFilter; +import io.javaoperatorsdk.operator.processing.event.source.filter.VoidOnDeleteFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.VoidOnUpdateFilter; import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_NAMESPACES_SET; @@ -276,10 +277,10 @@ private Object createKubernetesResourceConfig(Class onAddFilter = kubeDependent.onAddFilter() != VoidOnAddFilter.class ? kubeDependent.onAddFilter().getConstructor().newInstance() : null; - onUpdateFilter = kubeDependent.onAddFilter() != VoidOnAddFilter.class + onUpdateFilter = kubeDependent.onUpdateFilter() != VoidOnUpdateFilter.class ? kubeDependent.onUpdateFilter().getConstructor().newInstance() : null; - onDeleteFilter = kubeDependent.onAddFilter() != VoidOnAddFilter.class + onDeleteFilter = kubeDependent.onDeleteFilter() != VoidOnDeleteFilter.class ? kubeDependent.onDeleteFilter().getConstructor().newInstance() : null; } catch (InstantiationException | IllegalAccessException | InvocationTargetException 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 1b1be4ec1f..c636eff35b 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 @@ -68,7 +68,6 @@ @Deprecated Class[] eventFilters() default {}; - // todo document missing delete filter Class> onAddFilter() default VoidOnAddFilter.class; Class> onUpdateFilter() default VoidOnUpdateFilter.class; 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 5e676cbf95..7ba9d5fa95 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 @@ -157,12 +157,14 @@ private synchronized void onAddOrUpdate(Operation operation, R newObject, R oldO superOnOp.run(); } else { superOnOp.run(); - log.debug( - "Propagating event for {}, resource with same version not result of a reconciliation. Resource ID: {}", - operation, - resourceID); if (eventAcceptedByFilter(operation, newObject, oldObject)) { + log.debug( + "Propagating event for {}, resource with same version not result of a reconciliation. Resource ID: {}", + operation, + resourceID); propagateEvent(newObject); + } else { + log.debug("Event filtered out for operation: {}, resourceID: {}", operation, resourceID); } } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/DependentFilterIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/DependentFilterIT.java new file mode 100644 index 0000000000..a900d0645d --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/DependentFilterIT.java @@ -0,0 +1,60 @@ +package io.javaoperatorsdk.operator; + +import java.time.Duration; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; +import io.javaoperatorsdk.operator.sample.dependentfilter.DependentFilterTestCustomResource; +import io.javaoperatorsdk.operator.sample.dependentfilter.DependentFilterTestReconciler; +import io.javaoperatorsdk.operator.sample.dependentfilter.DependentFilterTestResourceSpec; + +import static io.javaoperatorsdk.operator.sample.dependentfilter.DependentFilterTestReconciler.CM_VALUE_KEY; +import static io.javaoperatorsdk.operator.sample.dependentfilter.DependentFilterTestReconciler.CONFIG_MAP_FILTER_VALUE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +class DependentFilterIT { + + public static final String RESOURCE_NAME = "test1"; + + @RegisterExtension + LocallyRunOperatorExtension operator = + LocallyRunOperatorExtension.builder().withReconciler(DependentFilterTestReconciler.class) + .build(); + + @Test + void filtersUpdateOnConfigMap() { + var resource = createResource(); + operator.create(DependentFilterTestCustomResource.class, resource); + + await().pollDelay(Duration.ofMillis(150)).untilAsserted(() -> { + assertThat(operator.getReconcilerOfType(DependentFilterTestReconciler.class) + .getNumberOfExecutions()).isEqualTo(1); + }); + + var configMap = operator.get(ConfigMap.class, RESOURCE_NAME); + configMap.setData(Map.of(CM_VALUE_KEY, CONFIG_MAP_FILTER_VALUE)); + operator.replace(ConfigMap.class, configMap); + + await().pollDelay(Duration.ofMillis(150)).untilAsserted(() -> { + assertThat(operator.getReconcilerOfType(DependentFilterTestReconciler.class) + .getNumberOfExecutions()).isEqualTo(1); + }); + } + + DependentFilterTestCustomResource createResource() { + DependentFilterTestCustomResource resource = new DependentFilterTestCustomResource(); + resource.setMetadata(new ObjectMetaBuilder() + .withName(RESOURCE_NAME) + .build()); + resource.setSpec(new DependentFilterTestResourceSpec()); + resource.getSpec().setValue("value1"); + return resource; + } + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentfilter/DependentFilterTestCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentfilter/DependentFilterTestCustomResource.java new file mode 100644 index 0000000000..0930c29774 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentfilter/DependentFilterTestCustomResource.java @@ -0,0 +1,19 @@ +package io.javaoperatorsdk.operator.sample.dependentfilter; + +import io.fabric8.kubernetes.api.model.Namespaced; +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.ShortNames; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("sample.javaoperatorsdk") +@Version("v1") +@ShortNames("dft") +public class DependentFilterTestCustomResource + extends CustomResource + implements Namespaced { + + public String getConfigMapName(int id) { + return "configmap" + id; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentfilter/DependentFilterTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentfilter/DependentFilterTestReconciler.java new file mode 100644 index 0000000000..114491d9b9 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentfilter/DependentFilterTestReconciler.java @@ -0,0 +1,29 @@ +package io.javaoperatorsdk.operator.sample.dependentfilter; + +import java.util.concurrent.atomic.AtomicInteger; + +import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; + +@ControllerConfiguration(onUpdateFilter = UpdateFilter.class, + dependents = {@Dependent(type = FilteredDependentConfigMap.class)}) +public class DependentFilterTestReconciler + implements Reconciler { + + public static final String CONFIG_MAP_FILTER_VALUE = "config_map_skip_this"; + public static final String CM_VALUE_KEY = "value"; + + private final AtomicInteger numberOfExecutions = new AtomicInteger(0); + + @Override + public UpdateControl reconcile( + DependentFilterTestCustomResource resource, + Context context) { + numberOfExecutions.addAndGet(1); + return UpdateControl.noUpdate(); + } + + public int getNumberOfExecutions() { + return numberOfExecutions.get(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentfilter/DependentFilterTestResourceSpec.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentfilter/DependentFilterTestResourceSpec.java new file mode 100644 index 0000000000..cf6b02e936 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentfilter/DependentFilterTestResourceSpec.java @@ -0,0 +1,15 @@ +package io.javaoperatorsdk.operator.sample.dependentfilter; + +public class DependentFilterTestResourceSpec { + + private String value; + + public String getValue() { + return value; + } + + public DependentFilterTestResourceSpec setValue(String value) { + this.value = value; + return this; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentfilter/DependentFilterTestResourceStatus.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentfilter/DependentFilterTestResourceStatus.java new file mode 100644 index 0000000000..99e8d54514 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentfilter/DependentFilterTestResourceStatus.java @@ -0,0 +1,5 @@ +package io.javaoperatorsdk.operator.sample.dependentfilter; + +public class DependentFilterTestResourceStatus { + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentfilter/FilteredDependentConfigMap.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentfilter/FilteredDependentConfigMap.java new file mode 100644 index 0000000000..ee4bd4cde7 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentfilter/FilteredDependentConfigMap.java @@ -0,0 +1,32 @@ +package io.javaoperatorsdk.operator.sample.dependentfilter; + +import java.util.Map; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; + +import static io.javaoperatorsdk.operator.sample.dependentfilter.DependentFilterTestReconciler.CM_VALUE_KEY; + +@KubernetesDependent(onUpdateFilter = UpdateFilter.class) +public class FilteredDependentConfigMap + extends CRUDKubernetesDependentResource { + + public FilteredDependentConfigMap() { + super(ConfigMap.class); + } + + @Override + protected ConfigMap desired(DependentFilterTestCustomResource primary, + Context context) { + ConfigMap configMap = new ConfigMap(); + configMap.setMetadata(new ObjectMetaBuilder() + .withName(primary.getMetadata().getName()) + .withNamespace(primary.getMetadata().getNamespace()) + .build()); + configMap.setData(Map.of(CM_VALUE_KEY, primary.getSpec().getValue())); + return configMap; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentfilter/UpdateFilter.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentfilter/UpdateFilter.java new file mode 100644 index 0000000000..60dc00ce8e --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentfilter/UpdateFilter.java @@ -0,0 +1,16 @@ +package io.javaoperatorsdk.operator.sample.dependentfilter; + +import java.util.function.BiPredicate; + +import io.fabric8.kubernetes.api.model.ConfigMap; + +import static io.javaoperatorsdk.operator.sample.dependentfilter.DependentFilterTestReconciler.CM_VALUE_KEY; +import static io.javaoperatorsdk.operator.sample.dependentfilter.DependentFilterTestReconciler.CONFIG_MAP_FILTER_VALUE; + +public class UpdateFilter + implements BiPredicate { + @Override + public boolean test(ConfigMap resource, ConfigMap oldResource) { + return !resource.getData().get(CM_VALUE_KEY).equals(CONFIG_MAP_FILTER_VALUE); + } +} From 5ba87b6e6dd4aeec82d905999cfd3039643ac27a Mon Sep 17 00:00:00 2001 From: csviri Date: Wed, 22 Jun 2022 10:11:06 +0200 Subject: [PATCH 15/33] javadocs --- .../operator/api/reconciler/ControllerConfiguration.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 c636eff35b..f6ba0b4660 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 @@ -68,8 +68,14 @@ @Deprecated Class[] eventFilters() default {}; + /** + * Filter of onAdd events of resources. (Note that onDelete is missing, delete events don't + * trigger reconciliation - since if finalizer used there the event is an update that triggers the + * cleanup. If not used the resources are cleaned up by garbage collector.) + **/ Class> onAddFilter() default VoidOnAddFilter.class; + /** Filter of onUpdate events of resources. */ Class> onUpdateFilter() default VoidOnUpdateFilter.class; /** From c8b02ccb8f9b33eecac42cf3dcb146ec9547b258 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Thu, 23 Jun 2022 22:14:22 +0200 Subject: [PATCH 16/33] refactor: clean-up InformerEventSource constructors --- .../api/config/ControllerConfigurationOverrider.java | 12 +++++++----- .../kubernetes/KubernetesDependentResource.java | 3 +-- .../event/source/informer/InformerEventSource.java | 9 ++++++--- .../source/informer/InformerEventSourceTest.java | 11 ++++++----- .../CreateUpdateEventFilterTestReconciler.java | 3 ++- 5 files changed, 22 insertions(+), 16 deletions(-) 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 fe3bf26e1c..5ec8a610d7 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java @@ -1,7 +1,11 @@ package io.javaoperatorsdk.operator.api.config; import java.time.Duration; -import java.util.*; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Optional; +import java.util.Set; import java.util.function.BiPredicate; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -125,14 +129,12 @@ public ControllerConfigurationOverrider withReconciliationMaxInterval( return this; } - public ControllerConfigurationOverrider withOnAddFilter( - Predicate onAddFilter) { + public ControllerConfigurationOverrider withOnAddFilter(Predicate onAddFilter) { this.onAddFilter = onAddFilter; return this; } - public ControllerConfigurationOverrider withOnUpdateFilter( - BiPredicate onUpdateFilter) { + public ControllerConfigurationOverrider withOnUpdateFilter(BiPredicate onUpdateFilter) { this.onUpdateFilter = onUpdateFilter; return this; } 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 a61c898359..58edbaeb20 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 @@ -60,7 +60,6 @@ public void configureWith(KubernetesDependentResourceConfig config) { this.kubernetesDependentResourceConfig = config; } - @SuppressWarnings("unchecked") private void configureWith(String labelSelector, Set namespaces, boolean inheritNamespacesOnChange, EventSourceContext

context) { @@ -74,7 +73,7 @@ private void configureWith(String labelSelector, Set namespaces, .withNamespaces(namespaces, inheritNamespacesOnChange) .build(); - configureWith(new InformerEventSource<>(ic, client)); + configureWith(new InformerEventSource<>(ic, context)); } @SuppressWarnings("unchecked") 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 7ba9d5fa95..6bef2587b7 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 @@ -8,7 +8,9 @@ import org.slf4j.LoggerFactory; import io.fabric8.kubernetes.api.model.HasMetadata; -import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.api.model.KubernetesResourceList; +import io.fabric8.kubernetes.client.dsl.MixedOperation; +import io.fabric8.kubernetes.client.dsl.Resource; import io.fabric8.kubernetes.client.informers.ResourceEventHandler; import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; @@ -86,8 +88,9 @@ public InformerEventSource( this(configuration, context.getClient()); } - public InformerEventSource(InformerConfiguration configuration, KubernetesClient client) { - super(client.resources(configuration.getResourceClass()), configuration); + public InformerEventSource(InformerConfiguration configuration, + MixedOperation, Resource> client) { + super(client, configuration); this.configuration = configuration; primaryToSecondaryIndex = new PrimaryToSecondaryIndex<>(configuration.getSecondaryToPrimaryMapper()); 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 b30dc32f8f..85cfd0c181 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 @@ -8,7 +8,6 @@ import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.apps.Deployment; -import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable; import io.fabric8.kubernetes.client.dsl.FilterWatchListMultiDeletable; import io.fabric8.kubernetes.client.dsl.MixedOperation; @@ -22,7 +21,11 @@ import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_NAMESPACES_SET; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @SuppressWarnings({"rawtypes", "unchecked"}) class InformerEventSourceTest { @@ -32,7 +35,6 @@ class InformerEventSourceTest { private static final String NEXT_RESOURCE_VERSION = "2"; private InformerEventSource informerEventSource; - private final KubernetesClient clientMock = mock(KubernetesClient.class); private final TemporaryResourceCache temporaryResourceCacheMock = mock(TemporaryResourceCache.class); private final EventHandler eventHandlerMock = mock(EventHandler.class); @@ -47,7 +49,6 @@ class InformerEventSourceTest { @BeforeEach void setup() { - when(clientMock.resources(any())).thenReturn(crClientMock); when(crClientMock.inAnyNamespace()).thenReturn(specificResourceClientMock); when(specificResourceClientMock.withLabelSelector((String) null)) .thenReturn(labeledResourceClientMock); @@ -60,7 +61,7 @@ void setup() { .thenReturn(mock(SecondaryToPrimaryMapper.class)); when(informerConfiguration.getResourceClass()).thenReturn(Deployment.class); - informerEventSource = new InformerEventSource<>(informerConfiguration, clientMock); + informerEventSource = new InformerEventSource<>(informerConfiguration, crClientMock); informerEventSource.setTemporalResourceCache(temporaryResourceCacheMock); informerEventSource.setEventHandler(eventHandlerMock); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/createupdateeventfilter/CreateUpdateEventFilterTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/createupdateeventfilter/CreateUpdateEventFilterTestReconciler.java index 256d861791..8779c30277 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/createupdateeventfilter/CreateUpdateEventFilterTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/createupdateeventfilter/CreateUpdateEventFilterTestReconciler.java @@ -105,7 +105,8 @@ public Map prepareEventSources( InformerConfiguration.from(ConfigMap.class) .withLabelSelector("integrationtest = " + this.getClass().getSimpleName()) .build(); - informerEventSource = new InformerEventSource<>(informerConfiguration, client); + informerEventSource = + new InformerEventSource<>(informerConfiguration, client.resources(ConfigMap.class)); return EventSourceInitializer.nameEventSources(informerEventSource); } From c60d934c0637ac6286ce493258059a141ee49eb5 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Fri, 24 Jun 2022 09:30:20 +0200 Subject: [PATCH 17/33] refactor: unify how filters are instantiated --- .../AnnotationControllerConfiguration.java | 83 ++++++++++--------- .../KubernetesDependentResourceConfig.java | 6 +- 2 files changed, 46 insertions(+), 43 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java index 597ab4307c..3309e0f5b5 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java @@ -152,35 +152,43 @@ public Optional reconciliationMaxInterval() { @Override @SuppressWarnings("unchecked") public Optional> onAddFilter() { - var onAddFilter = annotation.onAddFilter(); - if (onAddFilter.equals(VoidOnAddFilter.class)) { + return (Optional>) createFilter(annotation.onAddFilter(), FilterType.onAdd, + annotation.getClass().getSimpleName()); + } + + private enum FilterType { + onAdd(VoidOnAddFilter.class), onUpdate(VoidOnUpdateFilter.class), onDelete( + VoidOnDeleteFilter.class); + + final Class defaultValue; + + FilterType(Class defaultValue) { + this.defaultValue = defaultValue; + } + } + + private Optional createFilter(Class filter, FilterType filterType, String origin) { + if (filterType.defaultValue.equals(filter)) { return Optional.empty(); } else { try { - var instance = (Predicate) onAddFilter.getDeclaredConstructor().newInstance(); + var instance = (T) filter.getDeclaredConstructor().newInstance(); return Optional.of(instance); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { - throw new OperatorException(e); + throw new OperatorException( + "Couldn't create " + filterType + " filter from " + filter.getName() + " class in " + + origin + " for reconciler " + getName(), + e); } } } - @Override @SuppressWarnings("unchecked") + @Override public Optional> onUpdateFilter() { - var onUpdateFilter = annotation.onUpdateFilter(); - if (onUpdateFilter.equals(VoidOnUpdateFilter.class)) { - return Optional.empty(); - } else { - try { - var instance = (BiPredicate) onUpdateFilter.getDeclaredConstructor().newInstance(); - return Optional.of(instance); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException - | NoSuchMethodException e) { - throw new OperatorException(e); - } - } + return (Optional>) createFilter(annotation.onUpdateFilter(), + FilterType.onUpdate, annotation.getClass().getSimpleName()); } @SuppressWarnings({"rawtypes", "unchecked"}) @@ -258,37 +266,32 @@ private Object createKubernetesResourceConfig(Class var namespaces = getNamespaces(); var configuredNS = false; - if (kubeDependent != null && !Arrays.equals(KubernetesDependent.DEFAULT_NAMESPACES, - kubeDependent.namespaces())) { - namespaces = Set.of(kubeDependent.namespaces()); - configuredNS = true; - } - String labelSelector = null; - if (kubeDependent != null) { - final var fromAnnotation = kubeDependent.labelSelector(); - labelSelector = Constants.NO_VALUE_SET.equals(fromAnnotation) ? null : fromAnnotation; - } Predicate onAddFilter = null; BiPredicate onUpdateFilter = null; BiPredicate onDeleteFilter = null; if (kubeDependent != null) { - try { - onAddFilter = kubeDependent.onAddFilter() != VoidOnAddFilter.class - ? kubeDependent.onAddFilter().getConstructor().newInstance() - : null; - onUpdateFilter = kubeDependent.onUpdateFilter() != VoidOnUpdateFilter.class - ? kubeDependent.onUpdateFilter().getConstructor().newInstance() - : null; - onDeleteFilter = kubeDependent.onDeleteFilter() != VoidOnDeleteFilter.class - ? kubeDependent.onDeleteFilter().getConstructor().newInstance() - : null; - } catch (InstantiationException | IllegalAccessException | InvocationTargetException - | NoSuchMethodException e) { - throw new IllegalStateException(e); + if (!Arrays.equals(KubernetesDependent.DEFAULT_NAMESPACES, + kubeDependent.namespaces())) { + namespaces = Set.of(kubeDependent.namespaces()); + configuredNS = true; } + + final var fromAnnotation = kubeDependent.labelSelector(); + labelSelector = Constants.NO_VALUE_SET.equals(fromAnnotation) ? null : fromAnnotation; + + final var kubeDependentName = KubernetesDependent.class.getSimpleName(); + onAddFilter = createFilter(kubeDependent.onAddFilter(), FilterType.onAdd, kubeDependentName) + .orElse(null); + onUpdateFilter = + createFilter(kubeDependent.onUpdateFilter(), FilterType.onUpdate, kubeDependentName) + .orElse(null); + onDeleteFilter = + createFilter(kubeDependent.onDeleteFilter(), FilterType.onDelete, kubeDependentName) + .orElse(null); } + config = new KubernetesDependentResourceConfig(namespaces, labelSelector, configuredNS, onAddFilter, onUpdateFilter, onDeleteFilter); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfig.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfig.java index eb05843586..4a31d102bd 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfig.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfig.java @@ -15,11 +15,11 @@ public class KubernetesDependentResourceConfig { private boolean namespacesWereConfigured = false; @SuppressWarnings("rawtypes") - protected Predicate onAddFilter; + private Predicate onAddFilter; @SuppressWarnings("rawtypes") - protected BiPredicate onUpdateFilter; + private BiPredicate onUpdateFilter; @SuppressWarnings("rawtypes") - protected BiPredicate onDeleteFilter; + private BiPredicate onDeleteFilter; public KubernetesDependentResourceConfig() {} From 193c6cb16b02833b399cab2f479176276cfbcfb0 Mon Sep 17 00:00:00 2001 From: csviri Date: Fri, 24 Jun 2022 09:58:19 +0200 Subject: [PATCH 18/33] docsfix --- .../operator/api/reconciler/ControllerConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f6ba0b4660..6df87bb60d 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 @@ -56,7 +56,7 @@ String labelSelector() default Constants.NO_VALUE_SET; /** - * Use onAddFilter, onUpdateFilter, onDeleteFilter instead. + * Use onAddFilter, onUpdateFilter instead. * *

* Resource event filters only applies on events of the main custom resource. Not on events from From 876fabbeaa266122f112082868fe75e5931f426f Mon Sep 17 00:00:00 2001 From: csviri Date: Fri, 24 Jun 2022 10:06:14 +0200 Subject: [PATCH 19/33] fix renaming caused problem --- ...ndaryIndexTest.java => PrimaryToSecondaryIndexTest.java} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/{DefaultPrimaryToSecondaryIndexTest.java => PrimaryToSecondaryIndexTest.java} (94%) diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/DefaultPrimaryToSecondaryIndexTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/PrimaryToSecondaryIndexTest.java similarity index 94% rename from operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/DefaultPrimaryToSecondaryIndexTest.java rename to operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/PrimaryToSecondaryIndexTest.java index da2d1b7cf0..ca73b135a7 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/DefaultPrimaryToSecondaryIndexTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/PrimaryToSecondaryIndexTest.java @@ -15,12 +15,12 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -class DefaultPrimaryToSecondaryIndexTest { +class PrimaryToSecondaryIndexTest { private SecondaryToPrimaryMapper secondaryToPrimaryMapperMock = mock(SecondaryToPrimaryMapper.class); - private DefaultPrimaryToSecondaryIndex primaryToSecondaryIndex = - new DefaultPrimaryToSecondaryIndex<>(secondaryToPrimaryMapperMock); + private PrimaryToSecondaryIndex primaryToSecondaryIndex = + new PrimaryToSecondaryIndex<>(secondaryToPrimaryMapperMock); private ResourceID primaryID1 = new ResourceID("id1", "default"); private ResourceID primaryID2 = new ResourceID("id2", "default"); From 124a15d4ca6b74ef9de4b42169e56cdd9ea337ae Mon Sep 17 00:00:00 2001 From: csviri Date: Fri, 24 Jun 2022 10:11:24 +0200 Subject: [PATCH 20/33] deprecated annotation --- .../dependent/AbstractEventSourceHolderDependentResource.java | 4 +++- .../operator/processing/event/source/EventFilter.java | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractEventSourceHolderDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractEventSourceHolderDependentResource.java index 4ecc9f2404..1c75056b47 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractEventSourceHolderDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractEventSourceHolderDependentResource.java @@ -28,7 +28,9 @@ public EventSource initEventSource(EventSourceContext

context) { // some sub-classes (e.g. KubernetesDependentResource) can have their event source created // before this method is called in the managed case, so only create the event source if it // hasn't already been set. - // The filters are applied automatically only if event source is created automatically. + // The filters are applied automatically only if event source is created automatically. So if an + // event source + // is shared between dependent resources this does not override the existing filters. if (eventSource == null) { eventSource = createEventSource(context); applyFilters(); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/EventFilter.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/EventFilter.java index bd8cf0a894..df01449bed 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/EventFilter.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/EventFilter.java @@ -1,5 +1,6 @@ package io.javaoperatorsdk.operator.processing.event.source; +@Deprecated public interface EventFilter { default boolean acceptAdd(T newResource) { From afb0bb6a89f033a25d535d73f34307f52f42f7cf Mon Sep 17 00:00:00 2001 From: csviri Date: Fri, 24 Jun 2022 10:18:46 +0200 Subject: [PATCH 21/33] deprecations and remove unused class --- .../reconciler/ControllerConfiguration.java | 16 +++-- .../processing/event/source/EventFilter.java | 60 ------------------- .../controller/ResourceEventFilter.java | 1 + 3 files changed, 8 insertions(+), 69 deletions(-) delete mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/EventFilter.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java index 6df87bb60d..a3cf113805 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 @@ -56,22 +56,20 @@ String labelSelector() default Constants.NO_VALUE_SET; /** - * Use onAddFilter, onUpdateFilter instead. + * @deprecated Use onAddFilter, onUpdateFilter instead. * - *

- * Resource event filters only applies on events of the main custom resource. Not on events from - * other event sources nor the periodic events. - *

+ *

+ * Resource event filters only applies on events of the main custom resource. Not on + * events from other event sources nor the periodic events. + *

* * @return the list of event filters. */ - @Deprecated + @Deprecated(forRemoval = true) Class[] eventFilters() default {}; /** - * Filter of onAdd events of resources. (Note that onDelete is missing, delete events don't - * trigger reconciliation - since if finalizer used there the event is an update that triggers the - * cleanup. If not used the resources are cleaned up by garbage collector.) + * Filter of onAdd events of resources. **/ Class> onAddFilter() default VoidOnAddFilter.class; diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/EventFilter.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/EventFilter.java deleted file mode 100644 index df01449bed..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/EventFilter.java +++ /dev/null @@ -1,60 +0,0 @@ -package io.javaoperatorsdk.operator.processing.event.source; - -@Deprecated -public interface EventFilter { - - default boolean acceptAdd(T newResource) { - return true; - } - - default boolean acceptUpdate(T newResource, T oldResource) { - return true; - } - - default boolean acceptDelete(T resource, boolean deletedFinalStateUnknown) { - return true; - } - - default EventFilter or(EventFilter eventFilter) { - return new EventFilter<>() { - @Override - public boolean acceptAdd(T newResource) { - return EventFilter.this.acceptAdd(newResource) || eventFilter.acceptAdd(newResource); - } - - @Override - public boolean acceptUpdate(T newResource, T oldResource) { - return EventFilter.this.acceptUpdate(newResource, oldResource) || - eventFilter.acceptUpdate(newResource, oldResource); - } - - @Override - public boolean acceptDelete(T resource, boolean deletedFinalStateUnknown) { - return EventFilter.this.acceptDelete(resource, deletedFinalStateUnknown) || - eventFilter.acceptDelete(resource, deletedFinalStateUnknown); - } - }; - } - - default EventFilter and(EventFilter eventFilter) { - return new EventFilter<>() { - @Override - public boolean acceptAdd(T newResource) { - return EventFilter.this.acceptAdd(newResource) && eventFilter.acceptAdd(newResource); - } - - @Override - public boolean acceptUpdate(T newResource, T oldResource) { - return EventFilter.this.acceptUpdate(newResource, oldResource) && - eventFilter.acceptUpdate(newResource, oldResource); - } - - @Override - public boolean acceptDelete(T resource, boolean deletedFinalStateUnknown) { - return EventFilter.this.acceptDelete(resource, deletedFinalStateUnknown) && - eventFilter.acceptDelete(resource, deletedFinalStateUnknown); - } - }; - } - -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ResourceEventFilter.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ResourceEventFilter.java index 17cb27fd71..08a86c92ae 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ResourceEventFilter.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ResourceEventFilter.java @@ -10,6 +10,7 @@ * * @param

the type of custom resources handled by this filter */ +@Deprecated(forRemoval = true) @FunctionalInterface public interface ResourceEventFilter

{ From 89c2c73b92705943913a50159a9246ba86ed6245 Mon Sep 17 00:00:00 2001 From: csviri Date: Fri, 24 Jun 2022 10:47:10 +0200 Subject: [PATCH 22/33] fix: generics for kube config --- .../AnnotationControllerConfiguration.java | 34 +++++++++---------- .../KubernetesDependentResource.java | 6 ++-- .../KubernetesDependentResourceConfig.java | 24 ++++++------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java index 3309e0f5b5..8d0a5471a7 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java @@ -36,15 +36,15 @@ import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_NAMESPACES_SET; @SuppressWarnings("rawtypes") -public class AnnotationControllerConfiguration - implements io.javaoperatorsdk.operator.api.config.ControllerConfiguration { +public class AnnotationControllerConfiguration

+ implements io.javaoperatorsdk.operator.api.config.ControllerConfiguration

{ - protected final Reconciler reconciler; + protected final Reconciler

reconciler; private final ControllerConfiguration annotation; private List specs; - private Class resourceClass; + private Class

resourceClass; - public AnnotationControllerConfiguration(Reconciler reconciler) { + public AnnotationControllerConfiguration(Reconciler

reconciler) { this.reconciler = reconciler; this.annotation = reconciler.getClass().getAnnotation(ControllerConfiguration.class); if (annotation == null) { @@ -89,10 +89,10 @@ public Set getNamespaces() { @Override @SuppressWarnings("unchecked") - public Class getResourceClass() { + public Class

getResourceClass() { if (resourceClass == null) { resourceClass = - (Class) Utils.getFirstTypeArgumentFromSuperClassOrInterface(reconciler.getClass(), + (Class

) Utils.getFirstTypeArgumentFromSuperClassOrInterface(reconciler.getClass(), Reconciler.class); } return resourceClass; @@ -110,16 +110,16 @@ public String getAssociatedReconcilerClassName() { @SuppressWarnings("unchecked") @Override - public ResourceEventFilter getEventFilter() { - ResourceEventFilter answer = null; + public ResourceEventFilter

getEventFilter() { + ResourceEventFilter

answer = null; - Class>[] filterTypes = - (Class>[]) valueOrDefault(annotation, + Class>[] filterTypes = + (Class>[]) valueOrDefault(annotation, ControllerConfiguration::eventFilters, new Object[] {}); if (filterTypes.length > 0) { for (var filterType : filterTypes) { try { - ResourceEventFilter filter = filterType.getConstructor().newInstance(); + ResourceEventFilter

filter = filterType.getConstructor().newInstance(); if (answer == null) { answer = filter; @@ -151,8 +151,8 @@ public Optional reconciliationMaxInterval() { @Override @SuppressWarnings("unchecked") - public Optional> onAddFilter() { - return (Optional>) createFilter(annotation.onAddFilter(), FilterType.onAdd, + public Optional> onAddFilter() { + return (Optional>) createFilter(annotation.onAddFilter(), FilterType.onAdd, annotation.getClass().getSimpleName()); } @@ -186,8 +186,8 @@ private Optional createFilter(Class filter, FilterType filterType, Str @SuppressWarnings("unchecked") @Override - public Optional> onUpdateFilter() { - return (Optional>) createFilter(annotation.onUpdateFilter(), + public Optional> onUpdateFilter() { + return (Optional>) createFilter(annotation.onUpdateFilter(), FilterType.onUpdate, annotation.getClass().getSimpleName()); } @@ -259,6 +259,7 @@ private String getName(Dependent dependent, Class d return name; } + @SuppressWarnings("rawtypes") private Object createKubernetesResourceConfig(Class dependentType) { Object config; @@ -291,7 +292,6 @@ private Object createKubernetesResourceConfig(Class .orElse(null); } - config = new KubernetesDependentResourceConfig(namespaces, labelSelector, configuredNS, onAddFilter, onUpdateFilter, onDeleteFilter); 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 58edbaeb20..f5be48a8ae 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 @@ -162,9 +162,9 @@ protected NonNamespaceOperation, Resource> prepa protected InformerEventSource createEventSource(EventSourceContext

context) { if (kubernetesDependentResourceConfig != null) { // sets the filters for the dependent resource, which are applied by parent class - setOnAddFilter(kubernetesDependentResourceConfig.onAddFilter()); - setOnUpdateFilter(kubernetesDependentResourceConfig.onUpdateFilter()); - setOnDeleteFilter(kubernetesDependentResourceConfig.onDeleteFilter()); + onAddFilter = kubernetesDependentResourceConfig.onAddFilter(); + onUpdateFilter = kubernetesDependentResourceConfig.onUpdateFilter(); + onDeleteFilter = kubernetesDependentResourceConfig.onDeleteFilter(); configureWith(kubernetesDependentResourceConfig.labelSelector(), kubernetesDependentResourceConfig.namespaces(), diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfig.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfig.java index 4a31d102bd..ec4b9662a1 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfig.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfig.java @@ -8,26 +8,26 @@ import static io.javaoperatorsdk.operator.api.reconciler.Constants.NO_VALUE_SET; -public class KubernetesDependentResourceConfig { +public class KubernetesDependentResourceConfig { private Set namespaces = Constants.SAME_AS_CONTROLLER_NAMESPACES_SET; private String labelSelector = NO_VALUE_SET; private boolean namespacesWereConfigured = false; - @SuppressWarnings("rawtypes") - private Predicate onAddFilter; - @SuppressWarnings("rawtypes") - private BiPredicate onUpdateFilter; - @SuppressWarnings("rawtypes") - private BiPredicate onDeleteFilter; + + private Predicate onAddFilter; + + private BiPredicate onUpdateFilter; + + private BiPredicate onDeleteFilter; public KubernetesDependentResourceConfig() {} @SuppressWarnings("rawtypes") public KubernetesDependentResourceConfig(Set namespaces, String labelSelector, - boolean configuredNS, Predicate onAddFilter, - BiPredicate onUpdateFilter, - BiPredicate onDeleteFilter) { + boolean configuredNS, Predicate onAddFilter, + BiPredicate onUpdateFilter, + BiPredicate onDeleteFilter) { this.namespaces = namespaces; this.labelSelector = labelSelector; this.namespacesWereConfigured = configuredNS; @@ -40,13 +40,13 @@ public KubernetesDependentResourceConfig(Set namespaces, String labelSel this(namespaces, labelSelector, true, null, null, null); } - public KubernetesDependentResourceConfig setNamespaces(Set namespaces) { + public KubernetesDependentResourceConfig setNamespaces(Set namespaces) { this.namespacesWereConfigured = true; this.namespaces = namespaces; return this; } - public KubernetesDependentResourceConfig setLabelSelector(String labelSelector) { + public KubernetesDependentResourceConfig setLabelSelector(String labelSelector) { this.labelSelector = labelSelector; return this; } From 5638d43f2935006844306105ae2d07a8e1ac7792 Mon Sep 17 00:00:00 2001 From: csviri Date: Fri, 24 Jun 2022 15:56:41 +0200 Subject: [PATCH 23/33] new internal event filters start --- .../event/source/controller/InternalEventFilters.java | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/InternalEventFilters.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/InternalEventFilters.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/InternalEventFilters.java new file mode 100644 index 0000000000..42e48daaaa --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/InternalEventFilters.java @@ -0,0 +1,4 @@ +package io.javaoperatorsdk.operator.processing.event.source.controller; + +public class InternalEventFilters { +} From ea095c8a9ff9c3bddd60761f2640be479cdaed5b Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 27 Jun 2022 11:06:17 +0200 Subject: [PATCH 24/33] removed old filters --- .../ControllerResourceEventSource.java | 30 ++-- .../controller/InternalEventFilters.java | 39 +++++ .../controller/ResourceEventFilters.java | 140 +----------------- .../event/source/ResourceEventFilterTest.java | 7 +- 4 files changed, 62 insertions(+), 154 deletions(-) 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 04b6378f09..09df14dd9b 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 @@ -20,6 +20,7 @@ import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getName; import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getUID; import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getVersion; +import static io.javaoperatorsdk.operator.processing.event.source.controller.InternalEventFilters.*; public class ControllerResourceEventSource extends ManagedInformerEventSource> @@ -34,19 +35,19 @@ public class ControllerResourceEventSource public ControllerResourceEventSource(Controller controller) { super(controller.getCRClient(), controller.getConfiguration()); this.controller = controller; - var filters = new ResourceEventFilter[] { - ResourceEventFilters.finalizerNeededAndApplied(), - ResourceEventFilters.markedForDeletion(), - ResourceEventFilters.generationAware(), - }; - if (controller.getConfiguration().getEventFilter() != null) { - legacyFilters = - controller.getConfiguration().getEventFilter().and(ResourceEventFilters.or(filters)); - } else { - legacyFilters = ResourceEventFilters.or(filters); - } + + BiPredicate internalOnUpdateFilter = onUpdateFinalizerNeededAndApplied(controller) + .or(onUpdateGenerationAware(controller.getConfiguration().isGenerationAware())) + .or(onUpdateMarkedForDeletion()); + + legacyFilters = controller.getConfiguration().getEventFilter(); + + // by default the on add should be processed in all cases regarding internal filters controller.getConfiguration().onAddFilter().ifPresent(this::setOnAddFilter); - controller.getConfiguration().onUpdateFilter().ifPresent(this::setOnUpdateFilter); + + controller.getConfiguration().onUpdateFilter() + .ifPresentOrElse(filter -> setOnUpdateFilter(filter.and(internalOnUpdateFilter)), + () -> setOnUpdateFilter(internalOnUpdateFilter)); } @Override @@ -64,7 +65,8 @@ public void eventReceived(ResourceAction action, T resource, T oldResource) { log.debug("Event received for resource: {}", getName(resource)); MDCUtils.addResourceInfo(resource); controller.getEventSourceManager().broadcastOnResourceEvent(action, resource, oldResource); - if (legacyFilters.acceptChange(controller, oldResource, resource) + if ((legacyFilters == null || + legacyFilters.acceptChange(controller, oldResource, resource)) && acceptFilters(action, resource, oldResource)) { getEventHandler().handleEvent( new ResourceEvent(action, ResourceID.fromResource(resource), resource)); @@ -83,7 +85,7 @@ private boolean acceptFilters(ResourceAction action, T resource, T oldResource) case ADDED: return onAddFilter == null || onAddFilter.test(resource); case UPDATED: - return onUpdateFilter == null || onUpdateFilter.test(resource, oldResource); + return onUpdateFilter.test(resource, oldResource); } return true; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/InternalEventFilters.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/InternalEventFilters.java index 42e48daaaa..e3582ca118 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/InternalEventFilters.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/InternalEventFilters.java @@ -1,4 +1,43 @@ package io.javaoperatorsdk.operator.processing.event.source.controller; +import java.util.function.BiPredicate; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.processing.Controller; + public class InternalEventFilters { + + static BiPredicate onUpdateMarkedForDeletion() { + return (newResource, oldResource) -> newResource.isMarkedForDeletion(); + } + + static BiPredicate onUpdateGenerationAware( + boolean generationAware) { + + return (newResource, oldResource) -> { + if (!generationAware) { + return true; + } + return oldResource.getMetadata().getGeneration() < newResource + .getMetadata().getGeneration(); + }; + } + + static BiPredicate onUpdateFinalizerNeededAndApplied( + Controller controller) { + return (newResource, oldResource) -> { + if (controller.useFinalizer()) { + final var finalizer = controller.getConfiguration().getFinalizerName(); + boolean oldFinalizer = oldResource.hasFinalizer(finalizer); + boolean newFinalizer = newResource.hasFinalizer(finalizer); + // accepts event if old did not have finalizer, since it was just added, so the event needs + // to + // be published. + return !newFinalizer || !oldFinalizer; + } else { + return false; + } + }; + } + } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ResourceEventFilters.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ResourceEventFilters.java index 5a22cafac8..7024388b8b 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ResourceEventFilters.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ResourceEventFilters.java @@ -5,37 +5,12 @@ /** * Convenience implementations of, and utility methods for, {@link ResourceEventFilter}. */ +@Deprecated public final class ResourceEventFilters { - private static final ResourceEventFilter USE_FINALIZER = - (controller, oldResource, newResource) -> { - if (controller.useFinalizer()) { - final var finalizer = controller.getConfiguration().getFinalizerName(); - boolean oldFinalizer = oldResource == null || oldResource.hasFinalizer(finalizer); - boolean newFinalizer = newResource.hasFinalizer(finalizer); - - return !newFinalizer || !oldFinalizer; - } else { - return false; - } - }; - - private static final ResourceEventFilter GENERATION_AWARE = - (controller, oldResource, newResource) -> { - final var generationAware = controller.getConfiguration().isGenerationAware(); - return oldResource == null || !generationAware || - oldResource.getMetadata().getGeneration() < newResource.getMetadata().getGeneration(); - }; - private static final ResourceEventFilter PASSTHROUGH = (configuration, oldResource, newResource) -> true; - private static final ResourceEventFilter NONE = - (configuration, oldResource, newResource) -> false; - - private static final ResourceEventFilter MARKED_FOR_DELETION = - (configuration, oldResource, newResource) -> newResource.isMarkedForDeletion(); - private ResourceEventFilters() {} /** @@ -49,117 +24,4 @@ public static ResourceEventFilter passthrough() { return (ResourceEventFilter) PASSTHROUGH; } - /** - * Retrieves a filter that reject all events. - * - * @param the type of custom resource the filter should handle - * @return a filter that reject all events - */ - @SuppressWarnings("unchecked") - public static ResourceEventFilter none() { - return (ResourceEventFilter) NONE; - } - - /** - * Retrieves a filter that accepts all events if generation-aware processing is not activated but - * only changes that represent a generation increase otherwise. - * - * @param the type of custom resource the filter should handle - * @return a filter accepting changes based on generation information - */ - @SuppressWarnings("unchecked") - public static ResourceEventFilter generationAware() { - return (ResourceEventFilter) GENERATION_AWARE; - } - - /** - * Retrieves a filter that accepts changes if the target controller uses a finalizer and that - * finalizer hasn't already been applied, rejecting them otherwise. - * - * @param the type of custom resource the filter should handle - * @return a filter accepting changes based on whether the finalizer is needed and has been - * applied - */ - @SuppressWarnings("unchecked") - public static ResourceEventFilter finalizerNeededAndApplied() { - return (ResourceEventFilter) USE_FINALIZER; - } - - /** - * Retrieves a filter that accepts changes if the custom resource is marked for deletion. - * - * @param the type of custom resource the filter should handle - * @return a filter accepting changes based on whether the Custom Resource is marked for deletion. - */ - @SuppressWarnings("unchecked") - public static ResourceEventFilter markedForDeletion() { - return (ResourceEventFilter) MARKED_FOR_DELETION; - } - - /** - * Combines the provided, potentially {@code null} filters with an AND logic, i.e. the resulting - * filter will only accept the change if all filters accept it, reject it otherwise. - *

- * Note that the evaluation of filters is lazy: the result is returned as soon as possible without - * evaluating all filters if possible. - * - * @param items the filters to combine - * @param the type of custom resources the filters are supposed to handle - * @return a combined filter implementing the AND logic combination of the provided filters - */ - @SafeVarargs - public static ResourceEventFilter and( - ResourceEventFilter... items) { - if (items == null) { - return none(); - } - - return (configuration, oldResource, newResource) -> { - for (ResourceEventFilter item : items) { - if (item == null) { - continue; - } - - if (!item.acceptChange(configuration, oldResource, newResource)) { - return false; - } - } - - return true; - }; - } - - /** - * Combines the provided, potentially {@code null} filters with an OR logic, i.e. the resulting - * filter will accept the change if any of the filters accepts it, rejecting it only if all reject - * it. - *

- * Note that the evaluation of filters is lazy: the result is returned as soon as possible without - * evaluating all filters if possible. - * - * @param items the filters to combine - * @param the type of custom resources the filters are supposed to handle - * @return a combined filter implementing the OR logic combination of both provided filters - */ - @SafeVarargs - public static ResourceEventFilter or( - ResourceEventFilter... items) { - if (items == null) { - return none(); - } - - return (configuration, oldResource, newResource) -> { - for (ResourceEventFilter item : items) { - if (item == null) { - continue; - } - - if (item.acceptChange(configuration, oldResource, newResource)) { - return true; - } - } - - return false; - }; - } } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventFilterTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventFilterTest.java index ebdfd712cd..5e309a61bf 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventFilterTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventFilterTest.java @@ -56,7 +56,12 @@ public void eventFilteredByCustomPredicate() { cr.getMetadata().setGeneration(1L); cr.getStatus().setConfigMapStatus("1"); - eventSource.eventReceived(ResourceAction.UPDATED, cr, null); + TestCustomResource cr2 = TestUtils.testCustomResource(); + cr.getMetadata().setFinalizers(List.of(FINALIZER)); + cr.getMetadata().setGeneration(1L); + cr.getStatus().setConfigMapStatus("2"); + + eventSource.eventReceived(ResourceAction.UPDATED, cr, cr2); verify(eventHandler, times(1)).handleEvent(any()); cr.getMetadata().setGeneration(1L); From aba5d421d5021e4e35bed99b56a9c86c3861b327 Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 27 Jun 2022 13:31:02 +0200 Subject: [PATCH 25/33] generic filter --- .../AnnotationControllerConfiguration.java | 10 ++++++++- .../ControllerConfigurationOverrider.java | 7 +++++++ .../DefaultControllerConfiguration.java | 3 ++- .../config/DefaultResourceConfiguration.java | 12 ++++++++--- .../api/config/ResourceConfiguration.java | 4 ++++ .../informer/InformerConfiguration.java | 15 ++++++++++--- .../reconciler/ControllerConfiguration.java | 6 ++++++ ...actEventSourceHolderDependentResource.java | 2 ++ .../source/AbstractResourceEventSource.java | 5 +++++ .../ExternalResourceCachingEventSource.java | 17 ++++++++++++--- .../event/source/ResourceEventSource.java | 1 + .../ControllerResourceEventSource.java | 5 ++++- .../controller/InternalEventFilters.java | 2 ++ .../source/filter/VoidGenericFilter.java | 12 +++++++++++ .../source/informer/InformerEventSource.java | 21 +++++++++++++------ .../source/polling/PollingEventSource.java | 5 +---- .../operator/ControllerManagerTest.java | 2 +- .../source/CustomResourceSelectorTest.java | 3 ++- .../event/source/ResourceEventFilterTest.java | 2 +- .../ControllerResourceEventSourceTest.java | 2 +- 20 files changed, 110 insertions(+), 26 deletions(-) create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/filter/VoidGenericFilter.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java index 8d0a5471a7..268793a3c9 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java @@ -29,6 +29,7 @@ import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition; import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter; import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilters; +import io.javaoperatorsdk.operator.processing.event.source.filter.VoidGenericFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.VoidOnAddFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.VoidOnDeleteFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.VoidOnUpdateFilter; @@ -158,7 +159,7 @@ public Optional> onAddFilter() { private enum FilterType { onAdd(VoidOnAddFilter.class), onUpdate(VoidOnUpdateFilter.class), onDelete( - VoidOnDeleteFilter.class); + VoidOnDeleteFilter.class), generic(VoidGenericFilter.class); final Class defaultValue; @@ -191,6 +192,13 @@ public Optional> onUpdateFilter() { FilterType.onUpdate, annotation.getClass().getSimpleName()); } + @SuppressWarnings("unchecked") + @Override + public Optional> genericFilter() { + return (Optional>) createFilter(annotation.genericFilter(), + FilterType.generic, annotation.getClass().getSimpleName()); + } + @SuppressWarnings({"rawtypes", "unchecked"}) @Override public List getDependentResources() { 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 5ec8a610d7..dc579bb67f 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 @@ -34,6 +34,7 @@ public class ControllerConfigurationOverrider { private final LinkedHashMap namedDependentResourceSpecs; private Predicate onAddFilter; private BiPredicate onUpdateFilter; + private Predicate genericFilter; private ControllerConfigurationOverrider(ControllerConfiguration original) { finalizer = original.getFinalizerName(); @@ -48,6 +49,7 @@ private ControllerConfigurationOverrider(ControllerConfiguration original) { namedDependentResourceSpecs = new LinkedHashMap<>(dependentResources.size()); this.onAddFilter = original.onAddFilter().orElse(null); this.onUpdateFilter = original.onUpdateFilter().orElse(null); + this.genericFilter = original.genericFilter().orElse(null); dependentResources.forEach(drs -> namedDependentResourceSpecs.put(drs.getName(), drs)); this.original = original; } @@ -139,6 +141,10 @@ public ControllerConfigurationOverrider withOnUpdateFilter(BiPredicate return this; } + public ControllerConfigurationOverrider withGenericFilter(Predicate genericFilter) { + this.genericFilter = genericFilter; + return this; + } public ControllerConfigurationOverrider replacingNamedDependentResourceConfig(String name, Object dependentResourceConfig) { @@ -189,6 +195,7 @@ public ControllerConfiguration build() { reconciliationMaxInterval, onAddFilter, onUpdateFilter, + genericFilter, newDependentSpecs); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java index 6fe14671d6..b68c5e8f4f 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java @@ -43,8 +43,9 @@ public DefaultControllerConfiguration( Duration reconciliationMaxInterval, Predicate onAddFilter, BiPredicate onUpdateFilter, + Predicate genericFilter, List dependents) { - super(labelSelector, resourceClass, onAddFilter, onUpdateFilter, namespaces); + super(labelSelector, resourceClass, onAddFilter, onUpdateFilter, genericFilter, namespaces); this.associatedControllerClassName = associatedControllerClassName; this.name = name; this.crdName = crdName; diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceConfiguration.java index 9793e43001..4bb13fa06c 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceConfiguration.java @@ -17,22 +17,24 @@ public class DefaultResourceConfiguration private final Class resourceClass; private final Predicate onAddFilter; private final BiPredicate onUpdateFilter; + private final Predicate genericFilter; public DefaultResourceConfiguration(String labelSelector, Class resourceClass, Predicate onAddFilter, - BiPredicate onUpdateFilter, String... namespaces) { - this(labelSelector, resourceClass, onAddFilter, onUpdateFilter, + BiPredicate onUpdateFilter, Predicate genericFilter, String... namespaces) { + this(labelSelector, resourceClass, onAddFilter, onUpdateFilter, genericFilter, namespaces == null || namespaces.length == 0 ? DEFAULT_NAMESPACES_SET : Set.of(namespaces)); } public DefaultResourceConfiguration(String labelSelector, Class resourceClass, Predicate onAddFilter, - BiPredicate onUpdateFilter, Set namespaces) { + BiPredicate onUpdateFilter, Predicate genericFilter, Set namespaces) { this.labelSelector = labelSelector; this.resourceClass = resourceClass; this.onAddFilter = onAddFilter; this.onUpdateFilter = onUpdateFilter; + this.genericFilter = genericFilter; this.namespaces = namespaces == null || namespaces.isEmpty() ? DEFAULT_NAMESPACES_SET : namespaces; @@ -67,4 +69,8 @@ public Optional> onAddFilter() { public Optional> onUpdateFilter() { return Optional.ofNullable(onUpdateFilter); } + + public Optional> genericFilter() { + return Optional.ofNullable(genericFilter); + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResourceConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResourceConfiguration.java index f918def476..636ae99b49 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResourceConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResourceConfiguration.java @@ -28,6 +28,10 @@ default Optional> onUpdateFilter() { return Optional.empty(); } + default Optional> genericFilter() { + return Optional.empty(); + } + /** * Retrieves the label selector that is used to filter which resources are actually watched by the * associated event source. See the official documentation on the 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 b545cad3fb..d9ba2c0d8d 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 @@ -35,8 +35,9 @@ protected DefaultInformerConfiguration(String labelSelector, Set namespaces, boolean followControllerNamespaceChanges, Predicate onAddFilter, BiPredicate onUpdateFilter, - BiPredicate onDeleteFilter) { - super(labelSelector, resourceClass, onAddFilter, onUpdateFilter, namespaces); + BiPredicate onDeleteFilter, + Predicate genericFilter) { + super(labelSelector, resourceClass, onAddFilter, onUpdateFilter, genericFilter, namespaces); this.followControllerNamespaceChanges = followControllerNamespaceChanges; this.primaryToSecondaryMapper = primaryToSecondaryMapper; @@ -81,6 +82,8 @@ public

PrimaryToSecondaryMapper

getPrimaryToSecondary Optional> onDeleteFilter(); + Optional> genericFilter(); +

PrimaryToSecondaryMapper

getPrimaryToSecondaryMapper(); @SuppressWarnings("unused") @@ -94,6 +97,7 @@ class InformerConfigurationBuilder { private Predicate onAddFilter; private BiPredicate onUpdateFilter; private BiPredicate onDeleteFilter; + private Predicate genericFilter; private boolean inheritControllerNamespacesOnChange = false; private InformerConfigurationBuilder(Class resourceClass) { @@ -190,12 +194,17 @@ public InformerConfigurationBuilder withOnDeleteFilter( return this; } + public InformerConfigurationBuilder withGenericFilter(Predicate genericFilter) { + this.genericFilter = genericFilter; + return this; + } + public InformerConfiguration build() { return new DefaultInformerConfiguration<>(labelSelector, resourceClass, primaryToSecondaryMapper, secondaryToPrimaryMapper, namespaces, inheritControllerNamespacesOnChange, onAddFilter, onUpdateFilter, - onDeleteFilter); + onDeleteFilter, genericFilter); } } 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 a3cf113805..4c6621bb57 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 @@ -10,6 +10,7 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter; +import io.javaoperatorsdk.operator.processing.event.source.filter.VoidGenericFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.VoidOnAddFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.VoidOnUpdateFilter; @@ -76,6 +77,11 @@ /** Filter of onUpdate events of resources. */ Class> onUpdateFilter() default VoidOnUpdateFilter.class; + /** + * Filter applied to all operations (add, update, delete). Used to ignore some resources. + **/ + Class> genericFilter() default VoidGenericFilter.class; + /** * Optional configuration of the maximal interval the SDK will wait for a reconciliation request * to happen before one will be automatically triggered. diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractEventSourceHolderDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractEventSourceHolderDependentResource.java index 1c75056b47..2f7747cec6 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractEventSourceHolderDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractEventSourceHolderDependentResource.java @@ -22,6 +22,7 @@ public abstract class AbstractEventSourceHolderDependentResource onAddFilter; protected BiPredicate onUpdateFilter; protected BiPredicate onDeleteFilter; + protected Predicate genericFilter; public EventSource initEventSource(EventSourceContext

context) { @@ -50,6 +51,7 @@ protected void applyFilters() { this.eventSource.setOnAddFilter(onAddFilter); this.eventSource.setOnUpdateFilter(onUpdateFilter); this.eventSource.setOnDeleteFilter(onDeleteFilter); + this.eventSource.setGenericFilter(genericFilter); } protected T eventSource() { diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/AbstractResourceEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/AbstractResourceEventSource.java index 9b97529ce5..d2e8ed8c1d 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/AbstractResourceEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/AbstractResourceEventSource.java @@ -13,6 +13,7 @@ public abstract class AbstractResourceEventSource protected Predicate onAddFilter; protected BiPredicate onUpdateFilter; protected BiPredicate onDeleteFilter; + protected Predicate genericFilter; protected AbstractResourceEventSource(Class resourceClass) { this.resourceClass = resourceClass; @@ -36,4 +37,8 @@ public void setOnDeleteFilter( BiPredicate onDeleteFilter) { this.onDeleteFilter = onDeleteFilter; } + + public void setGenericFilter(Predicate genericFilter) { + this.genericFilter = genericFilter; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java index 4fe5f78cea..c20adf78cc 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java @@ -121,7 +121,9 @@ private boolean acceptedByFiler(Map cachedResourceMap, var addedResources = new HashMap<>(newResourcesMap); addedResources.keySet().removeAll(cachedResourceMap.keySet()); if (onAddFilter != null) { - var anyAddAccepted = addedResources.values().stream().anyMatch(onAddFilter::test); + var anyAddAccepted = + addedResources.values().stream().anyMatch(r -> acceptedByGenericFiler(r) && + onAddFilter.test(r)); if (anyAddAccepted) { return true; } @@ -133,7 +135,8 @@ private boolean acceptedByFiler(Map cachedResourceMap, deletedResource.keySet().removeAll(newResourcesMap.keySet()); if (onDeleteFilter != null) { var anyDeleteAccepted = - deletedResource.values().stream().anyMatch(r -> onDeleteFilter.test(r, false)); + deletedResource.values().stream() + .anyMatch(r -> acceptedByGenericFiler(r) && onDeleteFilter.test(r, false)); if (anyDeleteAccepted) { return true; } @@ -151,7 +154,11 @@ private boolean acceptedByFiler(Map cachedResourceMap, if (onUpdateFilter != null) { var anyUpdated = possibleUpdatedResources.entrySet().stream() .anyMatch( - entry -> onUpdateFilter.test(newResourcesMap.get(entry.getKey()), entry.getValue())); + entry -> { + var newResource = newResourcesMap.get(entry.getKey()); + return acceptedByGenericFiler(newResourcesMap.get(entry.getKey())) && + onUpdateFilter.test(newResourcesMap.get(entry.getKey()), entry.getValue()); + }); if (anyUpdated) { return true; } @@ -162,6 +169,10 @@ private boolean acceptedByFiler(Map cachedResourceMap, return false; } + private boolean acceptedByGenericFiler(R resource) { + return genericFilter == null || genericFilter.test(resource); + } + @Override public synchronized void handleRecentResourceCreate(ResourceID primaryID, R resource) { var actualValues = cache.get(primaryID); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventSource.java index 5e762b98ea..fc6e6b2300 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventSource.java @@ -31,4 +31,5 @@ default Optional getSecondaryResource(P primary) { void setOnDeleteFilter(BiPredicate onDeleteFilter); + void setGenericFilter(Predicate onUpdateFilter); } 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 09df14dd9b..fea3745650 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 @@ -80,7 +80,10 @@ && acceptFilters(action, resource, oldResource)) { } private boolean acceptFilters(ResourceAction action, T resource, T oldResource) { - // delete event not filtered, there is no reconciliation for delete anyways + // delete event is filtered for generic filter only. + if (genericFilter != null && !genericFilter.test(resource)) { + return false; + } switch (action) { case ADDED: return onAddFilter == null || onAddFilter.test(resource); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/InternalEventFilters.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/InternalEventFilters.java index e3582ca118..43f1760f0d 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/InternalEventFilters.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/InternalEventFilters.java @@ -7,6 +7,8 @@ public class InternalEventFilters { + // todo unit tests + static BiPredicate onUpdateMarkedForDeletion() { return (newResource, oldResource) -> newResource.isMarkedForDeletion(); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/filter/VoidGenericFilter.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/filter/VoidGenericFilter.java new file mode 100644 index 0000000000..c02af4e421 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/filter/VoidGenericFilter.java @@ -0,0 +1,12 @@ +package io.javaoperatorsdk.operator.processing.event.source.filter; + +import java.util.function.Predicate; + +import io.fabric8.kubernetes.api.model.HasMetadata; + +public class VoidGenericFilter implements Predicate { + @Override + public boolean test(HasMetadata hasMetadata) { + return true; + } +} 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 6bef2587b7..410c2177d2 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 @@ -79,6 +79,8 @@ public class InformerEventSource // we need direct control for the indexer to propagate the just update resource also to the index private final PrimaryToSecondaryIndex primaryToSecondaryIndex; + private final PrimaryToSecondaryMapper primaryToSecondaryMapper; + protected final Predicate onAddFilter; protected final BiPredicate onUpdateFilter; protected final BiPredicate onDeleteFilter; @@ -92,11 +94,6 @@ public InformerEventSource(InformerConfiguration configuration, MixedOperation, Resource> client) { super(client, configuration); this.configuration = configuration; - primaryToSecondaryIndex = - new PrimaryToSecondaryIndex<>(configuration.getSecondaryToPrimaryMapper()); - onAddFilter = configuration.onAddFilter().orElse(null); - onUpdateFilter = configuration.onUpdateFilter().orElse(null); - onDeleteFilter = configuration.onDeleteFilter().orElse(null); primaryToSecondaryMapper = configuration.getPrimaryToSecondaryMapper(); if (primaryToSecondaryMapper == null) { primaryToSecondaryIndex = @@ -104,6 +101,10 @@ public InformerEventSource(InformerConfiguration configuration, } else { primaryToSecondaryIndex = NOOPPrimaryToSecondaryIndex.getInstance(); } + onAddFilter = configuration.onAddFilter().orElse(null); + onUpdateFilter = configuration.onUpdateFilter().orElse(null); + onDeleteFilter = configuration.onDeleteFilter().orElse(null); + genericFilter = configuration.genericFilter().orElse(null); } @Override @@ -139,7 +140,7 @@ public void onDelete(R resource, boolean b) { } primaryToSecondaryIndex.onDelete(resource); super.onDelete(resource, b); - if (onDeleteFilter == null || onDeleteFilter.test(resource, b)) { + if (acceptedByDeleteFilters(resource, b)) { propagateEvent(resource); } } @@ -316,6 +317,9 @@ public boolean allowsNamespaceChanges() { private boolean eventAcceptedByFilter(Operation operation, R newObject, R oldObject) { + if (genericFilter != null && !genericFilter.test(newObject)) { + return false; + } if (operation == Operation.ADD) { return onAddFilter == null || onAddFilter.test(newObject); } else { @@ -326,4 +330,9 @@ private boolean eventAcceptedByFilter(Operation operation, R newObject, R oldObj private enum Operation { ADD, UPDATE } + + private boolean acceptedByDeleteFilters(R resource, boolean b) { + return (onDeleteFilter == null || onDeleteFilter.test(resource, b)) && + (genericFilter == null || genericFilter.test(resource)); + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PollingEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PollingEventSource.java index 09ff2e8b0e..94efbf25aa 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PollingEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PollingEventSource.java @@ -1,9 +1,6 @@ package io.javaoperatorsdk.operator.processing.event.source.polling; -import java.util.Map; -import java.util.Set; -import java.util.Timer; -import java.util.TimerTask; +import java.util.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ControllerManagerTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ControllerManagerTest.java index d008b92cf1..6c7358d86e 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ControllerManagerTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ControllerManagerTest.java @@ -63,7 +63,7 @@ private static class TestControllerConfiguration public TestControllerConfiguration(Reconciler controller, Class crClass) { super(null, getControllerName(controller), CustomResource.getCRDName(crClass), null, false, null, null, null, null, crClass, - null, null, null, null); + null, null, null, null, null); this.controller = controller; } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/CustomResourceSelectorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/CustomResourceSelectorTest.java index cdca109628..bee750a324 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/CustomResourceSelectorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/CustomResourceSelectorTest.java @@ -136,7 +136,8 @@ public static class MyConfiguration extends DefaultControllerConfiguration null, TestCustomResource.class, null, - onAddFilter, onUpdateFilter, null); + onAddFilter, onUpdateFilter, null, null); } } } From ba10b29ad4854a1b05c1b975455e6ec84aff338a Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 27 Jun 2022 14:00:06 +0200 Subject: [PATCH 26/33] unit tests --- .../ExternalResourceCachingEventSource.java | 10 ++-- ...xternalResourceCachingEventSourceTest.java | 20 +++++++- .../informer/InformerEventSourceTest.java | 46 +++++++++++++++++++ 3 files changed, 69 insertions(+), 7 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java index c20adf78cc..275a41775a 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java @@ -120,7 +120,7 @@ private boolean acceptedByFiler(Map cachedResourceMap, var addedResources = new HashMap<>(newResourcesMap); addedResources.keySet().removeAll(cachedResourceMap.keySet()); - if (onAddFilter != null) { + if (onAddFilter != null || genericFilter != null) { var anyAddAccepted = addedResources.values().stream().anyMatch(r -> acceptedByGenericFiler(r) && onAddFilter.test(r)); @@ -133,7 +133,7 @@ private boolean acceptedByFiler(Map cachedResourceMap, var deletedResource = new HashMap<>(cachedResourceMap); deletedResource.keySet().removeAll(newResourcesMap.keySet()); - if (onDeleteFilter != null) { + if (onDeleteFilter != null || genericFilter != null) { var anyDeleteAccepted = deletedResource.values().stream() .anyMatch(r -> acceptedByGenericFiler(r) && onDeleteFilter.test(r, false)); @@ -151,13 +151,13 @@ private boolean acceptedByFiler(Map cachedResourceMap, .get(entry.getKey()).equals(entry.getValue())) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - if (onUpdateFilter != null) { + if (onUpdateFilter != null || genericFilter != null) { var anyUpdated = possibleUpdatedResources.entrySet().stream() .anyMatch( entry -> { var newResource = newResourcesMap.get(entry.getKey()); - return acceptedByGenericFiler(newResourcesMap.get(entry.getKey())) && - onUpdateFilter.test(newResourcesMap.get(entry.getKey()), entry.getValue()); + return acceptedByGenericFiler(newResource) && + onUpdateFilter.test(newResource, entry.getValue()); }); if (anyUpdated) { return true; diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSourceTest.java index 9529ba2111..6b851fd135 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSourceTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSourceTest.java @@ -138,7 +138,7 @@ void canFilterOnDeleteEvents() { } @Test - void filtersAddOnAddEvents() { + void filtersAddEvents() { TestExternalCachingEventSource delFilteringEventSource = new TestExternalCachingEventSource(); delFilteringEventSource.setOnAddFilter((res) -> false); setUpSource(delFilteringEventSource); @@ -151,7 +151,7 @@ void filtersAddOnAddEvents() { } @Test - void filtersAddOnUpdateEvents() { + void filtersUpdateEvents() { TestExternalCachingEventSource delFilteringEventSource = new TestExternalCachingEventSource(); delFilteringEventSource.setOnUpdateFilter((res, res2) -> false); setUpSource(delFilteringEventSource); @@ -178,6 +178,22 @@ void filtersImplicitDeleteEvents() { verify(eventHandler, times(1)).handleEvent(any()); } + @Test + void genericFilteringEvents() { + TestExternalCachingEventSource delFilteringEventSource = new TestExternalCachingEventSource(); + delFilteringEventSource.setGenericFilter(res -> false); + setUpSource(delFilteringEventSource); + + source.handleResources(primaryID1(), Set.of(testResource1())); + verify(eventHandler, times(0)).handleEvent(any()); + + source.handleResources(primaryID1(), Set.of(testResource1(), testResource2())); + verify(eventHandler, times(0)).handleEvent(any()); + + source.handleResources(primaryID1(), Set.of(testResource2())); + verify(eventHandler, times(0)).handleEvent(any()); + } + public static class TestExternalCachingEventSource extends ExternalResourceCachingEventSource { public TestExternalCachingEventSource() { 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 85cfd0c181..e8c1a9db6f 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 @@ -207,6 +207,52 @@ void putsResourceOnTempCacheIfNoEventRecordedWithSameResourceVersion() { verify(temporaryResourceCacheMock, times(1)).unconditionallyCacheResource(any()); } + @Test + void genericFilterForEvents() { + informerEventSource.setGenericFilter(r -> false); + when(temporaryResourceCacheMock.getResourceFromCache(any())) + .thenReturn(Optional.empty()); + + informerEventSource.onAdd(testDeployment()); + informerEventSource.onUpdate(testDeployment(), testDeployment()); + informerEventSource.onDelete(testDeployment(), true); + + verify(eventHandlerMock, never()).handleEvent(any()); + } + + @Test + void filtersOnAddEvents() { + informerEventSource.setOnAddFilter(r -> false); + when(temporaryResourceCacheMock.getResourceFromCache(any())) + .thenReturn(Optional.empty()); + + informerEventSource.onAdd(testDeployment()); + + verify(eventHandlerMock, never()).handleEvent(any()); + } + + @Test + void filtersOnUpdateEvents() { + informerEventSource.setOnUpdateFilter((r1, r2) -> false); + when(temporaryResourceCacheMock.getResourceFromCache(any())) + .thenReturn(Optional.empty()); + + informerEventSource.onUpdate(testDeployment(), testDeployment()); + + verify(eventHandlerMock, never()).handleEvent(any()); + } + + @Test + void filtersOnDeleteEvents() { + informerEventSource.setOnDeleteFilter((r, b) -> false); + when(temporaryResourceCacheMock.getResourceFromCache(any())) + .thenReturn(Optional.empty()); + + informerEventSource.onDelete(testDeployment(), true); + + verify(eventHandlerMock, never()).handleEvent(any()); + } + Deployment testDeployment() { Deployment deployment = new Deployment(); deployment.setMetadata(new ObjectMeta()); From bd6e21c26a908512dd161335caf3063b79f6e913 Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 27 Jun 2022 14:23:41 +0200 Subject: [PATCH 27/33] controller unit test --- .../ControllerResourceEventSource.java | 2 +- .../ControllerResourceEventSourceTest.java | 30 +++++++++++++++---- 2 files changed, 25 insertions(+), 7 deletions(-) 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 fea3745650..8e43e58764 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 @@ -44,10 +44,10 @@ public ControllerResourceEventSource(Controller controller) { // by default the on add should be processed in all cases regarding internal filters controller.getConfiguration().onAddFilter().ifPresent(this::setOnAddFilter); - controller.getConfiguration().onUpdateFilter() .ifPresentOrElse(filter -> setOnUpdateFilter(filter.and(internalOnUpdateFilter)), () -> setOnUpdateFilter(internalOnUpdateFilter)); + controller.getConfiguration().genericFilter().ifPresent(this::setGenericFilter); } @Override diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java index 3fc404edec..062298754d 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java @@ -115,7 +115,8 @@ void filtersOutEventsOnAddAndUpdate() { Predicate onAddPredicate = (res) -> false; BiPredicate onUpdatePredicate = (res, res2) -> false; source = - new ControllerResourceEventSource<>(new TestController(onAddPredicate, onUpdatePredicate)); + new ControllerResourceEventSource<>( + new TestController(onAddPredicate, onUpdatePredicate, null)); setUpSource(source); source.eventReceived(ResourceAction.ADDED, cr, null); @@ -124,6 +125,21 @@ void filtersOutEventsOnAddAndUpdate() { verify(eventHandler, never()).handleEvent(any()); } + @Test + void genericFilterFiltersOutAddUpdateAndDeleteEvents() { + TestCustomResource cr = TestUtils.testCustomResource(); + + source = + new ControllerResourceEventSource<>(new TestController(null, null, res -> false)); + setUpSource(source); + + source.eventReceived(ResourceAction.ADDED, cr, null); + source.eventReceived(ResourceAction.UPDATED, cr, cr); + source.eventReceived(ResourceAction.DELETED, cr, cr); + + verify(eventHandler, never()).handleEvent(any()); + } + @SuppressWarnings("unchecked") private static class TestController extends Controller { @@ -131,13 +147,14 @@ private static class TestController extends Controller { mock(EventSourceManager.class); public TestController(Predicate onAddFilter, - BiPredicate onUpdateFilter) { - super(null, new TestConfiguration(true, onAddFilter, onUpdateFilter), + BiPredicate onUpdateFilter, + Predicate genericFilter) { + super(null, new TestConfiguration(true, onAddFilter, onUpdateFilter, genericFilter), MockKubernetesClient.client(TestCustomResource.class)); } public TestController(boolean generationAware) { - super(null, new TestConfiguration(generationAware, null, null), + super(null, new TestConfiguration(generationAware, null, null, null), MockKubernetesClient.client(TestCustomResource.class)); } @@ -156,7 +173,8 @@ private static class TestConfiguration extends DefaultControllerConfiguration { public TestConfiguration(boolean generationAware, Predicate onAddFilter, - BiPredicate onUpdateFilter) { + BiPredicate onUpdateFilter, + Predicate genericFilter) { super( null, null, @@ -169,7 +187,7 @@ public TestConfiguration(boolean generationAware, Predicate null, TestCustomResource.class, null, - onAddFilter, onUpdateFilter, null, null); + onAddFilter, onUpdateFilter, genericFilter, null); } } } From abb7332e869ec013106031c9de4566d57fb92c2b Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 27 Jun 2022 15:41:00 +0200 Subject: [PATCH 28/33] tests --- .../operator/processing/Controller.java | 2 +- .../ControllerResourceEventSource.java | 8 ++- .../controller/InternalEventFilters.java | 14 ++--- .../javaoperatorsdk/operator/TestUtils.java | 3 +- .../controller/InternalEventFiltersTest.java | 60 +++++++++++++++++++ 5 files changed, 74 insertions(+), 13 deletions(-) create mode 100644 operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/InternalEventFiltersTest.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java index 5c25787f1f..acbf2f2632 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java @@ -64,10 +64,10 @@ public Controller(Reconciler

reconciler, this.metrics = Optional.ofNullable(ConfigurationServiceProvider.instance().getMetrics()) .orElse(Metrics.NOOP); contextInitializer = reconciler instanceof ContextInitializer; - eventSourceManager = new EventSourceManager<>(this); isCleaner = reconciler instanceof Cleaner; managedWorkflow = ManagedWorkflow.workflowFor(kubernetesClient, configuration.getDependentResources()); + eventSourceManager = new EventSourceManager<>(this); } @Override 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 8e43e58764..011b16a1e8 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 @@ -36,9 +36,11 @@ public ControllerResourceEventSource(Controller controller) { super(controller.getCRClient(), controller.getConfiguration()); this.controller = controller; - BiPredicate internalOnUpdateFilter = onUpdateFinalizerNeededAndApplied(controller) - .or(onUpdateGenerationAware(controller.getConfiguration().isGenerationAware())) - .or(onUpdateMarkedForDeletion()); + BiPredicate internalOnUpdateFilter = + (BiPredicate) onUpdateFinalizerNeededAndApplied(controller.useFinalizer(), + controller.getConfiguration().getFinalizerName()) + .or(onUpdateGenerationAware(controller.getConfiguration().isGenerationAware())) + .or(onUpdateMarkedForDeletion()); legacyFilters = controller.getConfiguration().getEventFilter(); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/InternalEventFilters.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/InternalEventFilters.java index 43f1760f0d..97da768979 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/InternalEventFilters.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/InternalEventFilters.java @@ -3,11 +3,10 @@ import java.util.function.BiPredicate; import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.processing.Controller; public class InternalEventFilters { - // todo unit tests + private InternalEventFilters() {} static BiPredicate onUpdateMarkedForDeletion() { return (newResource, oldResource) -> newResource.isMarkedForDeletion(); @@ -26,12 +25,12 @@ static BiPredicate onUpdateGenerationAware( } static BiPredicate onUpdateFinalizerNeededAndApplied( - Controller controller) { + boolean useFinalizer, + String finalizerName) { return (newResource, oldResource) -> { - if (controller.useFinalizer()) { - final var finalizer = controller.getConfiguration().getFinalizerName(); - boolean oldFinalizer = oldResource.hasFinalizer(finalizer); - boolean newFinalizer = newResource.hasFinalizer(finalizer); + if (useFinalizer) { + boolean oldFinalizer = oldResource.hasFinalizer(finalizerName); + boolean newFinalizer = newResource.hasFinalizer(finalizerName); // accepts event if old did not have finalizer, since it was just added, so the event needs // to // be published. @@ -41,5 +40,4 @@ static BiPredicate onUpdateFinalizerNeededAndAppli } }; } - } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/TestUtils.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/TestUtils.java index a7b6b46d17..3b2c5354f5 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/TestUtils.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/TestUtils.java @@ -49,8 +49,9 @@ public static TestCustomResource testCustomResource(ResourceID id) { return resource; } - public static void markForDeletion(HasMetadata customResource) { + public static T markForDeletion(T customResource) { customResource.getMetadata().setDeletionTimestamp("2019-8-10"); + return customResource; } } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/InternalEventFiltersTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/InternalEventFiltersTest.java new file mode 100644 index 0000000000..2f1fec6917 --- /dev/null +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/InternalEventFiltersTest.java @@ -0,0 +1,60 @@ +package io.javaoperatorsdk.operator.processing.event.source.controller; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import io.javaoperatorsdk.operator.TestUtils; + +import static io.javaoperatorsdk.operator.TestUtils.markForDeletion; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +class InternalEventFiltersTest { + + public static final String FINALIZER = "finalizer"; + + @Test + void onUpdateMarkedForDeletion() { + var res = markForDeletion(TestUtils.testCustomResource()); + assertThat(InternalEventFilters.onUpdateMarkedForDeletion().test(res, res)).isTrue(); + } + + @Test + void generationAware() { + var res = TestUtils.testCustomResource1(); + var res2 = TestUtils.testCustomResource1(); + res2.getMetadata().setGeneration(2L); + + assertThat(InternalEventFilters.onUpdateGenerationAware(true).test(res2, res)).isTrue(); + assertThat(InternalEventFilters.onUpdateGenerationAware(true).test(res, res)).isFalse(); + assertThat(InternalEventFilters.onUpdateGenerationAware(false).test(res, res)).isTrue(); + } + + @Test + void finalizerCheckedIfConfigured() { + assertThat(InternalEventFilters.onUpdateFinalizerNeededAndApplied(true, FINALIZER) + .test(TestUtils.testCustomResource1(), TestUtils.testCustomResource1())).isTrue(); + + var res = TestUtils.testCustomResource1(); + res.getMetadata().setFinalizers(List.of(FINALIZER)); + + assertThat(InternalEventFilters.onUpdateFinalizerNeededAndApplied(true, FINALIZER) + .test(res, res)).isFalse(); + } + + @Test + void acceptsIfFinalizerWasJustAdded() { + var res = TestUtils.testCustomResource1(); + res.getMetadata().setFinalizers(List.of(FINALIZER)); + + assertThat(InternalEventFilters.onUpdateFinalizerNeededAndApplied(true, "finalizer") + .test(res, TestUtils.testCustomResource1())).isTrue(); + } + + @Test + void dontAcceptIfFinalizerNotUsed() { + assertThat(InternalEventFilters.onUpdateFinalizerNeededAndApplied(false, FINALIZER) + .test(TestUtils.testCustomResource1(), TestUtils.testCustomResource1())).isFalse(); + } +} From b12b5ce749b64d9eab95a640bd72e4c84a85ce95 Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 27 Jun 2022 17:08:29 +0200 Subject: [PATCH 29/33] fix: problems with rebase --- .../config/informer/InformerConfiguration.java | 1 + .../source/informer/InformerEventSource.java | 17 ++++------------- .../operator/api/config/UtilsTest.java | 2 -- .../informer/InformerEventSourceTest.java | 11 +++++------ .../informer/PrimaryToSecondaryIndexTest.java | 2 +- .../junit/LocallyRunOperatorExtension.java | 2 +- .../CreateUpdateEventFilterTestReconciler.java | 2 +- 7 files changed, 13 insertions(+), 24 deletions(-) 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 d9ba2c0d8d..e9ee02c91b 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 @@ -60,6 +60,7 @@ public SecondaryToPrimaryMapper getSecondaryToPrimaryMapper() { public Optional> onDeleteFilter() { return Optional.ofNullable(onDeleteFilter); } + @Override public

PrimaryToSecondaryMapper

getPrimaryToSecondaryMapper() { return (PrimaryToSecondaryMapper

) primaryToSecondaryMapper; 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 410c2177d2..55300f8f28 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 @@ -8,9 +8,7 @@ import org.slf4j.LoggerFactory; import io.fabric8.kubernetes.api.model.HasMetadata; -import io.fabric8.kubernetes.api.model.KubernetesResourceList; -import io.fabric8.kubernetes.client.dsl.MixedOperation; -import io.fabric8.kubernetes.client.dsl.Resource; +import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.informers.ResourceEventHandler; import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; @@ -18,7 +16,6 @@ import io.javaoperatorsdk.operator.processing.event.Event; import io.javaoperatorsdk.operator.processing.event.EventHandler; import io.javaoperatorsdk.operator.processing.event.ResourceID; -import io.javaoperatorsdk.operator.processing.event.source.EventFilter; import io.javaoperatorsdk.operator.processing.event.source.PrimaryToSecondaryMapper; /** @@ -78,21 +75,15 @@ public class InformerEventSource private final EventRecorder eventRecorder = new EventRecorder<>(); // we need direct control for the indexer to propagate the just update resource also to the index private final PrimaryToSecondaryIndex primaryToSecondaryIndex; - - private final PrimaryToSecondaryMapper primaryToSecondaryMapper; - - protected final Predicate onAddFilter; - protected final BiPredicate onUpdateFilter; - protected final BiPredicate onDeleteFilter; + private final PrimaryToSecondaryMapper

primaryToSecondaryMapper; public InformerEventSource( InformerConfiguration configuration, EventSourceContext

context) { this(configuration, context.getClient()); } - public InformerEventSource(InformerConfiguration configuration, - MixedOperation, Resource> client) { - super(client, configuration); + public InformerEventSource(InformerConfiguration configuration, KubernetesClient client) { + super(client.resources(configuration.getResourceClass()), configuration); this.configuration = configuration; primaryToSecondaryMapper = configuration.getPrimaryToSecondaryMapper(); if (primaryToSecondaryMapper == null) { diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/UtilsTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/UtilsTest.java index fa43a350ee..87e60b8aa6 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/UtilsTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/UtilsTest.java @@ -9,8 +9,6 @@ import io.javaoperatorsdk.operator.api.reconciler.Reconciler; import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; -import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; -import io.javaoperatorsdk.operator.processing.dependent.EmptyTestDependentResource; import io.javaoperatorsdk.operator.processing.dependent.EmptyTestDependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; 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 e8c1a9db6f..623705b609 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 @@ -8,6 +8,7 @@ import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable; import io.fabric8.kubernetes.client.dsl.FilterWatchListMultiDeletable; import io.fabric8.kubernetes.client.dsl.MixedOperation; @@ -21,11 +22,7 @@ import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_NAMESPACES_SET; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; @SuppressWarnings({"rawtypes", "unchecked"}) class InformerEventSourceTest { @@ -35,6 +32,7 @@ class InformerEventSourceTest { private static final String NEXT_RESOURCE_VERSION = "2"; private InformerEventSource informerEventSource; + private final KubernetesClient clientMock = mock(KubernetesClient.class); private final TemporaryResourceCache temporaryResourceCacheMock = mock(TemporaryResourceCache.class); private final EventHandler eventHandlerMock = mock(EventHandler.class); @@ -49,6 +47,7 @@ class InformerEventSourceTest { @BeforeEach void setup() { + when(clientMock.resources(any())).thenReturn(crClientMock); when(crClientMock.inAnyNamespace()).thenReturn(specificResourceClientMock); when(specificResourceClientMock.withLabelSelector((String) null)) .thenReturn(labeledResourceClientMock); @@ -61,7 +60,7 @@ void setup() { .thenReturn(mock(SecondaryToPrimaryMapper.class)); when(informerConfiguration.getResourceClass()).thenReturn(Deployment.class); - informerEventSource = new InformerEventSource<>(informerConfiguration, crClientMock); + informerEventSource = new InformerEventSource<>(informerConfiguration, clientMock); informerEventSource.setTemporalResourceCache(temporaryResourceCacheMock); informerEventSource.setEventHandler(eventHandlerMock); diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/PrimaryToSecondaryIndexTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/PrimaryToSecondaryIndexTest.java index ca73b135a7..4baa531274 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/PrimaryToSecondaryIndexTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/PrimaryToSecondaryIndexTest.java @@ -20,7 +20,7 @@ class PrimaryToSecondaryIndexTest { private SecondaryToPrimaryMapper secondaryToPrimaryMapperMock = mock(SecondaryToPrimaryMapper.class); private PrimaryToSecondaryIndex primaryToSecondaryIndex = - new PrimaryToSecondaryIndex<>(secondaryToPrimaryMapperMock); + new DefaultPrimaryToSecondaryIndex<>(secondaryToPrimaryMapperMock); private ResourceID primaryID1 = new ResourceID("id1", "default"); private ResourceID primaryID2 = new ResourceID("id2", "default"); diff --git a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java index f6d98a7b45..e2f4234453 100644 --- a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java +++ b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java @@ -239,7 +239,7 @@ public Builder withPortForward(String namespace, String labelKey, String labelVa public Builder withAdditionalCustomResourceDefinition( - Class customResource) { + Class customResource) { additionalCustomResourceDefinitions.add(customResource); return this; } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/createupdateeventfilter/CreateUpdateEventFilterTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/createupdateeventfilter/CreateUpdateEventFilterTestReconciler.java index 8779c30277..988b1466c1 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/createupdateeventfilter/CreateUpdateEventFilterTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/createupdateeventfilter/CreateUpdateEventFilterTestReconciler.java @@ -106,7 +106,7 @@ public Map prepareEventSources( .withLabelSelector("integrationtest = " + this.getClass().getSimpleName()) .build(); informerEventSource = - new InformerEventSource<>(informerConfiguration, client.resources(ConfigMap.class)); + new InformerEventSource<>(informerConfiguration, client); return EventSourceInitializer.nameEventSources(informerEventSource); } From 4d71b660454491867c7190e1398ee9cb34fa35da Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Thu, 30 Jun 2022 09:49:10 +0200 Subject: [PATCH 30/33] refactor: rename more appropriately --- .../source/controller/ControllerResourceEventSource.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 011b16a1e8..3453eb34cc 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 @@ -20,7 +20,9 @@ import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getName; import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getUID; import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getVersion; -import static io.javaoperatorsdk.operator.processing.event.source.controller.InternalEventFilters.*; +import static io.javaoperatorsdk.operator.processing.event.source.controller.InternalEventFilters.onUpdateFinalizerNeededAndApplied; +import static io.javaoperatorsdk.operator.processing.event.source.controller.InternalEventFilters.onUpdateGenerationAware; +import static io.javaoperatorsdk.operator.processing.event.source.controller.InternalEventFilters.onUpdateMarkedForDeletion; public class ControllerResourceEventSource extends ManagedInformerEventSource> @@ -69,7 +71,7 @@ public void eventReceived(ResourceAction action, T resource, T oldResource) { controller.getEventSourceManager().broadcastOnResourceEvent(action, resource, oldResource); if ((legacyFilters == null || legacyFilters.acceptChange(controller, oldResource, resource)) - && acceptFilters(action, resource, oldResource)) { + && isAcceptedByFilters(action, resource, oldResource)) { getEventHandler().handleEvent( new ResourceEvent(action, ResourceID.fromResource(resource), resource)); } else { @@ -81,7 +83,7 @@ && acceptFilters(action, resource, oldResource)) { } } - private boolean acceptFilters(ResourceAction action, T resource, T oldResource) { + private boolean isAcceptedByFilters(ResourceAction action, T resource, T oldResource) { // delete event is filtered for generic filter only. if (genericFilter != null && !genericFilter.test(resource)) { return false; From 7db8f8d7337a2fe6bb16cd5f0c0dbb30f8f00859 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Thu, 30 Jun 2022 09:49:28 +0200 Subject: [PATCH 31/33] fix: incorrect error message --- .../event/source/controller/ControllerResourceEventSource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3453eb34cc..12d9d47fd5 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 @@ -128,6 +128,6 @@ public Set getSecondaryResources(T primary) { @Override public void setOnDeleteFilter(BiPredicate onDeleteFilter) { throw new IllegalStateException( - "onAddFilter is not supported for controller resource event source"); + "onDeleteFilter is not supported for controller resource event source"); } } From d615190bbf535501718f1ebaf6aa709c4f6f9300 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Thu, 30 Jun 2022 10:15:10 +0200 Subject: [PATCH 32/33] fix: incorrectly named parameter --- .../operator/processing/event/source/ResourceEventSource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventSource.java index fc6e6b2300..2e19603bd4 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventSource.java @@ -31,5 +31,5 @@ default Optional getSecondaryResource(P primary) { void setOnDeleteFilter(BiPredicate onDeleteFilter); - void setGenericFilter(Predicate onUpdateFilter); + void setGenericFilter(Predicate genericFilter); } From aeed0c723b652ae939600b638cb8556e4ae8b137 Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 4 Jul 2022 13:28:59 +0200 Subject: [PATCH 33/33] fix: add generic to dependent configuration --- .../api/config/AnnotationControllerConfiguration.java | 6 +++++- .../dependent/kubernetes/KubernetesDependent.java | 3 +++ .../kubernetes/KubernetesDependentResource.java | 1 + .../kubernetes/KubernetesDependentResourceConfig.java | 11 +++++++++-- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java index 268793a3c9..3cee86802c 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java @@ -279,6 +279,7 @@ private Object createKubernetesResourceConfig(Class Predicate onAddFilter = null; BiPredicate onUpdateFilter = null; BiPredicate onDeleteFilter = null; + Predicate genericFilter = null; if (kubeDependent != null) { if (!Arrays.equals(KubernetesDependent.DEFAULT_NAMESPACES, kubeDependent.namespaces())) { @@ -298,11 +299,14 @@ private Object createKubernetesResourceConfig(Class onDeleteFilter = createFilter(kubeDependent.onDeleteFilter(), FilterType.onDelete, kubeDependentName) .orElse(null); + genericFilter = + createFilter(kubeDependent.genericFilter(), FilterType.generic, kubeDependentName) + .orElse(null); } config = new KubernetesDependentResourceConfig(namespaces, labelSelector, configuredNS, onAddFilter, - onUpdateFilter, onDeleteFilter); + onUpdateFilter, onDeleteFilter, genericFilter); return config; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependent.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependent.java index 494d1d5f4d..6ef9aba571 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependent.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependent.java @@ -9,6 +9,7 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.Constants; +import io.javaoperatorsdk.operator.processing.event.source.filter.VoidGenericFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.VoidOnAddFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.VoidOnDeleteFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.VoidOnUpdateFilter; @@ -44,4 +45,6 @@ Class> onUpdateFilter() default VoidOnUpdateFilter.class; Class> onDeleteFilter() default VoidOnDeleteFilter.class; + + Class> genericFilter() default VoidGenericFilter.class; } 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 f5be48a8ae..ff5cc308bf 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 @@ -165,6 +165,7 @@ protected InformerEventSource createEventSource(EventSourceContext

cont onAddFilter = kubernetesDependentResourceConfig.onAddFilter(); onUpdateFilter = kubernetesDependentResourceConfig.onUpdateFilter(); onDeleteFilter = kubernetesDependentResourceConfig.onDeleteFilter(); + genericFilter = kubernetesDependentResourceConfig.genericFilter(); configureWith(kubernetesDependentResourceConfig.labelSelector(), kubernetesDependentResourceConfig.namespaces(), diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfig.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfig.java index ec4b9662a1..8fd3bc2311 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfig.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfig.java @@ -21,23 +21,26 @@ public class KubernetesDependentResourceConfig { private BiPredicate onDeleteFilter; + private Predicate genericFilter; + public KubernetesDependentResourceConfig() {} @SuppressWarnings("rawtypes") public KubernetesDependentResourceConfig(Set namespaces, String labelSelector, boolean configuredNS, Predicate onAddFilter, BiPredicate onUpdateFilter, - BiPredicate onDeleteFilter) { + BiPredicate onDeleteFilter, Predicate genericFilter) { this.namespaces = namespaces; this.labelSelector = labelSelector; this.namespacesWereConfigured = configuredNS; this.onAddFilter = onAddFilter; this.onUpdateFilter = onUpdateFilter; this.onDeleteFilter = onDeleteFilter; + this.genericFilter = genericFilter; } public KubernetesDependentResourceConfig(Set namespaces, String labelSelector) { - this(namespaces, labelSelector, true, null, null, null); + this(namespaces, labelSelector, true, null, null, null, null); } public KubernetesDependentResourceConfig setNamespaces(Set namespaces) { @@ -77,4 +80,8 @@ public BiPredicate onUpdateFilter() { public BiPredicate onDeleteFilter() { return onDeleteFilter; } + + public Predicate genericFilter() { + return genericFilter; + } }