From 26c9b87bf4a234bf56263e0352589eea13960dd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Wed, 28 Sep 2022 10:43:44 +0200 Subject: [PATCH 1/8] feat: decouple event source from cache + list discriminator (#1378) --- .../AnnotationControllerConfiguration.java | 29 +++-- .../operator/api/reconciler/Context.java | 15 ++- .../api/reconciler/DefaultContext.java | 12 ++ .../api/reconciler/ResourceDiscriminator.java | 11 ++ .../ResourceIDMatcherDiscriminator.java | 25 ++++ .../dependent/DependentResource.java | 16 ++- .../operator/processing/ResourceOwner.java | 26 ---- .../dependent/AbstractDependentResource.java | 21 ++- ...actEventSourceHolderDependentResource.java | 9 ++ .../AbstractCachingDependentResource.java | 29 ----- .../AbstractPollingDependentResource.java | 6 +- .../AbstractSimpleDependentResource.java | 2 +- .../kubernetes/KubernetesDependent.java | 4 + .../KubernetesDependentResource.java | 34 ++--- .../KubernetesDependentResourceConfig.java | 22 ++-- .../workflow/DependentResourceNode.java | 5 + .../workflow/WorkflowCleanupExecutor.java | 11 +- .../workflow/WorkflowReconcileExecutor.java | 12 +- .../processing/event/EventProcessor.java | 11 +- .../processing/event/EventSourceManager.java | 19 ++- .../event/EventSourceRetriever.java | 18 +++ .../processing/event/EventSources.java | 5 +- .../operator/processing/event/ResourceID.java | 6 + .../event/source/ResourceEventSource.java | 11 +- .../informer/ManagedInformerEventSource.java | 4 +- .../ControllerConfigurationOverriderTest.java | 5 - .../AbstractDependentResourceTest.java | 4 +- .../dependent/EmptyTestDependentResource.java | 7 - .../AbstractSimpleDependentResourceTest.java | 4 +- .../AbstractWorkflowExecutorTest.java | 11 -- .../workflow/WorkflowCleanupExecutorTest.java | 11 +- .../WorkflowReconcileExecutorTest.java | 46 ++++--- .../operator/IndexDiscriminatorIT.java | 77 +++++++++++ .../IndexDiscriminator.java | 41 ++++++ .../IndexDiscriminatorTestCustomResource.java | 16 +++ .../IndexDiscriminatorTestDRConfigMap.java | 38 ++++++ .../IndexDiscriminatorTestReconciler.java | 120 ++++++++++++++++++ .../IndexDiscriminatorTestSpec.java | 15 +++ .../IndexDiscriminatorTestStatus.java | 5 + .../MultipleDependentResourceConfigMap.java | 1 - .../MultipleDependentResourceReconciler.java | 44 ++++--- .../ConfigMapDependentResource1.java | 12 +- .../ConfigMapDependentResource2.java | 12 +- .../StandaloneDependentTestReconciler.java | 2 +- .../WebPageDependentsWorkflowReconciler.java | 4 +- ...WebPageStandaloneDependentsReconciler.java | 2 +- 46 files changed, 620 insertions(+), 220 deletions(-) create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceDiscriminator.java create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceIDMatcherDiscriminator.java delete mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/ResourceOwner.java delete mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractCachingDependentResource.java create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceRetriever.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/IndexDiscriminatorIT.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminator.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestCustomResource.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestDRConfigMap.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestReconciler.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestSpec.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestStatus.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 b5e3fffcf0..dbd09a32cc 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 @@ -17,9 +17,8 @@ import io.javaoperatorsdk.operator.OperatorException; import io.javaoperatorsdk.operator.ReconcilerUtils; import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec; -import io.javaoperatorsdk.operator.api.reconciler.Constants; +import io.javaoperatorsdk.operator.api.reconciler.*; import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; -import io.javaoperatorsdk.operator.api.reconciler.Reconciler; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; @@ -54,9 +53,8 @@ public AnnotationControllerConfiguration(Reconciler

reconciler) { this.reconciler = reconciler; this.annotation = reconciler.getClass().getAnnotation(ControllerConfiguration.class); if (annotation == null) { - throw new OperatorException( - "Missing mandatory @" + ControllerConfiguration.class.getSimpleName() + - " annotation for reconciler: " + reconciler); + throw new OperatorException("Missing mandatory @" + CONTROLLER_CONFIG_ANNOTATION + + " annotation for reconciler: " + reconciler); } } @@ -247,9 +245,9 @@ public List getDependentResources() { final var context = "DependentResource of type '" + dependentType.getName() + "'"; spec = new DependentResourceSpec(dependentType, config, name, Set.of(dependent.dependsOn()), - instantiateConditionIfNotDefault(dependent.readyPostcondition(), context), - instantiateConditionIfNotDefault(dependent.reconcilePrecondition(), context), - instantiateConditionIfNotDefault(dependent.deletePostcondition(), context)); + instantiateIfNotDefault(dependent.readyPostcondition(), Condition.class, context), + instantiateIfNotDefault(dependent.reconcilePrecondition(), Condition.class, context), + instantiateIfNotDefault(dependent.deletePostcondition(), Condition.class, context)); specsMap.put(name, spec); } @@ -258,10 +256,10 @@ public List getDependentResources() { return specs; } - protected Condition instantiateConditionIfNotDefault(Class condition, + protected T instantiateIfNotDefault(Class toInstantiate, Class defaultClass, String context) { - if (condition != Condition.class) { - return instantiateAndConfigureIfNeeded(condition, Condition.class, context); + if (!defaultClass.equals(toInstantiate)) { + return instantiateAndConfigureIfNeeded(toInstantiate, defaultClass, context); } return null; } @@ -287,6 +285,7 @@ private Object createKubernetesResourceConfig(Class OnUpdateFilter onUpdateFilter = null; OnDeleteFilter onDeleteFilter = null; GenericFilter genericFilter = null; + ResourceDiscriminator resourceDiscriminator = null; if (kubeDependent != null) { if (!Arrays.equals(KubernetesDependent.DEFAULT_NAMESPACES, kubeDependent.namespaces())) { @@ -297,7 +296,6 @@ private Object createKubernetesResourceConfig(Class final var fromAnnotation = kubeDependent.labelSelector(); labelSelector = Constants.NO_VALUE_SET.equals(fromAnnotation) ? null : fromAnnotation; - final var context = KUBE_DEPENDENT_NAME + " annotation on " + dependentType.getName() + " DependentResource"; onAddFilter = createFilter(kubeDependent.onAddFilter(), OnAddFilter.class, context) @@ -311,10 +309,15 @@ private Object createKubernetesResourceConfig(Class genericFilter = createFilter(kubeDependent.genericFilter(), GenericFilter.class, context) .orElse(null); + + resourceDiscriminator = + instantiateIfNotDefault(kubeDependent.resourceDiscriminator(), + ResourceDiscriminator.class, context); } config = - new KubernetesDependentResourceConfig(namespaces, labelSelector, configuredNS, onAddFilter, + new KubernetesDependentResourceConfig(namespaces, labelSelector, configuredNS, + resourceDiscriminator, onAddFilter, onUpdateFilter, onDeleteFilter, genericFilter); return config; 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 845810c8a1..2e4fb98e6f 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 @@ -6,20 +6,27 @@ 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.event.EventSourceRetriever; public interface Context

{ Optional getRetryInfo(); - default Optional getSecondaryResource(Class expectedType) { - return getSecondaryResource(expectedType, null); + default Optional getSecondaryResource(Class expectedType) { + return getSecondaryResource(expectedType, (String) null); } - Set getSecondaryResources(Class expectedType); + Set getSecondaryResources(Class expectedType); - Optional getSecondaryResource(Class expectedType, String eventSourceName); + @Deprecated(forRemoval = true) + Optional getSecondaryResource(Class expectedType, String eventSourceName); + + Optional getSecondaryResource(Class expectedType, + ResourceDiscriminator discriminator); ControllerConfiguration

getControllerConfiguration(); ManagedDependentResourceContext managedDependentResourceContext(); + + EventSourceRetriever

eventSourceRetriever(); } 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 afb37a8c53..cb7f4ae63b 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 @@ -9,6 +9,7 @@ import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DefaultManagedDependentResourceContext; import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.ManagedDependentResourceContext; import io.javaoperatorsdk.operator.processing.Controller; +import io.javaoperatorsdk.operator.processing.event.EventSourceRetriever; public class DefaultContext

implements Context

{ @@ -47,6 +48,12 @@ public Optional getSecondaryResource(Class expectedType, String eventS .getSecondaryResource(primaryResource); } + @Override + public Optional getSecondaryResource(Class expectedType, + ResourceDiscriminator discriminator) { + return discriminator.distinguish(expectedType, primaryResource, this); + } + @Override public ControllerConfiguration

getControllerConfiguration() { return controllerConfiguration; @@ -57,6 +64,11 @@ public ManagedDependentResourceContext managedDependentResourceContext() { return defaultManagedDependentResourceContext; } + @Override + public EventSourceRetriever

eventSourceRetriever() { + return controller.getEventSourceManager(); + } + public DefaultContext

setRetryInfo(RetryInfo retryInfo) { this.retryInfo = retryInfo; return this; diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceDiscriminator.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceDiscriminator.java new file mode 100644 index 0000000000..072e7d8078 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceDiscriminator.java @@ -0,0 +1,11 @@ +package io.javaoperatorsdk.operator.api.reconciler; + +import java.util.Optional; + +import io.fabric8.kubernetes.api.model.HasMetadata; + +public interface ResourceDiscriminator { + + Optional distinguish(Class resource, P primary, Context

context); + +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceIDMatcherDiscriminator.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceIDMatcherDiscriminator.java new file mode 100644 index 0000000000..f28633252a --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceIDMatcherDiscriminator.java @@ -0,0 +1,25 @@ +package io.javaoperatorsdk.operator.api.reconciler; + +import java.util.Optional; +import java.util.function.Function; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.processing.event.ResourceID; + +public class ResourceIDMatcherDiscriminator + implements ResourceDiscriminator { + + private final Function mapper; + + public ResourceIDMatcherDiscriminator(Function mapper) { + this.mapper = mapper; + } + + @Override + public Optional distinguish(Class resource, P primary, Context

context) { + var resourceID = mapper.apply(primary); + return context.getSecondaryResources(resource).stream() + .filter(resourceID::isSameResource) + .findFirst(); + } +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java index 0923d19473..8d31778488 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java @@ -1,8 +1,9 @@ package io.javaoperatorsdk.operator.api.reconciler.dependent; +import java.util.Optional; + import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.processing.ResourceOwner; /** * An interface to implement and provide dependent resource support. @@ -10,7 +11,7 @@ * @param the dependent resource type * @param

the associated primary resource type */ -public interface DependentResource extends ResourceOwner { +public interface DependentResource { /** * Reconciles the dependent resource given the desired primary state @@ -21,6 +22,17 @@ public interface DependentResource extends ResourceOwn */ ReconcileResult reconcile(P primary, Context

context); + /** + * Retrieves the resource type associated with this DependentResource + * + * @return the resource type associated with this DependentResource + */ + Class resourceType(); + + default Optional getSecondaryResource(P primary, Context

context) { + return Optional.empty(); + } + /** * Computes a default name for the specified DependentResource class * diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/ResourceOwner.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/ResourceOwner.java deleted file mode 100644 index f9c02a8a33..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/ResourceOwner.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.javaoperatorsdk.operator.processing; - -import java.util.Optional; - -import io.fabric8.kubernetes.api.model.HasMetadata; - -public interface ResourceOwner { - - /** - * Retrieves the resource type associated with this ResourceOwner - * - * @return the resource type associated with this ResourceOwner - */ - Class resourceType(); - - /** - * Retrieves the resource associated with the specified primary one, returning the actual state of - * the resource. Typically, this state might come from a local cache, updated after - * reconciliation. - * - * @param primary the primary resource for which we want to retrieve the secondary resource - * @return an {@link Optional} containing the secondary resource or {@link Optional#empty()} if it - * doesn't exist - */ - Optional getSecondaryResource(P primary); -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java index 5dbdba9358..1abfb3df4b 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java @@ -1,11 +1,14 @@ package io.javaoperatorsdk.operator.processing.dependent; +import java.util.Optional; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.Ignore; +import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; import io.javaoperatorsdk.operator.processing.event.ResourceID; @@ -21,6 +24,8 @@ public abstract class AbstractDependentResource protected Creator creator; protected Updater updater; + private ResourceDiscriminator resourceDiscriminator; + @SuppressWarnings("unchecked") public AbstractDependentResource() { creator = creatable ? (Creator) this : null; @@ -29,7 +34,7 @@ public AbstractDependentResource() { @Override public ReconcileResult reconcile(P primary, Context

context) { - var maybeActual = getSecondaryResource(primary); + Optional maybeActual = getSecondaryResource(primary, context); if (creatable || updatable) { if (maybeActual.isEmpty()) { if (creatable) { @@ -62,6 +67,11 @@ public ReconcileResult reconcile(P primary, Context

context) { return ReconcileResult.noOperation(maybeActual.orElse(null)); } + public Optional getSecondaryResource(P primary, Context

context) { + return resourceDiscriminator == null ? context.getSecondaryResource(resourceType()) + : resourceDiscriminator.distinguish(resourceType(), primary, context); + } + private void throwIfNull(R desired, P primary, String descriptor) { if (desired == null) { throw new DependentResourceException( @@ -118,4 +128,13 @@ protected R desired(P primary, Context

context) { throw new IllegalStateException( "desired method must be implemented if this DependentResource can be created and/or updated"); } + + public void setResourceDiscriminator( + ResourceDiscriminator resourceDiscriminator) { + this.resourceDiscriminator = resourceDiscriminator; + } + + public ResourceDiscriminator getResourceDiscriminator() { + return resourceDiscriminator; + } } 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 0ceba16826..be0db98393 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 @@ -19,12 +19,16 @@ public abstract class AbstractEventSourceHolderDependentResource { private T eventSource; + private final Class resourceType; private boolean isCacheFillerEventSource; protected OnAddFilter onAddFilter; protected OnUpdateFilter onUpdateFilter; protected OnDeleteFilter onDeleteFilter; protected GenericFilter genericFilter; + protected AbstractEventSourceHolderDependentResource(Class resourceType) { + this.resourceType = resourceType; + } public EventSource initEventSource(EventSourceContext

context) { // some sub-classes (e.g. KubernetesDependentResource) can have their event source created @@ -42,6 +46,11 @@ public EventSource initEventSource(EventSourceContext

context) { return eventSource; } + @Override + public Class resourceType() { + return resourceType; + } + protected abstract T createEventSource(EventSourceContext

context); protected void setEventSource(T eventSource) { diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractCachingDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractCachingDependentResource.java deleted file mode 100644 index 242625bc5d..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractCachingDependentResource.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.javaoperatorsdk.operator.processing.dependent.external; - -import java.util.Optional; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.api.reconciler.Ignore; -import io.javaoperatorsdk.operator.processing.dependent.AbstractEventSourceHolderDependentResource; -import io.javaoperatorsdk.operator.processing.event.source.ExternalResourceCachingEventSource; - -@Ignore -public abstract class AbstractCachingDependentResource - extends - AbstractEventSourceHolderDependentResource> { - private final Class resourceType; - - protected AbstractCachingDependentResource(Class resourceType) { - this.resourceType = resourceType; - } - - @Override - public Class resourceType() { - return resourceType; - } - - @Override - public Optional getSecondaryResource(P primaryResource) { - return eventSource().getSecondaryResource(primaryResource); - } -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractPollingDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractPollingDependentResource.java index 6bab6f48cf..2ccba025a7 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractPollingDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractPollingDependentResource.java @@ -2,11 +2,15 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.Ignore; +import io.javaoperatorsdk.operator.processing.dependent.AbstractEventSourceHolderDependentResource; import io.javaoperatorsdk.operator.processing.event.source.CacheKeyMapper; +import io.javaoperatorsdk.operator.processing.event.source.ExternalResourceCachingEventSource; @Ignore public abstract class AbstractPollingDependentResource - extends AbstractCachingDependentResource implements CacheKeyMapper { + extends + AbstractEventSourceHolderDependentResource> + implements CacheKeyMapper { public static final int DEFAULT_POLLING_PERIOD = 5000; private long pollingPeriod; diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractSimpleDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractSimpleDependentResource.java index 0675db5f1a..748452c30c 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractSimpleDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractSimpleDependentResource.java @@ -33,7 +33,7 @@ public AbstractSimpleDependentResource(UpdatableCache cache) { } @Override - public Optional getSecondaryResource(HasMetadata primaryResource) { + public Optional getSecondaryResource(P primaryResource, Context

context) { return cache.get(ResourceID.fromResource(primaryResource)); } 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 f66ff95373..603f4ae62e 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 @@ -6,6 +6,8 @@ import java.lang.annotation.Target; import io.javaoperatorsdk.operator.api.reconciler.Constants; +import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; +import io.javaoperatorsdk.operator.processing.event.source.filter.*; import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.OnDeleteFilter; @@ -68,4 +70,6 @@ * itself if no value is set */ Class genericFilter() default GenericFilter.class; + + Class resourceDiscriminator() default ResourceDiscriminator.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 930e5fd5b4..328a061e6b 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 @@ -1,16 +1,13 @@ package io.javaoperatorsdk.operator.processing.dependent.kubernetes; import java.util.HashMap; -import java.util.Optional; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import io.fabric8.kubernetes.api.model.HasMetadata; -import io.fabric8.kubernetes.api.model.KubernetesResourceList; import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; import io.fabric8.kubernetes.client.dsl.Resource; import io.javaoperatorsdk.operator.OperatorException; import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; @@ -41,13 +38,12 @@ public abstract class KubernetesDependentResource matcher; private final ResourceUpdatePreProcessor processor; - private final Class resourceType; private final boolean garbageCollected = this instanceof GarbageCollected; private KubernetesDependentResourceConfig kubernetesDependentResourceConfig; @SuppressWarnings("unchecked") public KubernetesDependentResource(Class resourceType) { - this.resourceType = resourceType; + super(resourceType); matcher = this instanceof Matcher ? (Matcher) this : GenericKubernetesResourceMatcher.matcherFor(resourceType, this); @@ -75,6 +71,7 @@ private void configureWith(String labelSelector, Set namespaces, .build(); configureWith(new InformerEventSource<>(ic, context)); + } @SuppressWarnings("unchecked") @@ -94,7 +91,7 @@ private SecondaryToPrimaryMapper getSecondaryToPrimaryMapper() { /** * Use to share informers between event more resources. * - * @param informerEventSource informer to use* + * @param informerEventSource informer to use */ public void configureWith(InformerEventSource informerEventSource) { setEventSource(informerEventSource); @@ -125,12 +122,12 @@ protected R handleUpdate(R actual, R desired, P primary, Context

context) { @SuppressWarnings("unused") public R create(R target, P primary, Context

context) { - return prepare(target, primary, "Creating").create(target); + return prepare(target, primary, "Creating").create(); } public R update(R actual, R target, P primary, Context

context) { var updatedActual = processor.replaceSpecOnActual(actual, target, context); - return prepare(target, primary, "Updating").replace(updatedActual); + return prepare(updatedActual, primary, "Updating").replace(); } public Result match(R actualResource, P primary, Context

context) { @@ -138,13 +135,11 @@ public Result match(R actualResource, P primary, Context

context) { } public void delete(P primary, Context

context) { - var resource = getSecondaryResource(primary); - resource.ifPresent(r -> client.resource(r).delete()); + getSecondaryResource(primary, context).ifPresent(r -> client.resource(r).delete()); } @SuppressWarnings("unchecked") - protected NonNamespaceOperation, Resource> prepare(R desired, - P primary, String actionName) { + protected Resource prepare(R desired, P primary, String actionName) { log.debug("{} target resource with type: {}, with id: {}", actionName, desired.getClass(), @@ -155,7 +150,8 @@ protected NonNamespaceOperation, Resource> prepa addDefaultSecondaryToPrimaryMapperAnnotations(desired, primary); } Class targetClass = (Class) desired.getClass(); - return client.resources(targetClass).inNamespace(desired.getMetadata().getNamespace()); + return client.resources(targetClass).inNamespace(desired.getMetadata().getNamespace()) + .resource(desired); } @Override @@ -167,6 +163,7 @@ protected InformerEventSource createEventSource(EventSourceContext

cont onUpdateFilter = kubernetesDependentResourceConfig.onUpdateFilter(); onDeleteFilter = kubernetesDependentResourceConfig.onDeleteFilter(); genericFilter = kubernetesDependentResourceConfig.genericFilter(); + setResourceDiscriminator(kubernetesDependentResourceConfig.getResourceDiscriminator()); configureWith(kubernetesDependentResourceConfig.labelSelector(), kubernetesDependentResourceConfig.namespaces(), @@ -203,16 +200,6 @@ protected boolean addOwnerReference() { return garbageCollected; } - @Override - public Class resourceType() { - return resourceType; - } - - @Override - public Optional getSecondaryResource(P primaryResource) { - return eventSource().getSecondaryResource(primaryResource); - } - @Override public void setKubernetesClient(KubernetesClient kubernetesClient) { this.client = kubernetesClient; @@ -235,5 +222,4 @@ private void prepareEventFiltering(R desired, ResourceID resourceID) { private void cleanupAfterEventFiltering(ResourceID resourceID) { eventSource().cleanupOnCreateOrUpdateEventFiltering(resourceID); } - } 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 e4a75b165a..e2a2c0f684 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 @@ -3,6 +3,7 @@ import java.util.Set; import io.javaoperatorsdk.operator.api.reconciler.Constants; +import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.OnDeleteFilter; @@ -15,7 +16,7 @@ public class KubernetesDependentResourceConfig { private Set namespaces = Constants.SAME_AS_CONTROLLER_NAMESPACES_SET; private String labelSelector = NO_VALUE_SET; private boolean namespacesWereConfigured = false; - + private ResourceDiscriminator resourceDiscriminator; private OnAddFilter onAddFilter; @@ -27,9 +28,9 @@ public class KubernetesDependentResourceConfig { public KubernetesDependentResourceConfig() {} - @SuppressWarnings("rawtypes") public KubernetesDependentResourceConfig(Set namespaces, String labelSelector, - boolean configuredNS, OnAddFilter onAddFilter, + boolean configuredNS, ResourceDiscriminator resourceDiscriminator, + OnAddFilter onAddFilter, OnUpdateFilter onUpdateFilter, OnDeleteFilter onDeleteFilter, GenericFilter genericFilter) { this.namespaces = namespaces; @@ -39,10 +40,11 @@ public KubernetesDependentResourceConfig(Set namespaces, String labelSel this.onUpdateFilter = onUpdateFilter; this.onDeleteFilter = onDeleteFilter; this.genericFilter = genericFilter; + this.resourceDiscriminator = resourceDiscriminator; } public KubernetesDependentResourceConfig(Set namespaces, String labelSelector) { - this(namespaces, labelSelector, true, null, null, null, null); + this(namespaces, labelSelector, true, null, null, null, null, null); } public KubernetesDependentResourceConfig setNamespaces(Set namespaces) { @@ -73,17 +75,21 @@ public OnAddFilter onAddFilter() { return onAddFilter; } - @SuppressWarnings("rawtypes") - public OnUpdateFilter onUpdateFilter() { + + public OnUpdateFilter onUpdateFilter() { return onUpdateFilter; } - @SuppressWarnings("rawtypes") - public OnDeleteFilter onDeleteFilter() { + public OnDeleteFilter onDeleteFilter() { return onDeleteFilter; } public GenericFilter genericFilter() { return genericFilter; } + + @SuppressWarnings("rawtypes") + public ResourceDiscriminator getResourceDiscriminator() { + return resourceDiscriminator; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java index e317145916..32cef6c68e 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java @@ -5,6 +5,7 @@ import java.util.Optional; import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; @SuppressWarnings("rawtypes") @@ -85,4 +86,8 @@ public DependentResourceNode setReadyPostcondition(Condition readyPo public List getParents() { return parents; } + + protected R getSecondaryResource(P primary, Context

context) { + return getDependentResource().getSecondaryResource(primary, context).orElse(null); + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutor.java index 98c6789869..45541b91d6 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutor.java @@ -104,12 +104,10 @@ public void run() { ((Deleter

) dependentResourceNode.getDependentResource()).delete(primary, context); deleteCalled.add(dependentResourceNode); } - boolean deletePostConditionMet = - deletePostCondition.map(c -> c.isMet(primary, - dependentResourceNode.getDependentResource().getSecondaryResource(primary) - .orElse(null), - context)).orElse(true); - + boolean deletePostConditionMet = deletePostCondition + .map(c -> c.isMet(primary, dependentResourceNode.getSecondaryResource(primary, context), + context)) + .orElse(true); if (deletePostConditionMet) { alreadyVisited.add(dependentResourceNode); handleDependentCleaned(dependentResourceNode); @@ -127,6 +125,7 @@ public void run() { } } + private synchronized void handleDependentCleaned( DependentResourceNode dependentResourceNode) { var dependOns = dependentResourceNode.getDependsOn(); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index 14028cb980..33ecd12f77 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -85,8 +85,7 @@ private synchronized void handleReconcile(DependentResourceNode depend } boolean reconcileConditionMet = dependentResourceNode.getReconcilePrecondition() - .map(rc -> rc.isMet(primary, - dependentResourceNode.getDependentResource().getSecondaryResource(primary).orElse(null), + .map(rc -> rc.isMet(primary, dependentResourceNode.getSecondaryResource(primary, context), context)) .orElse(true); @@ -167,9 +166,12 @@ public void run() { ReconcileResult reconcileResult = dependentResource.reconcile(primary, context); reconcileResults.put(dependentResource, reconcileResult); reconciled.add(dependentResourceNode); + boolean ready = dependentResourceNode.getReadyPostcondition() .map(rc -> rc.isMet(primary, - dependentResourceNode.getDependentResource().getSecondaryResource(primary) + context + .getSecondaryResource( + dependentResourceNode.getDependentResource().resourceType()) .orElse(null), context)) .orElse(true); @@ -210,8 +212,8 @@ public void run() { } boolean deletePostConditionMet = deletePostCondition.map(c -> c.isMet(primary, - dependentResourceNode.getDependentResource().getSecondaryResource(primary) - .orElse(null), + context.getSecondaryResource( + dependentResourceNode.getDependentResource().resourceType()).orElse(null), context)).orElse(true); if (deletePostConditionMet) { alreadyVisited.add(dependentResourceNode); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java index 58bf5ee00a..e0280796b2 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java @@ -135,7 +135,8 @@ private void handleMarkedEventForResource(ResourceState state) { private void submitReconciliationExecution(ResourceState state) { try { boolean controllerUnderExecution = isControllerUnderExecution(state); - Optional maybeLatest = cache.get(state.getId()); + final var resourceID = state.getId(); + Optional maybeLatest = cache.get(resourceID); maybeLatest.ifPresent(MDCUtils::addResourceInfo); if (!controllerUnderExecution && maybeLatest.isPresent()) { var rateLimit = state.getRateLimit(); @@ -145,24 +146,24 @@ private void submitReconciliationExecution(ResourceState state) { } var rateLimiterPermission = rateLimiter.isLimited(rateLimit); if (rateLimiterPermission.isPresent()) { - handleRateLimitedSubmission(state.getId(), rateLimiterPermission.get()); + handleRateLimitedSubmission(resourceID, rateLimiterPermission.get()); return; } state.setUnderProcessing(true); final var latest = maybeLatest.get(); ExecutionScope executionScope = new ExecutionScope<>(latest, state.getRetry()); state.unMarkEventReceived(); - metrics.reconcileCustomResource(state.getId(), state.getRetry(), metricsMetadata); + metrics.reconcileCustomResource(resourceID, state.getRetry(), metricsMetadata); log.debug("Executing events for custom resource. Scope: {}", executionScope); executor.execute(new ReconcilerExecutor(executionScope)); } else { log.debug( "Skipping executing controller for resource id: {}. Controller in execution: {}. Latest Resource present: {}", - state, + resourceID, controllerUnderExecution, maybeLatest.isPresent()); if (maybeLatest.isEmpty()) { - log.debug("no custom resource found in cache for ResourceID: {}", state); + log.debug("no custom resource found in cache for resource id: {}", resourceID); } } } finally { 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 3f1e5e848d..fc68e6e413 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 @@ -23,7 +23,8 @@ import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceAction; import io.javaoperatorsdk.operator.processing.event.source.timer.TimerEventSource; -public class EventSourceManager

implements LifecycleAware { +public class EventSourceManager

+ implements LifecycleAware, EventSourceRetriever

{ private static final Logger log = LoggerFactory.getLogger(EventSourceManager.class); @@ -171,17 +172,23 @@ public ControllerResourceEventSource

getControllerResourceEventSource() { return eventSources.controllerResourceEventSource(); } - ResourceEventSource getResourceEventSourceFor( - Class dependentType) { + @Override + public ResourceEventSource getResourceEventSourceFor(Class dependentType) { return getResourceEventSourceFor(dependentType, null); } - public List> getEventSourcesFor(Class dependentType) { + public List> getResourceEventSourcesFor(Class dependentType) { return eventSources.getEventSources(dependentType); } - public ResourceEventSource getResourceEventSourceFor( - Class dependentType, String qualifier) { + @Deprecated + public List> getEventSourcesFor(Class dependentType) { + return eventSources.getEventSources(dependentType); + } + + @Override + public ResourceEventSource getResourceEventSourceFor( + Class dependentType, String qualifier) { Objects.requireNonNull(dependentType, "dependentType is Mandatory"); return eventSources.get(dependentType, qualifier); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceRetriever.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceRetriever.java new file mode 100644 index 0000000000..a31d8902b0 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceRetriever.java @@ -0,0 +1,18 @@ +package io.javaoperatorsdk.operator.processing.event; + +import java.util.List; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.processing.event.source.ResourceEventSource; + +public interface EventSourceRetriever

{ + + ResourceEventSource getResourceEventSourceFor( + Class dependentType); + + ResourceEventSource getResourceEventSourceFor( + Class dependentType, String qualifier); + + List> getResourceEventSourcesFor(Class dependentType); + +} 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 17de7ff947..e4fabe7ff8 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 @@ -8,7 +8,6 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.processing.Controller; -import io.javaoperatorsdk.operator.processing.ResourceOwner; import io.javaoperatorsdk.operator.processing.event.source.EventSource; import io.javaoperatorsdk.operator.processing.event.source.ResourceEventSource; import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerResourceEventSource; @@ -86,8 +85,8 @@ public void add(String name, EventSource eventSource) { @SuppressWarnings("rawtypes") private Class getResourceType(EventSource source) { - return source instanceof ResourceOwner - ? ((ResourceOwner) source).resourceType() + return source instanceof ResourceEventSource + ? ((ResourceEventSource) source).resourceType() : source.getClass(); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceID.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceID.java index 7baeab6a4b..b6c2d976e3 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceID.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceID.java @@ -54,6 +54,12 @@ public boolean equals(Object o) { that.namespace); } + public boolean isSameResource(HasMetadata hasMetadata) { + final var metadata = hasMetadata.getMetadata(); + return getName().equals(metadata.getName()) && + getNamespace().map(ns -> ns.equals(metadata.getNamespace())).orElse(true); + } + @Override public int hashCode() { return Objects.hash(name, namespace); 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 bf6b9c0fd3..38b5d7007f 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 @@ -4,14 +4,19 @@ import java.util.Set; import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.processing.ResourceOwner; import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.OnDeleteFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter; -public interface ResourceEventSource extends EventSource, - ResourceOwner { +public interface ResourceEventSource extends EventSource { + + /** + * Retrieves the resource type associated with this ResourceEventSource + * + * @return the resource type associated with this ResourceEventSource + */ + Class resourceType(); default Optional getSecondaryResource(P primary) { var resources = getSecondaryResources(primary); 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 6ebd63a7eb..cc9af59094 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 @@ -93,10 +93,10 @@ public void handleRecentResourceCreate(ResourceID resourceID, R resource) { public Optional get(ResourceID resourceID) { Optional resource = temporaryResourceCache.getResourceFromCache(resourceID); if (resource.isPresent()) { - log.debug("Resource found in temporal cache for Resource ID: {}", resourceID); + log.debug("Resource found in temporary cache for Resource ID: {}", resourceID); return resource; } else { - log.debug("Resource not found in temporal cache reading it from informer cache," + + log.debug("Resource not found in temporary cache reading it from informer cache," + " for Resource ID: {}", resourceID); return cache.get(resourceID); } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverriderTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverriderTest.java index 127cd535b1..6e486b5347 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverriderTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverriderTest.java @@ -1,6 +1,5 @@ package io.javaoperatorsdk.operator.api.config; -import java.util.Optional; import java.util.Set; import org.junit.jupiter.api.Test; @@ -85,10 +84,6 @@ public Class resourceType() { return Object.class; } - @Override - public Optional getSecondaryResource(ConfigMap primary) { - return Optional.empty(); - } } } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResourceTest.java index 4edd3dfb4c..b93abd45c0 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResourceTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResourceTest.java @@ -16,7 +16,6 @@ class AbstractDependentResourceTest { - @Test void throwsExceptionIfDesiredIsNullOnCreate() { TestDependentResource testDependentResource = new TestDependentResource(); @@ -80,7 +79,8 @@ public Class resourceType() { } @Override - public Optional getSecondaryResource(TestCustomResource primary) { + public Optional getSecondaryResource(TestCustomResource primary, + Context context) { return Optional.ofNullable(secondary); } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/EmptyTestDependentResource.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/EmptyTestDependentResource.java index aa75849051..dab1bc0132 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/EmptyTestDependentResource.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/EmptyTestDependentResource.java @@ -1,7 +1,5 @@ package io.javaoperatorsdk.operator.processing.dependent; -import java.util.Optional; - import io.fabric8.kubernetes.api.model.apps.Deployment; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; @@ -17,11 +15,6 @@ public ReconcileResult reconcile(TestCustomResource primary, return null; } - @Override - public Optional getSecondaryResource(TestCustomResource primaryResource) { - return Optional.empty(); - } - @Override public Class resourceType() { return Deployment.class; diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractSimpleDependentResourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractSimpleDependentResourceTest.java index e020fd27a3..520f44365f 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractSimpleDependentResourceTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractSimpleDependentResourceTest.java @@ -45,14 +45,14 @@ void getsTheResourceFromSupplyIfReconciling() { simpleDependentResource.reconcile(TestUtils.testCustomResource1(), null); verify(supplierMock, times(1)).get(); - assertThat(simpleDependentResource.getSecondaryResource(TestUtils.testCustomResource1())) + assertThat(simpleDependentResource.getSecondaryResource(TestUtils.testCustomResource1(), null)) .isPresent() .isEqualTo(Optional.of(SampleExternalResource.testResource1())); } @Test void getResourceReadsTheResourceFromCache() { - simpleDependentResource.getSecondaryResource(TestUtils.testCustomResource1()); + simpleDependentResource.getSecondaryResource(TestUtils.testCustomResource1(), null); verify(supplierMock, times(0)).get(); verify(updatableCacheMock, times(1)).get(any()); diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutorTest.java index 8dfab3fa20..9c10c06cc0 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutorTest.java @@ -3,7 +3,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Optional; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; @@ -49,11 +48,6 @@ public Class resourceType() { return String.class; } - @Override - public Optional getSecondaryResource(TestCustomResource primary) { - return Optional.of(VALUE); - } - @Override public String toString() { return name; @@ -113,11 +107,6 @@ public Class resourceType() { return String.class; } - @Override - public Optional getSecondaryResource(TestCustomResource primary) { - return Optional.of(VALUE); - } - @Override public String toString() { return name; diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java index 7c1c5d6ff6..56bd876687 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java @@ -4,17 +4,20 @@ import org.junit.jupiter.api.Test; import io.javaoperatorsdk.operator.AggregatedOperatorException; +import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.processing.dependent.workflow.builder.WorkflowBuilder; import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; import static io.javaoperatorsdk.operator.processing.dependent.workflow.ExecutionAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; class WorkflowCleanupExecutorTest extends AbstractWorkflowExecutorTest { protected TestDeleterDependent dd1 = new TestDeleterDependent("DR_DELETER_1"); protected TestDeleterDependent dd2 = new TestDeleterDependent("DR_DELETER_2"); protected TestDeleterDependent dd3 = new TestDeleterDependent("DR_DELETER_3"); + Context mockContext = mock(Context.class); @Test void cleanUpDiamondWorkflow() { @@ -45,7 +48,7 @@ void dontDeleteIfDependentErrored() { .withThrowExceptionFurther(false) .build(); - var res = workflow.cleanup(new TestCustomResource(), null); + var res = workflow.cleanup(new TestCustomResource(), mockContext); assertThrows(AggregatedOperatorException.class, res::throwAggregateExceptionIfErrorsPresent); @@ -64,7 +67,7 @@ void cleanupConditionTrivialCase() { .addDependentResource(dd2).dependsOn(dd1).withDeletePostcondition(noMetDeletePostCondition) .build(); - var res = workflow.cleanup(new TestCustomResource(), null); + var res = workflow.cleanup(new TestCustomResource(), mockContext); assertThat(executionHistory).deleted(dd2).notReconciled(dd1); Assertions.assertThat(res.getDeleteCalledOnDependents()).containsExactlyInAnyOrder(dd2); @@ -79,7 +82,7 @@ void cleanupConditionMet() { .addDependentResource(dd2).dependsOn(dd1).withDeletePostcondition(metDeletePostCondition) .build(); - var res = workflow.cleanup(new TestCustomResource(), null); + var res = workflow.cleanup(new TestCustomResource(), mockContext); assertThat(executionHistory).deleted(dd2, dd1); @@ -99,7 +102,7 @@ void cleanupConditionDiamondWorkflow() { .addDependentResource(dd4).dependsOn(dd2, dd3) .build(); - var res = workflow.cleanup(new TestCustomResource(), null); + var res = workflow.cleanup(new TestCustomResource(), mockContext); assertThat(executionHistory) .reconciledInOrder(dd4, dd2) diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java index fa9d757b67..873cf66cb6 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java @@ -4,11 +4,13 @@ import org.junit.jupiter.api.Test; import io.javaoperatorsdk.operator.AggregatedOperatorException; +import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.processing.dependent.workflow.builder.WorkflowBuilder; import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; import static io.javaoperatorsdk.operator.processing.dependent.workflow.ExecutionAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; @SuppressWarnings("rawtypes") class WorkflowReconcileExecutorTest extends AbstractWorkflowExecutorTest { @@ -21,6 +23,8 @@ class WorkflowReconcileExecutorTest extends AbstractWorkflowExecutorTest { private final Condition notMetReadyCondition = (primary, secondary, context) -> false; + Context mockContext = mock(Context.class); + @Test void reconcileTopLevelResources() { var workflow = new WorkflowBuilder() @@ -28,7 +32,7 @@ void reconcileTopLevelResources() { .addDependentResource(dr2) .build(); - var res = workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), mockContext); assertThat(executionHistory).reconciled(dr1, dr2); Assertions.assertThat(res.getErroredDependents()).isEmpty(); @@ -42,7 +46,7 @@ void reconciliationWithSimpleDependsOn() { .addDependentResource(dr2).dependsOn(dr1) .build(); - var res = workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), mockContext); Assertions.assertThat(res.getErroredDependents()).isEmpty(); assertThat(executionHistory).reconciledInOrder(dr1, dr2); @@ -61,7 +65,7 @@ void reconciliationWithTwoTheDependsOns() { .addDependentResource(dr3).dependsOn(dr1) .build(); - var res = workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), mockContext); Assertions.assertThat(res.getErroredDependents()).isEmpty(); assertThat(executionHistory) @@ -83,7 +87,7 @@ void diamondShareWorkflowReconcile() { .addDependentResource(dr4).dependsOn(dr3).dependsOn(dr2) .build(); - var res = workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), mockContext); Assertions.assertThat(res.getErroredDependents()).isEmpty(); assertThat(executionHistory) @@ -103,7 +107,7 @@ void exceptionHandlingSimpleCases() { .withThrowExceptionFurther(false) .build(); - var res = workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), mockContext); assertThrows(AggregatedOperatorException.class, res::throwAggregateExceptionIfErrorsPresent); @@ -123,7 +127,7 @@ void dependentsOnErroredResourceNotReconciled() { .withThrowExceptionFurther(false) .build(); - var res = workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), mockContext); assertThrows(AggregatedOperatorException.class, res::throwAggregateExceptionIfErrorsPresent); @@ -145,7 +149,7 @@ void oneBranchErrorsOtherCompletes() { .withThrowExceptionFurther(false) .build(); - var res = workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), mockContext); assertThrows(AggregatedOperatorException.class, res::throwAggregateExceptionIfErrorsPresent); @@ -164,7 +168,7 @@ void onlyOneDependsOnErroredResourceNotReconciled() { .withThrowExceptionFurther(false) .build(); - var res = workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), mockContext); assertThrows(AggregatedOperatorException.class, res::throwAggregateExceptionIfErrorsPresent); @@ -182,7 +186,7 @@ void simpleReconcileCondition() { .addDependentResource(drDeleter).withReconcilePrecondition(not_met_reconcile_condition) .build(); - var res = workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), mockContext); assertThat(executionHistory).notReconciled(dr1).reconciled(dr2).deleted(drDeleter); Assertions.assertThat(res.getErroredDependents()).isEmpty(); @@ -200,7 +204,7 @@ void triangleOnceConditionNotMet() { .dependsOn(dr1) .build(); - var res = workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), mockContext); assertThat(executionHistory).reconciledInOrder(dr1, dr2).deleted(drDeleter); Assertions.assertThat(res.getErroredDependents()).isEmpty(); @@ -222,7 +226,7 @@ void reconcileConditionTransitiveDelete() { .withReconcilePrecondition(met_reconcile_condition) .build(); - var res = workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), mockContext); Assertions.assertThat(res.getErroredDependents()).isEmpty(); assertThat(executionHistory).notReconciled(dr2); @@ -246,7 +250,7 @@ void reconcileConditionAlsoErrorDependsOn() { .withThrowExceptionFurther(false) .build(); - var res = workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), mockContext); assertThrows(AggregatedOperatorException.class, res::throwAggregateExceptionIfErrorsPresent); @@ -267,7 +271,7 @@ void oneDependsOnConditionNotMet() { .addDependentResource(drDeleter).dependsOn(dr1, dr2) .build(); - var res = workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), mockContext); Assertions.assertThat(res.getErroredDependents()).isEmpty(); @@ -287,7 +291,7 @@ void deletedIfReconcileConditionNotMet() { .addDependentResource(drDeleter2).dependsOn(dr1, drDeleter) .build(); - var res = workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), mockContext); assertThat(executionHistory) .reconciledInOrder(dr1, drDeleter2, drDeleter) @@ -313,7 +317,7 @@ void deleteDoneInReverseOrder() { .addDependentResource(drDeleter4).dependsOn(drDeleter3) .build(); - var res = workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), mockContext); assertThat(executionHistory) .reconciledInOrder(dr1, drDeleter4, drDeleter3, drDeleter) @@ -339,7 +343,7 @@ void diamondDeleteWithPostConditionInMiddle() { .addDependentResource(drDeleter4).dependsOn(drDeleter3, drDeleter2) .build(); - var res = workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), mockContext); assertThat(executionHistory).notReconciled(drDeleter) .reconciledInOrder(drDeleter4, drDeleter2) @@ -363,7 +367,7 @@ void diamondDeleteErrorInMiddle() { .withThrowExceptionFurther(false) .build(); - var res = workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), mockContext); assertThat(executionHistory) .notReconciled(drDeleter, drError) @@ -381,7 +385,7 @@ void readyConditionTrivialCase() { .addDependentResource(dr2).dependsOn(dr1) .build(); - var res = workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), mockContext); assertThat(executionHistory).reconciledInOrder(dr1, dr2); @@ -397,7 +401,7 @@ void readyConditionNotMetTrivialCase() { .addDependentResource(dr2).dependsOn(dr1) .build(); - var res = workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), mockContext); assertThat(executionHistory).reconciled(dr1).notReconciled(dr2); @@ -417,7 +421,7 @@ void readyConditionNotMetInOneParent() { .addDependentResource(dr3).dependsOn(dr1, dr2) .build(); - var res = workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), mockContext); assertThat(executionHistory).reconciled(dr1, dr2).notReconciled(dr3); Assertions.assertThat(res.getErroredDependents()).isEmpty(); @@ -437,7 +441,7 @@ void diamondShareWithReadyCondition() { .addDependentResource(dr4).dependsOn(dr2, dr3) .build(); - var res = workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), mockContext); Assertions.assertThat(res.getErroredDependents()).isEmpty(); assertThat(executionHistory).reconciledInOrder(dr1, dr2) diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/IndexDiscriminatorIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/IndexDiscriminatorIT.java new file mode 100644 index 0000000000..fe5b63de8a --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/IndexDiscriminatorIT.java @@ -0,0 +1,77 @@ +package io.javaoperatorsdk.operator; + +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.indexdiscriminator.IndexDiscriminatorTestCustomResource; +import io.javaoperatorsdk.operator.sample.indexdiscriminator.IndexDiscriminatorTestReconciler; +import io.javaoperatorsdk.operator.sample.indexdiscriminator.IndexDiscriminatorTestSpec; + +import static io.javaoperatorsdk.operator.sample.indexdiscriminator.IndexDiscriminatorTestDRConfigMap.DATA_KEY; +import static io.javaoperatorsdk.operator.sample.indexdiscriminator.IndexDiscriminatorTestReconciler.FIRST_CONFIG_MAP_SUFFIX_1; +import static io.javaoperatorsdk.operator.sample.indexdiscriminator.IndexDiscriminatorTestReconciler.FIRST_CONFIG_MAP_SUFFIX_2; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +class IndexDiscriminatorIT { + + public static final String TEST_RESOURCE_1 = "test1"; + public static final String CHANGED_SPEC_VALUE = "otherValue"; + @RegisterExtension + LocallyRunOperatorExtension operator = + LocallyRunOperatorExtension.builder().withReconciler(IndexDiscriminatorTestReconciler.class) + .build(); + + @Test + void resourcesFoundAndReconciled() { + var res = operator.create(createTestCustomResource()); + var reconciler = operator.getReconcilerOfType(IndexDiscriminatorTestReconciler.class); + + await().untilAsserted(() -> { + assertThat(reconciler.getNumberOfExecutions()).isEqualTo(1); + assertThat(operator.get(ConfigMap.class, TEST_RESOURCE_1 + FIRST_CONFIG_MAP_SUFFIX_1)) + .isNotNull(); + assertThat(operator.get(ConfigMap.class, TEST_RESOURCE_1 + FIRST_CONFIG_MAP_SUFFIX_2)) + .isNotNull(); + }); + + res.getSpec().setValue(CHANGED_SPEC_VALUE); + res = operator.replace(res); + + await().untilAsserted(() -> { + assertThat(reconciler.getNumberOfExecutions()).isEqualTo(2); + var cm1 = operator.get(ConfigMap.class, TEST_RESOURCE_1 + FIRST_CONFIG_MAP_SUFFIX_1); + var cm2 = operator.get(ConfigMap.class, TEST_RESOURCE_1 + FIRST_CONFIG_MAP_SUFFIX_2); + assertThat(cm1).isNotNull(); + assertThat(cm2).isNotNull(); + assertThat(cm1.getData().get(DATA_KEY)).isEqualTo(CHANGED_SPEC_VALUE); + assertThat(cm2.getData().get(DATA_KEY)).isEqualTo(CHANGED_SPEC_VALUE); + }); + + operator.delete(res); + + await().untilAsserted(() -> { + var cm1 = operator.get(ConfigMap.class, TEST_RESOURCE_1 + FIRST_CONFIG_MAP_SUFFIX_1); + var cm2 = operator.get(ConfigMap.class, TEST_RESOURCE_1 + FIRST_CONFIG_MAP_SUFFIX_2); + assertThat(cm1).isNull(); + assertThat(cm2).isNull(); + }); + } + + public IndexDiscriminatorTestCustomResource createTestCustomResource() { + IndexDiscriminatorTestCustomResource resource = + new IndexDiscriminatorTestCustomResource(); + resource.setMetadata( + new ObjectMetaBuilder() + .withName(TEST_RESOURCE_1) + .withNamespace(operator.getNamespace()) + .build()); + resource.setSpec(new IndexDiscriminatorTestSpec()); + resource.getSpec().setValue("default"); + return resource; + } + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminator.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminator.java new file mode 100644 index 0000000000..eb6e193479 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminator.java @@ -0,0 +1,41 @@ +package io.javaoperatorsdk.operator.sample.indexdiscriminator; + +import java.util.Optional; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; +import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; + +import static io.javaoperatorsdk.operator.sample.indexdiscriminator.IndexDiscriminatorTestReconciler.configMapKeyFromPrimary; + +public class IndexDiscriminator + implements ResourceDiscriminator { + + private final String indexName; + private final String nameSuffix; + + public IndexDiscriminator(String indexName, String nameSuffix) { + this.indexName = indexName; + this.nameSuffix = nameSuffix; + } + + @Override + public Optional distinguish(Class resource, + IndexDiscriminatorTestCustomResource primary, + Context context) { + + InformerEventSource eventSource = + (InformerEventSource) context + .eventSourceRetriever() + .getResourceEventSourceFor(ConfigMap.class); + var resources = eventSource.byIndex(indexName, configMapKeyFromPrimary(primary, nameSuffix)); + if (resources.isEmpty()) { + return Optional.empty(); + } else if (resources.size() > 1) { + throw new IllegalStateException("more than one resource"); + } else { + return Optional.of(resources.get(0)); + } + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestCustomResource.java new file mode 100644 index 0000000000..729b1d80eb --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestCustomResource.java @@ -0,0 +1,16 @@ +package io.javaoperatorsdk.operator.sample.indexdiscriminator; + +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("idt") +public class IndexDiscriminatorTestCustomResource + extends CustomResource + implements Namespaced { + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestDRConfigMap.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestDRConfigMap.java new file mode 100644 index 0000000000..88dc40f55c --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestDRConfigMap.java @@ -0,0 +1,38 @@ +package io.javaoperatorsdk.operator.sample.indexdiscriminator; + +import java.util.HashMap; +import java.util.Map; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDNoGCKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; + +@KubernetesDependent +public class IndexDiscriminatorTestDRConfigMap + extends CRUDNoGCKubernetesDependentResource { + + public static final String DATA_KEY = "key"; + private final String suffix; + + public IndexDiscriminatorTestDRConfigMap(String value) { + super(ConfigMap.class); + this.suffix = value; + } + + @Override + protected ConfigMap desired(IndexDiscriminatorTestCustomResource primary, + Context context) { + Map data = new HashMap<>(); + data.put(DATA_KEY, primary.getSpec().getValue()); + + return new ConfigMapBuilder() + .withNewMetadata() + .withName(primary.getMetadata().getName() + suffix) + .withNamespace(primary.getMetadata().getNamespace()) + .endMetadata() + .withData(data) + .build(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestReconciler.java new file mode 100644 index 0000000000..0b0af2a1cc --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestReconciler.java @@ -0,0 +1,120 @@ +package io.javaoperatorsdk.operator.sample.indexdiscriminator; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import io.fabric8.kubernetes.api.model.ConfigMap; +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; +import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; + +@ControllerConfiguration +public class IndexDiscriminatorTestReconciler + implements Reconciler, + Cleaner, + TestExecutionInfoProvider, EventSourceInitializer, + KubernetesClientAware { + + public static final String FIRST_CONFIG_MAP_SUFFIX_1 = "-1"; + public static final String FIRST_CONFIG_MAP_SUFFIX_2 = "-2"; + public static final String CONFIG_MAP_INDEX_1 = "CONFIG_MAP_INDEX1"; + public static final String CONFIG_MAP_INDEX_2 = "CONFIG_MAP_INDEX2"; + + private final AtomicInteger numberOfExecutions = new AtomicInteger(0); + + private final IndexDiscriminatorTestDRConfigMap firstDependentResourceConfigMap; + private final IndexDiscriminatorTestDRConfigMap secondDependentResourceConfigMap; + private KubernetesClient client; + + public IndexDiscriminatorTestReconciler() { + firstDependentResourceConfigMap = + new IndexDiscriminatorTestDRConfigMap(FIRST_CONFIG_MAP_SUFFIX_1); + secondDependentResourceConfigMap = + new IndexDiscriminatorTestDRConfigMap(FIRST_CONFIG_MAP_SUFFIX_2); + } + + @Override + public UpdateControl reconcile( + IndexDiscriminatorTestCustomResource resource, + Context context) { + numberOfExecutions.getAndIncrement(); + firstDependentResourceConfigMap.reconcile(resource, context); + secondDependentResourceConfigMap.reconcile(resource, context); + return UpdateControl.noUpdate(); + } + + public int getNumberOfExecutions() { + return numberOfExecutions.get(); + } + + @Override + public Map prepareEventSources( + EventSourceContext context) { + + InformerEventSource eventSource = + new InformerEventSource<>(InformerConfiguration.from(ConfigMap.class, context) + .build(), context); + + eventSource.addIndexer(CONFIG_MAP_INDEX_1, cm -> { + if (cm.getMetadata().getName().endsWith(FIRST_CONFIG_MAP_SUFFIX_1)) { + return List.of(configMapKey(cm)); + } else { + return Collections.emptyList(); + } + }); + eventSource.addIndexer(CONFIG_MAP_INDEX_2, cm -> { + if (cm.getMetadata().getName().endsWith(FIRST_CONFIG_MAP_SUFFIX_2)) { + return List.of(configMapKey(cm)); + } else { + return Collections.emptyList(); + } + }); + + firstDependentResourceConfigMap.configureWith(eventSource); + secondDependentResourceConfigMap.configureWith(eventSource); + + firstDependentResourceConfigMap + .setResourceDiscriminator( + new IndexDiscriminator(CONFIG_MAP_INDEX_1, FIRST_CONFIG_MAP_SUFFIX_1)); + secondDependentResourceConfigMap + .setResourceDiscriminator( + new IndexDiscriminator(CONFIG_MAP_INDEX_2, FIRST_CONFIG_MAP_SUFFIX_2)); + return EventSourceInitializer.nameEventSources(eventSource); + } + + @Override + public KubernetesClient getKubernetesClient() { + return client; + } + + @Override + public void setKubernetesClient(KubernetesClient kubernetesClient) { + this.client = kubernetesClient; + firstDependentResourceConfigMap.setKubernetesClient(kubernetesClient); + secondDependentResourceConfigMap.setKubernetesClient(kubernetesClient); + } + + public static String configMapKey(ConfigMap configMap) { + return configMap.getMetadata().getName() + "#" + configMap.getMetadata().getNamespace(); + } + + public static String configMapKeyFromPrimary(IndexDiscriminatorTestCustomResource primary, + String nameSuffix) { + return primary.getMetadata().getName() + nameSuffix + "#" + + primary.getMetadata().getNamespace(); + } + + @Override + public DeleteControl cleanup(IndexDiscriminatorTestCustomResource resource, + Context context) { + firstDependentResourceConfigMap.delete(resource, context); + secondDependentResourceConfigMap.delete(resource, context); + return DeleteControl.defaultDelete(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestSpec.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestSpec.java new file mode 100644 index 0000000000..fcedd48abe --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestSpec.java @@ -0,0 +1,15 @@ +package io.javaoperatorsdk.operator.sample.indexdiscriminator; + +public class IndexDiscriminatorTestSpec { + + private String value; + + public String getValue() { + return value; + } + + public IndexDiscriminatorTestSpec setValue(String value) { + this.value = value; + return this; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestStatus.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestStatus.java new file mode 100644 index 0000000000..d31c86e8de --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestStatus.java @@ -0,0 +1,5 @@ +package io.javaoperatorsdk.operator.sample.indexdiscriminator; + +public class IndexDiscriminatorTestStatus { + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceConfigMap.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceConfigMap.java index 4cdc2e457d..1adbfb9f95 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceConfigMap.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceConfigMap.java @@ -31,7 +31,6 @@ protected ConfigMap desired(MultipleDependentResourceCustomResource primary, .withNewMetadata() .withName(primary.getConfigMapName(value)) .withNamespace(primary.getMetadata().getNamespace()) - .withLabels(Map.of(MultipleDependentResourceReconciler.LABEL, String.valueOf(value))) .endMetadata() .withData(data) .build(); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceReconciler.java index 8cdbb81eba..49f5ee64c1 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceReconciler.java @@ -3,16 +3,14 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.client.KubernetesClient; -import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; -import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; -import io.javaoperatorsdk.operator.api.reconciler.EventSourceInitializer; -import io.javaoperatorsdk.operator.api.reconciler.Reconciler; -import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; +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.dependent.kubernetes.KubernetesDependentResourceConfig; +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 @@ -23,7 +21,6 @@ public class MultipleDependentResourceReconciler public static final int FIRST_CONFIG_MAP_ID = 1; public static final int SECOND_CONFIG_MAP_ID = 2; - public static final String LABEL = "multipledependentresource"; private final AtomicInteger numberOfExecutions = new AtomicInteger(0); private final MultipleDependentResourceConfigMap firstDependentResourceConfigMap; @@ -32,18 +29,19 @@ public class MultipleDependentResourceReconciler public MultipleDependentResourceReconciler() { firstDependentResourceConfigMap = new MultipleDependentResourceConfigMap(FIRST_CONFIG_MAP_ID); - secondDependentResourceConfigMap = new MultipleDependentResourceConfigMap(SECOND_CONFIG_MAP_ID); - firstDependentResourceConfigMap.configureWith( - new KubernetesDependentResourceConfig() - .setLabelSelector(getLabelSelector(FIRST_CONFIG_MAP_ID))); - secondDependentResourceConfigMap.configureWith( - new KubernetesDependentResourceConfig() - .setLabelSelector(getLabelSelector(SECOND_CONFIG_MAP_ID))); - } + secondDependentResourceConfigMap = new MultipleDependentResourceConfigMap(SECOND_CONFIG_MAP_ID); - private String getLabelSelector(int resourceId) { - return LABEL + "=" + resourceId; + firstDependentResourceConfigMap + .setResourceDiscriminator( + new ResourceIDMatcherDiscriminator<>( + p -> new ResourceID(p.getConfigMapName(FIRST_CONFIG_MAP_ID), + p.getMetadata().getNamespace()))); + secondDependentResourceConfigMap + .setResourceDiscriminator( + new ResourceIDMatcherDiscriminator<>( + p -> new ResourceID(p.getConfigMapName(SECOND_CONFIG_MAP_ID), + p.getMetadata().getNamespace()))); } @Override @@ -64,9 +62,13 @@ public int getNumberOfExecutions() { @Override public Map prepareEventSources( EventSourceContext context) { - return EventSourceInitializer.nameEventSources( - firstDependentResourceConfigMap.initEventSource(context), - secondDependentResourceConfigMap.initEventSource(context)); + InformerEventSource eventSource = + new InformerEventSource<>(InformerConfiguration.from(ConfigMap.class, context) + .build(), context); + firstDependentResourceConfigMap.configureWith(eventSource); + secondDependentResourceConfigMap.configureWith(eventSource); + + return EventSourceInitializer.nameEventSources(eventSource); } @Override 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 14530cf17e..bf8d60d9c4 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 @@ -6,11 +6,14 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ResourceIDMatcherDiscriminator; import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; +import io.javaoperatorsdk.operator.processing.event.ResourceID; -@KubernetesDependent(labelSelector = "dependent = cm1") +@KubernetesDependent(labelSelector = "dependent = cm1", + resourceDiscriminator = ConfigMapDependentResource1.CM1ResourceDiscriminator.class) public class ConfigMapDependentResource1 extends CRUDKubernetesDependentResource { @@ -42,4 +45,11 @@ protected ConfigMap desired(OrderedManagedDependentCustomResource primary, return configMap; } + public static class CM1ResourceDiscriminator + extends ResourceIDMatcherDiscriminator { + public CM1ResourceDiscriminator() { + super(p -> new ResourceID(p.getMetadata().getName() + "1", p.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 35ae69586e..2b17d615b9 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 @@ -6,11 +6,14 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ResourceIDMatcherDiscriminator; import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; +import io.javaoperatorsdk.operator.processing.event.ResourceID; -@KubernetesDependent(labelSelector = "dependent = cm2") +@KubernetesDependent(labelSelector = "dependent = cm2", + resourceDiscriminator = ConfigMapDependentResource2.CM2ResourceDiscriminator.class) public class ConfigMapDependentResource2 extends CRUDKubernetesDependentResource { @@ -42,4 +45,11 @@ protected ConfigMap desired(OrderedManagedDependentCustomResource primary, return configMap; } + public static class CM2ResourceDiscriminator + extends ResourceIDMatcherDiscriminator { + public CM2ResourceDiscriminator() { + super(p -> new ResourceID(p.getMetadata().getName() + "2", p.getMetadata().getNamespace())); + } + } + } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/standalonedependent/StandaloneDependentTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/standalonedependent/StandaloneDependentTestReconciler.java index af6b2d7e25..2dace670ba 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/standalonedependent/StandaloneDependentTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/standalonedependent/StandaloneDependentTestReconciler.java @@ -47,7 +47,7 @@ public UpdateControl reconcile( StandaloneDependentTestCustomResource primary, Context context) { deploymentDependent.reconcile(primary, context); - Optional deployment = deploymentDependent.getSecondaryResource(primary); + Optional deployment = context.getSecondaryResource(Deployment.class); if (deployment.isEmpty()) { throw new IllegalStateException("Resource should not be empty after reconcile."); } diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageDependentsWorkflowReconciler.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageDependentsWorkflowReconciler.java index 6986180b89..6b81a9d6e3 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageDependentsWorkflowReconciler.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageDependentsWorkflowReconciler.java @@ -69,7 +69,7 @@ public UpdateControl reconcile(WebPage webPage, Context contex webPage.setStatus( createStatus( - configMapDR.getSecondaryResource(webPage).orElseThrow().getMetadata().getName())); + context.getSecondaryResource(ConfigMap.class).orElseThrow().getMetadata().getName())); return UpdateControl.patchStatus(webPage); } @@ -93,6 +93,4 @@ private void initDependentResources(KubernetesClient client) { }); } - - } diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java index 0bb692a4d0..b99e130135 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java @@ -65,7 +65,7 @@ public UpdateControl reconcile(WebPage webPage, Context contex webPage.setStatus( createStatus( - configMapDR.getSecondaryResource(webPage).orElseThrow().getMetadata().getName())); + context.getSecondaryResource(ConfigMap.class).orElseThrow().getMetadata().getName())); return UpdateControl.patchStatus(webPage); } From 733e362492df015c072e952ba496c6da98f6e607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Thu, 29 Sep 2022 16:31:43 +0200 Subject: [PATCH 2/8] feat: bulk dependent resources (#1448) --- .../api/reconciler/ResourceDiscriminator.java | 1 - .../reconciler/dependent/ReconcileResult.java | 50 ++++++-- .../dependent/AbstractDependentResource.java | 104 ++++++++++++++-- .../dependent/BulkDependentResource.java | 35 ++++++ .../processing/dependent/BulkUpdater.java | 20 +++ .../dependent/DesiredEqualsMatcher.java | 6 + .../processing/dependent/Matcher.java | 15 +++ .../processing/dependent/Updater.java | 4 + .../GenericKubernetesResourceMatcher.java | 103 +++++++++++----- .../KubernetesDependentResource.java | 30 ++++- .../KubernetesDependentResourceConfig.java | 7 +- .../workflow/WorkflowCleanupExecutor.java | 1 - .../bulkdependent/BulkDependentDeleterIT.java | 19 +++ .../bulkdependent/BulkDependentTestBase.java | 114 ++++++++++++++++++ .../BulkExternalDependentIT.java | 56 +++++++++ .../bulkdependent/ManagedBulkDependentIT.java | 20 +++ .../StandaloneBulkDependentIT.java | 19 +++ .../BulkDependentTestCustomResource.java | 15 +++ .../bulkdependent/BulkDependentTestSpec.java | 25 ++++ .../CRUDConfigMapBulkDependentResource.java | 7 ++ ...ConfigMapDeleterBulkDependentResource.java | 72 +++++++++++ .../ManagedBulkDependentReconciler.java | 25 ++++ .../ManagedDeleterBulkReconciler.java | 20 +++ .../StandaloneBulkDependentReconciler.java | 58 +++++++++ .../ExternalBulkDependentResource.java | 101 ++++++++++++++++ .../ExternalBulkResourceReconciler.java | 19 +++ .../external/ExternalResource.java | 37 ++++++ .../external/ExternalServiceMock.java | 39 ++++++ .../MultipleDependentResourceReconciler.java | 1 - 29 files changed, 956 insertions(+), 67 deletions(-) create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResource.java create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkUpdater.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/bulkdependent/BulkDependentDeleterIT.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/bulkdependent/BulkDependentTestBase.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/bulkdependent/BulkExternalDependentIT.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/bulkdependent/ManagedBulkDependentIT.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/bulkdependent/StandaloneBulkDependentIT.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/BulkDependentTestCustomResource.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/BulkDependentTestSpec.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/CRUDConfigMapBulkDependentResource.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ConfigMapDeleterBulkDependentResource.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ManagedBulkDependentReconciler.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ManagedDeleterBulkReconciler.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/StandaloneBulkDependentReconciler.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalBulkDependentResource.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalBulkResourceReconciler.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalResource.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalServiceMock.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceDiscriminator.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceDiscriminator.java index 072e7d8078..eb947fa440 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceDiscriminator.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceDiscriminator.java @@ -7,5 +7,4 @@ public interface ResourceDiscriminator { Optional distinguish(Class resource, P primary, Context

context); - } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/ReconcileResult.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/ReconcileResult.java index c83da1c8ea..468e14e8ea 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/ReconcileResult.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/ReconcileResult.java @@ -1,14 +1,14 @@ package io.javaoperatorsdk.operator.api.reconciler.dependent; -import java.util.Optional; +import java.util.*; +import java.util.stream.Collectors; import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.processing.event.ResourceID; public class ReconcileResult { - private final R resource; - private final Operation operation; + private final Map resourceOperations; public static ReconcileResult resourceCreated(T resource) { return new ReconcileResult<>(resource, Operation.CREATED); @@ -22,25 +22,49 @@ public static ReconcileResult noOperation(T resource) { return new ReconcileResult<>(resource, Operation.NONE); } + @SafeVarargs + public static ReconcileResult aggregatedResult(ReconcileResult... results) { + if (results == null) { + throw new IllegalArgumentException("Should provide results to aggregate"); + } + if (results.length == 1) { + return results[0]; + } + final Map operations = new HashMap<>(results.length); + for (ReconcileResult res : results) { + res.getSingleResource().ifPresent(r -> operations.put(r, res.getSingleOperation())); + } + return new ReconcileResult<>(operations); + } + @Override public String toString() { - return getResource() - .map(r -> r instanceof HasMetadata ? ResourceID.fromResource((HasMetadata) r) : r) - .orElse("no resource") - + " -> " + operation; + return resourceOperations.entrySet().stream().collect(Collectors.toMap( + e -> e instanceof HasMetadata ? ResourceID.fromResource((HasMetadata) e) : e, + Map.Entry::getValue)) + .toString(); } private ReconcileResult(R resource, Operation operation) { - this.resource = resource; - this.operation = operation; + resourceOperations = resource != null ? Map.of(resource, operation) : Collections.emptyMap(); + } + + private ReconcileResult(Map operations) { + resourceOperations = Collections.unmodifiableMap(operations); + } + + public Optional getSingleResource() { + return resourceOperations.entrySet().stream().findFirst().map(Map.Entry::getKey); } - public Optional getResource() { - return Optional.ofNullable(resource); + public Operation getSingleOperation() { + return resourceOperations.entrySet().stream().findFirst().map(Map.Entry::getValue) + .orElseThrow(); } - public Operation getOperation() { - return operation; + @SuppressWarnings("unused") + public Map getResourceOperations() { + return resourceOperations; } public enum Operation { diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java index 1abfb3df4b..078fc60b66 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java @@ -1,5 +1,7 @@ package io.javaoperatorsdk.operator.processing.dependent; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import org.slf4j.Logger; @@ -20,25 +22,73 @@ public abstract class AbstractDependentResource protected final boolean creatable = this instanceof Creator; protected final boolean updatable = this instanceof Updater; + protected final boolean bulk = this instanceof BulkDependentResource; protected Creator creator; protected Updater updater; + protected BulkDependentResource bulkDependentResource; - private ResourceDiscriminator resourceDiscriminator; + private final List> resourceDiscriminator = new ArrayList<>(1); @SuppressWarnings("unchecked") public AbstractDependentResource() { creator = creatable ? (Creator) this : null; updater = updatable ? (Updater) this : null; + + bulkDependentResource = bulk ? (BulkDependentResource) this : null; } @Override public ReconcileResult reconcile(P primary, Context

context) { - Optional maybeActual = getSecondaryResource(primary, context); + if (bulk) { + final var count = bulkDependentResource.count(primary, context); + deleteBulkResourcesIfRequired(count, lastKnownBulkSize(), primary, context); + adjustDiscriminators(count); + @SuppressWarnings("unchecked") + final ReconcileResult[] results = new ReconcileResult[count]; + for (int i = 0; i < count; i++) { + results[i] = reconcileIndexAware(primary, i, context); + } + return ReconcileResult.aggregatedResult(results); + } else { + return reconcileIndexAware(primary, 0, context); + } + } + + protected void deleteBulkResourcesIfRequired(int targetCount, int actualCount, P primary, + Context

context) { + if (targetCount >= actualCount) { + return; + } + for (int i = targetCount; i < actualCount; i++) { + var resource = getSecondaryResourceIndexAware(primary, i, context); + var index = i; + resource.ifPresent( + r -> bulkDependentResource.deleteBulkResourceWithIndex(primary, r, index, context)); + } + } + + private void adjustDiscriminators(int count) { + if (resourceDiscriminator.size() == count) { + return; + } + if (resourceDiscriminator.size() < count) { + for (int i = resourceDiscriminator.size(); i < count; i++) { + resourceDiscriminator.add(bulkDependentResource.getResourceDiscriminator(i)); + } + } + if (resourceDiscriminator.size() > count) { + resourceDiscriminator.subList(count, resourceDiscriminator.size()).clear(); + } + } + + protected ReconcileResult reconcileIndexAware(P primary, int i, Context

context) { + Optional maybeActual = bulk ? getSecondaryResourceIndexAware(primary, i, context) + : getSecondaryResource(primary, context); if (creatable || updatable) { if (maybeActual.isEmpty()) { if (creatable) { - var desired = desired(primary, context); + var desired = desiredIndexAware(primary, i, context); throwIfNull(desired, primary, "Desired"); logForOperation("Creating", primary, desired); var createdResource = handleCreate(desired, primary, context); @@ -47,9 +97,15 @@ public ReconcileResult reconcile(P primary, Context

context) { } else { final var actual = maybeActual.get(); if (updatable) { - final var match = updater.match(actual, primary, context); + final Matcher.Result match; + if (bulk) { + match = updater.match(actual, primary, i, context); + } else { + match = updater.match(actual, primary, context); + } if (!match.matched()) { - final var desired = match.computedDesired().orElse(desired(primary, context)); + final var desired = + match.computedDesired().orElse(desiredIndexAware(primary, i, context)); throwIfNull(desired, primary, "Desired"); logForOperation("Updating", primary, desired); var updatedResource = handleUpdate(actual, desired, primary, context); @@ -67,9 +123,18 @@ public ReconcileResult reconcile(P primary, Context

context) { return ReconcileResult.noOperation(maybeActual.orElse(null)); } + private R desiredIndexAware(P primary, int i, Context

context) { + return bulk ? desired(primary, i, context) + : desired(primary, context); + } + public Optional getSecondaryResource(P primary, Context

context) { - return resourceDiscriminator == null ? context.getSecondaryResource(resourceType()) - : resourceDiscriminator.distinguish(resourceType(), primary, context); + return resourceDiscriminator.isEmpty() ? context.getSecondaryResource(resourceType()) + : resourceDiscriminator.get(0).distinguish(resourceType(), primary, context); + } + + protected Optional getSecondaryResourceIndexAware(P primary, int index, Context

context) { + return context.getSecondaryResource(resourceType(), resourceDiscriminator.get(index)); } private void throwIfNull(R desired, P primary, String descriptor) { @@ -97,7 +162,7 @@ protected R handleCreate(R desired, P primary, Context

context) { } /** - * Allows sub-classes to perform additional processing (e.g. caching) on the created resource if + * Allows subclasses to perform additional processing (e.g. caching) on the created resource if * needed. * * @param primaryResourceId the {@link ResourceID} of the primary resource associated with the @@ -129,12 +194,29 @@ protected R desired(P primary, Context

context) { "desired method must be implemented if this DependentResource can be created and/or updated"); } - public void setResourceDiscriminator( + protected R desired(P primary, int index, Context

context) { + throw new IllegalStateException( + "Must be implemented for bulk DependentResource creation"); + } + + public AbstractDependentResource setResourceDiscriminator( ResourceDiscriminator resourceDiscriminator) { - this.resourceDiscriminator = resourceDiscriminator; + if (resourceDiscriminator != null) { + this.resourceDiscriminator.add(resourceDiscriminator); + } + return this; } public ResourceDiscriminator getResourceDiscriminator() { - return resourceDiscriminator; + if (this.resourceDiscriminator.isEmpty()) { + return null; + } else { + return this.resourceDiscriminator.get(0); + } } + + protected int lastKnownBulkSize() { + return resourceDiscriminator.size(); + } + } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResource.java new file mode 100644 index 0000000000..1f2688f5cb --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResource.java @@ -0,0 +1,35 @@ +package io.javaoperatorsdk.operator.processing.dependent; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; + +/** + * Manages dynamic number of resources created for a primary resource. Since the point of a bulk + * dependent resource is to manage the number of secondary resources dynamically it implement + * {@link Creator} and {@link Deleter} interfaces out of the box. A concrete dependent resource can + * implement additionally also {@link Updater}. + */ +public interface BulkDependentResource extends Creator, Deleter

{ + + /** + * @return number of resources to create + */ + int count(P primary, Context

context); + + R desired(P primary, int index, Context

context); + + /** + * Used to delete resource if the desired count is lower than the actual count of a resource. + * + * @param primary resource + * @param resource actual resource from the cache for the index + * @param i index of the resource + * @param context actual context + */ + void deleteBulkResourceWithIndex(P primary, R resource, int i, Context

context); + + ResourceDiscriminator getResourceDiscriminator(int index); + +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkUpdater.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkUpdater.java new file mode 100644 index 0000000000..9c00b47d0c --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkUpdater.java @@ -0,0 +1,20 @@ +package io.javaoperatorsdk.operator.processing.dependent; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.reconciler.Context; + +/** + * Helper for the Bulk Dependent Resources to make it more explicit that bulk needs to only + * implement the index aware match method. + * + * @param secondary resource type + * @param

primary resource type + */ +public interface BulkUpdater extends Updater { + + default Matcher.Result match(R actualResource, P primary, Context

context) { + throw new IllegalStateException(); + } + + Matcher.Result match(R actualResource, P primary, int index, Context

context); +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/DesiredEqualsMatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/DesiredEqualsMatcher.java index 459d7951d6..1d3b34a47b 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/DesiredEqualsMatcher.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/DesiredEqualsMatcher.java @@ -16,4 +16,10 @@ public Result match(R actualResource, P primary, Context

context) { var desired = abstractDependentResource.desired(primary, context); return Result.computed(actualResource.equals(desired), desired); } + + @Override + public Result match(R actualResource, P primary, int index, Context

context) { + var desired = abstractDependentResource.desired(primary, index, context); + return Result.computed(actualResource.equals(desired), desired); + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/Matcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/Matcher.java index 750fe89cbf..835f76ab3a 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/Matcher.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/Matcher.java @@ -95,4 +95,19 @@ public Optional computedDesired() { * {@link Result#computed(boolean, Object)}) */ Result match(R actualResource, P primary, Context

context); + + /** + * Determines whether the specified secondary resource matches the desired state with target index + * of a bulk resource as defined from the specified primary resource, given the specified + * {@link Context}. + * + * @param actualResource the resource we want to determine whether it's matching the desired state + * @param primary the primary resource from which the desired state is inferred + * @param context the context in which the resource is being matched + * @return a {@link Result} encapsulating whether the resource matched its desired state and this + * associated state if it was computed as part of the matching process. Use the static + * convenience methods ({@link Result#nonComputed(boolean)} and + * {@link Result#computed(boolean, Object)}) + */ + Result match(R actualResource, P primary, int index, Context

context); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/Updater.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/Updater.java index 828f9ad785..06b3cb52f6 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/Updater.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/Updater.java @@ -8,4 +8,8 @@ public interface Updater { R update(R actual, R desired, P primary, Context

context); Result match(R actualResource, P primary, Context

context); + + default Result match(R actualResource, P primary, int index, Context

context) { + throw new IllegalStateException("Implement this for bulk matching"); + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java index e294b1c938..bb066b5b24 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java @@ -24,17 +24,42 @@ private GenericKubernetesResourceMatcher(KubernetesDependentResource depen static Matcher matcherFor( Class resourceType, KubernetesDependentResource dependentResource) { if (Secret.class.isAssignableFrom(resourceType)) { - return (actual, primary, context) -> { - final var desired = dependentResource.desired(primary, context); - return Result.computed( - ResourceComparators.compareSecretData((Secret) desired, (Secret) actual), desired); + return new Matcher<>() { + @Override + public Result match(R actualResource, P primary, Context

context) { + final var desired = dependentResource.desired(primary, context); + return Result.computed( + ResourceComparators.compareSecretData((Secret) desired, (Secret) actualResource), + desired); + } + + @Override + public Result match(R actualResource, P primary, int index, Context

context) { + final var desired = dependentResource.desired(primary, index, context); + return Result.computed( + ResourceComparators.compareSecretData((Secret) desired, (Secret) actualResource), + desired); + } }; } else if (ConfigMap.class.isAssignableFrom(resourceType)) { - return (actual, primary, context) -> { - final var desired = dependentResource.desired(primary, context); - return Result.computed( - ResourceComparators.compareConfigMapData((ConfigMap) desired, (ConfigMap) actual), - desired); + return new Matcher<>() { + @Override + public Result match(R actualResource, P primary, Context

context) { + final var desired = dependentResource.desired(primary, context); + return Result.computed( + ResourceComparators.compareConfigMapData((ConfigMap) desired, + (ConfigMap) actualResource), + desired); + } + + @Override + public Result match(R actualResource, P primary, int index, Context

context) { + final var desired = dependentResource.desired(primary, index, context); + return Result.computed( + ResourceComparators.compareConfigMapData((ConfigMap) desired, + (ConfigMap) actualResource), + desired); + } }; } else { return new GenericKubernetesResourceMatcher(dependentResource); @@ -43,32 +68,18 @@ static Matcher matcherFor( @Override public Result match(R actualResource, P primary, Context

context) { - return match(dependentResource, actualResource, primary, context, false); + var desired = dependentResource.desired(primary, context); + return match(desired, actualResource, false); } - /** - * Determines whether the specified actual resource matches the desired state defined by the - * specified {@link KubernetesDependentResource} based on the observed state of the associated - * specified primary resource. - * - * @param dependentResource the {@link KubernetesDependentResource} implementation used to - * computed the desired state associated with the specified primary resource - * @param actualResource the observed dependent resource for which we want to determine whether it - * matches the desired state or not - * @param primary the primary resource from which we want to compute the desired state - * @param context the {@link Context} instance within which this method is called - * @param considerMetadata {@code true} to consider the metadata of the actual resource when - * determining if it matches the desired state, {@code false} if matching should occur only - * considering the spec of the resources - * @return a {@link io.javaoperatorsdk.operator.processing.dependent.Matcher.Result} object - * @param the type of resource we want to determine whether they match or not - * @param

the type of primary resources associated with the secondary resources we want to - * match - */ - public static Result match( - KubernetesDependentResource dependentResource, R actualResource, P primary, - Context

context, boolean considerMetadata) { - final var desired = dependentResource.desired(primary, context); + @Override + public Result match(R actualResource, P primary, int index, Context

context) { + var desired = dependentResource.desired(primary, index, context); + return match(desired, actualResource, false); + } + + public static Result match( + R desired, R actualResource, boolean considerMetadata) { if (considerMetadata) { final var desiredMetadata = desired.getMetadata(); final var actualMetadata = actualResource.getMetadata(); @@ -95,4 +106,30 @@ public static Result match( } return Result.computed(true, desired); } + + /** + * Determines whether the specified actual resource matches the desired state defined by the + * specified {@link KubernetesDependentResource} based on the observed state of the associated + * specified primary resource. + * + * @param dependentResource the {@link KubernetesDependentResource} implementation used to + * computed the desired state associated with the specified primary resource + * @param actualResource the observed dependent resource for which we want to determine whether it + * matches the desired state or not + * @param primary the primary resource from which we want to compute the desired state + * @param context the {@link Context} instance within which this method is called + * @param considerMetadata {@code true} to consider the metadata of the actual resource when + * determining if it matches the desired state, {@code false} if matching should occur only + * considering the spec of the resources + * @return a {@link io.javaoperatorsdk.operator.processing.dependent.Matcher.Result} object + * @param the type of resource we want to determine whether they match or not + * @param

the type of primary resources associated with the secondary resources we want to + * match + */ + public static Result match( + KubernetesDependentResource dependentResource, R actualResource, P primary, + Context

context, boolean considerMetadata) { + final var desired = dependentResource.desired(primary, context); + return match(desired, actualResource, considerMetadata); + } } 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 328a061e6b..3738a2e7d2 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 @@ -134,8 +134,21 @@ public Result match(R actualResource, P primary, Context

context) { return matcher.match(actualResource, primary, context); } + public Result match(R actualResource, P primary, int index, Context

context) { + return matcher.match(actualResource, primary, index, context); + } + public void delete(P primary, Context

context) { - getSecondaryResource(primary, context).ifPresent(r -> client.resource(r).delete()); + if (bulk) { + deleteBulkResourcesIfRequired(0, lastKnownBulkSize(), primary, context); + } else { + var resource = getSecondaryResource(primary, context); + resource.ifPresent(r -> client.resource(r).delete()); + } + } + + public void deleteBulkResourceWithIndex(P primary, R resource, int i, Context

context) { + client.resource(resource).delete(); } @SuppressWarnings("unchecked") @@ -149,9 +162,7 @@ protected Resource prepare(R desired, P primary, String actionName) { } else if (useDefaultAnnotationsToIdentifyPrimary()) { addDefaultSecondaryToPrimaryMapperAnnotations(desired, primary); } - Class targetClass = (Class) desired.getClass(); - return client.resources(targetClass).inNamespace(desired.getMetadata().getNamespace()) - .resource(desired); + return client.resource(desired).inNamespace(desired.getMetadata().getNamespace()); } @Override @@ -163,8 +174,10 @@ protected InformerEventSource createEventSource(EventSourceContext

cont onUpdateFilter = kubernetesDependentResourceConfig.onUpdateFilter(); onDeleteFilter = kubernetesDependentResourceConfig.onDeleteFilter(); genericFilter = kubernetesDependentResourceConfig.genericFilter(); - setResourceDiscriminator(kubernetesDependentResourceConfig.getResourceDiscriminator()); - + var discriminator = kubernetesDependentResourceConfig.getResourceDiscriminator(); + if (discriminator != null) { + setResourceDiscriminator(discriminator); + } configureWith(kubernetesDependentResourceConfig.labelSelector(), kubernetesDependentResourceConfig.namespaces(), !kubernetesDependentResourceConfig.wereNamespacesConfigured(), context); @@ -215,6 +228,11 @@ protected R desired(P primary, Context

context) { return super.desired(primary, context); } + @Override + protected R desired(P primary, int index, Context

context) { + return super.desired(primary, index, context); + } + private void prepareEventFiltering(R desired, ResourceID resourceID) { eventSource().prepareForCreateOrUpdateEventFiltering(resourceID, desired); } 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 e2a2c0f684..c7674dd1a7 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 @@ -75,7 +75,6 @@ public OnAddFilter onAddFilter() { return onAddFilter; } - public OnUpdateFilter onUpdateFilter() { return onUpdateFilter; } @@ -92,4 +91,10 @@ public GenericFilter genericFilter() { public ResourceDiscriminator getResourceDiscriminator() { return resourceDiscriminator; } + + public

KubernetesDependentResourceConfig setResourceDiscriminator( + ResourceDiscriminator resourceDiscriminator) { + this.resourceDiscriminator = resourceDiscriminator; + return this; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutor.java index 45541b91d6..ac08d2d874 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutor.java @@ -125,7 +125,6 @@ public void run() { } } - private synchronized void handleDependentCleaned( DependentResourceNode dependentResourceNode) { var dependOns = dependentResourceNode.getDependsOn(); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/bulkdependent/BulkDependentDeleterIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/bulkdependent/BulkDependentDeleterIT.java new file mode 100644 index 0000000000..a934bdd1f3 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/bulkdependent/BulkDependentDeleterIT.java @@ -0,0 +1,19 @@ +package io.javaoperatorsdk.operator.bulkdependent; + +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; +import io.javaoperatorsdk.operator.sample.bulkdependent.ManagedDeleterBulkReconciler; + +public class BulkDependentDeleterIT extends BulkDependentTestBase { + + @RegisterExtension + LocallyRunOperatorExtension extension = + LocallyRunOperatorExtension.builder().withReconciler(new ManagedDeleterBulkReconciler()) + .build(); + + @Override + LocallyRunOperatorExtension extension() { + return extension; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/bulkdependent/BulkDependentTestBase.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/bulkdependent/BulkDependentTestBase.java new file mode 100644 index 0000000000..605731623c --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/bulkdependent/BulkDependentTestBase.java @@ -0,0 +1,114 @@ +package io.javaoperatorsdk.operator.bulkdependent; + +import java.time.Duration; + +import org.junit.jupiter.api.Test; + +import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; +import io.javaoperatorsdk.operator.sample.bulkdependent.BulkDependentTestCustomResource; +import io.javaoperatorsdk.operator.sample.bulkdependent.BulkDependentTestSpec; +import io.javaoperatorsdk.operator.sample.bulkdependent.ConfigMapDeleterBulkDependentResource; + +import static io.javaoperatorsdk.operator.sample.bulkdependent.ConfigMapDeleterBulkDependentResource.LABEL_KEY; +import static io.javaoperatorsdk.operator.sample.bulkdependent.ConfigMapDeleterBulkDependentResource.LABEL_VALUE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +public abstract class BulkDependentTestBase { + + public static final String TEST_RESOURCE_NAME = "test"; + public static final int INITIAL_NUMBER_OF_CONFIG_MAPS = 3; + public static final String INITIAL_ADDITIONAL_DATA = "initialData"; + public static final String NEW_VERSION_OF_ADDITIONAL_DATA = "newVersionOfAdditionalData"; + + @Test + public void managesBulkConfigMaps() { + extension().create(testResource()); + assertNumberOfConfigMaps(3); + + updateSpecWithNumber(1); + assertNumberOfConfigMaps(1); + + updateSpecWithNumber(5); + assertNumberOfConfigMaps(5); + + extension().delete(testResource()); + assertNumberOfConfigMaps(0); + } + + @Test + public void updatesData() { + extension().create(testResource()); + assertNumberOfConfigMaps(3); + assertAdditionalDataOnConfigMaps(INITIAL_ADDITIONAL_DATA); + + updateSpecWithNewAdditionalData(NEW_VERSION_OF_ADDITIONAL_DATA); + assertAdditionalDataOnConfigMaps(NEW_VERSION_OF_ADDITIONAL_DATA); + } + + private void assertNumberOfConfigMaps(int n) { + // this test was failing with a lower timeout on GitHub, probably the garbage collection was + // slower there. + await().atMost(Duration.ofSeconds(30)) + .untilAsserted(() -> { + var cms = + extension().getKubernetesClient().configMaps().inNamespace(extension().getNamespace()) + .withLabel(LABEL_KEY, LABEL_VALUE) + .list().getItems(); + assertThat(cms).withFailMessage("Number of items is still: " + cms.size()) + .hasSize(n); + }); + } + + private void assertAdditionalDataOnConfigMaps(String expectedValue) { + await().atMost(Duration.ofSeconds(30)) + .untilAsserted(() -> { + var cms = + extension().getKubernetesClient().configMaps().inNamespace(extension().getNamespace()) + .withLabel(LABEL_KEY, LABEL_VALUE) + .list().getItems(); + cms.forEach(cm -> { + assertThat(cm.getData().get(ConfigMapDeleterBulkDependentResource.ADDITIONAL_DATA_KEY)) + .isEqualTo(expectedValue); + }); + }); + } + + public static BulkDependentTestCustomResource testResource() { + BulkDependentTestCustomResource cr = new BulkDependentTestCustomResource(); + cr.setMetadata(new ObjectMeta()); + cr.getMetadata().setName(TEST_RESOURCE_NAME); + cr.setSpec(new BulkDependentTestSpec()); + cr.getSpec().setNumberOfResources(INITIAL_NUMBER_OF_CONFIG_MAPS); + cr.getSpec().setAdditionalData(INITIAL_ADDITIONAL_DATA); + return cr; + } + + private void updateSpecWithNewAdditionalData(String data) { + var resource = testResource(); + resource.getSpec().setAdditionalData(data); + extension().replace(resource); + } + + public static void updateSpecWithNewAdditionalData(LocallyRunOperatorExtension extension, + String data) { + var resource = testResource(); + resource.getSpec().setAdditionalData(data); + extension.replace(resource); + } + + private void updateSpecWithNumber(int n) { + var resource = testResource(); + resource.getSpec().setNumberOfResources(n); + extension().replace(resource); + } + + public static void updateSpecWithNumber(LocallyRunOperatorExtension extension, int n) { + var resource = testResource(); + resource.getSpec().setNumberOfResources(n); + extension.replace(resource); + } + + abstract LocallyRunOperatorExtension extension(); +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/bulkdependent/BulkExternalDependentIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/bulkdependent/BulkExternalDependentIT.java new file mode 100644 index 0000000000..29f66e8205 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/bulkdependent/BulkExternalDependentIT.java @@ -0,0 +1,56 @@ +package io.javaoperatorsdk.operator.bulkdependent; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; +import io.javaoperatorsdk.operator.sample.bulkdependent.external.ExternalBulkResourceReconciler; +import io.javaoperatorsdk.operator.sample.bulkdependent.external.ExternalServiceMock; + +import static io.javaoperatorsdk.operator.bulkdependent.BulkDependentTestBase.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +class BulkExternalDependentIT { + + @RegisterExtension + LocallyRunOperatorExtension extension = + LocallyRunOperatorExtension.builder().withReconciler(new ExternalBulkResourceReconciler()) + .build(); + + ExternalServiceMock externalServiceMock = ExternalServiceMock.getInstance(); + + @Test + void managesExternalBulkResources() { + extension.create(testResource()); + assertResourceNumberAndData(3, INITIAL_ADDITIONAL_DATA); + + updateSpecWithNumber(extension, 1); + assertResourceNumberAndData(1, INITIAL_ADDITIONAL_DATA); + + updateSpecWithNumber(extension, 5); + assertResourceNumberAndData(5, INITIAL_ADDITIONAL_DATA); + + extension.delete(testResource()); + assertResourceNumberAndData(0, INITIAL_ADDITIONAL_DATA); + } + + + @Test + void handlesResourceUpdates() { + extension.create(testResource()); + assertResourceNumberAndData(3, INITIAL_ADDITIONAL_DATA); + + updateSpecWithNewAdditionalData(extension, NEW_VERSION_OF_ADDITIONAL_DATA); + assertResourceNumberAndData(3, NEW_VERSION_OF_ADDITIONAL_DATA); + } + + private void assertResourceNumberAndData(int n, String data) { + await().untilAsserted(() -> { + var resources = externalServiceMock.listResources(); + assertThat(resources).hasSize(n); + assertThat(resources).allMatch(r -> r.getData().equals(data)); + }); + } + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/bulkdependent/ManagedBulkDependentIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/bulkdependent/ManagedBulkDependentIT.java new file mode 100644 index 0000000000..7f074ac8f5 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/bulkdependent/ManagedBulkDependentIT.java @@ -0,0 +1,20 @@ +package io.javaoperatorsdk.operator.bulkdependent; + +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; +import io.javaoperatorsdk.operator.sample.bulkdependent.ManagedBulkDependentReconciler; + +class ManagedBulkDependentIT extends BulkDependentTestBase { + + @RegisterExtension + LocallyRunOperatorExtension extension = + LocallyRunOperatorExtension.builder().withReconciler(new ManagedBulkDependentReconciler()) + .build(); + + + @Override + LocallyRunOperatorExtension extension() { + return extension; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/bulkdependent/StandaloneBulkDependentIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/bulkdependent/StandaloneBulkDependentIT.java new file mode 100644 index 0000000000..683cc1662b --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/bulkdependent/StandaloneBulkDependentIT.java @@ -0,0 +1,19 @@ +package io.javaoperatorsdk.operator.bulkdependent; + +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; +import io.javaoperatorsdk.operator.sample.bulkdependent.StandaloneBulkDependentReconciler; + +class StandaloneBulkDependentIT extends BulkDependentTestBase { + + @RegisterExtension + LocallyRunOperatorExtension extension = + LocallyRunOperatorExtension.builder().withReconciler(new StandaloneBulkDependentReconciler()) + .build(); + + @Override + LocallyRunOperatorExtension extension() { + return extension; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/BulkDependentTestCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/BulkDependentTestCustomResource.java new file mode 100644 index 0000000000..68e6297f8c --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/BulkDependentTestCustomResource.java @@ -0,0 +1,15 @@ +package io.javaoperatorsdk.operator.sample.bulkdependent; + +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("sbd") +public class BulkDependentTestCustomResource + extends CustomResource + implements Namespaced { +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/BulkDependentTestSpec.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/BulkDependentTestSpec.java new file mode 100644 index 0000000000..5266950b41 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/BulkDependentTestSpec.java @@ -0,0 +1,25 @@ +package io.javaoperatorsdk.operator.sample.bulkdependent; + +public class BulkDependentTestSpec { + + private Integer numberOfResources; + private String additionalData; + + public Integer getNumberOfResources() { + return numberOfResources; + } + + public BulkDependentTestSpec setNumberOfResources(Integer numberOfResources) { + this.numberOfResources = numberOfResources; + return this; + } + + public BulkDependentTestSpec setAdditionalData(String additionalData) { + this.additionalData = additionalData; + return this; + } + + public String getAdditionalData() { + return additionalData; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/CRUDConfigMapBulkDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/CRUDConfigMapBulkDependentResource.java new file mode 100644 index 0000000000..83cec0bb69 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/CRUDConfigMapBulkDependentResource.java @@ -0,0 +1,7 @@ +package io.javaoperatorsdk.operator.sample.bulkdependent; + +import io.javaoperatorsdk.operator.api.reconciler.dependent.GarbageCollected; + +public class CRUDConfigMapBulkDependentResource extends ConfigMapDeleterBulkDependentResource + implements GarbageCollected { +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ConfigMapDeleterBulkDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ConfigMapDeleterBulkDependentResource.java new file mode 100644 index 0000000000..a7fbd9cb98 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ConfigMapDeleterBulkDependentResource.java @@ -0,0 +1,72 @@ +package io.javaoperatorsdk.operator.sample.bulkdependent; + +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +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.api.reconciler.ResourceDiscriminator; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; +import io.javaoperatorsdk.operator.processing.dependent.BulkDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.Creator; +import io.javaoperatorsdk.operator.processing.dependent.Updater; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; + +/** + * Not using CRUDKubernetesDependentResource so the delete functionality can be tested. + */ +public class ConfigMapDeleterBulkDependentResource + extends + KubernetesDependentResource + implements Creator, + Updater, + Deleter, + BulkDependentResource { + + public static final String LABEL_KEY = "bulk"; + public static final String LABEL_VALUE = "true"; + public static final String ADDITIONAL_DATA_KEY = "additionalData"; + + public ConfigMapDeleterBulkDependentResource() { + super(ConfigMap.class); + } + + @Override + public ConfigMap desired(BulkDependentTestCustomResource primary, + int index, Context context) { + ConfigMap configMap = new ConfigMap(); + configMap.setMetadata(new ObjectMetaBuilder() + .withName(primary.getMetadata().getName() + "-" + index) + .withNamespace(primary.getMetadata().getNamespace()) + .withLabels(Map.of(LABEL_KEY, LABEL_VALUE)) + .build()); + configMap.setData( + Map.of("number", "" + index, ADDITIONAL_DATA_KEY, primary.getSpec().getAdditionalData())); + return configMap; + } + + @Override + public int count(BulkDependentTestCustomResource primary, + Context context) { + return primary.getSpec().getNumberOfResources(); + } + + @Override + public ResourceDiscriminator getResourceDiscriminator( + int index) { + return (resource, primary, context) -> { + var resources = context.getSecondaryResources(resource).stream() + .filter(r -> r.getMetadata().getName().endsWith("-" + index)) + .collect(Collectors.toList()); + if (resources.isEmpty()) { + return Optional.empty(); + } else if (resources.size() > 1) { + throw new IllegalStateException("More than one resource found for index:" + index); + } else { + return Optional.of(resources.get(0)); + } + }; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ManagedBulkDependentReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ManagedBulkDependentReconciler.java new file mode 100644 index 0000000000..3b2acd942e --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ManagedBulkDependentReconciler.java @@ -0,0 +1,25 @@ +package io.javaoperatorsdk.operator.sample.bulkdependent; + +import java.util.concurrent.atomic.AtomicInteger; + +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; + +@ControllerConfiguration(dependents = @Dependent(type = CRUDConfigMapBulkDependentResource.class)) +public class ManagedBulkDependentReconciler + implements Reconciler { + + private final AtomicInteger numberOfExecutions = new AtomicInteger(0); + + @Override + public UpdateControl reconcile( + BulkDependentTestCustomResource resource, + Context context) throws Exception { + + numberOfExecutions.addAndGet(1); + return UpdateControl.noUpdate(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ManagedDeleterBulkReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ManagedDeleterBulkReconciler.java new file mode 100644 index 0000000000..e759bdd200 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ManagedDeleterBulkReconciler.java @@ -0,0 +1,20 @@ +package io.javaoperatorsdk.operator.sample.bulkdependent; + +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; + +@ControllerConfiguration( + dependents = @Dependent(type = ConfigMapDeleterBulkDependentResource.class)) +public class ManagedDeleterBulkReconciler implements Reconciler { + @Override + public UpdateControl reconcile( + BulkDependentTestCustomResource resource, + Context context) + throws Exception { + + return UpdateControl.noUpdate(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/StandaloneBulkDependentReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/StandaloneBulkDependentReconciler.java new file mode 100644 index 0000000000..4033583340 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/StandaloneBulkDependentReconciler.java @@ -0,0 +1,58 @@ +package io.javaoperatorsdk.operator.sample.bulkdependent; + +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.junit.KubernetesClientAware; +import io.javaoperatorsdk.operator.processing.event.source.EventSource; +import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; + +@ControllerConfiguration +public class StandaloneBulkDependentReconciler + implements Reconciler, TestExecutionInfoProvider, + EventSourceInitializer, KubernetesClientAware { + + private final AtomicInteger numberOfExecutions = new AtomicInteger(0); + + private ConfigMapDeleterBulkDependentResource dependent; + private KubernetesClient kubernetesClient; + + public StandaloneBulkDependentReconciler() { + dependent = new CRUDConfigMapBulkDependentResource(); + } + + @Override + public UpdateControl reconcile( + BulkDependentTestCustomResource resource, + Context context) { + numberOfExecutions.addAndGet(1); + + dependent.reconcile(resource, context); + + return UpdateControl.noUpdate(); + } + + public int getNumberOfExecutions() { + return numberOfExecutions.get(); + } + + @Override + public Map prepareEventSources( + EventSourceContext context) { + return EventSourceInitializer + .nameEventSources(dependent.initEventSource(context)); + } + + @Override + public KubernetesClient getKubernetesClient() { + return kubernetesClient; + } + + @Override + public void setKubernetesClient(KubernetesClient kubernetesClient) { + this.kubernetesClient = kubernetesClient; + dependent.setKubernetesClient(kubernetesClient); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalBulkDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalBulkDependentResource.java new file mode 100644 index 0000000000..110626a923 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalBulkDependentResource.java @@ -0,0 +1,101 @@ +package io.javaoperatorsdk.operator.sample.bulkdependent.external; + +import java.util.*; +import java.util.stream.Collectors; + +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; +import io.javaoperatorsdk.operator.processing.dependent.*; +import io.javaoperatorsdk.operator.processing.dependent.external.PollingDependentResource; +import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.sample.bulkdependent.BulkDependentTestCustomResource; + +public class ExternalBulkDependentResource + extends PollingDependentResource + implements BulkDependentResource, + BulkUpdater { + + public static final String EXTERNAL_RESOURCE_NAME_DELIMITER = "#"; + + private final ExternalServiceMock externalServiceMock = ExternalServiceMock.getInstance(); + + public ExternalBulkDependentResource() { + super(ExternalResource.class, ExternalResource::getId); + } + + @Override + public Map> fetchResources() { + Map> result = new HashMap<>(); + var resources = externalServiceMock.listResources(); + resources.forEach(er -> { + var resourceID = toResourceID(er); + result.putIfAbsent(resourceID, new HashSet<>()); + result.get(resourceID).add(er); + }); + return result; + } + + @Override + public void delete(BulkDependentTestCustomResource primary, + Context context) { + deleteBulkResourcesIfRequired(0, lastKnownBulkSize(), primary, context); + } + + @Override + public int count(BulkDependentTestCustomResource primary, + Context context) { + return primary.getSpec().getNumberOfResources(); + } + + @Override + public void deleteBulkResourceWithIndex(BulkDependentTestCustomResource primary, + ExternalResource resource, int i, Context context) { + externalServiceMock.delete(resource.getId()); + } + + @Override + public ExternalResource desired(BulkDependentTestCustomResource primary, int index, + Context context) { + return new ExternalResource(toExternalResourceId(primary, index), + primary.getSpec().getAdditionalData()); + } + + @Override + public ExternalResource create(ExternalResource desired, BulkDependentTestCustomResource primary, + Context context) { + return externalServiceMock.create(desired); + } + + @Override + public ExternalResource update(ExternalResource actual, ExternalResource desired, + BulkDependentTestCustomResource primary, Context context) { + return externalServiceMock.update(desired); + } + + @Override + public Matcher.Result match(ExternalResource actualResource, + BulkDependentTestCustomResource primary, + int index, Context context) { + var desired = desired(primary, index, context); + return Matcher.Result.computed(desired.equals(actualResource), desired); + } + + private static String toExternalResourceId(BulkDependentTestCustomResource primary, int i) { + return primary.getMetadata().getName() + EXTERNAL_RESOURCE_NAME_DELIMITER + + primary.getMetadata().getNamespace() + + EXTERNAL_RESOURCE_NAME_DELIMITER + i; + } + + private ResourceID toResourceID(ExternalResource externalResource) { + var parts = externalResource.getId().split(EXTERNAL_RESOURCE_NAME_DELIMITER); + return new ResourceID(parts[0], parts[1]); + } + + @Override + public ResourceDiscriminator getResourceDiscriminator( + int index) { + return (resource, primary, context) -> context.getSecondaryResources(resource).stream() + .filter(r -> r.getId().endsWith(EXTERNAL_RESOURCE_NAME_DELIMITER + index)) + .collect(Collectors.toList()).stream().findFirst(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalBulkResourceReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalBulkResourceReconciler.java new file mode 100644 index 0000000000..2543422d74 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalBulkResourceReconciler.java @@ -0,0 +1,19 @@ +package io.javaoperatorsdk.operator.sample.bulkdependent.external; + +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; +import io.javaoperatorsdk.operator.sample.bulkdependent.BulkDependentTestCustomResource; + +@ControllerConfiguration(dependents = @Dependent(type = ExternalBulkDependentResource.class)) +public class ExternalBulkResourceReconciler implements Reconciler { + + @Override + public UpdateControl reconcile( + BulkDependentTestCustomResource resource, Context context) + throws Exception { + return UpdateControl.noUpdate(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalResource.java new file mode 100644 index 0000000000..935fd99e47 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalResource.java @@ -0,0 +1,37 @@ +package io.javaoperatorsdk.operator.sample.bulkdependent.external; + +import java.util.Objects; + +public class ExternalResource { + + private String id; + private String data; + + public ExternalResource(String id, String data) { + this.id = id; + this.data = data; + } + + public String getId() { + return id; + } + + public String getData() { + return data; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + ExternalResource that = (ExternalResource) o; + return Objects.equals(id, that.id) && Objects.equals(data, that.data); + } + + @Override + public int hashCode() { + return Objects.hash(id, data); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalServiceMock.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalServiceMock.java new file mode 100644 index 0000000000..e73062ccf2 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalServiceMock.java @@ -0,0 +1,39 @@ +package io.javaoperatorsdk.operator.sample.bulkdependent.external; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +public class ExternalServiceMock { + + private static ExternalServiceMock serviceMock = new ExternalServiceMock(); + + private Map resourceMap = new ConcurrentHashMap<>(); + + public ExternalResource create(ExternalResource externalResource) { + resourceMap.put(externalResource.getId(), externalResource); + return externalResource; + } + + public Optional read(String id) { + return Optional.ofNullable(resourceMap.get(id)); + } + + public ExternalResource update(ExternalResource externalResource) { + return resourceMap.put(externalResource.getId(), externalResource); + } + + public Optional delete(String id) { + return Optional.ofNullable(resourceMap.remove(id)); + } + + public List listResources() { + return new ArrayList<>(resourceMap.values()); + } + + public static ExternalServiceMock getInstance() { + return serviceMock; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceReconciler.java index 49f5ee64c1..0994e6b9b0 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceReconciler.java @@ -29,7 +29,6 @@ public class MultipleDependentResourceReconciler public MultipleDependentResourceReconciler() { firstDependentResourceConfigMap = new MultipleDependentResourceConfigMap(FIRST_CONFIG_MAP_ID); - secondDependentResourceConfigMap = new MultipleDependentResourceConfigMap(SECOND_CONFIG_MAP_ID); firstDependentResourceConfigMap From 79c63fbb1d25bdd3df8e6c6f5b9d9addeca34471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Tue, 4 Oct 2022 10:24:59 +0200 Subject: [PATCH 3/8] feat: optional eventsource on dependent resources (#1479) --- .../AnnotationControllerConfiguration.java | 9 +- .../ControllerConfigurationOverrider.java | 4 +- .../dependent/DependentResourceSpec.java | 10 +- .../reconciler/EventSourceInitializer.java | 20 +++ .../api/reconciler/ResourceDiscriminator.java | 1 + .../api/reconciler/dependent/Dependent.java | 9 ++ .../dependent/DependentResource.java | 27 ++++ .../dependent/EventSourceAware.java | 10 ++ .../dependent/EventSourceProvider.java | 5 + .../operator/processing/Controller.java | 31 ++-- .../dependent/AbstractDependentResource.java | 27 +++- ...actEventSourceHolderDependentResource.java | 46 ++++-- .../BulkResourceDiscriminatorFactory.java | 10 ++ .../AbstractSimpleDependentResource.java | 81 ---------- .../kubernetes/KubernetesDependent.java | 2 + .../KubernetesDependentResource.java | 25 ++- .../KubernetesDependentResourceConfig.java | 15 +- .../workflow/ManagedWorkflowSupport.java | 4 +- .../workflow/WorkflowCleanupExecutor.java | 1 + .../event/EventSourceRetriever.java | 2 +- .../ControllerConfigurationOverriderTest.java | 7 +- .../AbstractDependentResourceTest.java | 8 + .../dependent/EmptyTestDependentResource.java | 3 + .../AbstractSimpleDependentResourceTest.java | 149 ------------------ .../AbstractWorkflowExecutorTest.java | 6 + .../workflow/ManagedWorkflowTestUtils.java | 3 +- .../operator/IntegrationTestConstants.java | 7 + ...ubernetesDependentGarbageCollectionIT.java | 9 +- .../MultipleManagedDependentSameTypeIT.java | 80 ++++++++++ ...pleManagedExternalDependentSameTypeIT.java | 71 +++++++++ ...endentGarbageCollectionTestReconciler.java | 4 +- .../MultipleDependentResourceConfigMap.java | 2 - .../MultipleDependentResourceReconciler.java | 1 + .../ConfigMap1Discriminator.java | 26 +++ .../ConfigMap2Discriminator.java | 26 +++ ...pleManagedDependentResourceConfigMap1.java | 40 +++++ ...pleManagedDependentResourceConfigMap2.java | 41 +++++ ...anagedDependentResourceCustomResource.java | 16 ++ ...pleManagedDependentResourceReconciler.java | 53 +++++++ .../MultipleManagedDependentResourceSpec.java | 15 ++ .../AbstractExternalDependentResource.java | 75 +++++++++ .../ExternalDependentResource1.java | 15 ++ .../ExternalDependentResource2.java | 15 ++ .../ExternalResourceDiscriminator.java | 25 +++ ...ternalDependentResourceCustomResource.java | 17 ++ ...edExternalDependentResourceReconciler.java | 64 ++++++++ ...DependentPrimaryIndexerTestReconciler.java | 7 +- .../StandaloneDependentTestReconciler.java | 4 +- .../operator/support/ExternalResource.java | 58 +++++++ .../operator/support/ExternalServiceMock.java | 39 +++++ .../dependent/SchemaDependentResource.java | 5 +- .../WebPageDependentsWorkflowReconciler.java | 6 +- ...WebPageStandaloneDependentsReconciler.java | 5 +- 53 files changed, 941 insertions(+), 300 deletions(-) create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/EventSourceAware.java create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkResourceDiscriminatorFactory.java delete mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractSimpleDependentResource.java delete mode 100644 operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractSimpleDependentResourceTest.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/IntegrationTestConstants.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleManagedDependentSameTypeIT.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleManagedExternalDependentSameTypeIT.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/ConfigMap1Discriminator.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/ConfigMap2Discriminator.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap1.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap2.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceCustomResource.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceReconciler.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceSpec.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/AbstractExternalDependentResource.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/ExternalDependentResource1.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/ExternalDependentResource2.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/ExternalResourceDiscriminator.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/MultipleManagedExternalDependentResourceCustomResource.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/MultipleManagedExternalDependentResourceReconciler.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/support/ExternalResource.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/support/ExternalServiceMock.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 dbd09a32cc..fa2b5c1927 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 @@ -247,7 +247,8 @@ public List getDependentResources() { Set.of(dependent.dependsOn()), instantiateIfNotDefault(dependent.readyPostcondition(), Condition.class, context), instantiateIfNotDefault(dependent.reconcilePrecondition(), Condition.class, context), - instantiateIfNotDefault(dependent.deletePostcondition(), Condition.class, context)); + instantiateIfNotDefault(dependent.deletePostcondition(), Condition.class, context), + dependent.provideEventSource()); specsMap.put(name, spec); } @@ -286,13 +287,13 @@ private Object createKubernetesResourceConfig(Class OnDeleteFilter onDeleteFilter = null; GenericFilter genericFilter = null; ResourceDiscriminator resourceDiscriminator = null; + String eventSourceNameToUse = null; if (kubeDependent != null) { 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; @@ -313,12 +314,14 @@ private Object createKubernetesResourceConfig(Class resourceDiscriminator = instantiateIfNotDefault(kubeDependent.resourceDiscriminator(), ResourceDiscriminator.class, context); + eventSourceNameToUse = Constants.NO_VALUE_SET.equals(kubeDependent.eventSourceToUse()) ? null + : kubeDependent.eventSourceToUse(); } config = new KubernetesDependentResourceConfig(namespaces, labelSelector, configuredNS, resourceDiscriminator, onAddFilter, - onUpdateFilter, onDeleteFilter, genericFilter); + onUpdateFilter, onDeleteFilter, genericFilter, eventSourceNameToUse); return config; } 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 e9cae2dccf..ee153c879a 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 @@ -174,7 +174,7 @@ private void replaceConfig(String name, Object newConfig, DependentResourceSpec< namedDependentResourceSpecs.put(name, new DependentResourceSpec<>(current.getDependentResourceClass(), newConfig, name, current.getDependsOn(), current.getReadyCondition(), current.getReconcileCondition(), - current.getDeletePostCondition())); + current.getDeletePostCondition(), current.provideEventSource())); } @SuppressWarnings("unchecked") @@ -220,7 +220,7 @@ public ControllerConfiguration build() { KubernetesDependentResourceConfig c) { return new DependentResourceSpec(spec.getDependentResourceClass(), c.setNamespaces(namespaces), name, spec.getDependsOn(), spec.getReadyCondition(), - spec.getReconcileCondition(), spec.getDeletePostCondition()); + spec.getReconcileCondition(), spec.getDeletePostCondition(), spec.provideEventSource()); } public static ControllerConfigurationOverrider override( diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceSpec.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceSpec.java index f146d127d0..71476cc2d3 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceSpec.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceSpec.java @@ -23,9 +23,12 @@ public class DependentResourceSpec, C> { private final Condition deletePostCondition; + private final boolean provideEventSource; + public DependentResourceSpec(Class dependentResourceClass, C dependentResourceConfig, String name, Set dependsOn, Condition readyCondition, - Condition reconcileCondition, Condition deletePostCondition) { + Condition reconcileCondition, Condition deletePostCondition, + boolean provideEventSource) { this.dependentResourceClass = dependentResourceClass; this.dependentResourceConfig = dependentResourceConfig; this.name = name; @@ -33,6 +36,7 @@ public DependentResourceSpec(Class dependentResourceClass, C dependentResourc this.readyCondition = readyCondition; this.reconcileCondition = reconcileCondition; this.deletePostCondition = deletePostCondition; + this.provideEventSource = provideEventSource; } public Class getDependentResourceClass() { @@ -89,4 +93,8 @@ public Condition getReconcileCondition() { public Condition getDeletePostCondition() { return deletePostCondition; } + + public boolean provideEventSource() { + return provideEventSource; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceInitializer.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceInitializer.java index 9b3c7a67bd..017418ea35 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceInitializer.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceInitializer.java @@ -1,10 +1,14 @@ package io.javaoperatorsdk.operator.api.reconciler; +import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.processing.event.source.EventSource; +import io.javaoperatorsdk.operator.processing.event.source.ResourceEventSource; /** * An interface that a {@link Reconciler} can implement to have the SDK register the provided @@ -39,6 +43,22 @@ static Map nameEventSources(EventSource... eventSources) { return eventSourceMap; } + @SuppressWarnings("unchecked,rawtypes") + static Map nameEventSourcesFromDependentResource( + EventSourceContext context, DependentResource... dependentResources) { + + if (dependentResources != null) { + Map eventSourceMap = new HashMap<>(dependentResources.length); + for (DependentResource dependentResource : dependentResources) { + Optional es = dependentResource.eventSource(context); + es.ifPresent(e -> eventSourceMap.put(generateNameFor(e), e)); + } + return eventSourceMap; + } else { + return Collections.emptyMap(); + } + } + /** * This is for the use case when the event sources are not access explicitly by name in the * reconciler. diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceDiscriminator.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceDiscriminator.java index eb947fa440..072e7d8078 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceDiscriminator.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceDiscriminator.java @@ -7,4 +7,5 @@ public interface ResourceDiscriminator { Optional distinguish(Class resource, P primary, Context

context); + } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/Dependent.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/Dependent.java index 90ba701a6a..00d2ad1882 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/Dependent.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/Dependent.java @@ -57,4 +57,13 @@ * one can be */ String[] dependsOn() default {}; + + /** + * Setting this to false means that the event source provided by the dependent resource won't be + * used. This is helpful if more dependent resources created for the same type, and want to share + * a common event source. In that case an event source needs to be explicitly registered. + * + * @return if the event source (if any) provided by the dependent resource should be used or not. + */ + boolean provideEventSource() default true; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java index 8d31778488..d098137b46 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java @@ -4,6 +4,8 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; +import io.javaoperatorsdk.operator.processing.event.source.ResourceEventSource; /** * An interface to implement and provide dependent resource support. @@ -29,6 +31,31 @@ public interface DependentResource { */ Class resourceType(); + /** + * Dependent resources are designed to by default provide event sources. There are cases where it + * might not: + *

    + *
  • If an event source is shared between multiple dependent resources. In this case only one or + * none of the dependent resources sharing the event source should provide one.
  • + *
  • Some special implementation of an event source. That just execute some action might not + * provide one.
  • + *
+ * + * @param eventSourceContext context of event source initialization + * @return an optional event source + */ + default Optional> eventSource( + EventSourceContext

eventSourceContext) { + return Optional.empty(); + } + + /** + * Calling this method, instructs the implementation to not provide an event source, even if it + * normally does. + */ + void doNotProvideEventSource(); + + default Optional getSecondaryResource(P primary, Context

context) { return Optional.empty(); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/EventSourceAware.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/EventSourceAware.java new file mode 100644 index 0000000000..09b13d90c5 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/EventSourceAware.java @@ -0,0 +1,10 @@ +package io.javaoperatorsdk.operator.api.reconciler.dependent; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.processing.event.EventSourceRetriever; + +public interface EventSourceAware

{ + + void selectEventSources(EventSourceRetriever

eventSourceRetriever); + +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/EventSourceProvider.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/EventSourceProvider.java index 98190cb7ef..c83af1270a 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/EventSourceProvider.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/EventSourceProvider.java @@ -4,6 +4,11 @@ import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; import io.javaoperatorsdk.operator.processing.event.source.EventSource; +/** + * @deprecated now event source related methods are directly on {@link DependentResource} + * @param

primary resource + */ +@Deprecated(forRemoval = true) public interface EventSourceProvider

{ /** * @param context - event source context where the event source is initialized 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 92b70e722d..57ce0a3a5e 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 @@ -32,6 +32,7 @@ import io.javaoperatorsdk.operator.api.reconciler.Ignore; import io.javaoperatorsdk.operator.api.reconciler.Reconciler; import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; +import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceAware; import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceProvider; import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DefaultManagedDependentResourceContext; import io.javaoperatorsdk.operator.processing.dependent.workflow.ManagedWorkflow; @@ -39,6 +40,7 @@ import io.javaoperatorsdk.operator.processing.event.EventProcessor; import io.javaoperatorsdk.operator.processing.event.EventSourceManager; import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.processing.event.source.ResourceEventSource; import static io.javaoperatorsdk.operator.api.reconciler.Constants.WATCH_CURRENT_NAMESPACE; @@ -207,21 +209,30 @@ private void initContextIfNeeded(P resource, Context

context) { } public void initAndRegisterEventSources(EventSourceContext

context) { - managedWorkflow - .getDependentResourcesByName().entrySet().stream() - .filter(drEntry -> drEntry.getValue() instanceof EventSourceProvider) - .forEach(drEntry -> { - final var provider = (EventSourceProvider) drEntry.getValue(); - final var source = provider.initEventSource(context); - eventSourceManager.registerEventSource(drEntry.getKey(), source); - }); - - // add manually defined event sources if (reconciler instanceof EventSourceInitializer) { final var provider = (EventSourceInitializer

) this.reconciler; final var ownSources = provider.prepareEventSources(context); ownSources.forEach(eventSourceManager::registerEventSource); } + managedWorkflow + .getDependentResourcesByName().entrySet().stream() + .forEach(drEntry -> { + if (drEntry.getValue() instanceof EventSourceProvider) { + final var provider = (EventSourceProvider) drEntry.getValue(); + final var source = provider.initEventSource(context); + eventSourceManager.registerEventSource(drEntry.getKey(), source); + } else { + Optional eventSource = + drEntry.getValue().eventSource(context); + eventSource.ifPresent(es -> { + eventSourceManager.registerEventSource(drEntry.getKey(), es); + }); + } + }); + managedWorkflow.getDependentResourcesByName().entrySet().stream().map(Map.Entry::getValue) + .filter(EventSourceAware.class::isInstance) + .forEach(dr -> ((EventSourceAware) dr) + .selectEventSources(eventSourceManager)); } @Override diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java index 078fc60b66..fe7717e846 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java @@ -9,11 +9,13 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; import io.javaoperatorsdk.operator.api.reconciler.Ignore; import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.processing.event.source.ResourceEventSource; @Ignore public abstract class AbstractDependentResource @@ -27,8 +29,9 @@ public abstract class AbstractDependentResource protected Creator creator; protected Updater updater; protected BulkDependentResource bulkDependentResource; + private boolean returnEventSource = true; - private final List> resourceDiscriminator = new ArrayList<>(1); + protected List> resourceDiscriminator = new ArrayList<>(1); @SuppressWarnings("unchecked") public AbstractDependentResource() { @@ -38,6 +41,23 @@ public AbstractDependentResource() { bulkDependentResource = bulk ? (BulkDependentResource) this : null; } + @Override + public void doNotProvideEventSource() { + this.returnEventSource = false; + } + + @Override + public Optional> eventSource(EventSourceContext

eventSourceContext) { + if (!returnEventSource) { + return Optional.empty(); + } else { + return Optional.of(provideEventSource(eventSourceContext)); + } + } + + protected abstract ResourceEventSource provideEventSource( + EventSourceContext

eventSourceContext); + @Override public ReconcileResult reconcile(P primary, Context

context) { if (bulk) { @@ -172,7 +192,7 @@ protected R handleCreate(R desired, P primary, Context

context) { protected abstract void onCreated(ResourceID primaryResourceId, R created); /** - * Allows sub-classes to perform additional processing on the updated resource if needed. + * Allows subclasses to perform additional processing on the updated resource if needed. * * @param primaryResourceId the {@link ResourceID} of the primary resource associated with the * newly updated resource @@ -219,4 +239,7 @@ protected int lastKnownBulkSize() { return resourceDiscriminator.size(); } + protected boolean getReturnEventSource() { + return returnEventSource; + } } 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 be0db98393..a90017897a 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,12 +1,14 @@ package io.javaoperatorsdk.operator.processing.dependent; +import java.util.Optional; + import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; import io.javaoperatorsdk.operator.api.reconciler.Ignore; -import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceProvider; +import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceAware; import io.javaoperatorsdk.operator.api.reconciler.dependent.RecentOperationCacheFiller; +import io.javaoperatorsdk.operator.processing.event.EventSourceRetriever; import io.javaoperatorsdk.operator.processing.event.ResourceID; -import io.javaoperatorsdk.operator.processing.event.source.EventSource; import io.javaoperatorsdk.operator.processing.event.source.ResourceEventSource; import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter; @@ -15,8 +17,7 @@ @Ignore public abstract class AbstractEventSourceHolderDependentResource> - extends AbstractDependentResource - implements EventSourceProvider

{ + extends AbstractDependentResource implements EventSourceAware

{ private T eventSource; private final Class resourceType; @@ -25,12 +26,14 @@ public abstract class AbstractEventSourceHolderDependentResource onUpdateFilter; protected OnDeleteFilter onDeleteFilter; protected GenericFilter genericFilter; + protected String eventSourceToUse; protected AbstractEventSourceHolderDependentResource(Class resourceType) { this.resourceType = resourceType; } - public EventSource initEventSource(EventSourceContext

context) { + + public ResourceEventSource provideEventSource(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. @@ -38,14 +41,30 @@ public EventSource initEventSource(EventSourceContext

context) { // event source // is shared between dependent resources this does not override the existing filters. if (eventSource == null) { - eventSource = createEventSource(context); + setEventSource(createEventSource(context)); applyFilters(); } - - isCacheFillerEventSource = eventSource instanceof RecentOperationCacheFiller; return eventSource; } + @SuppressWarnings("unchecked") + @Override + public void selectEventSources(EventSourceRetriever

eventSourceRetriever) { + if (!getReturnEventSource()) { + if (eventSourceToUse != null) { + setEventSource( + (T) eventSourceRetriever.getResourceEventSourceFor(resourceType(), eventSourceToUse)); + } else { + setEventSource((T) eventSourceRetriever.getResourceEventSourceFor(resourceType())); + } + } + } + + /** To make this backwards compatible even for respect of overriding */ + public T initEventSource(EventSourceContext

context) { + return (T) eventSource(context).orElseThrow(); + } + @Override public Class resourceType() { return resourceType; @@ -54,6 +73,7 @@ public Class resourceType() { protected abstract T createEventSource(EventSourceContext

context); protected void setEventSource(T eventSource) { + isCacheFillerEventSource = eventSource instanceof RecentOperationCacheFiller; this.eventSource = eventSource; } @@ -64,8 +84,8 @@ protected void applyFilters() { this.eventSource.setGenericFilter(genericFilter); } - protected T eventSource() { - return eventSource; + public Optional> eventSource() { + return Optional.ofNullable(eventSource); } protected void onCreated(ResourceID primaryResourceId, R created) { @@ -96,4 +116,10 @@ public void setOnUpdateFilter(OnUpdateFilter onUpdateFilter) { public void setOnDeleteFilter(OnDeleteFilter onDeleteFilter) { this.onDeleteFilter = onDeleteFilter; } + + public AbstractEventSourceHolderDependentResource setEventSourceToUse( + String eventSourceToUse) { + this.eventSourceToUse = eventSourceToUse; + return this; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkResourceDiscriminatorFactory.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkResourceDiscriminatorFactory.java new file mode 100644 index 0000000000..8b9b6e968d --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkResourceDiscriminatorFactory.java @@ -0,0 +1,10 @@ +package io.javaoperatorsdk.operator.processing.dependent; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; + +public interface BulkResourceDiscriminatorFactory { + + ResourceDiscriminator createResourceDiscriminator(int index); + +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractSimpleDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractSimpleDependentResource.java deleted file mode 100644 index 748452c30c..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractSimpleDependentResource.java +++ /dev/null @@ -1,81 +0,0 @@ -package io.javaoperatorsdk.operator.processing.dependent.external; - -import java.util.Optional; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.Ignore; -import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; -import io.javaoperatorsdk.operator.processing.dependent.AbstractDependentResource; -import io.javaoperatorsdk.operator.processing.dependent.DesiredEqualsMatcher; -import io.javaoperatorsdk.operator.processing.dependent.Matcher; -import io.javaoperatorsdk.operator.processing.event.ResourceID; -import io.javaoperatorsdk.operator.processing.event.source.ConcurrentHashMapCache; -import io.javaoperatorsdk.operator.processing.event.source.UpdatableCache; - -/** A base class for external dependent resources that don't have an event source. */ -@Ignore -public abstract class AbstractSimpleDependentResource - extends AbstractDependentResource { - - // cache serves only to keep the resource readable again until next reconciliation when the - // new resource is read again. - protected final UpdatableCache cache; - protected Matcher matcher; - - public AbstractSimpleDependentResource() { - this(new ConcurrentHashMapCache<>()); - } - - public AbstractSimpleDependentResource(UpdatableCache cache) { - this.cache = cache; - initMatcher(); - } - - @Override - public Optional getSecondaryResource(P primaryResource, Context

context) { - return cache.get(ResourceID.fromResource(primaryResource)); - } - - /** - * Actually read the resource from the target API - * - * @param primaryResource the primary associated resource - * @return fetched resource if present - **/ - public abstract Optional fetchResource(HasMetadata primaryResource); - - @Override - public ReconcileResult reconcile(P primary, Context

context) { - var resourceId = ResourceID.fromResource(primary); - Optional resource = fetchResource(primary); - resource.ifPresentOrElse(r -> cache.put(resourceId, r), () -> cache.remove(resourceId)); - return super.reconcile(primary, context); - } - - public final void delete(P primary, Context

context) { - deleteResource(primary, context); - cache.remove(ResourceID.fromResource(primary)); - } - - protected abstract void deleteResource(P primary, Context

context); - - @Override - protected void onCreated(ResourceID primaryResourceId, R created) { - cache.put(primaryResourceId, created); - } - - @Override - protected void onUpdated(ResourceID primaryResourceId, R updated, R actual) { - cache.put(primaryResourceId, updated); - } - - public Matcher.Result match(R actualResource, P primary, Context

context) { - return matcher.match(actualResource, primary, context); - } - - protected void initMatcher() { - matcher = new DesiredEqualsMatcher<>(this); - } - -} 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 603f4ae62e..2ccd4da82a 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 @@ -72,4 +72,6 @@ Class genericFilter() default GenericFilter.class; Class resourceDiscriminator() default ResourceDiscriminator.class; + + String eventSourceToUse() default NO_VALUE_SET; } 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 3738a2e7d2..37d2246a58 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 @@ -31,7 +31,7 @@ public abstract class KubernetesDependentResource extends AbstractEventSourceHolderDependentResource> implements KubernetesClientAware, - DependentResourceConfigurator { + DependentResourceConfigurator> { private static final Logger log = LoggerFactory.getLogger(KubernetesDependentResource.class); @@ -52,9 +52,18 @@ public KubernetesDependentResource(Class resourceType) { : GenericResourceUpdatePreProcessor.processorFor(resourceType); } + @SuppressWarnings("unchecked") @Override - public void configureWith(KubernetesDependentResourceConfig config) { + public void configureWith(KubernetesDependentResourceConfig config) { this.kubernetesDependentResourceConfig = config; + var discriminator = kubernetesDependentResourceConfig.getResourceDiscriminator(); + if (discriminator != null) { + setResourceDiscriminator(discriminator); + } + config.getEventSourceToUse().ifPresent(n -> { + doNotProvideEventSource(); + setEventSourceToUse(n); + }); } private void configureWith(String labelSelector, Set namespaces, @@ -152,7 +161,8 @@ public void deleteBulkResourceWithIndex(P primary, R resource, int i, Context

} @SuppressWarnings("unchecked") - protected Resource prepare(R desired, P primary, String actionName) { + protected Resource prepare(R desired, + P primary, String actionName) { log.debug("{} target resource with type: {}, with id: {}", actionName, desired.getClass(), @@ -170,6 +180,7 @@ protected Resource prepare(R desired, P primary, String actionName) { protected InformerEventSource createEventSource(EventSourceContext

context) { if (kubernetesDependentResourceConfig != null) { // sets the filters for the dependent resource, which are applied by parent class + onAddFilter = kubernetesDependentResourceConfig.onAddFilter(); onUpdateFilter = kubernetesDependentResourceConfig.onUpdateFilter(); onDeleteFilter = kubernetesDependentResourceConfig.onDeleteFilter(); @@ -188,7 +199,7 @@ protected InformerEventSource createEventSource(EventSourceContext

cont "Using default configuration for {} KubernetesDependentResource, call configureWith to provide configuration", resourceType().getSimpleName()); } - return eventSource(); + return (InformerEventSource) eventSource().orElseThrow(); } private boolean useDefaultAnnotationsToIdentifyPrimary() { @@ -234,10 +245,12 @@ protected R desired(P primary, int index, Context

context) { } private void prepareEventFiltering(R desired, ResourceID resourceID) { - eventSource().prepareForCreateOrUpdateEventFiltering(resourceID, desired); + ((InformerEventSource) eventSource().orElseThrow()) + .prepareForCreateOrUpdateEventFiltering(resourceID, desired); } private void cleanupAfterEventFiltering(ResourceID resourceID) { - eventSource().cleanupOnCreateOrUpdateEventFiltering(resourceID); + ((InformerEventSource) eventSource().orElseThrow()) + .cleanupOnCreateOrUpdateEventFiltering(resourceID); } } 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 c7674dd1a7..8d092fdfa5 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,5 +1,6 @@ package io.javaoperatorsdk.operator.processing.dependent.kubernetes; +import java.util.Optional; import java.util.Set; import io.javaoperatorsdk.operator.api.reconciler.Constants; @@ -17,6 +18,7 @@ public class KubernetesDependentResourceConfig { private String labelSelector = NO_VALUE_SET; private boolean namespacesWereConfigured = false; private ResourceDiscriminator resourceDiscriminator; + private String eventSourceToUse; private OnAddFilter onAddFilter; @@ -32,7 +34,7 @@ public KubernetesDependentResourceConfig(Set namespaces, String labelSel boolean configuredNS, ResourceDiscriminator resourceDiscriminator, OnAddFilter onAddFilter, OnUpdateFilter onUpdateFilter, - OnDeleteFilter onDeleteFilter, GenericFilter genericFilter) { + OnDeleteFilter onDeleteFilter, GenericFilter genericFilter, String eventSourceToUse) { this.namespaces = namespaces; this.labelSelector = labelSelector; this.namespacesWereConfigured = configuredNS; @@ -41,10 +43,12 @@ public KubernetesDependentResourceConfig(Set namespaces, String labelSel this.onDeleteFilter = onDeleteFilter; this.genericFilter = genericFilter; this.resourceDiscriminator = resourceDiscriminator; + this.eventSourceToUse = eventSourceToUse; } public KubernetesDependentResourceConfig(Set namespaces, String labelSelector) { - this(namespaces, labelSelector, true, null, null, null, null, null); + this(namespaces, labelSelector, true, null, null, null, + null, null, null); } public KubernetesDependentResourceConfig setNamespaces(Set namespaces) { @@ -75,6 +79,7 @@ public OnAddFilter onAddFilter() { return onAddFilter; } + public OnUpdateFilter onUpdateFilter() { return onUpdateFilter; } @@ -92,9 +97,7 @@ public ResourceDiscriminator getResourceDiscriminator() { return resourceDiscriminator; } - public

KubernetesDependentResourceConfig setResourceDiscriminator( - ResourceDiscriminator resourceDiscriminator) { - this.resourceDiscriminator = resourceDiscriminator; - return this; + public Optional getEventSourceToUse() { + return Optional.ofNullable(eventSourceToUse); } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowSupport.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowSupport.java index aad9475518..8ca56960b5 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowSupport.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowSupport.java @@ -81,7 +81,9 @@ public DependentResource createAndConfigureFrom(DependentResourceSpec spec, if (dependentResource instanceof KubernetesClientAware) { ((KubernetesClientAware) dependentResource).setKubernetesClient(client); } - + if (!spec.provideEventSource()) { + dependentResource.doNotProvideEventSource(); + } if (dependentResource instanceof DependentResourceConfigurator) { final var configurator = (DependentResourceConfigurator) dependentResource; spec.getDependentResourceConfiguration().ifPresent(configurator::configureWith); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutor.java index ac08d2d874..45541b91d6 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutor.java @@ -125,6 +125,7 @@ public void run() { } } + private synchronized void handleDependentCleaned( DependentResourceNode dependentResourceNode) { var dependOns = dependentResourceNode.getDependsOn(); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceRetriever.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceRetriever.java index a31d8902b0..421092fb6a 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceRetriever.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceRetriever.java @@ -11,7 +11,7 @@ ResourceEventSource getResourceEventSourceFor( Class dependentType); ResourceEventSource getResourceEventSourceFor( - Class dependentType, String qualifier); + Class dependentType, String name); List> getResourceEventSourcesFor(Class dependentType); diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverriderTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverriderTest.java index 6e486b5347..312d307cc1 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverriderTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverriderTest.java @@ -5,11 +5,8 @@ import org.junit.jupiter.api.Test; import io.fabric8.kubernetes.api.model.ConfigMap; -import io.javaoperatorsdk.operator.api.reconciler.Constants; -import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.*; import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; -import io.javaoperatorsdk.operator.api.reconciler.Reconciler; -import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; @@ -84,6 +81,8 @@ public Class resourceType() { return Object.class; } + @Override + public void doNotProvideEventSource() {} } } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResourceTest.java index b93abd45c0..0fd85c5afc 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResourceTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResourceTest.java @@ -7,7 +7,9 @@ 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.api.reconciler.EventSourceContext; import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.processing.event.source.ResourceEventSource; import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; import static org.junit.jupiter.api.Assertions.*; @@ -78,6 +80,12 @@ public Class resourceType() { return ConfigMap.class; } + @Override + protected ResourceEventSource provideEventSource( + EventSourceContext eventSourceContext) { + return null; + } + @Override public Optional getSecondaryResource(TestCustomResource primary, Context context) { diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/EmptyTestDependentResource.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/EmptyTestDependentResource.java index dab1bc0132..dd42a80392 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/EmptyTestDependentResource.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/EmptyTestDependentResource.java @@ -20,5 +20,8 @@ public Class resourceType() { return Deployment.class; } + @Override + public void doNotProvideEventSource() {} + } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractSimpleDependentResourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractSimpleDependentResourceTest.java deleted file mode 100644 index 520f44365f..0000000000 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractSimpleDependentResourceTest.java +++ /dev/null @@ -1,149 +0,0 @@ -package io.javaoperatorsdk.operator.processing.dependent.external; - -import java.util.Optional; -import java.util.function.Supplier; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.TestUtils; -import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; -import io.javaoperatorsdk.operator.processing.dependent.Creator; -import io.javaoperatorsdk.operator.processing.dependent.Updater; -import io.javaoperatorsdk.operator.processing.event.ResourceID; -import io.javaoperatorsdk.operator.processing.event.source.SampleExternalResource; -import io.javaoperatorsdk.operator.processing.event.source.UpdatableCache; -import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; - -import static org.assertj.core.api.Assertions.assertThat; -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.when; - -@SuppressWarnings("unchecked") -class AbstractSimpleDependentResourceTest { - - UpdatableCache updatableCacheMock = mock(UpdatableCache.class); - Supplier supplierMock = mock(Supplier.class); - - SimpleDependentResource simpleDependentResource = - new SimpleDependentResource(updatableCacheMock, supplierMock); - - @BeforeEach - void setup() { - when(supplierMock.get()).thenReturn(SampleExternalResource.testResource1()); - } - - @Test - void getsTheResourceFromSupplyIfReconciling() { - simpleDependentResource = new SimpleDependentResource(supplierMock); - - simpleDependentResource.reconcile(TestUtils.testCustomResource1(), null); - - verify(supplierMock, times(1)).get(); - assertThat(simpleDependentResource.getSecondaryResource(TestUtils.testCustomResource1(), null)) - .isPresent() - .isEqualTo(Optional.of(SampleExternalResource.testResource1())); - } - - @Test - void getResourceReadsTheResourceFromCache() { - simpleDependentResource.getSecondaryResource(TestUtils.testCustomResource1(), null); - - verify(supplierMock, times(0)).get(); - verify(updatableCacheMock, times(1)).get(any()); - } - - @Test - void createPutsNewResourceToTheCache() { - when(supplierMock.get()).thenReturn(null); - when(updatableCacheMock.get(any())).thenReturn(Optional.empty()); - - simpleDependentResource.reconcile(TestUtils.testCustomResource1(), null); - - verify(updatableCacheMock, times(1)).put(any(), any()); - } - - @Test - void updatePutsNewResourceToCache() { - var actual = SampleExternalResource.testResource1(); - actual.setValue("changedValue"); - when(supplierMock.get()).thenReturn(actual); - when(updatableCacheMock.get(any())).thenReturn(Optional.of(actual)); - - simpleDependentResource.reconcile(TestUtils.testCustomResource1(), null); - - verify(updatableCacheMock, times(1)) - .put(ResourceID.fromResource(TestUtils.testCustomResource1()), actual); - - verify(updatableCacheMock, times(1)) - .put( - ResourceID.fromResource(TestUtils.testCustomResource1()), - SampleExternalResource.testResource1()); - } - - @Test - void deleteRemovesResourceFromCache() { - simpleDependentResource.delete(TestUtils.testCustomResource1(), null); - verify(updatableCacheMock, times(1)).remove(any()); - } - - private static class SimpleDependentResource - extends AbstractSimpleDependentResource - implements Creator, - Updater, - Deleter { - - private final Supplier supplier; - - public SimpleDependentResource(Supplier supplier) { - this.supplier = supplier; - } - - public SimpleDependentResource( - UpdatableCache cache, Supplier supplier) { - super(cache); - this.supplier = supplier; - } - - @Override - public Optional fetchResource(HasMetadata primaryResource) { - return Optional.ofNullable(supplier.get()); - } - - @Override - protected void deleteResource(TestCustomResource primary, - Context context) {} - - @Override - public SampleExternalResource create( - SampleExternalResource desired, TestCustomResource primary, - Context context) { - return SampleExternalResource.testResource1(); - } - - @Override - public SampleExternalResource update( - SampleExternalResource actual, - SampleExternalResource desired, - TestCustomResource primary, - Context context) { - return SampleExternalResource.testResource1(); - } - - @Override - protected SampleExternalResource desired(TestCustomResource primary, - Context context) { - return SampleExternalResource.testResource1(); - } - - @Override - public Class resourceType() { - return SampleExternalResource.class; - } - } -} diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutorTest.java index 9c10c06cc0..2ecb6bde24 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutorTest.java @@ -48,6 +48,9 @@ public Class resourceType() { return String.class; } + @Override + public void doNotProvideEventSource() {} + @Override public String toString() { return name; @@ -107,6 +110,9 @@ public Class resourceType() { return String.class; } + @Override + public void doNotProvideEventSource() {} + @Override public String toString() { return name; diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowTestUtils.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowTestUtils.java index 2332778e2e..72a3886963 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowTestUtils.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowTestUtils.java @@ -11,7 +11,8 @@ public class ManagedWorkflowTestUtils { @SuppressWarnings("unchecked") public static DependentResourceSpec createDRS(String name, String... dependOns) { return new DependentResourceSpec(EmptyTestDependentResource.class, - null, name, Set.of(dependOns), null, null, null); + null, name, Set.of(dependOns), null, null, null, + true); } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/IntegrationTestConstants.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/IntegrationTestConstants.java new file mode 100644 index 0000000000..1191e6a121 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/IntegrationTestConstants.java @@ -0,0 +1,7 @@ +package io.javaoperatorsdk.operator; + +public class IntegrationTestConstants { + + public static final int GARBAGE_COLLECTION_TIMEOUT_SECONDS = 30; + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/KubernetesDependentGarbageCollectionIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/KubernetesDependentGarbageCollectionIT.java index dc3f080ab7..c32f328d76 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/KubernetesDependentGarbageCollectionIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/KubernetesDependentGarbageCollectionIT.java @@ -43,10 +43,11 @@ void resourceSecondaryResourceIsGarbageCollected() { operator.delete(createdResources); - await().atMost(Duration.ofSeconds(30)).untilAsserted(() -> { - ConfigMap cm = operator.get(ConfigMap.class, TEST_RESOURCE_NAME); - assertThat(cm).isNull(); - }); + await().atMost(Duration.ofSeconds(IntegrationTestConstants.GARBAGE_COLLECTION_TIMEOUT_SECONDS)) + .untilAsserted(() -> { + ConfigMap cm = operator.get(ConfigMap.class, TEST_RESOURCE_NAME); + assertThat(cm).isNull(); + }); } @Test diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleManagedDependentSameTypeIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleManagedDependentSameTypeIT.java new file mode 100644 index 0000000000..32fe0eaec4 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleManagedDependentSameTypeIT.java @@ -0,0 +1,80 @@ +package io.javaoperatorsdk.operator; + +import java.time.Duration; +import java.util.stream.Collectors; + +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.multiplemanageddependentsametype.MultipleManagedDependentResourceCustomResource; +import io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype.MultipleManagedDependentResourceReconciler; +import io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype.MultipleManagedDependentResourceSpec; + +import static io.javaoperatorsdk.operator.IntegrationTestConstants.GARBAGE_COLLECTION_TIMEOUT_SECONDS; +import static io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype.MultipleManagedDependentResourceReconciler.DATA_KEY; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +class MultipleManagedDependentSameTypeIT { + + public static final String TEST_RESOURCE_NAME = "test1"; + public static final String DEFAULT_SPEC_VALUE = "val"; + public static final String UPDATED_SPEC_VALUE = "updated-val"; + public static final int SECONDS = 30; + + @RegisterExtension + LocallyRunOperatorExtension operator = + LocallyRunOperatorExtension.builder() + .withReconciler(new MultipleManagedDependentResourceReconciler()) + .build(); + + + @Test + void handlesCrudOperations() { + operator.create(testResource()); + assertConfigMapsPresent(DEFAULT_SPEC_VALUE); + + var updatedResource = testResource(); + updatedResource.getSpec().setValue(UPDATED_SPEC_VALUE); + operator.replace(updatedResource); + assertConfigMapsPresent(UPDATED_SPEC_VALUE); + + operator.delete(testResource()); + assertConfigMapsDeleted(); + } + + private void assertConfigMapsPresent(String expectedData) { + await().untilAsserted(() -> { + var maps = operator.getKubernetesClient().configMaps() + .inNamespace(operator.getNamespace()).list().getItems().stream() + .filter(cm -> cm.getMetadata().getName().startsWith(TEST_RESOURCE_NAME)) + .collect(Collectors.toList()); + assertThat(maps).hasSize(2); + assertThat(maps).allMatch(cm -> cm.getData().get(DATA_KEY).equals(expectedData)); + }); + } + + private void assertConfigMapsDeleted() { + await().atMost(Duration.ofSeconds(GARBAGE_COLLECTION_TIMEOUT_SECONDS)).untilAsserted(() -> { + var maps = operator.getKubernetesClient().configMaps() + .inNamespace(operator.getNamespace()).list().getItems().stream() + .filter(cm -> cm.getMetadata().getName().startsWith(TEST_RESOURCE_NAME)) + .collect(Collectors.toList()); + assertThat(maps).hasSize(0); + }); + } + + private MultipleManagedDependentResourceCustomResource testResource() { + var res = new MultipleManagedDependentResourceCustomResource(); + res.setMetadata(new ObjectMetaBuilder() + .withName(TEST_RESOURCE_NAME) + .build()); + + res.setSpec(new MultipleManagedDependentResourceSpec()); + res.getSpec().setValue(DEFAULT_SPEC_VALUE); + return res; + } + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleManagedExternalDependentSameTypeIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleManagedExternalDependentSameTypeIT.java new file mode 100644 index 0000000000..ec2b7a394a --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleManagedExternalDependentSameTypeIT.java @@ -0,0 +1,71 @@ +package io.javaoperatorsdk.operator; + +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.multiplemanageddependentsametype.MultipleManagedDependentResourceSpec; +import io.javaoperatorsdk.operator.sample.multiplemanagedexternaldependenttype.MultipleManagedExternalDependentResourceCustomResource; +import io.javaoperatorsdk.operator.sample.multiplemanagedexternaldependenttype.MultipleManagedExternalDependentResourceReconciler; +import io.javaoperatorsdk.operator.support.ExternalServiceMock; + +import static io.javaoperatorsdk.operator.MultipleManagedDependentSameTypeIT.DEFAULT_SPEC_VALUE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +class MultipleManagedExternalDependentSameTypeIT { + + @RegisterExtension + LocallyRunOperatorExtension operator = + LocallyRunOperatorExtension.builder() + .withReconciler(new MultipleManagedExternalDependentResourceReconciler()) + .build(); + + public static final String TEST_RESOURCE_NAME = "test1"; + public static final String DEFAULT_SPEC_VALUE = "val"; + public static final String UPDATED_SPEC_VALUE = "updated-val"; + + protected ExternalServiceMock externalServiceMock = ExternalServiceMock.getInstance(); + + @Test + void handlesExternalCrudOperations() { + operator.create(testResource()); + assertResourceCreatedWithData(DEFAULT_SPEC_VALUE); + + var updatedResource = testResource(); + updatedResource.getSpec().setValue(UPDATED_SPEC_VALUE); + operator.replace(updatedResource); + assertResourceCreatedWithData(UPDATED_SPEC_VALUE); + + operator.delete(testResource()); + assertExternalResourceDeleted(); + } + + private void assertExternalResourceDeleted() { + await().untilAsserted(() -> { + var resources = externalServiceMock.listResources(); + assertThat(resources).hasSize(0); + }); + } + + private void assertResourceCreatedWithData(String expectedData) { + await().untilAsserted(() -> { + var resources = externalServiceMock.listResources(); + assertThat(resources).hasSize(2); + assertThat(resources).allMatch(er -> er.getData().equals(expectedData)); + }); + } + + private MultipleManagedExternalDependentResourceCustomResource testResource() { + var res = new MultipleManagedExternalDependentResourceCustomResource(); + res.setMetadata(new ObjectMetaBuilder() + .withName(TEST_RESOURCE_NAME) + .build()); + + res.setSpec(new MultipleManagedDependentResourceSpec()); + res.getSpec().setValue(DEFAULT_SPEC_VALUE); + return res; + } + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/kubernetesdependentgarbagecollection/DependentGarbageCollectionTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/kubernetesdependentgarbagecollection/DependentGarbageCollectionTestReconciler.java index e9b947a83b..c20d573a03 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/kubernetesdependentgarbagecollection/DependentGarbageCollectionTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/kubernetesdependentgarbagecollection/DependentGarbageCollectionTestReconciler.java @@ -32,8 +32,8 @@ public DependentGarbageCollectionTestReconciler() { @Override public Map prepareEventSources( EventSourceContext context) { - return EventSourceInitializer - .nameEventSources(configMapDependent.initEventSource(context)); + return EventSourceInitializer.nameEventSourcesFromDependentResource(context, + configMapDependent); } @Override diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceConfigMap.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceConfigMap.java index 1adbfb9f95..5c2e9974b5 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceConfigMap.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceConfigMap.java @@ -7,9 +7,7 @@ import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; -import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; -@KubernetesDependent public class MultipleDependentResourceConfigMap extends CRUDKubernetesDependentResource { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceReconciler.java index 0994e6b9b0..49f5ee64c1 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceReconciler.java @@ -29,6 +29,7 @@ public class MultipleDependentResourceReconciler public MultipleDependentResourceReconciler() { firstDependentResourceConfigMap = new MultipleDependentResourceConfigMap(FIRST_CONFIG_MAP_ID); + secondDependentResourceConfigMap = new MultipleDependentResourceConfigMap(SECOND_CONFIG_MAP_ID); firstDependentResourceConfigMap diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/ConfigMap1Discriminator.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/ConfigMap1Discriminator.java new file mode 100644 index 0000000000..cc20dfa45e --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/ConfigMap1Discriminator.java @@ -0,0 +1,26 @@ +package io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype; + +import java.util.Optional; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; +import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; + +import static io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype.MultipleManagedDependentResourceConfigMap1.NAME_SUFFIX; + +public class ConfigMap1Discriminator + implements ResourceDiscriminator { + @Override + public Optional distinguish(Class resource, + MultipleManagedDependentResourceCustomResource primary, + Context context) { + InformerEventSource ies = + (InformerEventSource) context + .eventSourceRetriever().getResourceEventSourceFor(ConfigMap.class); + + return ies.get(new ResourceID(primary.getMetadata().getName() + NAME_SUFFIX, + primary.getMetadata().getNamespace())); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/ConfigMap2Discriminator.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/ConfigMap2Discriminator.java new file mode 100644 index 0000000000..8bda6afcee --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/ConfigMap2Discriminator.java @@ -0,0 +1,26 @@ +package io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype; + +import java.util.Optional; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; +import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; + +import static io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype.MultipleManagedDependentResourceConfigMap2.NAME_SUFFIX; + +public class ConfigMap2Discriminator + implements ResourceDiscriminator { + @Override + public Optional distinguish(Class resource, + MultipleManagedDependentResourceCustomResource primary, + Context context) { + InformerEventSource ies = + (InformerEventSource) context + .eventSourceRetriever().getResourceEventSourceFor(ConfigMap.class); + + return ies.get(new ResourceID(primary.getMetadata().getName() + NAME_SUFFIX, + primary.getMetadata().getNamespace())); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap1.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap1.java new file mode 100644 index 0000000000..0d450c1871 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap1.java @@ -0,0 +1,40 @@ +package io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype; + +import java.util.HashMap; +import java.util.Map; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +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.multiplemanageddependentsametype.MultipleManagedDependentResourceReconciler.CONFIG_MAP_EVENT_SOURCE; + +@KubernetesDependent(eventSourceToUse = CONFIG_MAP_EVENT_SOURCE, + resourceDiscriminator = ConfigMap1Discriminator.class) +public class MultipleManagedDependentResourceConfigMap1 + extends + CRUDKubernetesDependentResource { + + public static final String NAME_SUFFIX = "-1"; + + public MultipleManagedDependentResourceConfigMap1() { + super(ConfigMap.class); + } + + @Override + protected ConfigMap desired(MultipleManagedDependentResourceCustomResource primary, + Context context) { + Map data = new HashMap<>(); + data.put(MultipleManagedDependentResourceReconciler.DATA_KEY, primary.getSpec().getValue()); + + return new ConfigMapBuilder() + .withNewMetadata() + .withName(primary.getMetadata().getName() + NAME_SUFFIX) + .withNamespace(primary.getMetadata().getNamespace()) + .endMetadata() + .withData(data) + .build(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap2.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap2.java new file mode 100644 index 0000000000..11902fc518 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap2.java @@ -0,0 +1,41 @@ +package io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype; + +import java.util.HashMap; +import java.util.Map; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +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.multiplemanageddependentsametype.MultipleManagedDependentResourceReconciler.CONFIG_MAP_EVENT_SOURCE; +import static io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype.MultipleManagedDependentResourceReconciler.DATA_KEY; + +@KubernetesDependent(eventSourceToUse = CONFIG_MAP_EVENT_SOURCE, + resourceDiscriminator = ConfigMap2Discriminator.class) +public class MultipleManagedDependentResourceConfigMap2 + extends + CRUDKubernetesDependentResource { + + public static final String NAME_SUFFIX = "-2"; + + public MultipleManagedDependentResourceConfigMap2() { + super(ConfigMap.class); + } + + @Override + protected ConfigMap desired(MultipleManagedDependentResourceCustomResource primary, + Context context) { + Map data = new HashMap<>(); + data.put(DATA_KEY, primary.getSpec().getValue()); + + return new ConfigMapBuilder() + .withNewMetadata() + .withName(primary.getMetadata().getName() + NAME_SUFFIX) + .withNamespace(primary.getMetadata().getNamespace()) + .endMetadata() + .withData(data) + .build(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceCustomResource.java new file mode 100644 index 0000000000..44564727a2 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceCustomResource.java @@ -0,0 +1,16 @@ +package io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype; + +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("mmd") +public class MultipleManagedDependentResourceCustomResource + extends CustomResource + implements Namespaced { + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceReconciler.java new file mode 100644 index 0000000000..edfa7ad2bd --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceReconciler.java @@ -0,0 +1,53 @@ +package io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype; + +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; +import io.javaoperatorsdk.operator.processing.event.source.EventSource; +import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; +import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; + +@ControllerConfiguration(dependents = { + @Dependent(type = MultipleManagedDependentResourceConfigMap1.class), + @Dependent(type = MultipleManagedDependentResourceConfigMap2.class) +}) +public class MultipleManagedDependentResourceReconciler + implements Reconciler, + TestExecutionInfoProvider, + EventSourceInitializer { + + public static final String CONFIG_MAP_EVENT_SOURCE = "ConfigMapEventSource"; + public static final String DATA_KEY = "key"; + + private final AtomicInteger numberOfExecutions = new AtomicInteger(0); + + public MultipleManagedDependentResourceReconciler() {} + + @Override + public UpdateControl reconcile( + MultipleManagedDependentResourceCustomResource resource, + Context context) { + numberOfExecutions.getAndIncrement(); + + return UpdateControl.noUpdate(); + } + + + public int getNumberOfExecutions() { + return numberOfExecutions.get(); + } + + @Override + public Map prepareEventSources( + EventSourceContext context) { + InformerEventSource ies = + new InformerEventSource<>(InformerConfiguration.from(ConfigMap.class, context) + .build(), context); + + return Map.of(CONFIG_MAP_EVENT_SOURCE, ies); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceSpec.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceSpec.java new file mode 100644 index 0000000000..cdd524e03e --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceSpec.java @@ -0,0 +1,15 @@ +package io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype; + +public class MultipleManagedDependentResourceSpec { + + private String value; + + public String getValue() { + return value; + } + + public MultipleManagedDependentResourceSpec setValue(String value) { + this.value = value; + return this; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/AbstractExternalDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/AbstractExternalDependentResource.java new file mode 100644 index 0000000000..f7d3c91dd3 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/AbstractExternalDependentResource.java @@ -0,0 +1,75 @@ +package io.javaoperatorsdk.operator.sample.multiplemanagedexternaldependenttype; + +import java.util.Map; +import java.util.Set; + +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; +import io.javaoperatorsdk.operator.processing.dependent.Creator; +import io.javaoperatorsdk.operator.processing.dependent.Matcher; +import io.javaoperatorsdk.operator.processing.dependent.Updater; +import io.javaoperatorsdk.operator.processing.dependent.external.PollingDependentResource; +import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.support.ExternalResource; +import io.javaoperatorsdk.operator.support.ExternalServiceMock; + +public abstract class AbstractExternalDependentResource extends + PollingDependentResource + implements Creator, + Updater, + Deleter { + + protected ExternalServiceMock externalServiceMock = ExternalServiceMock.getInstance(); + + public AbstractExternalDependentResource() { + super(ExternalResource.class, ExternalResource::getId); + } + + @Override + public Map> fetchResources() { + throw new IllegalStateException("Should not be called"); + } + + @Override + public ExternalResource create(ExternalResource desired, + MultipleManagedExternalDependentResourceCustomResource primary, + Context context) { + return externalServiceMock.create(desired); + } + + @Override + public ExternalResource update(ExternalResource actual, + ExternalResource desired, + MultipleManagedExternalDependentResourceCustomResource primary, + Context context) { + return externalServiceMock.update(desired); + } + + @Override + public Matcher.Result match(ExternalResource actualResource, + MultipleManagedExternalDependentResourceCustomResource primary, + Context context) { + var desired = desired(primary, context); + return Matcher.Result.computed(actualResource.equals(desired), desired); + } + + @Override + public void delete(MultipleManagedExternalDependentResourceCustomResource primary, + Context context) { + externalServiceMock.delete(toExternalResourceID(primary)); + } + + protected ExternalResource desired(MultipleManagedExternalDependentResourceCustomResource primary, + Context context) { + return new ExternalResource(toExternalResourceID(primary), + primary.getSpec().getValue()); + } + + protected String toExternalResourceID( + MultipleManagedExternalDependentResourceCustomResource primary) { + return ExternalResource.toExternalResourceId(primary) + resourceIDSuffix(); + } + + protected abstract String resourceIDSuffix(); + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/ExternalDependentResource1.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/ExternalDependentResource1.java new file mode 100644 index 0000000000..cfe67a3796 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/ExternalDependentResource1.java @@ -0,0 +1,15 @@ +package io.javaoperatorsdk.operator.sample.multiplemanagedexternaldependenttype; + +public class ExternalDependentResource1 extends AbstractExternalDependentResource { + + public static final String SUFFIX = "-1"; + + public ExternalDependentResource1() { + setResourceDiscriminator(new ExternalResourceDiscriminator(SUFFIX)); + } + + @Override + protected String resourceIDSuffix() { + return SUFFIX; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/ExternalDependentResource2.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/ExternalDependentResource2.java new file mode 100644 index 0000000000..29bb237e1a --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/ExternalDependentResource2.java @@ -0,0 +1,15 @@ +package io.javaoperatorsdk.operator.sample.multiplemanagedexternaldependenttype; + +public class ExternalDependentResource2 extends AbstractExternalDependentResource { + + public static final String SUFFIX = "-2"; + + public ExternalDependentResource2() { + setResourceDiscriminator(new ExternalResourceDiscriminator(SUFFIX)); + } + + @Override + protected String resourceIDSuffix() { + return SUFFIX; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/ExternalResourceDiscriminator.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/ExternalResourceDiscriminator.java new file mode 100644 index 0000000000..ba265455de --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/ExternalResourceDiscriminator.java @@ -0,0 +1,25 @@ +package io.javaoperatorsdk.operator.sample.multiplemanagedexternaldependenttype; + +import java.util.Optional; + +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; +import io.javaoperatorsdk.operator.support.ExternalResource; + +public class ExternalResourceDiscriminator implements + ResourceDiscriminator { + + private String suffix; + + public ExternalResourceDiscriminator(String suffix) { + this.suffix = suffix; + } + + @Override + public Optional distinguish(Class resource, + MultipleManagedExternalDependentResourceCustomResource primary, + Context context) { + var resources = context.getSecondaryResources(ExternalResource.class); + return resources.stream().filter(r -> r.getId().endsWith(suffix)).findFirst(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/MultipleManagedExternalDependentResourceCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/MultipleManagedExternalDependentResourceCustomResource.java new file mode 100644 index 0000000000..c989a5c96c --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/MultipleManagedExternalDependentResourceCustomResource.java @@ -0,0 +1,17 @@ +package io.javaoperatorsdk.operator.sample.multiplemanagedexternaldependenttype; + +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; +import io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype.MultipleManagedDependentResourceSpec; + +@Group("sample.javaoperatorsdk") +@Version("v1") +@ShortNames("mme") +public class MultipleManagedExternalDependentResourceCustomResource + extends CustomResource + implements Namespaced { + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/MultipleManagedExternalDependentResourceReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/MultipleManagedExternalDependentResourceReconciler.java new file mode 100644 index 0000000000..573df92287 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/MultipleManagedExternalDependentResourceReconciler.java @@ -0,0 +1,64 @@ +package io.javaoperatorsdk.operator.sample.multiplemanagedexternaldependenttype; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; +import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.processing.event.source.EventSource; +import io.javaoperatorsdk.operator.processing.event.source.polling.PollingEventSource; +import io.javaoperatorsdk.operator.support.ExternalResource; +import io.javaoperatorsdk.operator.support.ExternalServiceMock; +import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; + +@ControllerConfiguration(dependents = { + @Dependent(type = ExternalDependentResource1.class, provideEventSource = false), + @Dependent(type = ExternalDependentResource2.class, provideEventSource = false) +}) +public class MultipleManagedExternalDependentResourceReconciler + implements Reconciler, + TestExecutionInfoProvider, + EventSourceInitializer { + + public static final String CONFIG_MAP_EVENT_SOURCE = "ConfigMapEventSource"; + protected ExternalServiceMock externalServiceMock = ExternalServiceMock.getInstance(); + private final AtomicInteger numberOfExecutions = new AtomicInteger(0); + + public MultipleManagedExternalDependentResourceReconciler() {} + + @Override + public UpdateControl reconcile( + MultipleManagedExternalDependentResourceCustomResource resource, + Context context) { + numberOfExecutions.getAndIncrement(); + + return UpdateControl.noUpdate(); + } + + public int getNumberOfExecutions() { + return numberOfExecutions.get(); + } + + @Override + public Map prepareEventSources( + EventSourceContext context) { + + PollingEventSource pollingEventSource = + new PollingEventSource<>(() -> { + var lists = externalServiceMock.listResources(); + Map> res = new HashMap<>(); + lists.forEach(er -> { + var resourceId = er.toResourceID(); + res.computeIfAbsent(resourceId, rid -> new HashSet<>()); + res.get(resourceId).add(er); + }); + return res; + }, 1000L, ExternalResource.class, ExternalResource::getId); + + return Map.of(CONFIG_MAP_EVENT_SOURCE, pollingEventSource); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primaryindexer/DependentPrimaryIndexerTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primaryindexer/DependentPrimaryIndexerTestReconciler.java index 59b6594846..6d79d4ee56 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primaryindexer/DependentPrimaryIndexerTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primaryindexer/DependentPrimaryIndexerTestReconciler.java @@ -1,5 +1,6 @@ package io.javaoperatorsdk.operator.sample.primaryindexer; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -10,8 +11,8 @@ import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; import io.javaoperatorsdk.operator.processing.event.ResourceID; -import io.javaoperatorsdk.operator.processing.event.source.EventSource; import io.javaoperatorsdk.operator.processing.event.source.IndexerResourceCache; +import io.javaoperatorsdk.operator.processing.event.source.ResourceEventSource; import io.javaoperatorsdk.operator.processing.event.source.SecondaryToPrimaryMapper; @ControllerConfiguration(dependents = @Dependent( @@ -38,11 +39,11 @@ public Set toPrimaryResourceIDs(ConfigMap dependentResource) { } @Override - public EventSource initEventSource( + public Optional> eventSource( EventSourceContext context) { cache = context.getPrimaryCache(); cache.addIndexer(CONFIG_MAP_RELATION_INDEXER, indexer); - return super.initEventSource(context); + return super.eventSource(context); } } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/standalonedependent/StandaloneDependentTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/standalonedependent/StandaloneDependentTestReconciler.java index 2dace670ba..70b119c2ab 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/standalonedependent/StandaloneDependentTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/standalonedependent/StandaloneDependentTestReconciler.java @@ -38,8 +38,8 @@ public StandaloneDependentTestReconciler() { @Override public Map prepareEventSources( EventSourceContext context) { - return EventSourceInitializer - .nameEventSources(deploymentDependent.initEventSource(context)); + return EventSourceInitializer.nameEventSourcesFromDependentResource(context, + deploymentDependent); } @Override diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/support/ExternalResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/support/ExternalResource.java new file mode 100644 index 0000000000..cb8d4b74e5 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/support/ExternalResource.java @@ -0,0 +1,58 @@ +package io.javaoperatorsdk.operator.support; + +import java.util.Objects; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.processing.event.ResourceID; + +public class ExternalResource { + + public static final String EXTERNAL_RESOURCE_NAME_DELIMITER = "#"; + + private String id; + private String data; + + public ExternalResource(String id, String data) { + this.id = id; + this.data = data; + } + + public String getId() { + return id; + } + + public String getData() { + return data; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + ExternalResource that = (ExternalResource) o; + return Objects.equals(id, that.id) && Objects.equals(data, that.data); + } + + @Override + public int hashCode() { + return Objects.hash(id, data); + } + + public ResourceID toResourceID() { + var parts = getId().split(EXTERNAL_RESOURCE_NAME_DELIMITER); + return new ResourceID(parts[0], parts[1]); + } + + public static String toExternalResourceId(HasMetadata primary, int i) { + return primary.getMetadata().getName() + EXTERNAL_RESOURCE_NAME_DELIMITER + + primary.getMetadata().getNamespace() + + EXTERNAL_RESOURCE_NAME_DELIMITER + i; + } + + public static String toExternalResourceId(HasMetadata primary) { + return primary.getMetadata().getName() + EXTERNAL_RESOURCE_NAME_DELIMITER + + primary.getMetadata().getNamespace(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/support/ExternalServiceMock.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/support/ExternalServiceMock.java new file mode 100644 index 0000000000..eea26637fc --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/support/ExternalServiceMock.java @@ -0,0 +1,39 @@ +package io.javaoperatorsdk.operator.support; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +public class ExternalServiceMock { + + private static ExternalServiceMock serviceMock = new ExternalServiceMock(); + + private Map resourceMap = new ConcurrentHashMap<>(); + + public ExternalResource create(ExternalResource externalResource) { + resourceMap.put(externalResource.getId(), externalResource); + return externalResource; + } + + public Optional read(String id) { + return Optional.ofNullable(resourceMap.get(id)); + } + + public ExternalResource update(ExternalResource externalResource) { + return resourceMap.put(externalResource.getId(), externalResource); + } + + public Optional delete(String id) { + return Optional.ofNullable(resourceMap.remove(id)); + } + + public List listResources() { + return new ArrayList<>(resourceMap.values()); + } + + public static ExternalServiceMock getInstance() { + return serviceMock; + } +} 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 48e3f37abe..c262822d21 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 @@ -11,7 +11,6 @@ import io.fabric8.kubernetes.api.model.Secret; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; -import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceProvider; import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DependentResourceConfigurator; import io.javaoperatorsdk.operator.processing.dependent.Creator; import io.javaoperatorsdk.operator.processing.dependent.external.PerResourcePollingDependentResource; @@ -26,8 +25,7 @@ public class SchemaDependentResource extends PerResourcePollingDependentResource - implements EventSourceProvider, - DependentResourceConfigurator, + implements DependentResourceConfigurator, Creator, Deleter { public static final String NAME = "schema"; @@ -97,5 +95,4 @@ public Set fetchResources(MySQLSchema primaryResource) { throw new RuntimeException("Error while trying read Schema", e); } } - } diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageDependentsWorkflowReconciler.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageDependentsWorkflowReconciler.java index 6b81a9d6e3..0eeba42083 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageDependentsWorkflowReconciler.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageDependentsWorkflowReconciler.java @@ -55,9 +55,9 @@ public WebPageDependentsWorkflowReconciler(KubernetesClient kubernetesClient) { @Override public Map prepareEventSources(EventSourceContext context) { - return EventSourceInitializer.nameEventSources(configMapDR.initEventSource(context), - deploymentDR.initEventSource(context), serviceDR.initEventSource(context), - ingressDR.initEventSource(context)); + return EventSourceInitializer.nameEventSourcesFromDependentResource(context, configMapDR, + deploymentDR, serviceDR, + ingressDR); } @Override diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java index b99e130135..15262acb48 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java @@ -40,9 +40,8 @@ public WebPageStandaloneDependentsReconciler(KubernetesClient kubernetesClient) @Override public Map prepareEventSources(EventSourceContext context) { - return EventSourceInitializer.nameEventSources(configMapDR.initEventSource(context), - deploymentDR.initEventSource(context), serviceDR.initEventSource(context), - ingressDR.initEventSource(context)); + return EventSourceInitializer.nameEventSourcesFromDependentResource(context, configMapDR, + deploymentDR, serviceDR, ingressDR); } @Override From f3ec6dba5875027a2947c3829c8aecbbb7cc9125 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Wed, 5 Oct 2022 10:11:42 +0200 Subject: [PATCH 4/8] refactor: simplify handling of reused event sources (#1518) Co-authored-by: Chris Laprun --- .../AnnotationControllerConfiguration.java | 11 ++-- .../ControllerConfigurationOverrider.java | 5 +- .../dependent/DependentResourceSpec.java | 10 ++-- .../api/reconciler/dependent/Dependent.java | 11 ++-- .../dependent/DependentResource.java | 7 --- .../dependent/EventSourceAware.java | 10 ---- .../EventSourceNotFoundException.java | 16 +++++ .../dependent/EventSourceReferencer.java | 17 ++++++ .../operator/processing/Controller.java | 59 ++++++++++++------- .../dependent/AbstractDependentResource.java | 23 -------- ...actEventSourceHolderDependentResource.java | 40 ++++++------- .../kubernetes/KubernetesDependent.java | 1 - .../KubernetesDependentResource.java | 4 -- .../KubernetesDependentResourceConfig.java | 10 +--- .../workflow/ManagedWorkflowSupport.java | 18 +++++- .../ControllerConfigurationOverriderTest.java | 3 - .../AbstractDependentResourceTest.java | 8 --- .../dependent/EmptyTestDependentResource.java | 4 -- .../AbstractWorkflowExecutorTest.java | 6 -- .../workflow/ManagedWorkflowTestUtils.java | 2 +- ...pleManagedDependentResourceConfigMap1.java | 5 +- ...pleManagedDependentResourceConfigMap2.java | 4 +- ...pleManagedDependentResourceReconciler.java | 8 ++- ...edExternalDependentResourceReconciler.java | 8 ++- 24 files changed, 144 insertions(+), 146 deletions(-) delete mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/EventSourceAware.java create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/EventSourceNotFoundException.java create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/EventSourceReferencer.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 fa2b5c1927..913b727eaa 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 @@ -242,13 +242,17 @@ public List getDependentResources() { throw new IllegalArgumentException( "A DependentResource named '" + name + "' already exists: " + spec); } + + var eventSourceName = dependent.useEventSourceWithName(); + eventSourceName = Constants.NO_VALUE_SET.equals(eventSourceName) ? null : eventSourceName; + final var context = "DependentResource of type '" + dependentType.getName() + "'"; spec = new DependentResourceSpec(dependentType, config, name, Set.of(dependent.dependsOn()), instantiateIfNotDefault(dependent.readyPostcondition(), Condition.class, context), instantiateIfNotDefault(dependent.reconcilePrecondition(), Condition.class, context), instantiateIfNotDefault(dependent.deletePostcondition(), Condition.class, context), - dependent.provideEventSource()); + eventSourceName); specsMap.put(name, spec); } @@ -287,7 +291,6 @@ private Object createKubernetesResourceConfig(Class OnDeleteFilter onDeleteFilter = null; GenericFilter genericFilter = null; ResourceDiscriminator resourceDiscriminator = null; - String eventSourceNameToUse = null; if (kubeDependent != null) { if (!Arrays.equals(KubernetesDependent.DEFAULT_NAMESPACES, kubeDependent.namespaces())) { @@ -314,14 +317,12 @@ private Object createKubernetesResourceConfig(Class resourceDiscriminator = instantiateIfNotDefault(kubeDependent.resourceDiscriminator(), ResourceDiscriminator.class, context); - eventSourceNameToUse = Constants.NO_VALUE_SET.equals(kubeDependent.eventSourceToUse()) ? null - : kubeDependent.eventSourceToUse(); } config = new KubernetesDependentResourceConfig(namespaces, labelSelector, configuredNS, resourceDiscriminator, onAddFilter, - onUpdateFilter, onDeleteFilter, genericFilter, eventSourceNameToUse); + onUpdateFilter, onDeleteFilter, genericFilter); return config; } 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 ee153c879a..511596db00 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 @@ -174,7 +174,7 @@ private void replaceConfig(String name, Object newConfig, DependentResourceSpec< namedDependentResourceSpecs.put(name, new DependentResourceSpec<>(current.getDependentResourceClass(), newConfig, name, current.getDependsOn(), current.getReadyCondition(), current.getReconcileCondition(), - current.getDeletePostCondition(), current.provideEventSource())); + current.getDeletePostCondition(), current.getUseEventSourceWithName().orElse(null))); } @SuppressWarnings("unchecked") @@ -220,7 +220,8 @@ public ControllerConfiguration build() { KubernetesDependentResourceConfig c) { return new DependentResourceSpec(spec.getDependentResourceClass(), c.setNamespaces(namespaces), name, spec.getDependsOn(), spec.getReadyCondition(), - spec.getReconcileCondition(), spec.getDeletePostCondition(), spec.provideEventSource()); + spec.getReconcileCondition(), spec.getDeletePostCondition(), + (String) spec.getUseEventSourceWithName().orElse(null)); } public static ControllerConfigurationOverrider override( diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceSpec.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceSpec.java index 71476cc2d3..426c1d3325 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceSpec.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceSpec.java @@ -23,12 +23,12 @@ public class DependentResourceSpec, C> { private final Condition deletePostCondition; - private final boolean provideEventSource; + private final String useEventSourceWithName; public DependentResourceSpec(Class dependentResourceClass, C dependentResourceConfig, String name, Set dependsOn, Condition readyCondition, Condition reconcileCondition, Condition deletePostCondition, - boolean provideEventSource) { + String useEventSourceWithName) { this.dependentResourceClass = dependentResourceClass; this.dependentResourceConfig = dependentResourceConfig; this.name = name; @@ -36,7 +36,7 @@ public DependentResourceSpec(Class dependentResourceClass, C dependentResourc this.readyCondition = readyCondition; this.reconcileCondition = reconcileCondition; this.deletePostCondition = deletePostCondition; - this.provideEventSource = provideEventSource; + this.useEventSourceWithName = useEventSourceWithName; } public Class getDependentResourceClass() { @@ -94,7 +94,7 @@ public Condition getDeletePostCondition() { return deletePostCondition; } - public boolean provideEventSource() { - return provideEventSource; + public Optional getUseEventSourceWithName() { + return Optional.ofNullable(useEventSourceWithName); } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/Dependent.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/Dependent.java index 00d2ad1882..1a52cbcfc3 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/Dependent.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/Dependent.java @@ -1,5 +1,6 @@ package io.javaoperatorsdk.operator.api.reconciler.dependent; +import io.javaoperatorsdk.operator.api.reconciler.Constants; import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition; import static io.javaoperatorsdk.operator.api.reconciler.Constants.NO_VALUE_SET; @@ -59,11 +60,11 @@ String[] dependsOn() default {}; /** - * Setting this to false means that the event source provided by the dependent resource won't be - * used. This is helpful if more dependent resources created for the same type, and want to share - * a common event source. In that case an event source needs to be explicitly registered. + * Setting here a name of the event source means that dependent resource will use an event source + * registered with that name. So won't create one. This is helpful if more dependent resources + * created for the same type, and want to share a common event source. * - * @return if the event source (if any) provided by the dependent resource should be used or not. + * @return event source name (if any) provided by the dependent resource should be used. */ - boolean provideEventSource() default true; + String useEventSourceWithName() default NO_VALUE_SET; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java index d098137b46..ea7759b8c4 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java @@ -49,13 +49,6 @@ default Optional> eventSource( return Optional.empty(); } - /** - * Calling this method, instructs the implementation to not provide an event source, even if it - * normally does. - */ - void doNotProvideEventSource(); - - default Optional getSecondaryResource(P primary, Context

context) { return Optional.empty(); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/EventSourceAware.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/EventSourceAware.java deleted file mode 100644 index 09b13d90c5..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/EventSourceAware.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.javaoperatorsdk.operator.api.reconciler.dependent; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.processing.event.EventSourceRetriever; - -public interface EventSourceAware

{ - - void selectEventSources(EventSourceRetriever

eventSourceRetriever); - -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/EventSourceNotFoundException.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/EventSourceNotFoundException.java new file mode 100644 index 0000000000..26681e2ca6 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/EventSourceNotFoundException.java @@ -0,0 +1,16 @@ +package io.javaoperatorsdk.operator.api.reconciler.dependent; + +import io.javaoperatorsdk.operator.OperatorException; + +public class EventSourceNotFoundException extends OperatorException { + + private String eventSourceName; + + public EventSourceNotFoundException(String eventSourceName) { + this.eventSourceName = eventSourceName; + } + + public String getEventSourceName() { + return eventSourceName; + } +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/EventSourceReferencer.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/EventSourceReferencer.java new file mode 100644 index 0000000000..13ac93e2bf --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/EventSourceReferencer.java @@ -0,0 +1,17 @@ +package io.javaoperatorsdk.operator.api.reconciler.dependent; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.processing.event.EventSourceRetriever; + +public interface EventSourceReferencer

{ + + default void useEventSourceWithName(String name) {} + + /** + * Throws {@link EventSourceNotFoundException} an exception if the target event source to use is + * not found. + */ + void resolveEventSource(EventSourceRetriever

eventSourceRetriever) + throws EventSourceNotFoundException; + +} 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 57ce0a3a5e..53bce9bace 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 @@ -1,5 +1,8 @@ package io.javaoperatorsdk.operator.processing; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -32,8 +35,9 @@ import io.javaoperatorsdk.operator.api.reconciler.Ignore; import io.javaoperatorsdk.operator.api.reconciler.Reconciler; import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; -import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceAware; +import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceNotFoundException; import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceProvider; +import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceReferencer; import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DefaultManagedDependentResourceContext; import io.javaoperatorsdk.operator.processing.dependent.workflow.ManagedWorkflow; import io.javaoperatorsdk.operator.processing.dependent.workflow.WorkflowCleanupResult; @@ -214,25 +218,40 @@ public void initAndRegisterEventSources(EventSourceContext

context) { final var ownSources = provider.prepareEventSources(context); ownSources.forEach(eventSourceManager::registerEventSource); } - managedWorkflow - .getDependentResourcesByName().entrySet().stream() - .forEach(drEntry -> { - if (drEntry.getValue() instanceof EventSourceProvider) { - final var provider = (EventSourceProvider) drEntry.getValue(); - final var source = provider.initEventSource(context); - eventSourceManager.registerEventSource(drEntry.getKey(), source); - } else { - Optional eventSource = - drEntry.getValue().eventSource(context); - eventSource.ifPresent(es -> { - eventSourceManager.registerEventSource(drEntry.getKey(), es); - }); - } - }); - managedWorkflow.getDependentResourcesByName().entrySet().stream().map(Map.Entry::getValue) - .filter(EventSourceAware.class::isInstance) - .forEach(dr -> ((EventSourceAware) dr) - .selectEventSources(eventSourceManager)); + + // register created event sources + final var dependentResourcesByName = managedWorkflow.getDependentResourcesByName(); + final var size = dependentResourcesByName.size(); + if (size > 0) { + dependentResourcesByName.forEach((key, value) -> { + if (value instanceof EventSourceProvider) { + final var provider = (EventSourceProvider) value; + final var source = provider.initEventSource(context); + eventSourceManager.registerEventSource(key, source); + } else { + Optional eventSource = value.eventSource(context); + eventSource.ifPresent(es -> eventSourceManager.registerEventSource(key, es)); + } + }); + + // resolve event sources referenced by name for dependents that reuse an existing event source + final Map> unresolvable = new HashMap<>(size); + dependentResourcesByName.values().stream() + .filter(EventSourceReferencer.class::isInstance) + .map(EventSourceReferencer.class::cast) + .forEach(dr -> { + try { + ((EventSourceReferencer

) dr) + .resolveEventSource(eventSourceManager); + } catch (EventSourceNotFoundException e) { + unresolvable.computeIfAbsent(e.getEventSourceName(), s -> new ArrayList<>()).add(dr); + } + }); + if (!unresolvable.isEmpty()) { + throw new IllegalStateException( + "Couldn't resolve referenced EventSources: " + unresolvable); + } + } } @Override diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java index fe7717e846..63e522ad7d 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java @@ -9,13 +9,11 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; import io.javaoperatorsdk.operator.api.reconciler.Ignore; import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; import io.javaoperatorsdk.operator.processing.event.ResourceID; -import io.javaoperatorsdk.operator.processing.event.source.ResourceEventSource; @Ignore public abstract class AbstractDependentResource @@ -29,7 +27,6 @@ public abstract class AbstractDependentResource protected Creator creator; protected Updater updater; protected BulkDependentResource bulkDependentResource; - private boolean returnEventSource = true; protected List> resourceDiscriminator = new ArrayList<>(1); @@ -41,23 +38,6 @@ public AbstractDependentResource() { bulkDependentResource = bulk ? (BulkDependentResource) this : null; } - @Override - public void doNotProvideEventSource() { - this.returnEventSource = false; - } - - @Override - public Optional> eventSource(EventSourceContext

eventSourceContext) { - if (!returnEventSource) { - return Optional.empty(); - } else { - return Optional.of(provideEventSource(eventSourceContext)); - } - } - - protected abstract ResourceEventSource provideEventSource( - EventSourceContext

eventSourceContext); - @Override public ReconcileResult reconcile(P primary, Context

context) { if (bulk) { @@ -239,7 +219,4 @@ protected int lastKnownBulkSize() { return resourceDiscriminator.size(); } - protected boolean getReturnEventSource() { - return returnEventSource; - } } 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 a90017897a..f514a00950 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 @@ -5,7 +5,8 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; import io.javaoperatorsdk.operator.api.reconciler.Ignore; -import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceAware; +import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceNotFoundException; +import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceReferencer; import io.javaoperatorsdk.operator.api.reconciler.dependent.RecentOperationCacheFiller; import io.javaoperatorsdk.operator.processing.event.EventSourceRetriever; import io.javaoperatorsdk.operator.processing.event.ResourceID; @@ -17,7 +18,7 @@ @Ignore public abstract class AbstractEventSourceHolderDependentResource> - extends AbstractDependentResource implements EventSourceAware

{ + extends AbstractDependentResource implements EventSourceReferencer

{ private T eventSource; private final Class resourceType; @@ -26,45 +27,50 @@ public abstract class AbstractEventSourceHolderDependentResource onUpdateFilter; protected OnDeleteFilter onDeleteFilter; protected GenericFilter genericFilter; - protected String eventSourceToUse; + protected String eventSourceNameToUse; protected AbstractEventSourceHolderDependentResource(Class resourceType) { this.resourceType = resourceType; } - - public ResourceEventSource provideEventSource(EventSourceContext

context) { + public Optional> eventSource(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. So if an // event source // is shared between dependent resources this does not override the existing filters. - if (eventSource == null) { + if (eventSource == null && eventSourceNameToUse == null) { setEventSource(createEventSource(context)); applyFilters(); } - return eventSource; + return Optional.ofNullable(eventSource); } @SuppressWarnings("unchecked") @Override - public void selectEventSources(EventSourceRetriever

eventSourceRetriever) { - if (!getReturnEventSource()) { - if (eventSourceToUse != null) { - setEventSource( - (T) eventSourceRetriever.getResourceEventSourceFor(resourceType(), eventSourceToUse)); - } else { - setEventSource((T) eventSourceRetriever.getResourceEventSourceFor(resourceType())); + public void resolveEventSource(EventSourceRetriever

eventSourceRetriever) { + if (eventSourceNameToUse != null && eventSource == null) { + final var source = + eventSourceRetriever.getResourceEventSourceFor(resourceType(), eventSourceNameToUse); + if (source == null) { + throw new EventSourceNotFoundException(eventSourceNameToUse); } + setEventSource((T) source); } } /** To make this backwards compatible even for respect of overriding */ + @SuppressWarnings("unchecked") public T initEventSource(EventSourceContext

context) { return (T) eventSource(context).orElseThrow(); } + @Override + public void useEventSourceWithName(String name) { + this.eventSourceNameToUse = name; + } + @Override public Class resourceType() { return resourceType; @@ -116,10 +122,4 @@ public void setOnUpdateFilter(OnUpdateFilter onUpdateFilter) { public void setOnDeleteFilter(OnDeleteFilter onDeleteFilter) { this.onDeleteFilter = onDeleteFilter; } - - public AbstractEventSourceHolderDependentResource setEventSourceToUse( - String eventSourceToUse) { - this.eventSourceToUse = eventSourceToUse; - return this; - } } 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 2ccd4da82a..004a56b5f6 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 @@ -73,5 +73,4 @@ Class resourceDiscriminator() default ResourceDiscriminator.class; - String eventSourceToUse() default NO_VALUE_SET; } 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 37d2246a58..d019e79a7f 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,10 +60,6 @@ public void configureWith(KubernetesDependentResourceConfig config) { if (discriminator != null) { setResourceDiscriminator(discriminator); } - config.getEventSourceToUse().ifPresent(n -> { - doNotProvideEventSource(); - setEventSourceToUse(n); - }); } private void configureWith(String labelSelector, Set 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 8d092fdfa5..5cab02d28c 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,5 @@ package io.javaoperatorsdk.operator.processing.dependent.kubernetes; -import java.util.Optional; import java.util.Set; import io.javaoperatorsdk.operator.api.reconciler.Constants; @@ -18,7 +17,6 @@ public class KubernetesDependentResourceConfig { private String labelSelector = NO_VALUE_SET; private boolean namespacesWereConfigured = false; private ResourceDiscriminator resourceDiscriminator; - private String eventSourceToUse; private OnAddFilter onAddFilter; @@ -34,7 +32,7 @@ public KubernetesDependentResourceConfig(Set namespaces, String labelSel boolean configuredNS, ResourceDiscriminator resourceDiscriminator, OnAddFilter onAddFilter, OnUpdateFilter onUpdateFilter, - OnDeleteFilter onDeleteFilter, GenericFilter genericFilter, String eventSourceToUse) { + OnDeleteFilter onDeleteFilter, GenericFilter genericFilter) { this.namespaces = namespaces; this.labelSelector = labelSelector; this.namespacesWereConfigured = configuredNS; @@ -43,12 +41,11 @@ public KubernetesDependentResourceConfig(Set namespaces, String labelSel this.onDeleteFilter = onDeleteFilter; this.genericFilter = genericFilter; this.resourceDiscriminator = resourceDiscriminator; - this.eventSourceToUse = eventSourceToUse; } public KubernetesDependentResourceConfig(Set namespaces, String labelSelector) { this(namespaces, labelSelector, true, null, null, null, - null, null, null); + null, null); } public KubernetesDependentResourceConfig setNamespaces(Set namespaces) { @@ -97,7 +94,4 @@ public ResourceDiscriminator getResourceDiscriminator() { return resourceDiscriminator; } - public Optional getEventSourceToUse() { - return Optional.ofNullable(eventSourceToUse); - } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowSupport.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowSupport.java index 8ca56960b5..a6e6d27ce3 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowSupport.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowSupport.java @@ -15,6 +15,7 @@ import io.javaoperatorsdk.operator.api.config.ConfigurationServiceProvider; import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceReferencer; import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DependentResourceConfigurator; import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.KubernetesClientAware; import io.javaoperatorsdk.operator.processing.dependent.workflow.builder.WorkflowBuilder; @@ -81,9 +82,20 @@ public DependentResource createAndConfigureFrom(DependentResourceSpec spec, if (dependentResource instanceof KubernetesClientAware) { ((KubernetesClientAware) dependentResource).setKubernetesClient(client); } - if (!spec.provideEventSource()) { - dependentResource.doNotProvideEventSource(); - } + + spec.getUseEventSourceWithName() + .ifPresent(esName -> { + final var name = (String) esName; + if (dependentResource instanceof EventSourceReferencer) { + ((EventSourceReferencer) dependentResource).useEventSourceWithName(name); + } else { + throw new IllegalStateException( + "DependentResource " + spec + " wants to use EventSource named " + name + + " but doesn't implement support for this feature by implementing " + + EventSourceReferencer.class.getSimpleName()); + } + }); + if (dependentResource instanceof DependentResourceConfigurator) { final var configurator = (DependentResourceConfigurator) dependentResource; spec.getDependentResourceConfiguration().ifPresent(configurator::configureWith); diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverriderTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverriderTest.java index 312d307cc1..4dd6938728 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverriderTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverriderTest.java @@ -80,9 +80,6 @@ public ReconcileResult reconcile(ConfigMap primary, Context c public Class resourceType() { return Object.class; } - - @Override - public void doNotProvideEventSource() {} } } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResourceTest.java index 0fd85c5afc..b93abd45c0 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResourceTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResourceTest.java @@ -7,9 +7,7 @@ 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.api.reconciler.EventSourceContext; import io.javaoperatorsdk.operator.processing.event.ResourceID; -import io.javaoperatorsdk.operator.processing.event.source.ResourceEventSource; import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; import static org.junit.jupiter.api.Assertions.*; @@ -80,12 +78,6 @@ public Class resourceType() { return ConfigMap.class; } - @Override - protected ResourceEventSource provideEventSource( - EventSourceContext eventSourceContext) { - return null; - } - @Override public Optional getSecondaryResource(TestCustomResource primary, Context context) { diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/EmptyTestDependentResource.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/EmptyTestDependentResource.java index dd42a80392..4fe88296ac 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/EmptyTestDependentResource.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/EmptyTestDependentResource.java @@ -19,9 +19,5 @@ public ReconcileResult reconcile(TestCustomResource primary, public Class resourceType() { return Deployment.class; } - - @Override - public void doNotProvideEventSource() {} - } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutorTest.java index 2ecb6bde24..9c10c06cc0 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutorTest.java @@ -48,9 +48,6 @@ public Class resourceType() { return String.class; } - @Override - public void doNotProvideEventSource() {} - @Override public String toString() { return name; @@ -110,9 +107,6 @@ public Class resourceType() { return String.class; } - @Override - public void doNotProvideEventSource() {} - @Override public String toString() { return name; diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowTestUtils.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowTestUtils.java index 72a3886963..e3b97afb3c 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowTestUtils.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowTestUtils.java @@ -12,7 +12,7 @@ public class ManagedWorkflowTestUtils { public static DependentResourceSpec createDRS(String name, String... dependOns) { return new DependentResourceSpec(EmptyTestDependentResource.class, null, name, Set.of(dependOns), null, null, null, - true); + null); } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap1.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap1.java index 0d450c1871..98f8033076 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap1.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap1.java @@ -9,10 +9,7 @@ import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; -import static io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype.MultipleManagedDependentResourceReconciler.CONFIG_MAP_EVENT_SOURCE; - -@KubernetesDependent(eventSourceToUse = CONFIG_MAP_EVENT_SOURCE, - resourceDiscriminator = ConfigMap1Discriminator.class) +@KubernetesDependent(resourceDiscriminator = ConfigMap1Discriminator.class) public class MultipleManagedDependentResourceConfigMap1 extends CRUDKubernetesDependentResource { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap2.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap2.java index 11902fc518..d4cdd4170f 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap2.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap2.java @@ -9,11 +9,9 @@ import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; -import static io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype.MultipleManagedDependentResourceReconciler.CONFIG_MAP_EVENT_SOURCE; import static io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype.MultipleManagedDependentResourceReconciler.DATA_KEY; -@KubernetesDependent(eventSourceToUse = CONFIG_MAP_EVENT_SOURCE, - resourceDiscriminator = ConfigMap2Discriminator.class) +@KubernetesDependent(resourceDiscriminator = ConfigMap2Discriminator.class) public class MultipleManagedDependentResourceConfigMap2 extends CRUDKubernetesDependentResource { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceReconciler.java index edfa7ad2bd..2d9b4f3ee9 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceReconciler.java @@ -11,9 +11,13 @@ import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; +import static io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype.MultipleManagedDependentResourceReconciler.CONFIG_MAP_EVENT_SOURCE; + @ControllerConfiguration(dependents = { - @Dependent(type = MultipleManagedDependentResourceConfigMap1.class), - @Dependent(type = MultipleManagedDependentResourceConfigMap2.class) + @Dependent(type = MultipleManagedDependentResourceConfigMap1.class, + useEventSourceWithName = CONFIG_MAP_EVENT_SOURCE), + @Dependent(type = MultipleManagedDependentResourceConfigMap2.class, + useEventSourceWithName = CONFIG_MAP_EVENT_SOURCE) }) public class MultipleManagedDependentResourceReconciler implements Reconciler, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/MultipleManagedExternalDependentResourceReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/MultipleManagedExternalDependentResourceReconciler.java index 573df92287..349409ec73 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/MultipleManagedExternalDependentResourceReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/MultipleManagedExternalDependentResourceReconciler.java @@ -15,9 +15,13 @@ import io.javaoperatorsdk.operator.support.ExternalServiceMock; import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; +import static io.javaoperatorsdk.operator.sample.multiplemanagedexternaldependenttype.MultipleManagedExternalDependentResourceReconciler.CONFIG_MAP_EVENT_SOURCE; + @ControllerConfiguration(dependents = { - @Dependent(type = ExternalDependentResource1.class, provideEventSource = false), - @Dependent(type = ExternalDependentResource2.class, provideEventSource = false) + @Dependent(type = ExternalDependentResource1.class, + useEventSourceWithName = CONFIG_MAP_EVENT_SOURCE), + @Dependent(type = ExternalDependentResource2.class, + useEventSourceWithName = CONFIG_MAP_EVENT_SOURCE) }) public class MultipleManagedExternalDependentResourceReconciler implements Reconciler, From cf222eaa5d258af47fe545269f2ed752cf0f7476 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Wed, 5 Oct 2022 12:18:31 +0200 Subject: [PATCH 5/8] refactor: isolate index handling to BulkDependentResource interface (#1517) --- .../dependent/AbstractDependentResource.java | 91 ++++++++----------- .../dependent/BulkDependentResource.java | 21 ++++- .../processing/dependent/BulkUpdater.java | 10 +- .../dependent/DesiredEqualsMatcher.java | 6 -- .../processing/dependent/Matcher.java | 15 --- .../processing/dependent/Updater.java | 4 - .../GenericKubernetesResourceMatcher.java | 84 +++++------------ .../KubernetesDependentResource.java | 20 ++-- ...ConfigMapDeleterBulkDependentResource.java | 27 +++--- .../ExternalBulkDependentResource.java | 19 ++-- 10 files changed, 119 insertions(+), 178 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java index 63e522ad7d..201f08add6 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java @@ -1,7 +1,5 @@ package io.javaoperatorsdk.operator.processing.dependent; -import java.util.ArrayList; -import java.util.List; import java.util.Optional; import org.slf4j.Logger; @@ -20,15 +18,15 @@ public abstract class AbstractDependentResource implements DependentResource { private static final Logger log = LoggerFactory.getLogger(AbstractDependentResource.class); - protected final boolean creatable = this instanceof Creator; - protected final boolean updatable = this instanceof Updater; - protected final boolean bulk = this instanceof BulkDependentResource; + private final boolean creatable = this instanceof Creator; + private final boolean updatable = this instanceof Updater; + private final boolean bulk = this instanceof BulkDependentResource; protected Creator creator; protected Updater updater; protected BulkDependentResource bulkDependentResource; - - protected List> resourceDiscriminator = new ArrayList<>(1); + private ResourceDiscriminator resourceDiscriminator; + private int currentCount; @SuppressWarnings("unchecked") public AbstractDependentResource() { @@ -42,48 +40,33 @@ public AbstractDependentResource() { public ReconcileResult reconcile(P primary, Context

context) { if (bulk) { final var count = bulkDependentResource.count(primary, context); - deleteBulkResourcesIfRequired(count, lastKnownBulkSize(), primary, context); - adjustDiscriminators(count); + deleteBulkResourcesIfRequired(count, primary, context); @SuppressWarnings("unchecked") final ReconcileResult[] results = new ReconcileResult[count]; for (int i = 0; i < count; i++) { results[i] = reconcileIndexAware(primary, i, context); } + currentCount = count; return ReconcileResult.aggregatedResult(results); } else { return reconcileIndexAware(primary, 0, context); } } - protected void deleteBulkResourcesIfRequired(int targetCount, int actualCount, P primary, - Context

context) { - if (targetCount >= actualCount) { + protected void deleteBulkResourcesIfRequired(int targetCount, P primary, Context

context) { + if (targetCount >= currentCount) { return; } - for (int i = targetCount; i < actualCount; i++) { - var resource = getSecondaryResourceIndexAware(primary, i, context); + for (int i = targetCount; i < currentCount; i++) { + var resource = bulkDependentResource.getSecondaryResource(primary, i, context); var index = i; resource.ifPresent( r -> bulkDependentResource.deleteBulkResourceWithIndex(primary, r, index, context)); } } - private void adjustDiscriminators(int count) { - if (resourceDiscriminator.size() == count) { - return; - } - if (resourceDiscriminator.size() < count) { - for (int i = resourceDiscriminator.size(); i < count; i++) { - resourceDiscriminator.add(bulkDependentResource.getResourceDiscriminator(i)); - } - } - if (resourceDiscriminator.size() > count) { - resourceDiscriminator.subList(count, resourceDiscriminator.size()).clear(); - } - } - protected ReconcileResult reconcileIndexAware(P primary, int i, Context

context) { - Optional maybeActual = bulk ? getSecondaryResourceIndexAware(primary, i, context) + Optional maybeActual = bulk ? bulkDependentResource.getSecondaryResource(primary, i, context) : getSecondaryResource(primary, context); if (creatable || updatable) { if (maybeActual.isEmpty()) { @@ -99,7 +82,7 @@ protected ReconcileResult reconcileIndexAware(P primary, int i, Context

co if (updatable) { final Matcher.Result match; if (bulk) { - match = updater.match(actual, primary, i, context); + match = bulkDependentResource.match(actual, primary, i, context); } else { match = updater.match(actual, primary, context); } @@ -124,17 +107,12 @@ protected ReconcileResult reconcileIndexAware(P primary, int i, Context

co } private R desiredIndexAware(P primary, int i, Context

context) { - return bulk ? desired(primary, i, context) - : desired(primary, context); + return bulk ? desired(primary, i, context) : desired(primary, context); } public Optional getSecondaryResource(P primary, Context

context) { - return resourceDiscriminator.isEmpty() ? context.getSecondaryResource(resourceType()) - : resourceDiscriminator.get(0).distinguish(resourceType(), primary, context); - } - - protected Optional getSecondaryResourceIndexAware(P primary, int index, Context

context) { - return context.getSecondaryResource(resourceType(), resourceDiscriminator.get(index)); + return resourceDiscriminator == null ? context.getSecondaryResource(resourceType()) + : resourceDiscriminator.distinguish(resourceType(), primary, context); } private void throwIfNull(R desired, P primary, String descriptor) { @@ -195,28 +173,35 @@ protected R desired(P primary, Context

context) { } protected R desired(P primary, int index, Context

context) { - throw new IllegalStateException( - "Must be implemented for bulk DependentResource creation"); + throw new IllegalStateException("Must be implemented for bulk DependentResource creation"); } - public AbstractDependentResource setResourceDiscriminator( - ResourceDiscriminator resourceDiscriminator) { - if (resourceDiscriminator != null) { - this.resourceDiscriminator.add(resourceDiscriminator); + public void delete(P primary, Context

context) { + if (bulk) { + deleteBulkResourcesIfRequired(0, primary, context); + } else { + handleDelete(primary, context); } - return this; } - public ResourceDiscriminator getResourceDiscriminator() { - if (this.resourceDiscriminator.isEmpty()) { - return null; - } else { - return this.resourceDiscriminator.get(0); - } + protected void handleDelete(P primary, Context

context) { + throw new IllegalStateException("delete method be implemented if Deleter trait is supported"); } - protected int lastKnownBulkSize() { - return resourceDiscriminator.size(); + public void setResourceDiscriminator( + ResourceDiscriminator resourceDiscriminator) { + this.resourceDiscriminator = resourceDiscriminator; } + protected boolean isCreatable() { + return creatable; + } + + protected boolean isUpdatable() { + return updatable; + } + + protected boolean isBulk() { + return bulk; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResource.java index 1f2688f5cb..64a174e201 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResource.java @@ -1,9 +1,11 @@ package io.javaoperatorsdk.operator.processing.dependent; +import java.util.Optional; + import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; +import io.javaoperatorsdk.operator.processing.dependent.Matcher.Result; /** * Manages dynamic number of resources created for a primary resource. Since the point of a bulk @@ -30,6 +32,21 @@ public interface BulkDependentResource extends Creator */ void deleteBulkResourceWithIndex(P primary, R resource, int i, Context

context); - ResourceDiscriminator getResourceDiscriminator(int index); + /** + * Determines whether the specified secondary resource matches the desired state with target index + * of a bulk resource as defined from the specified primary resource, given the specified + * {@link Context}. + * + * @param actualResource the resource we want to determine whether it's matching the desired state + * @param primary the primary resource from which the desired state is inferred + * @param context the context in which the resource is being matched + * @return a {@link Result} encapsulating whether the resource matched its desired state and this + * associated state if it was computed as part of the matching process. Use the static + * convenience methods ({@link Result#nonComputed(boolean)} and + * {@link Result#computed(boolean, Object)}) + */ + Result match(R actualResource, P primary, int index, Context

context); + + Optional getSecondaryResource(P primary, int index, Context

context); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkUpdater.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkUpdater.java index 9c00b47d0c..ee8f08a68d 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkUpdater.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkUpdater.java @@ -13,8 +13,12 @@ public interface BulkUpdater extends Updater { default Matcher.Result match(R actualResource, P primary, Context

context) { - throw new IllegalStateException(); + if (!(this instanceof BulkDependentResource)) { + throw new IllegalStateException( + BulkUpdater.class.getSimpleName() + " interface should only be implemented by " + + BulkDependentResource.class.getSimpleName() + " implementations"); + } + throw new IllegalStateException("This method should not be called from a " + + BulkDependentResource.class.getSimpleName() + " implementation"); } - - Matcher.Result match(R actualResource, P primary, int index, Context

context); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/DesiredEqualsMatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/DesiredEqualsMatcher.java index 1d3b34a47b..459d7951d6 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/DesiredEqualsMatcher.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/DesiredEqualsMatcher.java @@ -16,10 +16,4 @@ public Result match(R actualResource, P primary, Context

context) { var desired = abstractDependentResource.desired(primary, context); return Result.computed(actualResource.equals(desired), desired); } - - @Override - public Result match(R actualResource, P primary, int index, Context

context) { - var desired = abstractDependentResource.desired(primary, index, context); - return Result.computed(actualResource.equals(desired), desired); - } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/Matcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/Matcher.java index 835f76ab3a..750fe89cbf 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/Matcher.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/Matcher.java @@ -95,19 +95,4 @@ public Optional computedDesired() { * {@link Result#computed(boolean, Object)}) */ Result match(R actualResource, P primary, Context

context); - - /** - * Determines whether the specified secondary resource matches the desired state with target index - * of a bulk resource as defined from the specified primary resource, given the specified - * {@link Context}. - * - * @param actualResource the resource we want to determine whether it's matching the desired state - * @param primary the primary resource from which the desired state is inferred - * @param context the context in which the resource is being matched - * @return a {@link Result} encapsulating whether the resource matched its desired state and this - * associated state if it was computed as part of the matching process. Use the static - * convenience methods ({@link Result#nonComputed(boolean)} and - * {@link Result#computed(boolean, Object)}) - */ - Result match(R actualResource, P primary, int index, Context

context); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/Updater.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/Updater.java index 06b3cb52f6..828f9ad785 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/Updater.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/Updater.java @@ -8,8 +8,4 @@ public interface Updater { R update(R actual, R desired, P primary, Context

context); Result match(R actualResource, P primary, Context

context); - - default Result match(R actualResource, P primary, int index, Context

context) { - throw new IllegalStateException("Implement this for bulk matching"); - } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java index bb066b5b24..9952763e6a 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java @@ -23,47 +23,7 @@ private GenericKubernetesResourceMatcher(KubernetesDependentResource depen @SuppressWarnings({"unchecked", "rawtypes"}) static Matcher matcherFor( Class resourceType, KubernetesDependentResource dependentResource) { - if (Secret.class.isAssignableFrom(resourceType)) { - return new Matcher<>() { - @Override - public Result match(R actualResource, P primary, Context

context) { - final var desired = dependentResource.desired(primary, context); - return Result.computed( - ResourceComparators.compareSecretData((Secret) desired, (Secret) actualResource), - desired); - } - - @Override - public Result match(R actualResource, P primary, int index, Context

context) { - final var desired = dependentResource.desired(primary, index, context); - return Result.computed( - ResourceComparators.compareSecretData((Secret) desired, (Secret) actualResource), - desired); - } - }; - } else if (ConfigMap.class.isAssignableFrom(resourceType)) { - return new Matcher<>() { - @Override - public Result match(R actualResource, P primary, Context

context) { - final var desired = dependentResource.desired(primary, context); - return Result.computed( - ResourceComparators.compareConfigMapData((ConfigMap) desired, - (ConfigMap) actualResource), - desired); - } - - @Override - public Result match(R actualResource, P primary, int index, Context

context) { - final var desired = dependentResource.desired(primary, index, context); - return Result.computed( - ResourceComparators.compareConfigMapData((ConfigMap) desired, - (ConfigMap) actualResource), - desired); - } - }; - } else { - return new GenericKubernetesResourceMatcher(dependentResource); - } + return new GenericKubernetesResourceMatcher(dependentResource); } @Override @@ -72,14 +32,8 @@ public Result match(R actualResource, P primary, Context

context) { return match(desired, actualResource, false); } - @Override - public Result match(R actualResource, P primary, int index, Context

context) { - var desired = dependentResource.desired(primary, index, context); - return match(desired, actualResource, false); - } - - public static Result match( - R desired, R actualResource, boolean considerMetadata) { + public static Result match(R desired, R actualResource, + boolean considerMetadata) { if (considerMetadata) { final var desiredMetadata = desired.getMetadata(); final var actualMetadata = actualResource.getMetadata(); @@ -91,20 +45,30 @@ public static Result match( } } - final var objectMapper = ConfigurationServiceProvider.instance().getObjectMapper(); + if (desired instanceof ConfigMap) { + return Result.computed( + ResourceComparators.compareConfigMapData((ConfigMap) desired, (ConfigMap) actualResource), + desired); + } else if (desired instanceof Secret) { + return Result.computed( + ResourceComparators.compareSecretData((Secret) desired, (Secret) actualResource), + desired); + } else { + final var objectMapper = ConfigurationServiceProvider.instance().getObjectMapper(); - // reflection will be replaced by this: - // https://github.com/fabric8io/kubernetes-client/issues/3816 - var desiredSpecNode = objectMapper.valueToTree(ReconcilerUtils.getSpec(desired)); - var actualSpecNode = objectMapper.valueToTree(ReconcilerUtils.getSpec(actualResource)); - var diffJsonPatch = JsonDiff.asJson(desiredSpecNode, actualSpecNode); - for (int i = 0; i < diffJsonPatch.size(); i++) { - String operation = diffJsonPatch.get(i).get("op").asText(); - if (!operation.equals("add")) { - return Result.computed(false, desired); + // reflection will be replaced by this: + // https://github.com/fabric8io/kubernetes-client/issues/3816 + var desiredSpecNode = objectMapper.valueToTree(ReconcilerUtils.getSpec(desired)); + var actualSpecNode = objectMapper.valueToTree(ReconcilerUtils.getSpec(actualResource)); + var diffJsonPatch = JsonDiff.asJson(desiredSpecNode, actualSpecNode); + for (int i = 0; i < diffJsonPatch.size(); i++) { + String operation = diffJsonPatch.get(i).get("op").asText(); + if (!operation.equals("add")) { + return Result.computed(false, desired); + } } + return Result.computed(true, desired); } - return Result.computed(true, desired); } /** 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 d019e79a7f..1aaed6f12e 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 @@ -140,25 +140,20 @@ public Result match(R actualResource, P primary, Context

context) { } public Result match(R actualResource, P primary, int index, Context

context) { - return matcher.match(actualResource, primary, index, context); + final var desired = desired(primary, index, context); + return GenericKubernetesResourceMatcher.match(desired, actualResource, false); } - public void delete(P primary, Context

context) { - if (bulk) { - deleteBulkResourcesIfRequired(0, lastKnownBulkSize(), primary, context); - } else { - var resource = getSecondaryResource(primary, context); - resource.ifPresent(r -> client.resource(r).delete()); - } + protected void handleDelete(P primary, Context

context) { + var resource = getSecondaryResource(primary, context); + resource.ifPresent(r -> client.resource(r).delete()); } public void deleteBulkResourceWithIndex(P primary, R resource, int i, Context

context) { client.resource(resource).delete(); } - @SuppressWarnings("unchecked") - protected Resource prepare(R desired, - P primary, String actionName) { + protected Resource prepare(R desired, P primary, String actionName) { log.debug("{} target resource with type: {}, with id: {}", actionName, desired.getClass(), @@ -176,7 +171,6 @@ protected Resource prepare(R desired, protected InformerEventSource createEventSource(EventSourceContext

context) { if (kubernetesDependentResourceConfig != null) { // sets the filters for the dependent resource, which are applied by parent class - onAddFilter = kubernetesDependentResourceConfig.onAddFilter(); onUpdateFilter = kubernetesDependentResourceConfig.onUpdateFilter(); onDeleteFilter = kubernetesDependentResourceConfig.onDeleteFilter(); @@ -199,7 +193,7 @@ protected InformerEventSource createEventSource(EventSourceContext

cont } private boolean useDefaultAnnotationsToIdentifyPrimary() { - return !(this instanceof SecondaryToPrimaryMapper) && !garbageCollected && creatable; + return !(this instanceof SecondaryToPrimaryMapper) && !garbageCollected && isCreatable(); } private void addDefaultSecondaryToPrimaryMapperAnnotations(R desired, P primary) { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ConfigMapDeleterBulkDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ConfigMapDeleterBulkDependentResource.java index a7fbd9cb98..d8f85bb10a 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ConfigMapDeleterBulkDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ConfigMapDeleterBulkDependentResource.java @@ -7,7 +7,6 @@ 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.api.reconciler.ResourceDiscriminator; import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; import io.javaoperatorsdk.operator.processing.dependent.BulkDependentResource; import io.javaoperatorsdk.operator.processing.dependent.Creator; @@ -54,19 +53,17 @@ public int count(BulkDependentTestCustomResource primary, } @Override - public ResourceDiscriminator getResourceDiscriminator( - int index) { - return (resource, primary, context) -> { - var resources = context.getSecondaryResources(resource).stream() - .filter(r -> r.getMetadata().getName().endsWith("-" + index)) - .collect(Collectors.toList()); - if (resources.isEmpty()) { - return Optional.empty(); - } else if (resources.size() > 1) { - throw new IllegalStateException("More than one resource found for index:" + index); - } else { - return Optional.of(resources.get(0)); - } - }; + public Optional getSecondaryResource(BulkDependentTestCustomResource primary, + int index, Context context) { + var resources = context.getSecondaryResources(resourceType()).stream() + .filter(r -> r.getMetadata().getName().endsWith("-" + index)) + .collect(Collectors.toList()); + if (resources.isEmpty()) { + return Optional.empty(); + } else if (resources.size() > 1) { + throw new IllegalStateException("More than one resource found for index:" + index); + } else { + return Optional.of(resources.get(0)); + } } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalBulkDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalBulkDependentResource.java index 110626a923..ed486f6f34 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalBulkDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalBulkDependentResource.java @@ -1,11 +1,16 @@ package io.javaoperatorsdk.operator.sample.bulkdependent.external; -import java.util.*; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; -import io.javaoperatorsdk.operator.processing.dependent.*; +import io.javaoperatorsdk.operator.processing.dependent.BulkDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.BulkUpdater; +import io.javaoperatorsdk.operator.processing.dependent.Matcher; import io.javaoperatorsdk.operator.processing.dependent.external.PollingDependentResource; import io.javaoperatorsdk.operator.processing.event.ResourceID; import io.javaoperatorsdk.operator.sample.bulkdependent.BulkDependentTestCustomResource; @@ -38,7 +43,7 @@ public Map> fetchResources() { @Override public void delete(BulkDependentTestCustomResource primary, Context context) { - deleteBulkResourcesIfRequired(0, lastKnownBulkSize(), primary, context); + deleteBulkResourcesIfRequired(0, primary, context); } @Override @@ -92,9 +97,9 @@ private ResourceID toResourceID(ExternalResource externalResource) { } @Override - public ResourceDiscriminator getResourceDiscriminator( - int index) { - return (resource, primary, context) -> context.getSecondaryResources(resource).stream() + public Optional getSecondaryResource(BulkDependentTestCustomResource primary, + int index, Context context) { + return context.getSecondaryResources(resourceType()).stream() .filter(r -> r.getId().endsWith(EXTERNAL_RESOURCE_NAME_DELIMITER + index)) .collect(Collectors.toList()).stream().findFirst(); } From 2743be8a8998e9e5808bbdb2051de97d04921e7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Thu, 6 Oct 2022 09:16:17 +0200 Subject: [PATCH 6/8] feat: key based bulk resource creation (#1521) --- .../reconciler/dependent/ReconcileResult.java | 9 +- .../dependent/AbstractDependentResource.java | 82 +++++++++--------- .../dependent/BulkDependentResource.java | 21 +++-- .../KubernetesDependentResource.java | 14 ++- ...ConfigMapDeleterBulkDependentResource.java | 52 +++++------ .../ExternalBulkDependentResource.java | 86 ++++++++++--------- 6 files changed, 133 insertions(+), 131 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/ReconcileResult.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/ReconcileResult.java index 468e14e8ea..66d982f01d 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/ReconcileResult.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/ReconcileResult.java @@ -22,15 +22,14 @@ public static ReconcileResult noOperation(T resource) { return new ReconcileResult<>(resource, Operation.NONE); } - @SafeVarargs - public static ReconcileResult aggregatedResult(ReconcileResult... results) { + public static ReconcileResult aggregatedResult(List> results) { if (results == null) { throw new IllegalArgumentException("Should provide results to aggregate"); } - if (results.length == 1) { - return results[0]; + if (results.size() == 1) { + return results.get(0); } - final Map operations = new HashMap<>(results.length); + final Map operations = new HashMap<>(results.size()); for (ReconcileResult res : results) { res.getSingleResource().ifPresent(r -> operations.put(r, res.getSingleOperation())); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java index 201f08add6..1786c3a568 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java @@ -1,6 +1,6 @@ package io.javaoperatorsdk.operator.processing.dependent; -import java.util.Optional; +import java.util.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,76 +26,75 @@ public abstract class AbstractDependentResource protected Updater updater; protected BulkDependentResource bulkDependentResource; private ResourceDiscriminator resourceDiscriminator; - private int currentCount; - @SuppressWarnings("unchecked") - public AbstractDependentResource() { + @SuppressWarnings({"unchecked", "rawtypes"}) + protected AbstractDependentResource() { creator = creatable ? (Creator) this : null; updater = updatable ? (Updater) this : null; - bulkDependentResource = bulk ? (BulkDependentResource) this : null; + bulkDependentResource = bulk ? (BulkDependentResource) this : null; } + @Override public ReconcileResult reconcile(P primary, Context

context) { if (bulk) { - final var count = bulkDependentResource.count(primary, context); - deleteBulkResourcesIfRequired(count, primary, context); - @SuppressWarnings("unchecked") - final ReconcileResult[] results = new ReconcileResult[count]; - for (int i = 0; i < count; i++) { - results[i] = reconcileIndexAware(primary, i, context); + final var targetKeys = bulkDependentResource.targetKeys(primary, context); + Map actualResources = + bulkDependentResource.getSecondaryResources(primary, context); + + deleteBulkResourcesIfRequired(targetKeys, actualResources, primary, context); + final List> results = new ArrayList<>(targetKeys.size()); + + for (String key : targetKeys) { + results.add(reconcileIndexAware(primary, actualResources.get(key), key, context)); } - currentCount = count; return ReconcileResult.aggregatedResult(results); } else { - return reconcileIndexAware(primary, 0, context); + var actualResource = getSecondaryResource(primary, context); + return reconcileIndexAware(primary, actualResource.orElse(null), null, context); } } - protected void deleteBulkResourcesIfRequired(int targetCount, P primary, Context

context) { - if (targetCount >= currentCount) { - return; - } - for (int i = targetCount; i < currentCount; i++) { - var resource = bulkDependentResource.getSecondaryResource(primary, i, context); - var index = i; - resource.ifPresent( - r -> bulkDependentResource.deleteBulkResourceWithIndex(primary, r, index, context)); - } + @SuppressWarnings({"rawtypes"}) + protected void deleteBulkResourcesIfRequired(Set targetKeys, Map actualResources, + P primary, Context

context) { + actualResources.forEach((key, value) -> { + if (!targetKeys.contains(key)) { + bulkDependentResource.deleteBulkResource(primary, value, key, context); + } + }); } - protected ReconcileResult reconcileIndexAware(P primary, int i, Context

context) { - Optional maybeActual = bulk ? bulkDependentResource.getSecondaryResource(primary, i, context) - : getSecondaryResource(primary, context); + protected ReconcileResult reconcileIndexAware(P primary, R resource, String key, + Context

context) { if (creatable || updatable) { - if (maybeActual.isEmpty()) { + if (resource == null) { if (creatable) { - var desired = desiredIndexAware(primary, i, context); + var desired = desiredIndexAware(primary, key, context); throwIfNull(desired, primary, "Desired"); logForOperation("Creating", primary, desired); var createdResource = handleCreate(desired, primary, context); return ReconcileResult.resourceCreated(createdResource); } } else { - final var actual = maybeActual.get(); if (updatable) { final Matcher.Result match; if (bulk) { - match = bulkDependentResource.match(actual, primary, i, context); + match = bulkDependentResource.match(resource, primary, key, context); } else { - match = updater.match(actual, primary, context); + match = updater.match(resource, primary, context); } if (!match.matched()) { final var desired = - match.computedDesired().orElse(desiredIndexAware(primary, i, context)); + match.computedDesired().orElse(desiredIndexAware(primary, key, context)); throwIfNull(desired, primary, "Desired"); logForOperation("Updating", primary, desired); - var updatedResource = handleUpdate(actual, desired, primary, context); + var updatedResource = handleUpdate(resource, desired, primary, context); return ReconcileResult.resourceUpdated(updatedResource); } } else { - log.debug("Update skipped for dependent {} as it matched the existing one", actual); + log.debug("Update skipped for dependent {} as it matched the existing one", resource); } } } else { @@ -103,13 +102,15 @@ protected ReconcileResult reconcileIndexAware(P primary, int i, Context

co "Dependent {} is read-only, implement Creator and/or Updater interfaces to modify it", getClass().getSimpleName()); } - return ReconcileResult.noOperation(maybeActual.orElse(null)); + return ReconcileResult.noOperation(resource); } - private R desiredIndexAware(P primary, int i, Context

context) { - return bulk ? desired(primary, i, context) : desired(primary, context); + private R desiredIndexAware(P primary, String key, Context

context) { + return bulk ? bulkDependentResource.desired(primary, key, context) + : desired(primary, context); } + @Override public Optional getSecondaryResource(P primary, Context

context) { return resourceDiscriminator == null ? context.getSecondaryResource(resourceType()) : resourceDiscriminator.distinguish(resourceType(), primary, context); @@ -172,13 +173,10 @@ protected R desired(P primary, Context

context) { "desired method must be implemented if this DependentResource can be created and/or updated"); } - protected R desired(P primary, int index, Context

context) { - throw new IllegalStateException("Must be implemented for bulk DependentResource creation"); - } - public void delete(P primary, Context

context) { if (bulk) { - deleteBulkResourcesIfRequired(0, primary, context); + var actualResources = bulkDependentResource.getSecondaryResources(primary, context); + deleteBulkResourcesIfRequired(Collections.emptySet(), actualResources, primary, context); } else { handleDelete(primary, context); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResource.java index 64a174e201..1c7189293b 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResource.java @@ -1,6 +1,7 @@ package io.javaoperatorsdk.operator.processing.dependent; -import java.util.Optional; +import java.util.Map; +import java.util.Set; import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.Context; @@ -13,24 +14,27 @@ * {@link Creator} and {@link Deleter} interfaces out of the box. A concrete dependent resource can * implement additionally also {@link Updater}. */ -public interface BulkDependentResource extends Creator, Deleter

{ +public interface BulkDependentResource + extends Creator, Deleter

{ /** * @return number of resources to create */ - int count(P primary, Context

context); + Set targetKeys(P primary, Context

context); - R desired(P primary, int index, Context

context); + Map getSecondaryResources(P primary, Context

context); + + R desired(P primary, String key, Context

context); /** * Used to delete resource if the desired count is lower than the actual count of a resource. * * @param primary resource * @param resource actual resource from the cache for the index - * @param i index of the resource + * @param key key of the resource * @param context actual context */ - void deleteBulkResourceWithIndex(P primary, R resource, int i, Context

context); + void deleteBulkResource(P primary, R resource, String key, Context

context); /** * Determines whether the specified secondary resource matches the desired state with target index @@ -39,14 +43,13 @@ public interface BulkDependentResource extends Creator * * @param actualResource the resource we want to determine whether it's matching the desired state * @param primary the primary resource from which the desired state is inferred + * @param key key of the resource * @param context the context in which the resource is being matched * @return a {@link Result} encapsulating whether the resource matched its desired state and this * associated state if it was computed as part of the matching process. Use the static * convenience methods ({@link Result#nonComputed(boolean)} and * {@link Result#computed(boolean, Object)}) */ - Result match(R actualResource, P primary, int index, Context

context); - - Optional getSecondaryResource(P primary, int index, Context

context); + Result match(R actualResource, P primary, String key, Context

context); } 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 1aaed6f12e..97ad7fa226 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 @@ -139,9 +139,9 @@ public Result match(R actualResource, P primary, Context

context) { return matcher.match(actualResource, primary, context); } - public Result match(R actualResource, P primary, int index, Context

context) { - final var desired = desired(primary, index, context); - return GenericKubernetesResourceMatcher.match(desired, actualResource, false); + public Result match(R actualResource, P primary, String key, Context

context) { + final var desired = bulkDependentResource.desired(primary, key, context); + return GenericKubernetesResourceMatcher.match((R) desired, actualResource, false); } protected void handleDelete(P primary, Context

context) { @@ -149,7 +149,8 @@ protected void handleDelete(P primary, Context

context) { resource.ifPresent(r -> client.resource(r).delete()); } - public void deleteBulkResourceWithIndex(P primary, R resource, int i, Context

context) { + + public void deleteBulkResource(P primary, R resource, String key, Context

context) { client.resource(resource).delete(); } @@ -229,11 +230,6 @@ protected R desired(P primary, Context

context) { return super.desired(primary, context); } - @Override - protected R desired(P primary, int index, Context

context) { - return super.desired(primary, index, context); - } - private void prepareEventFiltering(R desired, ResourceID resourceID) { ((InformerEventSource) eventSource().orElseThrow()) .prepareForCreateOrUpdateEventFiltering(resourceID, desired); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ConfigMapDeleterBulkDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ConfigMapDeleterBulkDependentResource.java index d8f85bb10a..e41b38c97e 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ConfigMapDeleterBulkDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ConfigMapDeleterBulkDependentResource.java @@ -1,8 +1,6 @@ package io.javaoperatorsdk.operator.sample.bulkdependent; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; +import java.util.*; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; @@ -27,43 +25,49 @@ public class ConfigMapDeleterBulkDependentResource public static final String LABEL_KEY = "bulk"; public static final String LABEL_VALUE = "true"; public static final String ADDITIONAL_DATA_KEY = "additionalData"; + public static final String INDEX_DELIMITER = "-"; public ConfigMapDeleterBulkDependentResource() { super(ConfigMap.class); } @Override - public ConfigMap desired(BulkDependentTestCustomResource primary, - int index, Context context) { + public Set targetKeys(BulkDependentTestCustomResource primary, + Context context) { + var number = primary.getSpec().getNumberOfResources(); + Set res = new HashSet<>(); + for (int i = 0; i < number; i++) { + res.add(Integer.toString(i)); + } + return res; + } + + @Override + public ConfigMap desired(BulkDependentTestCustomResource primary, String key, + Context context) { ConfigMap configMap = new ConfigMap(); configMap.setMetadata(new ObjectMetaBuilder() - .withName(primary.getMetadata().getName() + "-" + index) + .withName(primary.getMetadata().getName() + INDEX_DELIMITER + key) .withNamespace(primary.getMetadata().getNamespace()) .withLabels(Map.of(LABEL_KEY, LABEL_VALUE)) .build()); configMap.setData( - Map.of("number", "" + index, ADDITIONAL_DATA_KEY, primary.getSpec().getAdditionalData())); + Map.of("number", "" + key, ADDITIONAL_DATA_KEY, primary.getSpec().getAdditionalData())); return configMap; } @Override - public int count(BulkDependentTestCustomResource primary, + public Map getSecondaryResources(BulkDependentTestCustomResource primary, Context context) { - return primary.getSpec().getNumberOfResources(); - } - - @Override - public Optional getSecondaryResource(BulkDependentTestCustomResource primary, - int index, Context context) { - var resources = context.getSecondaryResources(resourceType()).stream() - .filter(r -> r.getMetadata().getName().endsWith("-" + index)) - .collect(Collectors.toList()); - if (resources.isEmpty()) { - return Optional.empty(); - } else if (resources.size() > 1) { - throw new IllegalStateException("More than one resource found for index:" + index); - } else { - return Optional.of(resources.get(0)); - } + var configMaps = context.getSecondaryResources(ConfigMap.class); + Map result = new HashMap<>(configMaps.size()); + configMaps.forEach(cm -> { + String name = cm.getMetadata().getName(); + if (name.startsWith(primary.getMetadata().getName())) { + String key = name.substring(name.lastIndexOf(INDEX_DELIMITER) + 1); + result.put(key, cm); + } + }); + return result; } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalBulkDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalBulkDependentResource.java index ed486f6f34..79b861d060 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalBulkDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalBulkDependentResource.java @@ -1,10 +1,6 @@ package io.javaoperatorsdk.operator.sample.bulkdependent.external; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; import io.javaoperatorsdk.operator.api.reconciler.Context; @@ -41,66 +37,72 @@ public Map> fetchResources() { } @Override - public void delete(BulkDependentTestCustomResource primary, + public ExternalResource create(ExternalResource desired, BulkDependentTestCustomResource primary, Context context) { - deleteBulkResourcesIfRequired(0, primary, context); + return externalServiceMock.create(desired); } @Override - public int count(BulkDependentTestCustomResource primary, - Context context) { - return primary.getSpec().getNumberOfResources(); + public ExternalResource update(ExternalResource actual, ExternalResource desired, + BulkDependentTestCustomResource primary, Context context) { + return externalServiceMock.update(desired); + } + + private static String toExternalResourceId(BulkDependentTestCustomResource primary, String i) { + return primary.getMetadata().getName() + EXTERNAL_RESOURCE_NAME_DELIMITER + + primary.getMetadata().getNamespace() + + EXTERNAL_RESOURCE_NAME_DELIMITER + i; + } + + private ResourceID toResourceID(ExternalResource externalResource) { + var parts = externalResource.getId().split(EXTERNAL_RESOURCE_NAME_DELIMITER); + return new ResourceID(parts[0], parts[1]); } @Override - public void deleteBulkResourceWithIndex(BulkDependentTestCustomResource primary, - ExternalResource resource, int i, Context context) { - externalServiceMock.delete(resource.getId()); + public Set targetKeys(BulkDependentTestCustomResource primary, + Context context) { + var number = primary.getSpec().getNumberOfResources(); + Set res = new HashSet<>(); + for (int i = 0; i < number; i++) { + res.add(Integer.toString(i)); + } + return res; } @Override - public ExternalResource desired(BulkDependentTestCustomResource primary, int index, + public Map getSecondaryResources( + BulkDependentTestCustomResource primary, Context context) { - return new ExternalResource(toExternalResourceId(primary, index), - primary.getSpec().getAdditionalData()); + return context.getSecondaryResources(resourceType()).stream() + .filter(r -> r.getId() + .startsWith(primary.getMetadata().getName() + EXTERNAL_RESOURCE_NAME_DELIMITER + + primary.getMetadata().getNamespace() + + EXTERNAL_RESOURCE_NAME_DELIMITER)) + .collect(Collectors.toMap( + r -> r.getId().substring(r.getId().lastIndexOf(EXTERNAL_RESOURCE_NAME_DELIMITER) + 1), + r -> r)); } @Override - public ExternalResource create(ExternalResource desired, BulkDependentTestCustomResource primary, + public ExternalResource desired(BulkDependentTestCustomResource primary, String key, Context context) { - return externalServiceMock.create(desired); + return new ExternalResource(toExternalResourceId(primary, key), + primary.getSpec().getAdditionalData()); } @Override - public ExternalResource update(ExternalResource actual, ExternalResource desired, - BulkDependentTestCustomResource primary, Context context) { - return externalServiceMock.update(desired); + public void deleteBulkResource(BulkDependentTestCustomResource primary, ExternalResource resource, + String key, + Context context) { + externalServiceMock.delete(resource.getId()); } @Override public Matcher.Result match(ExternalResource actualResource, - BulkDependentTestCustomResource primary, - int index, Context context) { + BulkDependentTestCustomResource primary, String index, + Context context) { var desired = desired(primary, index, context); return Matcher.Result.computed(desired.equals(actualResource), desired); } - - private static String toExternalResourceId(BulkDependentTestCustomResource primary, int i) { - return primary.getMetadata().getName() + EXTERNAL_RESOURCE_NAME_DELIMITER + - primary.getMetadata().getNamespace() + - EXTERNAL_RESOURCE_NAME_DELIMITER + i; - } - - private ResourceID toResourceID(ExternalResource externalResource) { - var parts = externalResource.getId().split(EXTERNAL_RESOURCE_NAME_DELIMITER); - return new ResourceID(parts[0], parts[1]); - } - - @Override - public Optional getSecondaryResource(BulkDependentTestCustomResource primary, - int index, Context context) { - return context.getSecondaryResources(resourceType()).stream() - .filter(r -> r.getId().endsWith(EXTERNAL_RESOURCE_NAME_DELIMITER + index)) - .collect(Collectors.toList()).stream().findFirst(); - } } From b4d32889c934ea93ab0e30738d858d3b20adc40e Mon Sep 17 00:00:00 2001 From: csviri Date: Thu, 6 Oct 2022 10:18:35 +0200 Subject: [PATCH 7/8] improvement: bulk dependent resource api --- .../dependent/AbstractDependentResource.java | 42 ++++++++++--------- .../dependent/BulkDependentResource.java | 7 +--- .../KubernetesDependentResource.java | 6 +-- ...ConfigMapDeleterBulkDependentResource.java | 8 ++-- .../ExternalBulkDependentResource.java | 17 +++----- 5 files changed, 37 insertions(+), 43 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java index 1786c3a568..9afddbe903 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java @@ -35,24 +35,25 @@ protected AbstractDependentResource() { bulkDependentResource = bulk ? (BulkDependentResource) this : null; } - @Override public ReconcileResult reconcile(P primary, Context

context) { if (bulk) { - final var targetKeys = bulkDependentResource.targetKeys(primary, context); + final var targetResources = bulkDependentResource.desiredResources(primary, context); + Map actualResources = bulkDependentResource.getSecondaryResources(primary, context); - deleteBulkResourcesIfRequired(targetKeys, actualResources, primary, context); - final List> results = new ArrayList<>(targetKeys.size()); + deleteBulkResourcesIfRequired(targetResources.keySet(), actualResources, primary, context); + final List> results = new ArrayList<>(targetResources.size()); + + targetResources.forEach((key, resource) -> { + results.add(reconcileIndexAware(primary, actualResources.get(key), resource, key, context)); + }); - for (String key : targetKeys) { - results.add(reconcileIndexAware(primary, actualResources.get(key), key, context)); - } return ReconcileResult.aggregatedResult(results); } else { var actualResource = getSecondaryResource(primary, context); - return reconcileIndexAware(primary, actualResource.orElse(null), null, context); + return reconcileIndexAware(primary, actualResource.orElse(null), null, null, context); } } @@ -66,12 +67,13 @@ protected void deleteBulkResourcesIfRequired(Set targetKeys, Map actu }); } - protected ReconcileResult reconcileIndexAware(P primary, R resource, String key, + protected ReconcileResult reconcileIndexAware(P primary, R actualResource, R desiredResource, + String key, Context

context) { if (creatable || updatable) { - if (resource == null) { + if (actualResource == null) { if (creatable) { - var desired = desiredIndexAware(primary, key, context); + var desired = bulkAwareDesired(primary, desiredResource, context); throwIfNull(desired, primary, "Desired"); logForOperation("Creating", primary, desired); var createdResource = handleCreate(desired, primary, context); @@ -81,20 +83,22 @@ protected ReconcileResult reconcileIndexAware(P primary, R resource, String k if (updatable) { final Matcher.Result match; if (bulk) { - match = bulkDependentResource.match(resource, primary, key, context); + match = + bulkDependentResource.match(actualResource, desiredResource, primary, key, context); } else { - match = updater.match(resource, primary, context); + match = updater.match(actualResource, primary, context); } if (!match.matched()) { final var desired = - match.computedDesired().orElse(desiredIndexAware(primary, key, context)); + match.computedDesired().orElse(bulkAwareDesired(primary, desiredResource, context)); throwIfNull(desired, primary, "Desired"); logForOperation("Updating", primary, desired); - var updatedResource = handleUpdate(resource, desired, primary, context); + var updatedResource = handleUpdate(actualResource, desired, primary, context); return ReconcileResult.resourceUpdated(updatedResource); } } else { - log.debug("Update skipped for dependent {} as it matched the existing one", resource); + log.debug("Update skipped for dependent {} as it matched the existing one", + actualResource); } } } else { @@ -102,11 +106,11 @@ protected ReconcileResult reconcileIndexAware(P primary, R resource, String k "Dependent {} is read-only, implement Creator and/or Updater interfaces to modify it", getClass().getSimpleName()); } - return ReconcileResult.noOperation(resource); + return ReconcileResult.noOperation(actualResource); } - private R desiredIndexAware(P primary, String key, Context

context) { - return bulk ? bulkDependentResource.desired(primary, key, context) + private R bulkAwareDesired(P primary, R alreadyComputedDesire, Context

context) { + return bulk ? alreadyComputedDesire : desired(primary, context); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResource.java index 1c7189293b..fe8da3a091 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResource.java @@ -1,7 +1,6 @@ package io.javaoperatorsdk.operator.processing.dependent; import java.util.Map; -import java.util.Set; import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.Context; @@ -20,12 +19,10 @@ public interface BulkDependentResource /** * @return number of resources to create */ - Set targetKeys(P primary, Context

context); + Map desiredResources(P primary, Context

context); Map getSecondaryResources(P primary, Context

context); - R desired(P primary, String key, Context

context); - /** * Used to delete resource if the desired count is lower than the actual count of a resource. * @@ -50,6 +47,6 @@ public interface BulkDependentResource * convenience methods ({@link Result#nonComputed(boolean)} and * {@link Result#computed(boolean, Object)}) */ - Result match(R actualResource, P primary, String key, Context

context); + Result match(R actualResource, R desired, P primary, String key, Context

context); } 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 97ad7fa226..82df9dbbb9 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 @@ -139,9 +139,8 @@ public Result match(R actualResource, P primary, Context

context) { return matcher.match(actualResource, primary, context); } - public Result match(R actualResource, P primary, String key, Context

context) { - final var desired = bulkDependentResource.desired(primary, key, context); - return GenericKubernetesResourceMatcher.match((R) desired, actualResource, false); + public Result match(R actualResource, R desired, P primary, String key, Context

context) { + return GenericKubernetesResourceMatcher.match(desired, actualResource, false); } protected void handleDelete(P primary, Context

context) { @@ -149,7 +148,6 @@ protected void handleDelete(P primary, Context

context) { resource.ifPresent(r -> client.resource(r).delete()); } - public void deleteBulkResource(P primary, R resource, String key, Context

context) { client.resource(resource).delete(); } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ConfigMapDeleterBulkDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ConfigMapDeleterBulkDependentResource.java index e41b38c97e..a04224790c 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ConfigMapDeleterBulkDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ConfigMapDeleterBulkDependentResource.java @@ -32,17 +32,17 @@ public ConfigMapDeleterBulkDependentResource() { } @Override - public Set targetKeys(BulkDependentTestCustomResource primary, + public Map desiredResources(BulkDependentTestCustomResource primary, Context context) { var number = primary.getSpec().getNumberOfResources(); - Set res = new HashSet<>(); + Map res = new HashMap<>(); for (int i = 0; i < number; i++) { - res.add(Integer.toString(i)); + var key = Integer.toString(i); + res.put(key, desired(primary, key, context)); } return res; } - @Override public ConfigMap desired(BulkDependentTestCustomResource primary, String key, Context context) { ConfigMap configMap = new ConfigMap(); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalBulkDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalBulkDependentResource.java index 79b861d060..3a13e29707 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalBulkDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalBulkDependentResource.java @@ -60,12 +60,14 @@ private ResourceID toResourceID(ExternalResource externalResource) { } @Override - public Set targetKeys(BulkDependentTestCustomResource primary, + public Map desiredResources(BulkDependentTestCustomResource primary, Context context) { var number = primary.getSpec().getNumberOfResources(); - Set res = new HashSet<>(); + Map res = new HashMap<>(); for (int i = 0; i < number; i++) { - res.add(Integer.toString(i)); + var key = Integer.toString(i); + res.put(key, new ExternalResource(toExternalResourceId(primary, key), + primary.getSpec().getAdditionalData())); } return res; } @@ -84,13 +86,6 @@ public Map getSecondaryResources( r -> r)); } - @Override - public ExternalResource desired(BulkDependentTestCustomResource primary, String key, - Context context) { - return new ExternalResource(toExternalResourceId(primary, key), - primary.getSpec().getAdditionalData()); - } - @Override public void deleteBulkResource(BulkDependentTestCustomResource primary, ExternalResource resource, String key, @@ -100,9 +95,9 @@ public void deleteBulkResource(BulkDependentTestCustomResource primary, External @Override public Matcher.Result match(ExternalResource actualResource, + ExternalResource desired, BulkDependentTestCustomResource primary, String index, Context context) { - var desired = desired(primary, index, context); return Matcher.Result.computed(desired.equals(actualResource), desired); } } From d6b9d387638c8e6198d4e1a349eb11ee31bff1ba Mon Sep 17 00:00:00 2001 From: csviri Date: Fri, 7 Oct 2022 13:23:57 +0200 Subject: [PATCH 8/8] merge --- .../api/config/AnnotationControllerConfiguration.java | 5 +++-- 1 file changed, 3 insertions(+), 2 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 94e774c9b7..8eb713fa73 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 @@ -47,8 +47,9 @@ public AnnotationControllerConfiguration(Reconciler

reconciler) { this.reconciler = reconciler; this.annotation = reconciler.getClass().getAnnotation(ControllerConfiguration.class); if (annotation == null) { - throw new OperatorException("Missing mandatory @" + CONTROLLER_CONFIG_ANNOTATION + - " annotation for reconciler: " + reconciler); + throw new OperatorException( + "Missing mandatory @" + ControllerConfiguration.class.getSimpleName() + + " annotation for reconciler: " + reconciler); } }