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 55d52f42be..3ec615ffcb 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,32 +8,26 @@ import io.javaoperatorsdk.operator.api.config.DefaultResourceConfiguration; import io.javaoperatorsdk.operator.api.config.ResourceConfiguration; import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; -import io.javaoperatorsdk.operator.processing.event.ResourceID; -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; @SuppressWarnings("rawtypes") -public interface InformerConfiguration +public interface InformerConfiguration extends ResourceConfiguration { - class DefaultInformerConfiguration extends - DefaultResourceConfiguration implements InformerConfiguration { + class DefaultInformerConfiguration extends + DefaultResourceConfiguration implements InformerConfiguration { private final SecondaryToPrimaryMapper secondaryToPrimaryMapper; - private final PrimaryToSecondaryMapper

primaryToSecondaryMapper; protected DefaultInformerConfiguration(String labelSelector, Class resourceClass, SecondaryToPrimaryMapper secondaryToPrimaryMapper, - PrimaryToSecondaryMapper

primaryToSecondaryMapper, Set namespaces) { super(labelSelector, resourceClass, namespaces); this.secondaryToPrimaryMapper = Objects.requireNonNullElse(secondaryToPrimaryMapper, Mappers.fromOwnerReference()); - this.primaryToSecondaryMapper = - Objects.requireNonNullElseGet(primaryToSecondaryMapper, () -> ResourceID::fromResource); } @@ -41,21 +35,14 @@ public SecondaryToPrimaryMapper getSecondaryToPrimaryMapper() { return secondaryToPrimaryMapper; } - public PrimaryToSecondaryMapper

getPrimaryToSecondaryMapper() { - return primaryToSecondaryMapper; - } - } SecondaryToPrimaryMapper getSecondaryToPrimaryMapper(); - PrimaryToSecondaryMapper

getPrimaryToSecondaryMapper(); - @SuppressWarnings("unused") class InformerConfigurationBuilder { - private SecondaryToPrimaryMapper secondaryToPrimaryResourcesIdSet; - private PrimaryToSecondaryMapper

associatedWith; + private SecondaryToPrimaryMapper secondaryToPrimaryMapper; private Set namespaces; private String labelSelector; private final Class resourceClass; @@ -66,17 +53,10 @@ private InformerConfigurationBuilder(Class resourceClass) { public InformerConfigurationBuilder withSecondaryToPrimaryMapper( SecondaryToPrimaryMapper secondaryToPrimaryMapper) { - this.secondaryToPrimaryResourcesIdSet = secondaryToPrimaryMapper; + this.secondaryToPrimaryMapper = secondaryToPrimaryMapper; return this; } - public InformerConfigurationBuilder withPrimaryToSecondaryMapper( - PrimaryToSecondaryMapper

associatedWith) { - this.associatedWith = associatedWith; - return this; - } - - public InformerConfigurationBuilder withNamespaces(String... namespaces) { this.namespaces = namespaces != null ? Set.of(namespaces) : Collections.emptySet(); return this; @@ -93,9 +73,9 @@ public InformerConfigurationBuilder withLabelSelector(String labelSelector return this; } - public InformerConfiguration build() { + public InformerConfiguration build() { return new DefaultInformerConfiguration<>(labelSelector, resourceClass, - secondaryToPrimaryResourcesIdSet, associatedWith, + secondaryToPrimaryMapper, namespaces); } } @@ -111,12 +91,10 @@ static InformerConfigurationBuilder from(Class resourceClass) { } static InformerConfigurationBuilder from( - InformerConfiguration configuration) { + InformerConfiguration configuration) { return new InformerConfigurationBuilder(configuration.getResourceClass()) .withNamespaces(configuration.getNamespaces()) .withLabelSelector(configuration.getLabelSelector()) - .withPrimaryToSecondaryMapper( - configuration.getPrimaryToSecondaryMapper()) .withSecondaryToPrimaryMapper(configuration.getSecondaryToPrimaryMapper()); } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java index b2c2270f0b..845810c8a1 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java @@ -1,6 +1,7 @@ package io.javaoperatorsdk.operator.api.reconciler; import java.util.Optional; +import java.util.Set; import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; @@ -14,6 +15,8 @@ default Optional getSecondaryResource(Class expectedType) { return getSecondaryResource(expectedType, null); } + Set getSecondaryResources(Class expectedType); + Optional getSecondaryResource(Class expectedType, String eventSourceName); ControllerConfiguration

getControllerConfiguration(); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java index 14fa516b53..00a94390c6 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java @@ -1,11 +1,16 @@ package io.javaoperatorsdk.operator.api.reconciler; +import java.util.Collections; +import java.util.List; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.ManagedDependentResourceContext; import io.javaoperatorsdk.operator.processing.Controller; +import io.javaoperatorsdk.operator.processing.MultiResourceOwner; public class DefaultContext

implements Context

{ @@ -28,9 +33,28 @@ public Optional getRetryInfo() { return Optional.ofNullable(retryInfo); } + @Override + @SuppressWarnings("unchecked") + public Set getSecondaryResources(Class expectedType) { + return controller.getEventSourceManager().getEventSourcesFor(expectedType).stream() + .map( + es -> { + if (es instanceof MultiResourceOwner) { + return ((MultiResourceOwner) es).getSecondaryResources(primaryResource); + } else { + return es.getSecondaryResource(primaryResource) + .map(List::of) + .orElse(Collections.emptyList()); + } + }) + .flatMap(List::stream) + .collect(Collectors.toSet()); + } + @Override public Optional getSecondaryResource(Class expectedType, String eventSourceName) { - return controller.getEventSourceManager() + return controller + .getEventSourceManager() .getResourceEventSourceFor(expectedType, eventSourceName) .getSecondaryResource(primaryResource); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/MultiResourceOwner.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/MultiResourceOwner.java new file mode 100644 index 0000000000..d3cc6b3770 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/MultiResourceOwner.java @@ -0,0 +1,23 @@ +package io.javaoperatorsdk.operator.processing; + +import java.util.List; +import java.util.Optional; + +import io.fabric8.kubernetes.api.model.HasMetadata; + +public interface MultiResourceOwner extends ResourceOwner { + + default Optional getSecondaryResource(P primary) { + var list = getSecondaryResources(primary); + if (list.isEmpty()) { + return Optional.empty(); + } else if (list.size() == 1) { + return Optional.of(list.get(0)); + } else { + throw new IllegalStateException("More than 1 secondary resource related to primary"); + } + + } + + List getSecondaryResources(P primary); +} 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 baf25aba81..f9dac7bc92 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 @@ -11,6 +11,7 @@ public abstract class AbstractEventSourceHolderDependentResource> extends AbstractDependentResource implements EventSourceProvider

{ + private T eventSource; private boolean isCacheFillerEventSource; @@ -48,7 +49,6 @@ protected void onUpdated(ResourceID primaryResourceId, R updated, R actual) { } } - @SuppressWarnings("unchecked") private RecentOperationCacheFiller recentOperationCacheFiller() { return (RecentOperationCacheFiller) eventSource; 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 abf6005597..b5dd2a720a 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 @@ -20,7 +20,6 @@ import io.javaoperatorsdk.operator.processing.dependent.Matcher; import io.javaoperatorsdk.operator.processing.dependent.Matcher.Result; import io.javaoperatorsdk.operator.processing.event.ResourceID; -import io.javaoperatorsdk.operator.processing.event.source.PrimaryToSecondaryMapper; import io.javaoperatorsdk.operator.processing.event.source.SecondaryToPrimaryMapper; import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; import io.javaoperatorsdk.operator.processing.event.source.informer.Mappers; @@ -58,16 +57,11 @@ private void configureWith(String labelSelector, Set namespaces) { final var primaryResourcesRetriever = (this instanceof SecondaryToPrimaryMapper) ? (SecondaryToPrimaryMapper) this : Mappers.fromOwnerReference(); - final PrimaryToSecondaryMapper

secondaryResourceIdentifier = - (this instanceof PrimaryToSecondaryMapper) - ? (PrimaryToSecondaryMapper

) this - : ResourceID::fromResource; - InformerConfiguration ic = + InformerConfiguration ic = InformerConfiguration.from(resourceType()) .withLabelSelector(labelSelector) .withNamespaces(namespaces) .withSecondaryToPrimaryMapper(primaryResourcesRetriever) - .withPrimaryToSecondaryMapper(secondaryResourceIdentifier) .build(); configureWith(new InformerEventSource<>(ic, client)); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceManager.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceManager.java index da42a1e1e7..1ccc0ad2d9 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceManager.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceManager.java @@ -1,6 +1,7 @@ package io.javaoperatorsdk.operator.processing.event; import java.util.LinkedHashSet; +import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.locks.ReentrantLock; @@ -176,6 +177,10 @@ ResourceEventSource getResourceEventSourceFor( return getResourceEventSourceFor(dependentType, null); } + public List> getEventSourcesFor(Class dependentType) { + return eventSources.getEventSources(dependentType); + } + public ResourceEventSource getResourceEventSourceFor( Class dependentType, String qualifier) { Objects.requireNonNull(dependentType, "dependentType is Mandatory"); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSources.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSources.java index 8ba4682da3..1d2d343831 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSources.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSources.java @@ -1,10 +1,9 @@ package io.javaoperatorsdk.operator.processing.event; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; +import java.util.*; import java.util.concurrent.ConcurrentNavigableMap; import java.util.concurrent.ConcurrentSkipListMap; +import java.util.stream.Collectors; import java.util.stream.Stream; import io.fabric8.kubernetes.api.model.HasMetadata; @@ -131,4 +130,13 @@ private String keyAsString(Class dependentType, String name) { ? "(" + dependentType.getName() + ", " + name + ")" : dependentType.getName(); } + + @SuppressWarnings("unchecked") + public List> getEventSources(Class dependentType) { + final var sourcesForType = sources.get(keyFor(dependentType)); + return sourcesForType.values().stream() + .filter(ResourceEventSource.class::isInstance) + .map(es -> (ResourceEventSource) es) + .collect(Collectors.toList()); + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ExternalResourceCachingEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ExternalResourceCachingEventSource.java index f69b33dd43..b4bb0f7ef7 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ExternalResourceCachingEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ExternalResourceCachingEventSource.java @@ -1,5 +1,7 @@ package io.javaoperatorsdk.operator.processing.event; +import java.util.Optional; + import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.dependent.RecentOperationCacheFiller; import io.javaoperatorsdk.operator.processing.event.source.CachingEventSource; @@ -50,4 +52,9 @@ public synchronized void handleRecentResourceUpdate(ResourceID resourceID, R res } }); } + + @Override + public Optional getSecondaryResource(P primary) { + return cache.get(ResourceID.fromResource(primary)); + } } 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 index 2ddbf0d54c..8453651b6b 100644 --- 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 @@ -51,9 +51,4 @@ public Optional getCachedValue(ResourceID resourceID) { return cache.get(resourceID); } - @Override - public Optional getSecondaryResource(P primary) { - return cache.get(ResourceID.fromResource(primary)); - } - } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/PrimaryToSecondaryMapper.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/PrimaryToSecondaryMapper.java deleted file mode 100644 index f0ea122bda..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/PrimaryToSecondaryMapper.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.javaoperatorsdk.operator.processing.event.source; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.processing.event.ResourceID; - -@FunctionalInterface -public interface PrimaryToSecondaryMapper

{ - ResourceID toSecondaryResourceID(P primary); -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/SecondaryToPrimaryMapper.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/SecondaryToPrimaryMapper.java index 9f4db0c098..45573542c2 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/SecondaryToPrimaryMapper.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/SecondaryToPrimaryMapper.java @@ -5,6 +5,6 @@ import io.javaoperatorsdk.operator.processing.event.ResourceID; @FunctionalInterface -public interface SecondaryToPrimaryMapper { - Set toPrimaryResourceIDs(T dependentResource); +public interface SecondaryToPrimaryMapper { + Set toPrimaryResourceIDs(R dependentResource); } 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 b3b1637b94..08facecbb7 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 @@ -1,6 +1,8 @@ package io.javaoperatorsdk.operator.processing.event.source.informer; +import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -11,6 +13,7 @@ import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; import io.javaoperatorsdk.operator.api.reconciler.dependent.RecentOperationEventFilter; +import io.javaoperatorsdk.operator.processing.MultiResourceOwner; import io.javaoperatorsdk.operator.processing.event.Event; import io.javaoperatorsdk.operator.processing.event.EventHandler; import io.javaoperatorsdk.operator.processing.event.ResourceID; @@ -62,24 +65,30 @@ * @param

type of the primary resource */ public class InformerEventSource - extends ManagedInformerEventSource> - implements ResourceEventHandler, RecentOperationEventFilter { + extends ManagedInformerEventSource> + implements MultiResourceOwner, ResourceEventHandler, RecentOperationEventFilter { private static final Logger log = LoggerFactory.getLogger(InformerEventSource.class); - private final InformerConfiguration configuration; + private final InformerConfiguration configuration; // always called from a synchronized method 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; public InformerEventSource( - InformerConfiguration configuration, EventSourceContext

context) { + InformerConfiguration configuration, EventSourceContext

context) { super(context.getClient().resources(configuration.getResourceClass()), configuration); this.configuration = configuration; + primaryToSecondaryIndex = + new PrimaryToSecondaryIndex<>(configuration.getSecondaryToPrimaryMapper()); } - public InformerEventSource(InformerConfiguration configuration, KubernetesClient client) { + public InformerEventSource(InformerConfiguration configuration, KubernetesClient client) { super(client.resources(configuration.getResourceClass()), configuration); this.configuration = configuration; + primaryToSecondaryIndex = + new PrimaryToSecondaryIndex<>(configuration.getSecondaryToPrimaryMapper()); } @Override @@ -87,6 +96,7 @@ public void onAdd(R resource) { if (log.isDebugEnabled()) { log.debug("On add event received for resource id: {}", ResourceID.fromResource(resource)); } + primaryToSecondaryIndex.onAddOrUpdate(resource); onAddOrUpdate("add", resource, () -> InformerEventSource.super.onAdd(resource)); } @@ -95,10 +105,21 @@ public void onUpdate(R oldObject, R newObject) { if (log.isDebugEnabled()) { log.debug("On update event received for resource id: {}", ResourceID.fromResource(newObject)); } + primaryToSecondaryIndex.onAddOrUpdate(newObject); onAddOrUpdate("update", newObject, () -> InformerEventSource.super.onUpdate(oldObject, newObject)); } + @Override + public void onDelete(R resource, boolean b) { + if (log.isDebugEnabled()) { + log.debug("On delete event received for resource id: {}", ResourceID.fromResource(resource)); + } + primaryToSecondaryIndex.onDelete(resource); + super.onDelete(resource, b); + propagateEvent(resource); + } + private synchronized void onAddOrUpdate(String operation, R newObject, Runnable superOnOp) { var resourceID = ResourceID.fromResource(newObject); if (eventRecorder.isRecordingFor(resourceID)) { @@ -106,7 +127,7 @@ private synchronized void onAddOrUpdate(String operation, R newObject, Runnable eventRecorder.recordEvent(newObject); return; } - if (temporalCacheHasResourceWithVersionAs(newObject)) { + if (temporaryCacheHasResourceWithSameVersionAs(newObject)) { log.debug( "Skipping event propagation for {}, since was a result of a reconcile action. Resource ID: {}", operation, @@ -122,13 +143,16 @@ private synchronized void onAddOrUpdate(String operation, R newObject, Runnable } } - @Override - public void onDelete(R resource, boolean b) { - if (log.isDebugEnabled()) { - log.debug("On delete event received for resource id: {}", ResourceID.fromResource(resource)); - } - super.onDelete(resource, b); - propagateEvent(resource); + private boolean temporaryCacheHasResourceWithSameVersionAs(R resource) { + var resourceID = ResourceID.fromResource(resource); + var res = temporaryResourceCache.getResourceFromCache(resourceID); + return res.map(r -> { + boolean resVersionsEqual = r.getMetadata().getResourceVersion() + .equals(resource.getMetadata().getResourceVersion()); + log.debug("Resource found in temporal cache for id: {} resource versions equal: {}", + resourceID, resVersionsEqual); + return resVersionsEqual; + }).orElse(false); } private void propagateEvent(R object) { @@ -152,20 +176,15 @@ private void propagateEvent(R object) { }); } - /** - * Retrieves the informed resource associated with the specified primary resource as defined by - * the function provided when this InformerEventSource was created - * - * @param resource the primary resource we want to retrieve the associated resource for - * @return the informed resource associated with the specified primary resource - */ @Override - public Optional getSecondaryResource(P resource) { - final var id = configuration.getPrimaryToSecondaryMapper().toSecondaryResourceID(resource); - return get(id); + public List getSecondaryResources(P primary) { + var secondaryIDs = + primaryToSecondaryIndex.getSecondaryResources(ResourceID.fromResource(primary)); + return secondaryIDs.stream().map(this::get).flatMap(Optional::stream) + .collect(Collectors.toList()); } - public InformerConfiguration getConfiguration() { + public InformerConfiguration getConfiguration() { return configuration; } @@ -183,6 +202,7 @@ public synchronized void handleRecentResourceCreate(ResourceID resourceID, R res } private void handleRecentCreateOrUpdate(R resource, Runnable runnable) { + primaryToSecondaryIndex.onAddOrUpdate(resource); if (eventRecorder.isRecordingFor(ResourceID.fromResource(resource))) { handleRecentResourceOperationAndStopEventRecording(resource); } else { 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 f7007072e9..606b0bc962 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 @@ -97,26 +97,11 @@ public Optional get(ResourceID resourceID) { } } - @Override - public abstract Optional getSecondaryResource(P primary); - @Override public Optional getCachedValue(ResourceID resourceID) { return get(resourceID); } - protected boolean temporalCacheHasResourceWithVersionAs(R resource) { - var resourceID = ResourceID.fromResource(resource); - var res = temporaryResourceCache.getResourceFromCache(resourceID); - return res.map(r -> { - boolean resVersionsEqual = r.getMetadata().getResourceVersion() - .equals(resource.getMetadata().getResourceVersion()); - log.debug("Resource found in temporal cache for id: {} resource versions equal: {}", - resourceID, resVersionsEqual); - return resVersionsEqual; - }).orElse(false); - } - @Override public Stream list(String namespace, Predicate predicate) { return manager().list(namespace, predicate); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/PrimaryToSecondaryIndex.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/PrimaryToSecondaryIndex.java new file mode 100644 index 0000000000..d83b60827b --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/PrimaryToSecondaryIndex.java @@ -0,0 +1,47 @@ +package io.javaoperatorsdk.operator.processing.event.source.informer; + +import java.util.*; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.processing.event.source.SecondaryToPrimaryMapper; + +class PrimaryToSecondaryIndex { + + private SecondaryToPrimaryMapper secondaryToPrimaryMapper; + private Map> index = new HashMap<>(); + + public PrimaryToSecondaryIndex(SecondaryToPrimaryMapper secondaryToPrimaryMapper) { + this.secondaryToPrimaryMapper = secondaryToPrimaryMapper; + } + + public synchronized void onAddOrUpdate(R resource) { + Set primaryResources = secondaryToPrimaryMapper.toPrimaryResourceIDs(resource); + primaryResources.forEach( + primaryResource -> { + var resourceSet = index.computeIfAbsent(primaryResource, pr -> new HashSet<>()); + resourceSet.add(ResourceID.fromResource(resource)); + }); + } + + public synchronized void onDelete(R resource) { + Set primaryResources = secondaryToPrimaryMapper.toPrimaryResourceIDs(resource); + primaryResources.forEach( + primaryResource -> { + var secondaryResources = index.get(primaryResource); + secondaryResources.remove(ResourceID.fromResource(resource)); + if (secondaryResources.isEmpty()) { + index.remove(primaryResource); + } + }); + } + + public synchronized Set getSecondaryResources(ResourceID primary) { + var resourceIDs = index.get(primary); + if (resourceIDs == null) { + return Collections.emptySet(); + } else { + return Collections.unmodifiableSet(resourceIDs); + } + } +} 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 96146789f6..7ac6779523 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 @@ -13,6 +13,7 @@ import io.fabric8.kubernetes.client.dsl.FilterWatchListMultiDeletable; import io.fabric8.kubernetes.client.dsl.MixedOperation; import io.fabric8.kubernetes.client.informers.SharedIndexInformer; +import io.fabric8.kubernetes.client.informers.cache.Indexer; import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; import io.javaoperatorsdk.operator.processing.event.EventHandler; import io.javaoperatorsdk.operator.processing.event.ResourceID; @@ -38,7 +39,7 @@ class InformerEventSourceTest { mock(FilterWatchListMultiDeletable.class); private FilterWatchListDeletable labeledResourceClientMock = mock(FilterWatchListDeletable.class); private SharedIndexInformer informer = mock(SharedIndexInformer.class); - private InformerConfiguration informerConfiguration = + private InformerConfiguration informerConfiguration = mock(InformerConfiguration.class); @BeforeEach @@ -48,6 +49,7 @@ void setup() { when(specificResourceClientMock.withLabelSelector((String) null)) .thenReturn(labeledResourceClientMock); when(labeledResourceClientMock.runnableInformer(0)).thenReturn(informer); + when(informer.getIndexer()).thenReturn(mock(Indexer.class)); when(informerConfiguration.getSecondaryToPrimaryMapper()) .thenReturn(mock(SecondaryToPrimaryMapper.class)); 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 new file mode 100644 index 0000000000..ca73b135a7 --- /dev/null +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/PrimaryToSecondaryIndexTest.java @@ -0,0 +1,95 @@ +package io.javaoperatorsdk.operator.processing.event.source.informer; + +import java.util.Set; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.processing.event.source.SecondaryToPrimaryMapper; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class PrimaryToSecondaryIndexTest { + + private SecondaryToPrimaryMapper secondaryToPrimaryMapperMock = + mock(SecondaryToPrimaryMapper.class); + private PrimaryToSecondaryIndex primaryToSecondaryIndex = + new PrimaryToSecondaryIndex<>(secondaryToPrimaryMapperMock); + + private ResourceID primaryID1 = new ResourceID("id1", "default"); + private ResourceID primaryID2 = new ResourceID("id2", "default"); + private ConfigMap secondary1 = secondary("secondary1"); + private ConfigMap secondary2 = secondary("secondary2"); + + @BeforeEach + void setup() { + when(secondaryToPrimaryMapperMock.toPrimaryResourceIDs(any())) + .thenReturn(Set.of(primaryID1, primaryID2)); + } + + @Test + void returnsEmptySetOnEmptyIndex() { + var res = primaryToSecondaryIndex.getSecondaryResources(ResourceID.fromResource(secondary1)); + assertThat(res).isEmpty(); + } + + @Test + void indexesNewResources() { + primaryToSecondaryIndex.onAddOrUpdate(secondary1); + + var secondaryResources1 = primaryToSecondaryIndex.getSecondaryResources(primaryID1); + var secondaryResources2 = primaryToSecondaryIndex.getSecondaryResources(primaryID2); + + assertThat(secondaryResources1).containsOnly(ResourceID.fromResource(secondary1)); + assertThat(secondaryResources2).containsOnly(ResourceID.fromResource(secondary1)); + } + + @Test + void indexesAdditionalResources() { + primaryToSecondaryIndex.onAddOrUpdate(secondary1); + primaryToSecondaryIndex.onAddOrUpdate(secondary2); + + var secondaryResources1 = primaryToSecondaryIndex.getSecondaryResources(primaryID1); + var secondaryResources2 = primaryToSecondaryIndex.getSecondaryResources(primaryID2); + + assertThat(secondaryResources1).containsOnly(ResourceID.fromResource(secondary1), + ResourceID.fromResource(secondary2)); + assertThat(secondaryResources2).containsOnly(ResourceID.fromResource(secondary1), + ResourceID.fromResource(secondary2)); + } + + @Test + void removingResourceFromIndex() { + primaryToSecondaryIndex.onAddOrUpdate(secondary1); + primaryToSecondaryIndex.onAddOrUpdate(secondary2); + primaryToSecondaryIndex.onDelete(secondary1); + + var secondaryResources1 = primaryToSecondaryIndex.getSecondaryResources(primaryID1); + var secondaryResources2 = primaryToSecondaryIndex.getSecondaryResources(primaryID2); + + assertThat(secondaryResources1).containsOnly(ResourceID.fromResource(secondary2)); + assertThat(secondaryResources2).containsOnly(ResourceID.fromResource(secondary2)); + + primaryToSecondaryIndex.onDelete(secondary2); + + secondaryResources1 = primaryToSecondaryIndex.getSecondaryResources(primaryID1); + secondaryResources2 = primaryToSecondaryIndex.getSecondaryResources(primaryID2); + + assertThat(secondaryResources1).isEmpty(); + assertThat(secondaryResources2).isEmpty(); + } + + ConfigMap secondary(String name) { + ConfigMap configMap = new ConfigMap(); + configMap.setMetadata(new ObjectMeta()); + configMap.getMetadata().setName(name); + configMap.getMetadata().setNamespace("default"); + return configMap; + } +} diff --git a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/OperatorExtension.java b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/OperatorExtension.java index 7602d40295..d95e9466a7 100644 --- a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/OperatorExtension.java +++ b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/OperatorExtension.java @@ -76,7 +76,7 @@ public Reconciler getFirstReconciler() { return reconcilers().findFirst().orElseThrow(); } - public T getControllerOfType(Class type) { + public T getReconcilerOfType(Class type) { return reconcilers() .filter(type::isInstance) .map(type::cast) diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/ControllerExecutionIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/ControllerExecutionIT.java index 67bc908482..7aeae40622 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/ControllerExecutionIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/ControllerExecutionIT.java @@ -23,7 +23,7 @@ class ControllerExecutionIT { @Test void configMapGetsCreatedForTestCustomResource() { - operator.getControllerOfType(TestReconciler.class).setUpdateStatus(true); + operator.getReconcilerOfType(TestReconciler.class).setUpdateStatus(true); TestCustomResource resource = TestUtils.testCustomResource(); operator.create(TestCustomResource.class, resource); @@ -35,8 +35,8 @@ void configMapGetsCreatedForTestCustomResource() { @Test void patchesStatusForTestCustomResource() { - operator.getControllerOfType(TestReconciler.class).setPatchStatus(true); - operator.getControllerOfType(TestReconciler.class).setUpdateStatus(true); + operator.getReconcilerOfType(TestReconciler.class).setPatchStatus(true); + operator.getReconcilerOfType(TestReconciler.class).setUpdateStatus(true); TestCustomResource resource = TestUtils.testCustomResource(); operator.create(TestCustomResource.class, resource); @@ -46,7 +46,7 @@ void patchesStatusForTestCustomResource() { @Test void eventIsSkippedChangedOnMetadataOnlyUpdate() { - operator.getControllerOfType(TestReconciler.class).setUpdateStatus(false); + operator.getReconcilerOfType(TestReconciler.class).setUpdateStatus(false); TestCustomResource resource = TestUtils.testCustomResource(); operator.create(TestCustomResource.class, resource); @@ -57,7 +57,7 @@ void eventIsSkippedChangedOnMetadataOnlyUpdate() { @Test void cleanupExecuted() { - operator.getControllerOfType(TestReconciler.class).setUpdateStatus(true); + operator.getReconcilerOfType(TestReconciler.class).setUpdateStatus(true); TestCustomResource resource = TestUtils.testCustomResource(); resource = operator.create(TestCustomResource.class, resource); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleSecondaryEventSourceIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleSecondaryEventSourceIT.java new file mode 100644 index 0000000000..9168b47164 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleSecondaryEventSourceIT.java @@ -0,0 +1,67 @@ +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.ConfigMap; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.junit.OperatorExtension; +import io.javaoperatorsdk.operator.sample.multiplesecondaryeventsource.MultipleSecondaryEventSourceCustomResource; +import io.javaoperatorsdk.operator.sample.multiplesecondaryeventsource.MultipleSecondaryEventSourceReconciler; + +import static org.awaitility.Awaitility.await; + +class MultipleSecondaryEventSourceIT { + + public static final String TEST_RESOURCE_NAME = "testresource"; + @RegisterExtension + OperatorExtension operator = + OperatorExtension.builder().withReconciler(MultipleSecondaryEventSourceReconciler.class) + .build(); + + @Test + void receivingPeriodicEvents() { + MultipleSecondaryEventSourceCustomResource resource = createTestCustomResource(); + + operator.create(MultipleSecondaryEventSourceCustomResource.class, resource); + + var reconciler = operator.getReconcilerOfType(MultipleSecondaryEventSourceReconciler.class); + + await().pollDelay(Duration.ofMillis(300)) + .until(() -> reconciler.getNumberOfExecutions() <= 3); + + int numberOfInitialExecutions = reconciler.getNumberOfExecutions(); + + updateConfigMap(resource, 1); + + await().pollDelay(Duration.ofMillis(300)) + .until(() -> reconciler.getNumberOfExecutions() == numberOfInitialExecutions + 1); + + updateConfigMap(resource, 2); + + await().pollDelay(Duration.ofMillis(300)) + .until(() -> reconciler.getNumberOfExecutions() == numberOfInitialExecutions + 2); + } + + private void updateConfigMap(MultipleSecondaryEventSourceCustomResource resource, int number) { + ConfigMap map1 = operator.get(ConfigMap.class, + number == 1 ? MultipleSecondaryEventSourceReconciler.getName1(resource) + : MultipleSecondaryEventSourceReconciler.getName2(resource)); + map1.getData().put("value2", "value2"); + operator.replace(ConfigMap.class, map1); + } + + public MultipleSecondaryEventSourceCustomResource createTestCustomResource() { + MultipleSecondaryEventSourceCustomResource resource = + new MultipleSecondaryEventSourceCustomResource(); + resource.setMetadata( + new ObjectMetaBuilder() + .withName(TEST_RESOURCE_NAME) + .withNamespace(operator.getNamespace()) + .build()); + return resource; + } + +} 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 aa50e5fa4f..acfc109f72 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 @@ -101,7 +101,7 @@ private ConfigMap createConfigMap(CreateUpdateEventFilterTestCustomResource reso @Override public Map prepareEventSources( EventSourceContext context) { - InformerConfiguration informerConfiguration = + InformerConfiguration informerConfiguration = InformerConfiguration.from(context, ConfigMap.class) .withLabelSelector("integrationtest = " + this.getClass().getSimpleName()) .build(); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/informereventsource/InformerEventSourceTestCustomReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/informereventsource/InformerEventSourceTestCustomReconciler.java index ebcbf85994..a4aa0f4ddd 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/informereventsource/InformerEventSourceTestCustomReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/informereventsource/InformerEventSourceTestCustomReconciler.java @@ -41,7 +41,7 @@ public class InformerEventSourceTestCustomReconciler public Map prepareEventSources( EventSourceContext context) { - InformerConfiguration config = + InformerConfiguration config = InformerConfiguration.from(context, ConfigMap.class) .withSecondaryToPrimaryMapper(Mappers.fromAnnotation(RELATED_RESOURCE_NAME)) .build(); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplesecondaryeventsource/MultipleSecondaryEventSourceCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplesecondaryeventsource/MultipleSecondaryEventSourceCustomResource.java new file mode 100644 index 0000000000..95330ef8b3 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplesecondaryeventsource/MultipleSecondaryEventSourceCustomResource.java @@ -0,0 +1,17 @@ +package io.javaoperatorsdk.operator.sample.multiplesecondaryeventsource; + +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.Kind; +import io.fabric8.kubernetes.model.annotation.ShortNames; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("sample.javaoperatorsdk") +@Version("v1") +@Kind("MaxIntervalTestCustomResource") +@ShortNames("mit") +public class MultipleSecondaryEventSourceCustomResource + extends CustomResource + implements Namespaced { +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplesecondaryeventsource/MultipleSecondaryEventSourceReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplesecondaryeventsource/MultipleSecondaryEventSourceReconciler.java new file mode 100644 index 0000000000..ce27f0f363 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplesecondaryeventsource/MultipleSecondaryEventSourceReconciler.java @@ -0,0 +1,106 @@ +package io.javaoperatorsdk.operator.sample.multiplesecondaryeventsource; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ObjectMeta; +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.ResourceID; +import io.javaoperatorsdk.operator.processing.event.source.EventSource; +import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; +import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; + +@ControllerConfiguration +public class MultipleSecondaryEventSourceReconciler + implements Reconciler, TestExecutionInfoProvider, + EventSourceInitializer, KubernetesClientAware { + + private final AtomicInteger numberOfExecutions = new AtomicInteger(0); + private KubernetesClient client; + + @Override + public UpdateControl reconcile( + MultipleSecondaryEventSourceCustomResource resource, + Context context) { + numberOfExecutions.addAndGet(1); + + if (client.configMaps().inNamespace(resource.getMetadata().getNamespace()) + .withName(getName1(resource)).get() == null) { + client.configMaps().inNamespace(resource.getMetadata().getNamespace()) + .createOrReplace(configMap(getName1(resource), resource)); + } + if (client.configMaps().inNamespace(resource.getMetadata().getNamespace()) + .withName(getName2(resource)).get() == null) { + client.configMaps().inNamespace(resource.getMetadata().getNamespace()) + .createOrReplace(configMap(getName2(resource), resource)); + } + + if (numberOfExecutions.get() >= 3) { + if (context.getSecondaryResources(ConfigMap.class).size() != 2) { + throw new IllegalStateException("There should be 2 related config maps"); + } + } + return UpdateControl.noUpdate(); + } + + public static String getName1(MultipleSecondaryEventSourceCustomResource resource) { + return resource.getMetadata().getName() + "1"; + } + + public static String getName2(MultipleSecondaryEventSourceCustomResource resource) { + return resource.getMetadata().getName() + "2"; + } + + public int getNumberOfExecutions() { + return numberOfExecutions.get(); + } + + @Override + public Map prepareEventSources( + EventSourceContext context) { + + + var config = InformerConfiguration.from(context, ConfigMap.class) + .withNamespaces(context.getControllerConfiguration().getNamespaces()) + .withLabelSelector("multisecondary") + .withSecondaryToPrimaryMapper(s -> { + var name = + s.getMetadata().getName().subSequence(0, s.getMetadata().getName().length() - 1); + return Set.of(new ResourceID(name.toString(), s.getMetadata().getNamespace())); + }).build(); + InformerEventSource configMapEventSource = + new InformerEventSource(config, + context); + return EventSourceInitializer.nameEventSources(configMapEventSource); + } + + ConfigMap configMap(String name, MultipleSecondaryEventSourceCustomResource resource) { + ConfigMap configMap = new ConfigMap(); + configMap.setMetadata(new ObjectMeta()); + configMap.getMetadata().setName(name); + configMap.getMetadata().setNamespace(resource.getMetadata().getNamespace()); + configMap.setData(new HashMap<>()); + configMap.getData().put(name, name); + HashMap labels = new HashMap<>(); + labels.put("multisecondary", "true"); + configMap.getMetadata().setLabels(labels); + configMap.addOwnerReference(resource); + return configMap; + } + + @Override + public KubernetesClient getKubernetesClient() { + return client; + } + + @Override + public void setKubernetesClient(KubernetesClient kubernetesClient) { + this.client = kubernetesClient; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplesecondaryeventsource/MultipleSecondaryEventSourceStatus.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplesecondaryeventsource/MultipleSecondaryEventSourceStatus.java new file mode 100644 index 0000000000..2a78bf0531 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplesecondaryeventsource/MultipleSecondaryEventSourceStatus.java @@ -0,0 +1,5 @@ +package io.javaoperatorsdk.operator.sample.multiplesecondaryeventsource; + +public class MultipleSecondaryEventSourceStatus { + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/ConfigMapDependentResource1.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/ConfigMapDependentResource1.java index c0469c6a7c..19fd28b631 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/ConfigMapDependentResource1.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/ConfigMapDependentResource1.java @@ -9,13 +9,10 @@ import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUKubernetesDependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; -import io.javaoperatorsdk.operator.processing.event.ResourceID; -import io.javaoperatorsdk.operator.processing.event.source.PrimaryToSecondaryMapper; @KubernetesDependent(labelSelector = "dependent = cm1") public class ConfigMapDependentResource1 extends - CRUKubernetesDependentResource - implements PrimaryToSecondaryMapper { + CRUKubernetesDependentResource { public ConfigMapDependentResource1() { super(ConfigMap.class); @@ -45,9 +42,4 @@ protected ConfigMap desired(OrderedManagedDependentCustomResource primary, return configMap; } - @Override - public ResourceID toSecondaryResourceID(OrderedManagedDependentCustomResource primary) { - return new ResourceID(primary.getMetadata().getName() + "1", - primary.getMetadata().getNamespace()); - } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/ConfigMapDependentResource2.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/ConfigMapDependentResource2.java index 40e55d4589..2bffdfa8c1 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/ConfigMapDependentResource2.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/ConfigMapDependentResource2.java @@ -9,13 +9,10 @@ import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUKubernetesDependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; -import io.javaoperatorsdk.operator.processing.event.ResourceID; -import io.javaoperatorsdk.operator.processing.event.source.PrimaryToSecondaryMapper; @KubernetesDependent(labelSelector = "dependent = cm2") public class ConfigMapDependentResource2 extends - CRUKubernetesDependentResource - implements PrimaryToSecondaryMapper { + CRUKubernetesDependentResource { public ConfigMapDependentResource2() { super(ConfigMap.class); @@ -45,10 +42,4 @@ protected ConfigMap desired(OrderedManagedDependentCustomResource primary, return configMap; } - @Override - public ResourceID toSecondaryResourceID(OrderedManagedDependentCustomResource primary) { - return new ResourceID(primary.getMetadata().getName() + "2", - primary.getMetadata().getNamespace()); - } - } diff --git a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/dependent/SecretDependentResource.java b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/dependent/SecretDependentResource.java index 7f39d09130..b1c516df8e 100644 --- a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/dependent/SecretDependentResource.java +++ b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/dependent/SecretDependentResource.java @@ -10,12 +10,10 @@ import io.javaoperatorsdk.operator.processing.dependent.Creator; import io.javaoperatorsdk.operator.processing.dependent.Matcher.Result; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; -import io.javaoperatorsdk.operator.processing.event.ResourceID; -import io.javaoperatorsdk.operator.processing.event.source.PrimaryToSecondaryMapper; import io.javaoperatorsdk.operator.sample.MySQLSchema; public class SecretDependentResource extends KubernetesDependentResource - implements PrimaryToSecondaryMapper, Creator { + implements Creator { public static final String SECRET_FORMAT = "%s-secret"; public static final String USERNAME_FORMAT = "%s-user"; @@ -57,10 +55,4 @@ public Result match(Secret actual, MySQLSchema primary, Context - implements Creator, Updater { +public class DeploymentDependentResource + extends CRUKubernetesDependentResource { public DeploymentDependentResource() { super(Deployment.class); diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappReconciler.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappReconciler.java index f317d17f91..697f72cfdd 100644 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappReconciler.java +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappReconciler.java @@ -29,7 +29,6 @@ import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; import io.javaoperatorsdk.operator.processing.event.ResourceID; import io.javaoperatorsdk.operator.processing.event.source.EventSource; -import io.javaoperatorsdk.operator.processing.event.source.PrimaryToSecondaryMapper; import io.javaoperatorsdk.operator.processing.event.source.SecondaryToPrimaryMapper; import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; @@ -59,18 +58,9 @@ public Map prepareEventSources(EventSourceContext c .map(ResourceID::fromResource) .collect(Collectors.toSet()); - /* - * We retrieve the Tomcat instance associated with out Webapp from its spec - */ - final PrimaryToSecondaryMapper tomcatFromWebAppSpec = - (Webapp webapp) -> new ResourceID( - webapp.getSpec().getTomcat(), - webapp.getMetadata().getNamespace()); - - InformerConfiguration configuration = + InformerConfiguration configuration = InformerConfiguration.from(context, Tomcat.class) .withSecondaryToPrimaryMapper(webappsMatchingTomcatName) - .withPrimaryToSecondaryMapper(tomcatFromWebAppSpec) .build(); return EventSourceInitializer .nameEventSources(new InformerEventSource<>(configuration, context)); diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ConfigMapDependentResource.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ConfigMapDependentResource.java index 35ceda38c7..bba817436c 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ConfigMapDependentResource.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ConfigMapDependentResource.java @@ -12,16 +12,14 @@ import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUKubernetesDependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; -import io.javaoperatorsdk.operator.processing.event.ResourceID; -import io.javaoperatorsdk.operator.processing.event.source.PrimaryToSecondaryMapper; import static io.javaoperatorsdk.operator.sample.Utils.configMapName; import static io.javaoperatorsdk.operator.sample.Utils.deploymentName; +import static io.javaoperatorsdk.operator.sample.WebPageManagedDependentsReconciler.SELECTOR; // this annotation only activates when using managed dependents and is not otherwise needed -@KubernetesDependent(labelSelector = WebPageManagedDependentsReconciler.SELECTOR) -class ConfigMapDependentResource extends CRUKubernetesDependentResource - implements PrimaryToSecondaryMapper { +@KubernetesDependent(labelSelector = SELECTOR) +class ConfigMapDependentResource extends CRUKubernetesDependentResource { private static final Logger log = LoggerFactory.getLogger(ConfigMapDependentResource.class); @@ -33,11 +31,14 @@ public ConfigMapDependentResource() { protected ConfigMap desired(WebPage webPage, Context context) { Map data = new HashMap<>(); data.put("index.html", webPage.getSpec().getHtml()); + Map labels = new HashMap<>(); + labels.put(SELECTOR, "true"); return new ConfigMapBuilder() .withMetadata( new ObjectMetaBuilder() .withName(configMapName(webPage)) .withNamespace(webPage.getMetadata().getNamespace()) + .withLabels(labels) .build()) .withData(data) .build(); @@ -57,9 +58,4 @@ public ConfigMap update(ConfigMap actual, ConfigMap target, WebPage primary, .delete(); return res; } - - @Override - public ResourceID toSecondaryResourceID(WebPage primary) { - return new ResourceID(configMapName(primary), primary.getMetadata().getNamespace()); - } } diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java index ee6322e058..80a41e24d8 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java @@ -1,5 +1,8 @@ package io.javaoperatorsdk.operator.sample; +import java.util.HashMap; +import java.util.Map; + import io.fabric8.kubernetes.api.model.ConfigMapVolumeSourceBuilder; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.javaoperatorsdk.operator.api.reconciler.Context; @@ -9,6 +12,7 @@ import static io.javaoperatorsdk.operator.ReconcilerUtils.loadYaml; import static io.javaoperatorsdk.operator.sample.Utils.configMapName; import static io.javaoperatorsdk.operator.sample.Utils.deploymentName; +import static io.javaoperatorsdk.operator.sample.WebPageManagedDependentsReconciler.SELECTOR; // this annotation only activates when using managed dependents and is not otherwise needed @KubernetesDependent(labelSelector = WebPageManagedDependentsReconciler.SELECTOR) @@ -20,10 +24,13 @@ public DeploymentDependentResource() { @Override protected Deployment desired(WebPage webPage, Context context) { + Map labels = new HashMap<>(); + labels.put(SELECTOR, "true"); var deploymentName = deploymentName(webPage); Deployment deployment = loadYaml(Deployment.class, getClass(), "deployment.yaml"); deployment.getMetadata().setName(deploymentName); deployment.getMetadata().setNamespace(webPage.getMetadata().getNamespace()); + deployment.getMetadata().setLabels(labels); deployment.getSpec().getSelector().getMatchLabels().put("app", deploymentName); deployment @@ -40,6 +47,7 @@ protected Deployment desired(WebPage webPage, Context context) { .get(0) .setConfigMap( new ConfigMapVolumeSourceBuilder().withName(configMapName(webPage)).build()); + return deployment; } } diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/IngressDependentResource.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/IngressDependentResource.java index a6de51919a..074f36cffb 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/IngressDependentResource.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/IngressDependentResource.java @@ -3,9 +3,12 @@ import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; import static io.javaoperatorsdk.operator.sample.Utils.*; +// this annotation only activates when using managed dependents and is not otherwise needed +@KubernetesDependent(labelSelector = WebPageManagedDependentsReconciler.SELECTOR) public class IngressDependentResource extends CRUKubernetesDependentResource { public IngressDependentResource() { diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java index b501fdb405..84d670cfc5 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java @@ -11,6 +11,7 @@ import static io.javaoperatorsdk.operator.ReconcilerUtils.loadYaml; import static io.javaoperatorsdk.operator.sample.Utils.deploymentName; import static io.javaoperatorsdk.operator.sample.Utils.serviceName; +import static io.javaoperatorsdk.operator.sample.WebPageManagedDependentsReconciler.SELECTOR; // this annotation only activates when using managed dependents and is not otherwise needed @KubernetesDependent(labelSelector = WebPageManagedDependentsReconciler.SELECTOR) @@ -22,9 +23,12 @@ public ServiceDependentResource() { @Override protected Service desired(WebPage webPage, Context context) { + Map serviceLabels = new HashMap<>(); + serviceLabels.put(SELECTOR, "true"); Service service = loadYaml(Service.class, getClass(), "service.yaml"); service.getMetadata().setName(serviceName(webPage)); service.getMetadata().setNamespace(webPage.getMetadata().getNamespace()); + service.getMetadata().setLabels(serviceLabels); Map labels = new HashMap<>(); labels.put("app", deploymentName(webPage)); service.getSpec().setSelector(labels); diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java index 7f8f38afb8..5bccefb56e 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java @@ -66,8 +66,7 @@ public Map prepareEventSources(EventSourceContext new InformerEventSource<>(InformerConfiguration.from(context, Ingress.class) .withLabelSelector(LOW_LEVEL_LABEL_KEY) .build(), context); - return EventSourceInitializer.nameEventSources(configMapEventSource, - deploymentEventSource, + return EventSourceInitializer.nameEventSources(configMapEventSource, deploymentEventSource, serviceEventSource, ingressEventSource); } @@ -208,7 +207,7 @@ private Deployment makeDesiredDeployment(WebPage webPage, String deploymentName, private ConfigMap makeDesiredHtmlConfigMap(String ns, String configMapName, WebPage webPage) { Map data = new HashMap<>(); - data.put("index.html", webPage.getSpec().getHtml()); + data.put(INDEX_HTML, webPage.getSpec().getHtml()); ConfigMap configMap = new ConfigMapBuilder() .withMetadata(