From bc131833c8f33e5495477598baa9ccf27639671f 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 1/4] 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 9c1be9d59151f1673b5b2a5bea60543021fabd34 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 2/4] 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 3ba134604c3b5ef68e3459bda3f8ab4e2a49c12a Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Mon, 3 Oct 2022 21:12:06 +0200 Subject: [PATCH 3/4] refactor: simplify handling of reused event sources DependentResources that provide an EventSource should implement EventSourceAware. If they want to reuse an already created event source instead of providing their own, they need to select the appropriate event source identified by the name specified by the useEventSourceNamed method. --- .../AnnotationControllerConfiguration.java | 7 ++-- .../ControllerConfigurationOverrider.java | 5 ++- .../dependent/DependentResourceSpec.java | 10 ++--- .../api/reconciler/EventSourceContext.java | 14 +++++-- .../reconciler/EventSourceInitializer.java | 13 +++---- .../api/reconciler/dependent/Dependent.java | 2 +- .../dependent/DependentResource.java | 27 ------------- .../dependent/EventSourceAware.java | 32 +++++++++++++-- .../operator/processing/Controller.java | 39 ++++++++----------- .../dependent/AbstractDependentResource.java | 24 ------------ ...actEventSourceHolderDependentResource.java | 39 ++++++++----------- .../KubernetesDependentResource.java | 5 --- .../KubernetesDependentResourceConfig.java | 13 +------ .../workflow/ManagedWorkflowSupport.java | 9 +++-- .../ControllerConfigurationOverriderTest.java | 3 -- .../AbstractDependentResourceTest.java | 8 ---- .../dependent/EmptyTestDependentResource.java | 3 -- .../AbstractWorkflowExecutorTest.java | 6 --- .../workflow/ManagedWorkflowTestUtils.java | 2 +- .../ExternalResourceDiscriminator.java | 2 +- ...edExternalDependentResourceReconciler.java | 27 ++++++------- 21 files changed, 115 insertions(+), 175 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 fa2b5c1927..ab13a6d5d5 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 @@ -248,7 +248,8 @@ public List getDependentResources() { instantiateIfNotDefault(dependent.readyPostcondition(), Condition.class, context), instantiateIfNotDefault(dependent.reconcilePrecondition(), Condition.class, context), instantiateIfNotDefault(dependent.deletePostcondition(), Condition.class, context), - dependent.provideEventSource()); + Constants.NO_VALUE_SET.equals(dependent.eventSource()) ? null + : dependent.eventSource()); specsMap.put(name, spec); } @@ -314,14 +315,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..454bd4ca49 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.getEventSourceName().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.getEventSourceName().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..fadd0a9816 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 eventSourceName; public DependentResourceSpec(Class dependentResourceClass, C dependentResourceConfig, String name, Set dependsOn, Condition readyCondition, Condition reconcileCondition, Condition deletePostCondition, - boolean provideEventSource) { + String eventSourceName) { 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.eventSourceName = eventSourceName; } public Class getDependentResourceClass() { @@ -94,7 +94,7 @@ public Condition getDeletePostCondition() { return deletePostCondition; } - public boolean provideEventSource() { - return provideEventSource; + public Optional getEventSourceName() { + return Optional.ofNullable(eventSourceName); } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceContext.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceContext.java index e8062e9651..2c1e82ba4c 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceContext.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceContext.java @@ -3,6 +3,8 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.client.KubernetesClient; import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; +import io.javaoperatorsdk.operator.processing.event.EventSourceManager; +import io.javaoperatorsdk.operator.processing.event.EventSourceRetriever; import io.javaoperatorsdk.operator.processing.event.source.EventSource; import io.javaoperatorsdk.operator.processing.event.source.IndexerResourceCache; @@ -13,14 +15,14 @@ */ public class EventSourceContext

{ - private final IndexerResourceCache

primaryCache; + private final EventSourceManager

eventSourceManager; private final ControllerConfiguration

controllerConfiguration; private final KubernetesClient client; - public EventSourceContext(IndexerResourceCache

primaryCache, + public EventSourceContext(EventSourceManager

eventSourceManager, ControllerConfiguration

controllerConfiguration, KubernetesClient client) { - this.primaryCache = primaryCache; + this.eventSourceManager = eventSourceManager; this.controllerConfiguration = controllerConfiguration; this.client = client; } @@ -31,7 +33,7 @@ public EventSourceContext(IndexerResourceCache

primaryCache, * @return the primary resource cache */ public IndexerResourceCache

getPrimaryCache() { - return primaryCache; + return eventSourceManager.getControllerResourceEventSource(); } /** @@ -54,4 +56,8 @@ public ControllerConfiguration

getControllerConfiguration() { public KubernetesClient getClient() { return client; } + + public EventSourceRetriever

getEventSourceRetriever() { + return eventSourceManager; + } } 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 017418ea35..c49332b468 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,14 +1,14 @@ package io.javaoperatorsdk.operator.api.reconciler; +import java.util.Arrays; 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.api.reconciler.dependent.EventSourceAware; 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 @@ -46,13 +46,12 @@ static Map nameEventSources(EventSource... eventSources) { @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)); - } + Arrays.stream(dependentResources) + .filter(EventSourceAware.class::isInstance) + .forEach(esa -> ((EventSourceAware) esa).eventSource(context) + .ifPresent(es -> eventSourceMap.put(generateNameFor(es), es))); return eventSourceMap; } else { return Collections.emptyMap(); 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..06ec90eba5 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 @@ -65,5 +65,5 @@ * * @return if the event source (if any) provided by the dependent resource should be used or not. */ - boolean provideEventSource() default true; + String eventSource() 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..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 @@ -4,8 +4,6 @@ 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. @@ -31,31 +29,6 @@ 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 index 09b13d90c5..c4f4fbcc4f 100644 --- 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 @@ -1,10 +1,36 @@ package io.javaoperatorsdk.operator.api.reconciler.dependent; +import java.util.Optional; + import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.processing.event.EventSourceRetriever; +import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; +import io.javaoperatorsdk.operator.processing.event.source.ResourceEventSource; + +public interface EventSourceAware { + + /** + * 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 context context of event source initialization + * @return an optional event source + */ + default Optional> eventSource(EventSourceContext

context) { + return getUsedEventSourceName().map( + name -> context.getEventSourceRetriever().getResourceEventSourceFor(resourceType(), name)); + } -public interface EventSourceAware

{ + Class resourceType(); - void selectEventSources(EventSourceRetriever

eventSourceRetriever); + void useEventSourceNamed(String eventSourceName); + default Optional getUsedEventSourceName() { + return Optional.empty(); + } } 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..f5ee0cd269 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 @@ -40,7 +40,6 @@ 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; @@ -214,25 +213,21 @@ 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)); + managedWorkflow.getDependentResourcesByName().entrySet().stream().filter(entry -> { + final var value = entry.getValue(); + return value instanceof EventSourceProvider || value instanceof EventSourceAware; + }).forEach(entry -> { + final var value = entry.getValue(); + final var key = entry.getKey(); + if (value instanceof EventSourceProvider) { + final var provider = (EventSourceProvider) value; + final var source = provider.initEventSource(context); + eventSourceManager.registerEventSource(key, source); + } else { + ((EventSourceAware) value).eventSource(context) + .ifPresent(es -> eventSourceManager.registerEventSource(key, es)); + } + }); } @Override @@ -299,8 +294,8 @@ public synchronized void start(boolean startEventProcessor) throws OperatorExcep try { // check that the custom resource is known by the cluster if configured that way validateCRDWithLocalModelIfRequired(resClass, controllerName, crdName, specVersion); - final var context = new EventSourceContext<>( - eventSourceManager.getControllerResourceEventSource(), configuration, kubernetesClient); + final var context = + new EventSourceContext<>(eventSourceManager, configuration, kubernetesClient); initAndRegisterEventSources(context); eventSourceManager.start(); 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..e3d399b67a 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) { @@ -238,8 +218,4 @@ public ResourceDiscriminator getResourceDiscriminator() { 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..06532200ae 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 @@ -7,7 +7,6 @@ import io.javaoperatorsdk.operator.api.reconciler.Ignore; 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.ResourceEventSource; import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter; @@ -17,7 +16,7 @@ @Ignore public abstract class AbstractEventSourceHolderDependentResource> - extends AbstractDependentResource implements EventSourceAware

{ + extends AbstractDependentResource implements EventSourceAware { private T eventSource; private final Class resourceType; @@ -33,7 +32,9 @@ protected AbstractEventSourceHolderDependentResource(Class resourceType) { } - public ResourceEventSource provideEventSource(EventSourceContext

context) { + @SuppressWarnings("unchecked") + @Override + 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. @@ -41,26 +42,16 @@ public ResourceEventSource provideEventSource(EventSourceContext

contex // event source // is shared between dependent resources this does not override the existing filters. if (eventSource == null) { - setEventSource(createEventSource(context)); + T localEventSource = + (T) EventSourceAware.super.eventSource(context).orElse(createEventSource(context)); + setEventSource(localEventSource); applyFilters(); } - 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())); - } - } + return Optional.of(eventSource); } /** To make this backwards compatible even for respect of overriding */ + @SuppressWarnings("unchecked") public T initEventSource(EventSourceContext

context) { return (T) eventSource(context).orElseThrow(); } @@ -117,9 +108,13 @@ public void setOnDeleteFilter(OnDeleteFilter onDeleteFilter) { this.onDeleteFilter = onDeleteFilter; } - public AbstractEventSourceHolderDependentResource setEventSourceToUse( - String eventSourceToUse) { - this.eventSourceToUse = eventSourceToUse; - return this; + @Override + public void useEventSourceNamed(String eventSourceName) { + this.eventSourceToUse = eventSourceName; + } + + @Override + public Optional getUsedEventSourceName() { + return Optional.ofNullable(eventSourceToUse); } } 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..df195fad29 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, @@ -160,7 +156,6 @@ public void deleteBulkResourceWithIndex(P primary, R resource, int i, Context

client.resource(resource).delete(); } - @SuppressWarnings("unchecked") protected Resource prepare(R desired, P primary, String actionName) { log.debug("{} target resource with type: {}, with id: {}", 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..188b805fc4 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,8 +17,6 @@ public class KubernetesDependentResourceConfig { private String labelSelector = NO_VALUE_SET; private boolean namespacesWereConfigured = false; private ResourceDiscriminator resourceDiscriminator; - private String eventSourceToUse; - private OnAddFilter onAddFilter; private OnUpdateFilter onUpdateFilter; @@ -34,7 +31,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 +40,10 @@ 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); + this(namespaces, labelSelector, true, null, null, null, null, null); } public KubernetesDependentResourceConfig setNamespaces(Set namespaces) { @@ -96,8 +91,4 @@ public GenericFilter genericFilter() { 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..b34d58af74 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.EventSourceAware; 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; @@ -78,12 +79,14 @@ public DependentResource createAndConfigureFrom(DependentResourceSpec spec, final var dependentResource = ConfigurationServiceProvider.instance().dependentResourceFactory().createFrom(spec); + if (dependentResource instanceof EventSourceAware) { + EventSourceAware eventSourceAware = (EventSourceAware) dependentResource; + spec.getEventSourceName().ifPresent(n -> eventSourceAware.useEventSourceNamed((String) n)); + } + 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/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..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 @@ -20,8 +20,5 @@ 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/multiplemanagedexternaldependenttype/ExternalResourceDiscriminator.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/ExternalResourceDiscriminator.java index ba265455de..5a394113c1 100644 --- 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 @@ -9,7 +9,7 @@ public class ExternalResourceDiscriminator implements ResourceDiscriminator { - private String suffix; + private final String suffix; public ExternalResourceDiscriminator(String suffix) { this.suffix = suffix; 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..28b94ac715 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 @@ -16,8 +16,10 @@ import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; @ControllerConfiguration(dependents = { - @Dependent(type = ExternalDependentResource1.class, provideEventSource = false), - @Dependent(type = ExternalDependentResource2.class, provideEventSource = false) + @Dependent(type = ExternalDependentResource1.class, + eventSource = MultipleManagedExternalDependentResourceReconciler.CONFIG_MAP_EVENT_SOURCE), + @Dependent(type = ExternalDependentResource2.class, + eventSource = MultipleManagedExternalDependentResourceReconciler.CONFIG_MAP_EVENT_SOURCE) }) public class MultipleManagedExternalDependentResourceReconciler implements Reconciler, @@ -47,17 +49,16 @@ public int getNumberOfExecutions() { 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); + final var 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); } From 0b74b490f49420b17fbc7d2b2218d6cc2d62319e Mon Sep 17 00:00:00 2001 From: csviri Date: Tue, 4 Oct 2022 09:46:04 +0200 Subject: [PATCH 4/4] docs: javadoc fix --- .../operator/api/reconciler/dependent/Dependent.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 06ec90eba5..2f2f1f1e0d 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 @@ -59,11 +59,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. */ String eventSource() default NO_VALUE_SET; }