From 6c0edb8abce589ca73cc929e62bdc873f6306524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Thu, 5 May 2022 09:23:42 +0200 Subject: [PATCH 01/51] feat: garbage collected interface (#1164) --- .../dependent/GarbageCollected.java | 15 +++ .../operator/processing/Controller.java | 12 ++- .../dependent/AbstractDependentResource.java | 3 +- .../CRUDKubernetesDependentResource.java | 13 ++- .../CRUKubernetesDependentResource.java | 21 ---- .../KubernetesDependentResource.java | 10 +- ...ubernetesDependentGarbageCollectionIT.java | 83 ++++++++++++++ .../ConfigMapDependentResource.java | 10 +- .../ConfigMapDependentResource.java | 4 +- ...ntGarbageCollectionTestCustomResource.java | 16 +++ ...rbageCollectionTestCustomResourceSpec.java | 16 +++ ...ageCollectionTestCustomResourceStatus.java | 5 + ...endentGarbageCollectionTestReconciler.java | 102 ++++++++++++++++++ .../ConfigMapDependentResource1.java | 4 +- .../ConfigMapDependentResource2.java | 4 +- .../StandaloneDependentTestReconciler.java | 8 +- .../dependent/SecretDependentResource.java | 15 ++- .../sample/DeploymentDependentResource.java | 4 +- .../sample/ServiceDependentResource.java | 10 +- .../sample/ConfigMapDependentResource.java | 4 +- .../sample/DeploymentDependentResource.java | 7 +- .../sample/IngressDependentResource.java | 6 +- .../sample/ServiceDependentResource.java | 6 +- 23 files changed, 305 insertions(+), 73 deletions(-) create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/GarbageCollected.java delete mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/CRUKubernetesDependentResource.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/KubernetesDependentGarbageCollectionIT.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/kubernetesdependentgarbagecollection/DependentGarbageCollectionTestCustomResource.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/kubernetesdependentgarbagecollection/DependentGarbageCollectionTestCustomResourceSpec.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/kubernetesdependentgarbagecollection/DependentGarbageCollectionTestCustomResourceStatus.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/kubernetesdependentgarbagecollection/DependentGarbageCollectionTestReconciler.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/GarbageCollected.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/GarbageCollected.java new file mode 100644 index 0000000000..1316c44873 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/GarbageCollected.java @@ -0,0 +1,15 @@ +package io.javaoperatorsdk.operator.api.reconciler.dependent; + +import io.fabric8.kubernetes.api.model.HasMetadata; + +/** + * Should be implemented by {@link DependentResource} implementations that are explicitly deleted + * during reconciliation but which should also benefit from Kubernetes' automated garbage collection + * during the cleanup phase. + *

+ * See this issue + * for more details. + */ +public interface GarbageCollected

extends Deleter

{ + +} 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 50411ac4e8..f60dffcc33 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 @@ -23,6 +23,7 @@ import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceProvider; +import io.javaoperatorsdk.operator.api.reconciler.dependent.GarbageCollected; import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DependentResourceConfigurator; import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.KubernetesClientAware; import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.ManagedDependentResourceException; @@ -62,15 +63,16 @@ public Controller(Reconciler

reconciler, final var hasDeleterHolder = new boolean[] {false}; final var specs = configuration.getDependentResources(); - final var size = specs.size(); - if (size == 0) { + final var specsSize = specs.size(); + if (specsSize == 0) { dependents = new LinkedHashMap<>(); } else { - final Map dependentsHolder = new LinkedHashMap<>(size); + final Map dependentsHolder = new LinkedHashMap<>(specsSize); specs.forEach(drs -> { final var dependent = createAndConfigureFrom(drs, kubernetesClient); // check if dependent implements Deleter to record that fact - if (!hasDeleterHolder[0] && dependent instanceof Deleter) { + if (!hasDeleterHolder[0] && dependent instanceof Deleter + && !(dependent instanceof GarbageCollected)) { hasDeleterHolder[0] = true; } dependentsHolder.put(drs.getName(), dependent); @@ -131,7 +133,7 @@ public DeleteControl execute() { initContextIfNeeded(resource, context); if (hasDeleterDependents) { dependents.values().stream() - .filter(d -> d instanceof Deleter) + .filter(d -> d instanceof Deleter && !(d instanceof GarbageCollected)) .map(Deleter.class::cast) .forEach(deleter -> deleter.delete(resource, context)); } 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 c9c1d770c5..1c873dd68f 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 @@ -5,7 +5,6 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; import io.javaoperatorsdk.operator.processing.event.ResourceID; @@ -16,7 +15,7 @@ public abstract class AbstractDependentResource protected final boolean creatable = this instanceof Creator; protected final boolean updatable = this instanceof Updater; - protected final boolean deletable = this instanceof Deleter; + protected Creator creator; protected Updater updater; diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/CRUDKubernetesDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/CRUDKubernetesDependentResource.java index 05195e39b9..057aabc9e9 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/CRUDKubernetesDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/CRUDKubernetesDependentResource.java @@ -1,19 +1,22 @@ package io.javaoperatorsdk.operator.processing.dependent.kubernetes; import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; +import io.javaoperatorsdk.operator.api.reconciler.dependent.GarbageCollected; import io.javaoperatorsdk.operator.processing.dependent.Creator; import io.javaoperatorsdk.operator.processing.dependent.Updater; /** - * Adaptor Class for standalone mode for resources that manages Create, Read, Update and Delete + * Adaptor class for standalone mode for resources that manage Create, Read and Update operations + * and that should be automatically garbage-collected by Kubernetes when the associated primary + * resource is destroyed. * - * @param Managed resource - * @param

Primary Resource + * @param the type of the managed dependent resource + * @param

the type of the associated primary resource */ public abstract class CRUDKubernetesDependentResource extends - KubernetesDependentResource implements Creator, Updater, Deleter

{ + KubernetesDependentResource + implements Creator, Updater, GarbageCollected

{ public CRUDKubernetesDependentResource(Class resourceType) { super(resourceType); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/CRUKubernetesDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/CRUKubernetesDependentResource.java deleted file mode 100644 index 7670367936..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/CRUKubernetesDependentResource.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.javaoperatorsdk.operator.processing.dependent.kubernetes; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.processing.dependent.Creator; -import io.javaoperatorsdk.operator.processing.dependent.Updater; - -/** - * Adaptor Class for standalone mode for resources that manages Create, Read and Update - * - * @param Managed resource - * @param

Primary Resource - */ -public abstract class CRUKubernetesDependentResource - extends - KubernetesDependentResource implements Creator, Updater { - - - public CRUKubernetesDependentResource(Class resourceType) { - super(resourceType); - } -} 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 10429185d3..b156ce5cb5 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 @@ -15,6 +15,7 @@ import io.javaoperatorsdk.operator.api.reconciler.Constants; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; +import io.javaoperatorsdk.operator.api.reconciler.dependent.GarbageCollected; import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DependentResourceConfigurator; import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.KubernetesClientAware; import io.javaoperatorsdk.operator.processing.dependent.AbstractEventSourceHolderDependentResource; @@ -36,6 +37,7 @@ public abstract class KubernetesDependentResource matcher; private final ResourceUpdatePreProcessor processor; private final Class resourceType; + private final boolean garbageCollected = this instanceof GarbageCollected; private KubernetesDependentResourceConfig kubernetesDependentResourceConfig; @SuppressWarnings("unchecked") @@ -121,10 +123,8 @@ public Result match(R actualResource, P primary, Context

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

context) { - if (!addOwnerReference()) { - var resource = getSecondaryResource(primary); - resource.ifPresent(r -> client.resource(r).delete()); - } + var resource = getSecondaryResource(primary); + resource.ifPresent(r -> client.resource(r).delete()); } @SuppressWarnings("unchecked") @@ -158,7 +158,7 @@ protected InformerEventSource createEventSource(EventSourceContext

cont } protected boolean addOwnerReference() { - return creatable && !deletable; + return garbageCollected; } @Override diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/KubernetesDependentGarbageCollectionIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/KubernetesDependentGarbageCollectionIT.java new file mode 100644 index 0000000000..50fa4ceea6 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/KubernetesDependentGarbageCollectionIT.java @@ -0,0 +1,83 @@ +package io.javaoperatorsdk.operator; + +import java.time.Duration; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.junit.OperatorExtension; +import io.javaoperatorsdk.operator.sample.kubernetesdependentgarbagecollection.DependentGarbageCollectionTestCustomResource; +import io.javaoperatorsdk.operator.sample.kubernetesdependentgarbagecollection.DependentGarbageCollectionTestCustomResourceSpec; +import io.javaoperatorsdk.operator.sample.kubernetesdependentgarbagecollection.DependentGarbageCollectionTestReconciler; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +class KubernetesDependentGarbageCollectionIT { + + public static final String TEST_RESOURCE_NAME = "test1"; + @RegisterExtension + OperatorExtension operator = + OperatorExtension.builder() + .withReconciler(new DependentGarbageCollectionTestReconciler()) + .build(); + + + @Test + void resourceSecondaryResourceIsGarbageCollected() { + var resource = customResource(); + var createdResources = + operator.create(DependentGarbageCollectionTestCustomResource.class, resource); + + await().untilAsserted(() -> { + ConfigMap configMap = operator.get(ConfigMap.class, TEST_RESOURCE_NAME); + assertThat(configMap).isNotNull(); + }); + + ConfigMap configMap = operator.get(ConfigMap.class, TEST_RESOURCE_NAME); + assertThat(configMap.getMetadata().getOwnerReferences()).hasSize(1); + assertThat(configMap.getMetadata().getOwnerReferences().get(0).getName()) + .isEqualTo(TEST_RESOURCE_NAME); + + operator.delete(DependentGarbageCollectionTestCustomResource.class, createdResources); + + await().atMost(Duration.ofSeconds(30)).untilAsserted(() -> { + ConfigMap cm = operator.get(ConfigMap.class, TEST_RESOURCE_NAME); + assertThat(cm).isNull(); + }); + } + + @Test + void deletesSecondaryResource() { + var resource = customResource(); + var createdResources = + operator.create(DependentGarbageCollectionTestCustomResource.class, resource); + + await().untilAsserted(() -> { + ConfigMap configMap = operator.get(ConfigMap.class, TEST_RESOURCE_NAME); + assertThat(configMap).isNotNull(); + }); + + createdResources.getSpec().setCreateConfigMap(false); + operator.replace(DependentGarbageCollectionTestCustomResource.class, createdResources); + + await().untilAsserted(() -> { + ConfigMap cm = operator.get(ConfigMap.class, TEST_RESOURCE_NAME); + assertThat(cm).isNull(); + }); + } + + DependentGarbageCollectionTestCustomResource customResource() { + DependentGarbageCollectionTestCustomResource resource = + new DependentGarbageCollectionTestCustomResource(); + resource.setMetadata(new ObjectMetaBuilder() + .withName(TEST_RESOURCE_NAME) + .build()); + resource.setSpec(new DependentGarbageCollectionTestCustomResourceSpec()); + resource.getSpec().setCreateConfigMap(true); + return resource; + } + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/cleanermanageddependent/ConfigMapDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/cleanermanageddependent/ConfigMapDependentResource.java index ff1d7f2cc8..9941779784 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/cleanermanageddependent/ConfigMapDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/cleanermanageddependent/ConfigMapDependentResource.java @@ -6,10 +6,16 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; +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.dependent.kubernetes.KubernetesDependentResource; public class ConfigMapDependentResource extends - CRUDKubernetesDependentResource { + KubernetesDependentResource + implements Creator, + Updater, + Deleter { private static final AtomicInteger numberOfCleanupExecutions = new AtomicInteger(0); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentoperationeventfiltering/ConfigMapDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentoperationeventfiltering/ConfigMapDependentResource.java index 17dbe20ddf..34fbd21c50 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentoperationeventfiltering/ConfigMapDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentoperationeventfiltering/ConfigMapDependentResource.java @@ -5,10 +5,10 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; public class ConfigMapDependentResource extends - CRUKubernetesDependentResource { + CRUDKubernetesDependentResource { public static final String KEY = "key1"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/kubernetesdependentgarbagecollection/DependentGarbageCollectionTestCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/kubernetesdependentgarbagecollection/DependentGarbageCollectionTestCustomResource.java new file mode 100644 index 0000000000..5f1e5a0435 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/kubernetesdependentgarbagecollection/DependentGarbageCollectionTestCustomResource.java @@ -0,0 +1,16 @@ +package io.javaoperatorsdk.operator.sample.kubernetesdependentgarbagecollection; + +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("dgc") +public class DependentGarbageCollectionTestCustomResource + extends + CustomResource + implements Namespaced { +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/kubernetesdependentgarbagecollection/DependentGarbageCollectionTestCustomResourceSpec.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/kubernetesdependentgarbagecollection/DependentGarbageCollectionTestCustomResourceSpec.java new file mode 100644 index 0000000000..9c29ebbacc --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/kubernetesdependentgarbagecollection/DependentGarbageCollectionTestCustomResourceSpec.java @@ -0,0 +1,16 @@ +package io.javaoperatorsdk.operator.sample.kubernetesdependentgarbagecollection; + +public class DependentGarbageCollectionTestCustomResourceSpec { + + private boolean createConfigMap; + + public boolean isCreateConfigMap() { + return createConfigMap; + } + + public DependentGarbageCollectionTestCustomResourceSpec setCreateConfigMap( + boolean createConfigMap) { + this.createConfigMap = createConfigMap; + return this; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/kubernetesdependentgarbagecollection/DependentGarbageCollectionTestCustomResourceStatus.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/kubernetesdependentgarbagecollection/DependentGarbageCollectionTestCustomResourceStatus.java new file mode 100644 index 0000000000..79f67c017e --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/kubernetesdependentgarbagecollection/DependentGarbageCollectionTestCustomResourceStatus.java @@ -0,0 +1,5 @@ +package io.javaoperatorsdk.operator.sample.kubernetesdependentgarbagecollection; + +public class DependentGarbageCollectionTestCustomResourceStatus { + +} 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 new file mode 100644 index 0000000000..e9b947a83b --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/kubernetesdependentgarbagecollection/DependentGarbageCollectionTestReconciler.java @@ -0,0 +1,102 @@ +package io.javaoperatorsdk.operator.sample.kubernetesdependentgarbagecollection; + +import java.util.Map; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClientException; +import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.api.reconciler.dependent.GarbageCollected; +import io.javaoperatorsdk.operator.junit.KubernetesClientAware; +import io.javaoperatorsdk.operator.processing.dependent.Creator; +import io.javaoperatorsdk.operator.processing.dependent.Updater; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.event.source.EventSource; + +@ControllerConfiguration +public class DependentGarbageCollectionTestReconciler + implements Reconciler, + EventSourceInitializer, + KubernetesClientAware, ErrorStatusHandler { + + private KubernetesClient kubernetesClient; + private volatile boolean errorOccurred = false; + + ConfigMapDependentResource configMapDependent; + + public DependentGarbageCollectionTestReconciler() { + configMapDependent = new ConfigMapDependentResource(); + } + + @Override + public Map prepareEventSources( + EventSourceContext context) { + return EventSourceInitializer + .nameEventSources(configMapDependent.initEventSource(context)); + } + + @Override + public UpdateControl reconcile( + DependentGarbageCollectionTestCustomResource primary, + Context context) { + + if (primary.getSpec().isCreateConfigMap()) { + configMapDependent.reconcile(primary, context); + } else { + configMapDependent.delete(primary, context); + } + + return UpdateControl.noUpdate(); + } + + @Override + public void setKubernetesClient(KubernetesClient kubernetesClient) { + this.kubernetesClient = kubernetesClient; + configMapDependent.setKubernetesClient(kubernetesClient); + } + + @Override + public KubernetesClient getKubernetesClient() { + return this.kubernetesClient; + } + + @Override + public ErrorStatusUpdateControl updateErrorStatus( + DependentGarbageCollectionTestCustomResource resource, + Context context, Exception e) { + // this can happen when a namespace is terminated in test + if (e instanceof KubernetesClientException) { + return ErrorStatusUpdateControl.noStatusUpdate(); + } + errorOccurred = true; + return ErrorStatusUpdateControl.noStatusUpdate(); + } + + public boolean isErrorOccurred() { + return errorOccurred; + } + + private static class ConfigMapDependentResource extends + KubernetesDependentResource + implements Creator, + Updater, + GarbageCollected { + + public ConfigMapDependentResource() { + super(ConfigMap.class); + } + + @Override + protected ConfigMap desired(DependentGarbageCollectionTestCustomResource primary, + Context context) { + ConfigMap configMap = new ConfigMap(); + configMap.setMetadata(new ObjectMetaBuilder() + .withName(primary.getMetadata().getName()) + .withNamespace(primary.getMetadata().getNamespace()) + .build()); + configMap.setData(Map.of("key", "data")); + return configMap; + } + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/ConfigMapDependentResource1.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/ConfigMapDependentResource1.java index 19fd28b631..14530cf17e 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/ConfigMapDependentResource1.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/ConfigMapDependentResource1.java @@ -7,12 +7,12 @@ import io.fabric8.kubernetes.api.model.ObjectMeta; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; -import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; @KubernetesDependent(labelSelector = "dependent = cm1") public class ConfigMapDependentResource1 extends - CRUKubernetesDependentResource { + CRUDKubernetesDependentResource { public ConfigMapDependentResource1() { super(ConfigMap.class); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/ConfigMapDependentResource2.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/ConfigMapDependentResource2.java index 2bffdfa8c1..35ae69586e 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/ConfigMapDependentResource2.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/ConfigMapDependentResource2.java @@ -7,12 +7,12 @@ import io.fabric8.kubernetes.api.model.ObjectMeta; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; -import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; @KubernetesDependent(labelSelector = "dependent = cm2") public class ConfigMapDependentResource2 extends - CRUKubernetesDependentResource { + CRUDKubernetesDependentResource { public ConfigMapDependentResource2() { super(ConfigMap.class); 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 2ecaa5cc27..4853a22e9a 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 @@ -16,9 +16,7 @@ import io.javaoperatorsdk.operator.api.reconciler.Reconciler; import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; import io.javaoperatorsdk.operator.junit.KubernetesClientAware; -import io.javaoperatorsdk.operator.processing.dependent.Creator; -import io.javaoperatorsdk.operator.processing.dependent.Updater; -import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; import io.javaoperatorsdk.operator.processing.event.source.EventSource; @ControllerConfiguration @@ -88,9 +86,7 @@ public boolean isErrorOccurred() { } private static class DeploymentDependentResource extends - KubernetesDependentResource - implements Creator, - Updater { + CRUDKubernetesDependentResource { public DeploymentDependentResource() { super(Deployment.class); diff --git a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/dependent/SecretDependentResource.java b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/dependent/SecretDependentResource.java index b1c516df8e..043b50a6cc 100644 --- a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/dependent/SecretDependentResource.java +++ b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/dependent/SecretDependentResource.java @@ -1,6 +1,7 @@ package io.javaoperatorsdk.operator.sample.dependent; import java.util.Base64; +import java.util.Set; import org.apache.commons.lang3.RandomStringUtils; @@ -10,12 +11,15 @@ import io.javaoperatorsdk.operator.processing.dependent.Creator; import io.javaoperatorsdk.operator.processing.dependent.Matcher.Result; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.processing.event.source.SecondaryToPrimaryMapper; import io.javaoperatorsdk.operator.sample.MySQLSchema; public class SecretDependentResource extends KubernetesDependentResource - implements Creator { + implements Creator, SecondaryToPrimaryMapper { - public static final String SECRET_FORMAT = "%s-secret"; + public static final String SECRET_SUFFIX = "-secret"; + public static final String SECRET_FORMAT = "%s" + SECRET_SUFFIX; public static final String USERNAME_FORMAT = "%s-user"; public static final String MYSQL_SECRET_USERNAME = "mysql.secret.user.name"; public static final String MYSQL_SECRET_PASSWORD = "mysql.secret.user.password"; @@ -55,4 +59,11 @@ public Result match(Secret actual, MySQLSchema primary, Context toPrimaryResourceIDs(Secret dependentResource) { + String name = dependentResource.getMetadata().getName(); + return Set.of(new ResourceID(name.substring(0, name.length() - SECRET_SUFFIX.length()), + dependentResource.getMetadata().getNamespace())); + } } diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java index 94726d40ae..25e46fad16 100644 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java @@ -5,12 +5,12 @@ import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder; import io.javaoperatorsdk.operator.ReconcilerUtils; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; @KubernetesDependent(labelSelector = "app.kubernetes.io/managed-by=tomcat-operator") public class DeploymentDependentResource - extends CRUKubernetesDependentResource { + extends CRUDKubernetesDependentResource { public DeploymentDependentResource() { super(Deployment.class); diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java index 8efaadc0a8..3b526d02bc 100644 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java @@ -5,12 +5,11 @@ import io.fabric8.kubernetes.api.model.ServiceBuilder; import io.javaoperatorsdk.operator.ReconcilerUtils; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.processing.dependent.Creator; -import io.javaoperatorsdk.operator.processing.dependent.Updater; -import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; -public class ServiceDependentResource extends KubernetesDependentResource - implements Creator, Updater { +@KubernetesDependent(labelSelector = "app.kubernetes.io/managed-by=tomcat-operator") +public class ServiceDependentResource extends CRUDKubernetesDependentResource { public ServiceDependentResource() { super(Service.class); @@ -23,6 +22,7 @@ protected Service desired(Tomcat tomcat, Context context) { .editMetadata() .withName(tomcatMetadata.getName()) .withNamespace(tomcatMetadata.getNamespace()) + .addToLabels("app.kubernetes.io/managed-by", "tomcat-operator") .endMetadata() .editSpec() .addToSelector("app", tomcatMetadata.getName()) diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ConfigMapDependentResource.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ConfigMapDependentResource.java index cfe0f79a0e..2fc9c35fe4 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ConfigMapDependentResource.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ConfigMapDependentResource.java @@ -10,7 +10,7 @@ import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; import static io.javaoperatorsdk.operator.sample.Utils.configMapName; @@ -19,7 +19,7 @@ // this annotation only activates when using managed dependents and is not otherwise needed @KubernetesDependent(labelSelector = SELECTOR) -public class ConfigMapDependentResource extends CRUKubernetesDependentResource { +class ConfigMapDependentResource extends CRUDKubernetesDependentResource { private static final Logger log = LoggerFactory.getLogger(ConfigMapDependentResource.class); diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java index 4991171f12..213be43428 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java @@ -6,7 +6,7 @@ import io.fabric8.kubernetes.api.model.ConfigMapVolumeSourceBuilder; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; import static io.javaoperatorsdk.operator.ReconcilerUtils.loadYaml; @@ -15,9 +15,8 @@ import static io.javaoperatorsdk.operator.sample.WebPageManagedDependentsReconciler.SELECTOR; // this annotation only activates when using managed dependents and is not otherwise needed -@KubernetesDependent(labelSelector = WebPageManagedDependentsReconciler.SELECTOR) -public class DeploymentDependentResource - extends CRUKubernetesDependentResource { +@KubernetesDependent(labelSelector = SELECTOR) +class DeploymentDependentResource extends CRUDKubernetesDependentResource { public DeploymentDependentResource() { super(Deployment.class); diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/IngressDependentResource.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/IngressDependentResource.java index 074f36cffb..703d3aceb1 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/IngressDependentResource.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/IngressDependentResource.java @@ -2,14 +2,14 @@ import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; -import static io.javaoperatorsdk.operator.sample.Utils.*; +import static io.javaoperatorsdk.operator.sample.Utils.makeDesiredIngress; // this annotation only activates when using managed dependents and is not otherwise needed @KubernetesDependent(labelSelector = WebPageManagedDependentsReconciler.SELECTOR) -public class IngressDependentResource extends CRUKubernetesDependentResource { +public class IngressDependentResource extends CRUDKubernetesDependentResource { public IngressDependentResource() { super(Ingress.class); diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java index a914aa5994..1b89aa5333 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java @@ -5,7 +5,6 @@ import io.fabric8.kubernetes.api.model.Service; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUKubernetesDependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; import static io.javaoperatorsdk.operator.ReconcilerUtils.loadYaml; @@ -14,8 +13,9 @@ import static io.javaoperatorsdk.operator.sample.WebPageManagedDependentsReconciler.SELECTOR; // this annotation only activates when using managed dependents and is not otherwise needed -@KubernetesDependent(labelSelector = WebPageManagedDependentsReconciler.SELECTOR) -public class ServiceDependentResource extends CRUKubernetesDependentResource { +@KubernetesDependent(labelSelector = SELECTOR) +class ServiceDependentResource extends + io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource { public ServiceDependentResource() { super(Service.class); From 5b8a9258f25c7b238af6dd35d821b506f6c705d3 Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 9 May 2022 10:23:22 +0200 Subject: [PATCH 02/51] fix: build --- .../operator/KubernetesDependentGarbageCollectionIT.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 50fa4ceea6..d47bc78cd7 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/KubernetesDependentGarbageCollectionIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/KubernetesDependentGarbageCollectionIT.java @@ -7,7 +7,7 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; -import io.javaoperatorsdk.operator.junit.OperatorExtension; +import io.javaoperatorsdk.operator.junit.LocalOperatorExtension; import io.javaoperatorsdk.operator.sample.kubernetesdependentgarbagecollection.DependentGarbageCollectionTestCustomResource; import io.javaoperatorsdk.operator.sample.kubernetesdependentgarbagecollection.DependentGarbageCollectionTestCustomResourceSpec; import io.javaoperatorsdk.operator.sample.kubernetesdependentgarbagecollection.DependentGarbageCollectionTestReconciler; @@ -19,8 +19,8 @@ class KubernetesDependentGarbageCollectionIT { public static final String TEST_RESOURCE_NAME = "test1"; @RegisterExtension - OperatorExtension operator = - OperatorExtension.builder() + LocalOperatorExtension operator = + LocalOperatorExtension.builder() .withReconciler(new DependentGarbageCollectionTestReconciler()) .build(); From dc5063071c79104f625a3cde081ed41c7faf1e2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Wed, 11 May 2022 13:35:07 +0200 Subject: [PATCH 03/51] Using Annotations to Identify primary for a secondary object if no owner reference can be added (#1197) --- .../KubernetesDependentResource.java | 41 ++++++++++-- .../KubernetesDependentResourceConfig.java | 3 +- .../event/source/informer/Mappers.java | 8 +++ .../DependentAnnotationSecondaryMapperIT.java | 66 +++++++++++++++++++ ...ntAnnotationSecondaryMapperReconciler.java | 58 ++++++++++++++++ ...dentAnnotationSecondaryMapperResource.java | 17 +++++ ...notationSecondaryMapperResourceStatus.java | 5 ++ 7 files changed, 193 insertions(+), 5 deletions(-) create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/DependentAnnotationSecondaryMapperIT.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentannotationsecondarymapper/DependentAnnotationSecondaryMapperReconciler.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentannotationsecondarymapper/DependentAnnotationSecondaryMapperResource.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentannotationsecondarymapper/DependentAnnotationSecondaryMapperResourceStatus.java 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 b156ce5cb5..2f8a0ab3f7 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java @@ -1,5 +1,6 @@ package io.javaoperatorsdk.operator.processing.dependent.kubernetes; +import java.util.HashMap; import java.util.Optional; import java.util.Set; @@ -11,6 +12,7 @@ import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; import io.fabric8.kubernetes.client.dsl.Resource; +import io.javaoperatorsdk.operator.OperatorException; import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; import io.javaoperatorsdk.operator.api.reconciler.Constants; import io.javaoperatorsdk.operator.api.reconciler.Context; @@ -64,18 +66,29 @@ private void configureWith(String labelSelector, Set namespaces, namespaces = context.getControllerConfiguration().getNamespaces(); } - final SecondaryToPrimaryMapper primaryResourcesRetriever = - (this instanceof SecondaryToPrimaryMapper) ? (SecondaryToPrimaryMapper) this - : Mappers.fromOwnerReference(); var ic = InformerConfiguration.from(resourceType()) .withLabelSelector(labelSelector) - .withSecondaryToPrimaryMapper(primaryResourcesRetriever) + .withSecondaryToPrimaryMapper(getSecondaryToPrimaryMapper()) .withNamespaces(namespaces, inheritNamespacesOnChange) .build(); configureWith(new InformerEventSource<>(ic, client)); } + @SuppressWarnings("unchecked") + private SecondaryToPrimaryMapper getSecondaryToPrimaryMapper() { + if (this instanceof SecondaryToPrimaryMapper) { + return (SecondaryToPrimaryMapper) this; + } else if (garbageCollected) { + return Mappers.fromOwnerReference(); + } else if (useDefaultAnnotationsToIdentifyPrimary()) { + return Mappers.fromDefaultAnnotations(); + } else { + throw new OperatorException("Provide a SecondaryToPrimaryMapper to associate " + + "this resource with the primary resource. DependentResource: " + getClass().getName()); + } + } + /** * Use to share informers between event more resources. * @@ -136,6 +149,8 @@ protected NonNamespaceOperation, Resource> prepa ResourceID.fromResource(desired)); if (addOwnerReference()) { desired.addOwnerReference(primary); + } else if (useDefaultAnnotationsToIdentifyPrimary()) { + addDefaultSecondaryToPrimaryMapperAnnotations(desired, primary); } Class targetClass = (Class) desired.getClass(); return client.resources(targetClass).inNamespace(desired.getMetadata().getNamespace()); @@ -157,6 +172,24 @@ protected InformerEventSource createEventSource(EventSourceContext

cont return eventSource(); } + private boolean useDefaultAnnotationsToIdentifyPrimary() { + return !(this instanceof SecondaryToPrimaryMapper) && !garbageCollected && creatable; + } + + private void addDefaultSecondaryToPrimaryMapperAnnotations(R desired, P primary) { + var annotations = desired.getMetadata().getAnnotations(); + if (annotations == null) { + annotations = new HashMap<>(); + desired.getMetadata().setAnnotations(annotations); + } + annotations.put(Mappers.DEFAULT_ANNOTATION_FOR_NAME, primary.getMetadata().getName()); + var primaryNamespaces = primary.getMetadata().getNamespace(); + if (primaryNamespaces != null) { + annotations.put( + Mappers.DEFAULT_ANNOTATION_FOR_NAMESPACE, primary.getMetadata().getNamespace()); + } + } + protected boolean addOwnerReference() { return garbageCollected; } 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 8cc8e12164..a28668055a 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 @@ -19,7 +19,7 @@ public KubernetesDependentResourceConfig(Set namespaces, String labelSel boolean configuredNS) { this.namespaces = namespaces; this.labelSelector = labelSelector; - namespacesWereConfigured = configuredNS; + this.namespacesWereConfigured = configuredNS; } public KubernetesDependentResourceConfig(Set namespaces, String labelSelector) { @@ -48,4 +48,5 @@ public String labelSelector() { public boolean wereNamespacesConfigured() { return namespacesWereConfigured; } + } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/Mappers.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/Mappers.java index 1c0150b084..a37f404f94 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/Mappers.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/Mappers.java @@ -9,6 +9,10 @@ public class Mappers { + public static final String DEFAULT_ANNOTATION_FOR_NAME = "io.javaoperatorsdk/primary-name"; + public static final String DEFAULT_ANNOTATION_FOR_NAMESPACE = + "io.javaoperatorsdk/primary-namespace"; + private Mappers() {} public static SecondaryToPrimaryMapper fromAnnotation( @@ -26,6 +30,10 @@ public static SecondaryToPrimaryMapper fromLabel( return fromMetadata(nameKey, null, true); } + public static SecondaryToPrimaryMapper fromDefaultAnnotations() { + return fromMetadata(DEFAULT_ANNOTATION_FOR_NAME, DEFAULT_ANNOTATION_FOR_NAMESPACE, false); + } + public static SecondaryToPrimaryMapper fromLabel( String nameKey, String namespaceKey) { return fromMetadata(nameKey, namespaceKey, true); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/DependentAnnotationSecondaryMapperIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/DependentAnnotationSecondaryMapperIT.java new file mode 100644 index 0000000000..24521d5c53 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/DependentAnnotationSecondaryMapperIT.java @@ -0,0 +1,66 @@ +package io.javaoperatorsdk.operator; + +import java.time.Duration; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.junit.LocalOperatorExtension; +import io.javaoperatorsdk.operator.sample.dependentannotationsecondarymapper.DependentAnnotationSecondaryMapperReconciler; +import io.javaoperatorsdk.operator.sample.dependentannotationsecondarymapper.DependentAnnotationSecondaryMapperResource; + +import static io.javaoperatorsdk.operator.processing.event.source.informer.Mappers.DEFAULT_ANNOTATION_FOR_NAME; +import static io.javaoperatorsdk.operator.processing.event.source.informer.Mappers.DEFAULT_ANNOTATION_FOR_NAMESPACE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +class DependentAnnotationSecondaryMapperIT { + + public static final String TEST_RESOURCE_NAME = "test1"; + + @RegisterExtension + LocalOperatorExtension operator = + LocalOperatorExtension.builder() + .withReconciler(DependentAnnotationSecondaryMapperReconciler.class) + .build(); + + @Test + void mapsSecondaryByAnnotation() { + operator.create(DependentAnnotationSecondaryMapperResource.class, testResource()); + + var reconciler = + operator.getReconcilerOfType(DependentAnnotationSecondaryMapperReconciler.class); + + await().pollDelay(Duration.ofMillis(150)).untilAsserted(() -> { + assertThat(reconciler.getNumberOfExecutions()).isEqualTo(1); + }); + var configMap = operator.get(ConfigMap.class, TEST_RESOURCE_NAME); + + var annotations = configMap.getMetadata().getAnnotations(); + + assertThat(annotations) + .containsEntry(DEFAULT_ANNOTATION_FOR_NAME, TEST_RESOURCE_NAME) + .containsEntry(DEFAULT_ANNOTATION_FOR_NAMESPACE, operator.getNamespace()); + + assertThat(configMap.getMetadata().getOwnerReferences()).isEmpty(); + + configMap.getData().put("additional_data", "data"); + operator.replace(ConfigMap.class, configMap); + + await().pollDelay(Duration.ofMillis(150)).untilAsserted(() -> { + assertThat(reconciler.getNumberOfExecutions()).isEqualTo(2); + }); + } + + + DependentAnnotationSecondaryMapperResource testResource() { + var res = new DependentAnnotationSecondaryMapperResource(); + res.setMetadata(new ObjectMetaBuilder() + .withName(TEST_RESOURCE_NAME) + .build()); + return res; + } + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentannotationsecondarymapper/DependentAnnotationSecondaryMapperReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentannotationsecondarymapper/DependentAnnotationSecondaryMapperReconciler.java new file mode 100644 index 0000000000..2608b8373c --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentannotationsecondarymapper/DependentAnnotationSecondaryMapperReconciler.java @@ -0,0 +1,58 @@ +package io.javaoperatorsdk.operator.sample.dependentannotationsecondarymapper; + +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; +import io.javaoperatorsdk.operator.processing.dependent.Creator; +import io.javaoperatorsdk.operator.processing.dependent.Updater; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; +import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; + +@ControllerConfiguration(dependents = {@Dependent( + type = DependentAnnotationSecondaryMapperReconciler.ConfigMapDependentResource.class)}) +public class DependentAnnotationSecondaryMapperReconciler + implements Reconciler, TestExecutionInfoProvider { + + private final AtomicInteger numberOfExecutions = new AtomicInteger(0); + + @Override + public UpdateControl reconcile( + DependentAnnotationSecondaryMapperResource resource, + Context context) { + numberOfExecutions.addAndGet(1); + return UpdateControl.noUpdate(); + } + + public int getNumberOfExecutions() { + return numberOfExecutions.get(); + } + + public static class ConfigMapDependentResource extends + KubernetesDependentResource + implements Creator, + Updater, + Deleter { + + public ConfigMapDependentResource() { + super(ConfigMap.class); + } + + @Override + protected ConfigMap desired(DependentAnnotationSecondaryMapperResource primary, + Context context) { + ConfigMap configMap = new ConfigMap(); + configMap.setMetadata(new ObjectMetaBuilder() + .withName(primary.getMetadata().getName()) + .withNamespace(primary.getMetadata().getNamespace()) + .build()); + configMap.setData(Map.of("data", primary.getMetadata().getName())); + return configMap; + } + } + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentannotationsecondarymapper/DependentAnnotationSecondaryMapperResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentannotationsecondarymapper/DependentAnnotationSecondaryMapperResource.java new file mode 100644 index 0000000000..22ff6256ae --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentannotationsecondarymapper/DependentAnnotationSecondaryMapperResource.java @@ -0,0 +1,17 @@ +package io.javaoperatorsdk.operator.sample.dependentannotationsecondarymapper; + +import io.fabric8.kubernetes.api.model.Namespaced; +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Kind; +import io.fabric8.kubernetes.model.annotation.ShortNames; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("sample.javaoperatorsdk") +@Version("v1") +@Kind("MaxIntervalTestCustomResource") +@ShortNames("mit") +public class DependentAnnotationSecondaryMapperResource + extends CustomResource + implements Namespaced { +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentannotationsecondarymapper/DependentAnnotationSecondaryMapperResourceStatus.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentannotationsecondarymapper/DependentAnnotationSecondaryMapperResourceStatus.java new file mode 100644 index 0000000000..33ea00e819 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentannotationsecondarymapper/DependentAnnotationSecondaryMapperResourceStatus.java @@ -0,0 +1,5 @@ +package io.javaoperatorsdk.operator.sample.dependentannotationsecondarymapper; + +public class DependentAnnotationSecondaryMapperResourceStatus { + +} From 889baa9ab375fa47508b2fa4d9e6189b66e6a384 Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 16 May 2022 11:43:39 +0200 Subject: [PATCH 04/51] fix: build --- .../DependentResourceCrossRefReconciler.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentresourcecrossref/DependentResourceCrossRefReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentresourcecrossref/DependentResourceCrossRefReconciler.java index 96e3029548..5def2381be 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentresourcecrossref/DependentResourceCrossRefReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentresourcecrossref/DependentResourceCrossRefReconciler.java @@ -10,7 +10,7 @@ import io.fabric8.kubernetes.api.model.Secret; import io.javaoperatorsdk.operator.api.reconciler.*; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; -import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; @ControllerConfiguration(dependents = { @Dependent(type = DependentResourceCrossRefReconciler.SecretDependentResource.class), @@ -47,7 +47,7 @@ public boolean isErrorHappened() { } public static class SecretDependentResource extends - CRUKubernetesDependentResource { + CRUDKubernetesDependentResource { public SecretDependentResource() { super(Secret.class); @@ -67,9 +67,8 @@ protected Secret desired(DependentResourceCrossRefResource primary, } public static class ConfigMapDependentResource extends - CRUKubernetesDependentResource { - - + CRUDKubernetesDependentResource { + public ConfigMapDependentResource() { super(ConfigMap.class); } From 8a72f350a11e7fc5876847907b773321d738aa8f Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 16 May 2022 11:48:23 +0200 Subject: [PATCH 05/51] fix: format --- .../DependentResourceCrossRefReconciler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentresourcecrossref/DependentResourceCrossRefReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentresourcecrossref/DependentResourceCrossRefReconciler.java index 5def2381be..d6eedc26dc 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentresourcecrossref/DependentResourceCrossRefReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentresourcecrossref/DependentResourceCrossRefReconciler.java @@ -68,7 +68,7 @@ protected Secret desired(DependentResourceCrossRefResource primary, public static class ConfigMapDependentResource extends CRUDKubernetesDependentResource { - + public ConfigMapDependentResource() { super(ConfigMap.class); } From 8021568d6f6f9292fed6067f4da8e4c13b3c4464 Mon Sep 17 00:00:00 2001 From: csviri Date: Thu, 19 May 2022 10:01:34 +0200 Subject: [PATCH 06/51] fix: e2e test issue --- .../operator/sample/ConfigMapDependentResource.java | 2 +- .../operator/sample/DeploymentDependentResource.java | 2 +- .../operator/sample/ServiceDependentResource.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ConfigMapDependentResource.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ConfigMapDependentResource.java index 2fc9c35fe4..1c076a5ca7 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ConfigMapDependentResource.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ConfigMapDependentResource.java @@ -19,7 +19,7 @@ // this annotation only activates when using managed dependents and is not otherwise needed @KubernetesDependent(labelSelector = SELECTOR) -class ConfigMapDependentResource extends CRUDKubernetesDependentResource { +public class ConfigMapDependentResource extends CRUDKubernetesDependentResource { private static final Logger log = LoggerFactory.getLogger(ConfigMapDependentResource.class); diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java index 213be43428..13752417cf 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java @@ -16,7 +16,7 @@ // this annotation only activates when using managed dependents and is not otherwise needed @KubernetesDependent(labelSelector = SELECTOR) -class DeploymentDependentResource extends CRUDKubernetesDependentResource { +public class DeploymentDependentResource extends CRUDKubernetesDependentResource { public DeploymentDependentResource() { super(Deployment.class); diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java index 1b89aa5333..1080b1b461 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java @@ -14,7 +14,7 @@ // this annotation only activates when using managed dependents and is not otherwise needed @KubernetesDependent(labelSelector = SELECTOR) -class ServiceDependentResource extends +public class ServiceDependentResource extends io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource { public ServiceDependentResource() { From 1183f3831b50d5519511bbc208077a0a1a0b58f2 Mon Sep 17 00:00:00 2001 From: csviri Date: Thu, 19 May 2022 10:05:15 +0200 Subject: [PATCH 07/51] fix: format --- .../operator/sample/ConfigMapDependentResource.java | 3 ++- .../operator/sample/DeploymentDependentResource.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ConfigMapDependentResource.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ConfigMapDependentResource.java index 1c076a5ca7..cf997f86d8 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ConfigMapDependentResource.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ConfigMapDependentResource.java @@ -19,7 +19,8 @@ // this annotation only activates when using managed dependents and is not otherwise needed @KubernetesDependent(labelSelector = SELECTOR) -public class ConfigMapDependentResource extends CRUDKubernetesDependentResource { +public class ConfigMapDependentResource + extends CRUDKubernetesDependentResource { private static final Logger log = LoggerFactory.getLogger(ConfigMapDependentResource.class); diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java index 13752417cf..8986660bdf 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java @@ -16,7 +16,8 @@ // this annotation only activates when using managed dependents and is not otherwise needed @KubernetesDependent(labelSelector = SELECTOR) -public class DeploymentDependentResource extends CRUDKubernetesDependentResource { +public class DeploymentDependentResource + extends CRUDKubernetesDependentResource { public DeploymentDependentResource() { super(Deployment.class); From 0fc449364c03b6786c32fc1ff9bdd45b33af06bd Mon Sep 17 00:00:00 2001 From: csviri Date: Wed, 6 Apr 2022 17:50:17 +0200 Subject: [PATCH 08/51] feat: workflow basic classes --- .../processing/dependent/workflow/Workflow.java | 4 ++++ .../dependent/workflow/WorkflowExecutionContext.java | 11 +++++++++++ .../workflow/condition/CleanupCondition.java | 4 ++++ .../dependent/workflow/condition/Condition.java | 4 ++++ .../dependent/workflow/condition/WaitCondition.java | 4 ++++ 5 files changed, 27 insertions(+) create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutionContext.java create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/CleanupCondition.java create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/Condition.java create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/WaitCondition.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java new file mode 100644 index 0000000000..ca6d5d115f --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java @@ -0,0 +1,4 @@ +package io.javaoperatorsdk.operator.processing.dependent.workflow; + +public class Workflow { +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutionContext.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutionContext.java new file mode 100644 index 0000000000..95f23aaa4c --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutionContext.java @@ -0,0 +1,11 @@ +package io.javaoperatorsdk.operator.processing.dependent.workflow; + +import java.util.List; + +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; + +public interface WorkflowExecutionContext { + + List getDependentResources(); + +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/CleanupCondition.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/CleanupCondition.java new file mode 100644 index 0000000000..cb506c1187 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/CleanupCondition.java @@ -0,0 +1,4 @@ +package io.javaoperatorsdk.operator.processing.dependent.workflow.condition; + +public class CleanupCondition { +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/Condition.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/Condition.java new file mode 100644 index 0000000000..8bf47907f6 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/Condition.java @@ -0,0 +1,4 @@ +package io.javaoperatorsdk.operator.processing.dependent.workflow.condition; + +public class Condition { +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/WaitCondition.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/WaitCondition.java new file mode 100644 index 0000000000..54206ae22f --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/WaitCondition.java @@ -0,0 +1,4 @@ +package io.javaoperatorsdk.operator.processing.dependent.workflow.condition; + +public class WaitCondition { +} From 4b2b8cb881987a825ef845b0d6f7b6230cde6e6d Mon Sep 17 00:00:00 2001 From: csviri Date: Sun, 10 Apr 2022 10:51:48 +0200 Subject: [PATCH 09/51] wip --- .../workflow/DependentResourceNode.java | 51 +++++++++++++ .../dependent/workflow/DependsOnRelation.java | 44 ++++++++++++ .../dependent/workflow/Workflow.java | 71 ++++++++++++++++++- .../dependent/workflow/WorkflowExecutor.java | 20 ++++++ ...Condition.java => ReconcileCondition.java} | 2 +- 5 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependsOnRelation.java create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutor.java rename operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/{Condition.java => ReconcileCondition.java} (70%) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java new file mode 100644 index 0000000000..6cf7545cb5 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java @@ -0,0 +1,51 @@ +package io.javaoperatorsdk.operator.processing.dependent.workflow; + +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.CleanupCondition; +import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReconcileCondition; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class DependentResourceNode { + + private final DependentResource dependentResource; + private final ReconcileCondition reconcileCondition; + private final CleanupCondition cleanupCondition; + private List dependsOnRelations = new ArrayList<>(1); + + public DependentResourceNode(DependentResource dependentResource) { + this(dependentResource,null,null); + } + + public DependentResourceNode(DependentResource dependentResource, ReconcileCondition reconcileCondition) { + this(dependentResource, reconcileCondition,null); + } + + public DependentResourceNode(DependentResource dependentResource, ReconcileCondition reconcileCondition, CleanupCondition cleanupCondition) { + this.dependentResource = dependentResource; + this.reconcileCondition = reconcileCondition; + this.cleanupCondition = cleanupCondition; + } + + public DependentResource getDependentResource() { + return dependentResource; + } + + public Optional getReconcileCondition() { + return Optional.ofNullable(reconcileCondition); + } + + public Optional getCleanupCondition() { + return Optional.ofNullable(cleanupCondition); + } + + public void setDependsOnRelations(List dependsOnRelations) { + this.dependsOnRelations = dependsOnRelations; + } + + public List getDependsOnRelations() { + return dependsOnRelations; + } +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependsOnRelation.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependsOnRelation.java new file mode 100644 index 0000000000..537a5bfd8d --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependsOnRelation.java @@ -0,0 +1,44 @@ +package io.javaoperatorsdk.operator.processing.dependent.workflow; + +import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.WaitCondition; + +public class DependsOnRelation { + + private DependentResourceNode owner; + private DependentResourceNode dependsOn; + private WaitCondition waitCondition; + + public DependsOnRelation() { + } + + public DependsOnRelation(DependentResourceNode owner, DependentResourceNode dependsOn, WaitCondition waitCondition) { + this.owner = owner; + this.dependsOn = dependsOn; + this.waitCondition = waitCondition; + } + + + public void setOwner(DependentResourceNode owner) { + this.owner = owner; + } + + public void setDependsOn(DependentResourceNode dependsOn) { + this.dependsOn = dependsOn; + } + + public void setWaitCondition(WaitCondition waitCondition) { + this.waitCondition = waitCondition; + } + + public DependentResourceNode getOwner() { + return owner; + } + + public DependentResourceNode getDependsOn() { + return dependsOn; + } + + public WaitCondition getWaitCondition() { + return waitCondition; + } +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java index ca6d5d115f..0e0d7ccda9 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java @@ -1,4 +1,73 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow; -public class Workflow { +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.reconciler.Context; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class Workflow

{ + + private final List dependentResourceNodes; + + private List topLevelResources = new ArrayList<>(); + private Map> reverseDependsOn; + + // it's "global" executor service shared between multiple reconciliations running parallel + private ExecutorService executorService; + + public Workflow(List dependentResourceNodes, ExecutorService executorService) { + this.executorService = executorService; + this.dependentResourceNodes = dependentResourceNodes; + preprocessForReconcile(); + } + + public Workflow(List dependentResourceNodes, int globalParallelism) { + this(dependentResourceNodes,Executors.newFixedThreadPool(globalParallelism)); + } + + public void reconcile(P primary, Context

context) { + + + } + + public void cleanup(P resource, Context

context) { + + } + + private void preprocessForReconcile() { + reverseDependsOn = new ConcurrentHashMap<>(dependentResourceNodes.size()); + + for (DependentResourceNode node : dependentResourceNodes) { + if (node.getDependsOnRelations().isEmpty()) { + topLevelResources.add(node); + } else { + for (DependsOnRelation relation : node.getDependsOnRelations()) { + reverseDependsOn.computeIfAbsent(relation.getDependsOn(), dr -> new ArrayList<>()); + reverseDependsOn.get(relation.getDependsOn()).add(relation.getOwner()); + } + } + } + } + + public void setExecutorService(ExecutorService executorService) { + this.executorService = executorService; + } + + List getTopLevelResources() { + return topLevelResources; + } + + Map> getReverseDependsOn() { + return reverseDependsOn; + } + + ExecutorService getExecutorService() { + return executorService; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutor.java new file mode 100644 index 0000000000..98ac47a7d3 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutor.java @@ -0,0 +1,20 @@ +package io.javaoperatorsdk.operator.processing.dependent.workflow; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.reconciler.Context; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public class WorkflowExecutor

{ + + private Set alreadyReconciled = ConcurrentHashMap.newKeySet(); + private Workflow

workflow; + + + + public void reconcile(P primary, Context

context) { + + } + +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/Condition.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReconcileCondition.java similarity index 70% rename from operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/Condition.java rename to operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReconcileCondition.java index 8bf47907f6..23235a8977 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/Condition.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReconcileCondition.java @@ -1,4 +1,4 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow.condition; -public class Condition { +public class ReconcileCondition { } From 408b501ad1c4edff2a132dccb9d83b8be893248e Mon Sep 17 00:00:00 2001 From: csviri Date: Sun, 10 Apr 2022 12:49:09 +0200 Subject: [PATCH 10/51] wip --- .../dependent/workflow/Workflow.java | 1 - .../dependent/workflow/WorkflowExecutor.java | 20 ------- .../workflow/WorkflowReconcileExecutor.java | 53 +++++++++++++++++++ 3 files changed, 53 insertions(+), 21 deletions(-) delete mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutor.java create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java index 0e0d7ccda9..4ca635c366 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java @@ -33,7 +33,6 @@ public Workflow(List dependentResourceNodes, int globalPa public void reconcile(P primary, Context

context) { - } public void cleanup(P resource, Context

context) { diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutor.java deleted file mode 100644 index 98ac47a7d3..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutor.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.javaoperatorsdk.operator.processing.dependent.workflow; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.api.reconciler.Context; - -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -public class WorkflowExecutor

{ - - private Set alreadyReconciled = ConcurrentHashMap.newKeySet(); - private Workflow

workflow; - - - - public void reconcile(P primary, Context

context) { - - } - -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java new file mode 100644 index 0000000000..a31f59e198 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -0,0 +1,53 @@ +package io.javaoperatorsdk.operator.processing.dependent.workflow; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.reconciler.Context; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.*; + +public class WorkflowReconcileExecutor

{ + + private Set alreadyReconciled = ConcurrentHashMap.newKeySet(); + + private Workflow

workflow; + private List exceptionsDuringExecution = Collections.synchronizedList(new ArrayList<>()); + private List> actualExecutions = Collections.synchronizedList(new ArrayList<>()); + + public WorkflowReconcileExecutor(Workflow

workflow) { + this.workflow = workflow; + } + + public synchronized void reconcile(P primary, Context

context) { + try { + for (DependentResourceNode dependentResourceNode : workflow.getTopLevelResources()) { + var nodeFuture = workflow.getExecutorService().submit(new NodeExecutor(dependentResourceNode)); + actualExecutions.add(nodeFuture); + } + this.wait(); + } catch (InterruptedException e) { + // todo check this better + throw new IllegalStateException(e); + } + } + + private boolean terminateExecution() { + return !exceptionsDuringExecution.isEmpty(); + } + private class NodeExecutor implements Runnable { + + private final DependentResourceNode dependentResourceNode; + + private NodeExecutor(DependentResourceNode dependentResourceNode) { + this.dependentResourceNode = dependentResourceNode; + } + + @Override + public void run() { + + } + } +} From 1aa332cf7c60dd3fa7b225c412c56e32734742ad Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 11 Apr 2022 13:36:32 +0200 Subject: [PATCH 11/51] wip --- .../workflow/DependentResourceNode.java | 93 +++++----- .../dependent/workflow/DependsOnRelation.java | 56 +++--- .../dependent/workflow/Workflow.java | 90 ++++----- .../workflow/WorkflowExecutionContext.java | 11 -- .../workflow/WorkflowReconcileExecutor.java | 174 ++++++++++++++---- 5 files changed, 266 insertions(+), 158 deletions(-) delete mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutionContext.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java index 6cf7545cb5..eabba881ab 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java @@ -1,51 +1,60 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow; -import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; -import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.CleanupCondition; -import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReconcileCondition; - import java.util.ArrayList; import java.util.List; import java.util.Optional; -public class DependentResourceNode { - - private final DependentResource dependentResource; - private final ReconcileCondition reconcileCondition; - private final CleanupCondition cleanupCondition; - private List dependsOnRelations = new ArrayList<>(1); - - public DependentResourceNode(DependentResource dependentResource) { - this(dependentResource,null,null); - } - - public DependentResourceNode(DependentResource dependentResource, ReconcileCondition reconcileCondition) { - this(dependentResource, reconcileCondition,null); - } - - public DependentResourceNode(DependentResource dependentResource, ReconcileCondition reconcileCondition, CleanupCondition cleanupCondition) { - this.dependentResource = dependentResource; - this.reconcileCondition = reconcileCondition; - this.cleanupCondition = cleanupCondition; - } - - public DependentResource getDependentResource() { - return dependentResource; - } - - public Optional getReconcileCondition() { - return Optional.ofNullable(reconcileCondition); - } - - public Optional getCleanupCondition() { - return Optional.ofNullable(cleanupCondition); - } +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.CleanupCondition; +import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReconcileCondition; - public void setDependsOnRelations(List dependsOnRelations) { - this.dependsOnRelations = dependsOnRelations; - } +public class DependentResourceNode { - public List getDependsOnRelations() { - return dependsOnRelations; - } + private final DependentResource dependentResource; + private final ReconcileCondition reconcileCondition; + private final CleanupCondition cleanupCondition; + private List dependsOnRelations = new ArrayList<>(1); + + public DependentResourceNode(DependentResource dependentResource) { + this(dependentResource, null, null); + } + + public DependentResourceNode(DependentResource dependentResource, + ReconcileCondition reconcileCondition) { + this(dependentResource, reconcileCondition, null); + } + + public DependentResourceNode(DependentResource dependentResource, + ReconcileCondition reconcileCondition, CleanupCondition cleanupCondition) { + this.dependentResource = dependentResource; + this.reconcileCondition = reconcileCondition; + this.cleanupCondition = cleanupCondition; + } + + public DependentResource getDependentResource() { + return dependentResource; + } + + public Optional getReconcileCondition() { + return Optional.ofNullable(reconcileCondition); + } + + public Optional getCleanupCondition() { + return Optional.ofNullable(cleanupCondition); + } + + public void setDependsOnRelations(List dependsOnRelations) { + this.dependsOnRelations = dependsOnRelations; + } + + public List getDependsOnRelations() { + return dependsOnRelations; + } + + @Override + public String toString() { + return "DependentResourceNode{" + + "dependentResource=" + dependentResource + + '}'; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependsOnRelation.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependsOnRelation.java index 537a5bfd8d..8e14d15e61 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependsOnRelation.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependsOnRelation.java @@ -4,41 +4,41 @@ public class DependsOnRelation { - private DependentResourceNode owner; - private DependentResourceNode dependsOn; - private WaitCondition waitCondition; + private DependentResourceNode owner; + private DependentResourceNode dependsOn; + private WaitCondition waitCondition; - public DependsOnRelation() { - } + public DependsOnRelation() {} - public DependsOnRelation(DependentResourceNode owner, DependentResourceNode dependsOn, WaitCondition waitCondition) { - this.owner = owner; - this.dependsOn = dependsOn; - this.waitCondition = waitCondition; - } + public DependsOnRelation(DependentResourceNode owner, DependentResourceNode dependsOn, + WaitCondition waitCondition) { + this.owner = owner; + this.dependsOn = dependsOn; + this.waitCondition = waitCondition; + } - public void setOwner(DependentResourceNode owner) { - this.owner = owner; - } + public void setOwner(DependentResourceNode owner) { + this.owner = owner; + } - public void setDependsOn(DependentResourceNode dependsOn) { - this.dependsOn = dependsOn; - } + public void setDependsOn(DependentResourceNode dependsOn) { + this.dependsOn = dependsOn; + } - public void setWaitCondition(WaitCondition waitCondition) { - this.waitCondition = waitCondition; - } + public void setWaitCondition(WaitCondition waitCondition) { + this.waitCondition = waitCondition; + } - public DependentResourceNode getOwner() { - return owner; - } + public DependentResourceNode getOwner() { + return owner; + } - public DependentResourceNode getDependsOn() { - return dependsOn; - } + public DependentResourceNode getDependsOn() { + return dependsOn; + } - public WaitCondition getWaitCondition() { - return waitCondition; - } + public WaitCondition getWaitCondition() { + return waitCondition; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java index 4ca635c366..ec3a614fc9 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java @@ -1,72 +1,72 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow; -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.api.reconciler.Context; - import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.reconciler.Context; + public class Workflow

{ - private final List dependentResourceNodes; + private final List dependentResourceNodes; - private List topLevelResources = new ArrayList<>(); - private Map> reverseDependsOn; + private List topLevelResources = new ArrayList<>(); + private Map> reverseDependsOn; - // it's "global" executor service shared between multiple reconciliations running parallel - private ExecutorService executorService; + // it's "global" executor service shared between multiple reconciliations running parallel + private ExecutorService executorService; - public Workflow(List dependentResourceNodes, ExecutorService executorService) { - this.executorService = executorService; - this.dependentResourceNodes = dependentResourceNodes; - preprocessForReconcile(); - } + public Workflow(List dependentResourceNodes, + ExecutorService executorService) { + this.executorService = executorService; + this.dependentResourceNodes = dependentResourceNodes; + preprocessForReconcile(); + } - public Workflow(List dependentResourceNodes, int globalParallelism) { - this(dependentResourceNodes,Executors.newFixedThreadPool(globalParallelism)); - } + public Workflow(List dependentResourceNodes, int globalParallelism) { + this(dependentResourceNodes, Executors.newFixedThreadPool(globalParallelism)); + } - public void reconcile(P primary, Context

context) { + public void reconcile(P primary, Context

context) { - } + } - public void cleanup(P resource, Context

context) { + public void cleanup(P resource, Context

context) { - } + } - private void preprocessForReconcile() { - reverseDependsOn = new ConcurrentHashMap<>(dependentResourceNodes.size()); - - for (DependentResourceNode node : dependentResourceNodes) { - if (node.getDependsOnRelations().isEmpty()) { - topLevelResources.add(node); - } else { - for (DependsOnRelation relation : node.getDependsOnRelations()) { - reverseDependsOn.computeIfAbsent(relation.getDependsOn(), dr -> new ArrayList<>()); - reverseDependsOn.get(relation.getDependsOn()).add(relation.getOwner()); - } - } + private void preprocessForReconcile() { + reverseDependsOn = new ConcurrentHashMap<>(dependentResourceNodes.size()); + + for (DependentResourceNode node : dependentResourceNodes) { + if (node.getDependsOnRelations().isEmpty()) { + topLevelResources.add(node); + } else { + for (DependsOnRelation relation : node.getDependsOnRelations()) { + reverseDependsOn.computeIfAbsent(relation.getDependsOn(), dr -> new ArrayList<>()); + reverseDependsOn.get(relation.getDependsOn()).add(relation.getOwner()); } + } } + } - public void setExecutorService(ExecutorService executorService) { - this.executorService = executorService; - } + public void setExecutorService(ExecutorService executorService) { + this.executorService = executorService; + } - List getTopLevelResources() { - return topLevelResources; - } + List getTopLevelDependentResources() { + return topLevelResources; + } - Map> getReverseDependsOn() { - return reverseDependsOn; - } + Map> getReverseDependsOn() { + return reverseDependsOn; + } - ExecutorService getExecutorService() { - return executorService; - } + ExecutorService getExecutorService() { + return executorService; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutionContext.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutionContext.java deleted file mode 100644 index 95f23aaa4c..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutionContext.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.javaoperatorsdk.operator.processing.dependent.workflow; - -import java.util.List; - -import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; - -public interface WorkflowExecutionContext { - - List getDependentResources(); - -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index a31f59e198..268dd4c5ce 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -1,53 +1,163 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow; +import java.util.*; +import java.util.concurrent.*; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.AggregatedOperatorException; import io.javaoperatorsdk.operator.api.reconciler.Context; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.concurrent.*; +public class WorkflowReconcileExecutor

{ -public class WorkflowReconcileExecutor

{ + // todo add log messages + private static final Logger log = LoggerFactory.getLogger(WorkflowReconcileExecutor.class); - private Set alreadyReconciled = ConcurrentHashMap.newKeySet(); + private Workflow

workflow; - private Workflow

workflow; - private List exceptionsDuringExecution = Collections.synchronizedList(new ArrayList<>()); - private List> actualExecutions = Collections.synchronizedList(new ArrayList<>()); + private Set alreadyReconciled = ConcurrentHashMap.newKeySet(); + private Set> actualExecutions = ConcurrentHashMap.newKeySet(); + private Map> nodeToFuture = new ConcurrentHashMap<>(); + private List exceptionsDuringExecution = + Collections.synchronizedList(new ArrayList<>()); + private Set markedToReconcileAgain = ConcurrentHashMap.newKeySet(); - public WorkflowReconcileExecutor(Workflow

workflow) { - this.workflow = workflow; - } + private final P primary; + private final Context

context; + + public WorkflowReconcileExecutor(Workflow

workflow, P primary, Context

context) { + this.primary = primary; + this.context = context; + this.workflow = workflow; + } - public synchronized void reconcile(P primary, Context

context) { - try { - for (DependentResourceNode dependentResourceNode : workflow.getTopLevelResources()) { - var nodeFuture = workflow.getExecutorService().submit(new NodeExecutor(dependentResourceNode)); - actualExecutions.add(nodeFuture); - } - this.wait(); - } catch (InterruptedException e) { - // todo check this better - throw new IllegalStateException(e); + public void reconcile() { + for (DependentResourceNode dependentResourceNode : workflow.getTopLevelDependentResources()) { + submitForReconcile(dependentResourceNode); + } + while (true) { + try { + this.wait(); + if (exceptionsPresent()) { + throw createFinalException(); + } else if (noMoreExecutionsScheduled()) { + break; } + } catch (InterruptedException e) { + // todo check this better + throw new IllegalStateException(e); + } + } + } + + private AggregatedOperatorException createFinalException() { + return new AggregatedOperatorException("Exception during workflow.", exceptionsDuringExecution); + } + + private synchronized boolean alreadyReconciled(DependentResourceNode dependentResourceNode) { + return alreadyReconciled.contains(dependentResourceNode); + } + + private synchronized boolean allDependOnsReconciled(DependentResourceNode dependentResourceNode) { + return dependentResourceNode.getDependsOnRelations().stream() + .allMatch(relation -> alreadyReconciled(relation.getDependsOn())); + } + + private synchronized void handleNodeExecutionFinish(DependentResourceNode dependentResourceNode) { + + var future = nodeToFuture.remove(dependentResourceNode); + actualExecutions.remove(future); + + if (exceptionsPresent()) { + if (actualExecutions.isEmpty()) { + notifyMainReconcile(); + } + return; } - private boolean terminateExecution() { - return !exceptionsDuringExecution.isEmpty(); + if (markedToReconcileAgain.contains(dependentResourceNode) + && alreadyReconciled(dependentResourceNode)) { + log.warn("Something happened, this never should be the case."); } - private class NodeExecutor implements Runnable { + if (markedToReconcileAgain.contains(dependentResourceNode) + && !alreadyReconciled(dependentResourceNode)) { + markedToReconcileAgain.remove(dependentResourceNode); + submitForReconcile(dependentResourceNode); + } + if (actualExecutions.isEmpty()) { + notifyMainReconcile(); + } + } - private final DependentResourceNode dependentResourceNode; + private synchronized void submitForReconcile(DependentResourceNode dependentResourceNode) { + if (nodeToFuture.containsKey(dependentResourceNode)) { + markedToReconcileAgain.add(dependentResourceNode); + return; + } - private NodeExecutor(DependentResourceNode dependentResourceNode) { - this.dependentResourceNode = dependentResourceNode; - } + Future nodeFuture = + workflow.getExecutorService().submit(new NodeExecutor(dependentResourceNode)); + actualExecutions.add(nodeFuture); + nodeToFuture.put(dependentResourceNode, nodeFuture); + } + + private synchronized void executeDependents(DependentResourceNode dependentResourceNode) { + if (!exceptionsPresent()) { + workflow.getReverseDependsOn().get(dependentResourceNode).forEach(this::submitForReconcile); + } + } + + private synchronized void handleExceptionInExecutor( + DependentResourceNode dependentResourceNode, RuntimeException e) { + exceptionsDuringExecution.add(e); + markedToReconcileAgain.clear(); + // todo optimize to cancel futures? + // actualExecutions.forEach(actualExecution -> actualExecution.cancel(false)); + // var doneExecutions = + // actualExecutions.stream().filter(Future::isDone).collect(Collectors.toSet()); + // actualExecutions.removeAll(doneExecutions); + } - @Override - public void run() { + private boolean exceptionsPresent() { + return !exceptionsDuringExecution.isEmpty(); + } + + private boolean noMoreExecutionsScheduled() { + return actualExecutions.isEmpty() && markedToReconcileAgain.isEmpty(); + } + + private synchronized void notifyMainReconcile() { + this.notifyAll(); + } + + private class NodeExecutor implements Runnable { + + private final DependentResourceNode dependentResourceNode; + + private NodeExecutor(DependentResourceNode dependentResourceNode) { + this.dependentResourceNode = dependentResourceNode; + } + // todo conditions + @Override + @SuppressWarnings("unchecked") + public void run() { + try { + if (alreadyReconciled(dependentResourceNode) + || allDependOnsReconciled(dependentResourceNode) + || exceptionsPresent()) { + return; } + dependentResourceNode.getDependentResource().reconcile(primary, context); + alreadyReconciled.add(dependentResourceNode); + executeDependents(dependentResourceNode); + } catch (RuntimeException e) { + handleExceptionInExecutor(dependentResourceNode, e); + } finally { + handleNodeExecutionFinish(dependentResourceNode); + } } + } } From c959d4b43f3e71fa789268fc2c9acdf8ee2e1ccd Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 11 Apr 2022 15:40:21 +0200 Subject: [PATCH 12/51] wip --- .../workflow/DependentResourceNode.java | 8 +++- .../dependent/workflow/DependsOnRelation.java | 4 ++ .../dependent/workflow/Workflow.java | 6 ++- .../workflow/WorkflowReconcileExecutor.java | 2 +- .../workflow/builder/DependentBuilder.java | 23 ++++++++++ .../workflow/builder/WorkflowBuilder.java | 45 +++++++++++++++++++ .../dependent/workflow/WorkflowTest.java | 29 ++++++++++++ 7 files changed, 112 insertions(+), 5 deletions(-) create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java create mode 100644 operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java index eabba881ab..cbc5176456 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java @@ -11,8 +11,8 @@ public class DependentResourceNode { private final DependentResource dependentResource; - private final ReconcileCondition reconcileCondition; - private final CleanupCondition cleanupCondition; + private ReconcileCondition reconcileCondition; + private CleanupCondition cleanupCondition; private List dependsOnRelations = new ArrayList<>(1); public DependentResourceNode(DependentResource dependentResource) { @@ -51,6 +51,10 @@ public List getDependsOnRelations() { return dependsOnRelations; } + public void addDependsOnRelation(DependsOnRelation dependsOnRelation) { + dependsOnRelations.add(dependsOnRelation); + } + @Override public String toString() { return "DependentResourceNode{" + diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependsOnRelation.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependsOnRelation.java index 8e14d15e61..00ba210b5c 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependsOnRelation.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependsOnRelation.java @@ -10,6 +10,10 @@ public class DependsOnRelation { public DependsOnRelation() {} + public DependsOnRelation(DependentResourceNode owner, DependentResourceNode dependsOn) { + this(owner, dependsOn, null); + } + public DependsOnRelation(DependentResourceNode owner, DependentResourceNode dependsOn, WaitCondition waitCondition) { this.owner = owner; diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java index ec3a614fc9..8d8efe9f61 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java @@ -32,16 +32,18 @@ public Workflow(List dependentResourceNodes, int globalPa } public void reconcile(P primary, Context

context) { - + WorkflowReconcileExecutor

workflowReconcileExecutor = + new WorkflowReconcileExecutor<>(this, primary, context); + workflowReconcileExecutor.reconcile(); } public void cleanup(P resource, Context

context) { } + // add cycle detection? private void preprocessForReconcile() { reverseDependsOn = new ConcurrentHashMap<>(dependentResourceNodes.size()); - for (DependentResourceNode node : dependentResourceNodes) { if (node.getDependsOnRelations().isEmpty()) { topLevelResources.add(node); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index 268dd4c5ce..0ffa76a794 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -69,7 +69,7 @@ private synchronized void handleNodeExecutionFinish(DependentResourceNode depend var future = nodeToFuture.remove(dependentResourceNode); actualExecutions.remove(future); - + if (exceptionsPresent()) { if (actualExecutions.isEmpty()) { notifyMainReconcile(); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java new file mode 100644 index 0000000000..5c41a3080d --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java @@ -0,0 +1,23 @@ +package io.javaoperatorsdk.operator.processing.dependent.workflow.builder; + +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.processing.dependent.workflow.DependentResourceNode; +import io.javaoperatorsdk.operator.processing.dependent.workflow.DependsOnRelation; + +public class DependentBuilder { + + private final WorkflowBuilder workflowBuilder; + private final DependentResourceNode node; + + public DependentBuilder(WorkflowBuilder workflowBuilder, DependentResourceNode node) { + this.workflowBuilder = workflowBuilder; + this.node = node; + } + + public DependentBuilder dependsOn(DependentResource dependentResource) { + var dependsOn = workflowBuilder.getNodeByDependentResource(dependentResource); + node.addDependsOnRelation(new DependsOnRelation(node, dependsOn, null)); + return this; + } + +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java new file mode 100644 index 0000000000..578fcd21e9 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java @@ -0,0 +1,45 @@ +package io.javaoperatorsdk.operator.processing.dependent.workflow.builder; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.processing.dependent.workflow.DependentResourceNode; +import io.javaoperatorsdk.operator.processing.dependent.workflow.Workflow; + +public class WorkflowBuilder

{ + + private List dependentResourceNodes = new ArrayList<>(); + + public DependentBuilder addDependent(DependentResource dependentResource) { + DependentResourceNode node = new DependentResourceNode(dependentResource); + dependentResourceNodes.add(node); + return new DependentBuilder(this, node); + } + + void addDependentResourceNode(DependentResourceNode node) { + dependentResourceNodes.add(node); + } + + DependentResourceNode getNodeByDependentResource(DependentResource dependentResource) { + return dependentResourceNodes.stream() + .filter(dr -> dr.getDependentResource() == dependentResource) + .findFirst() + .orElseThrow(); + } + + public Workflow

build() { + return new Workflow<>(dependentResourceNodes, Executors.newCachedThreadPool()); + } + + public Workflow

build(int parallelism) { + return new Workflow<>(dependentResourceNodes, parallelism); + } + + public Workflow

build(ExecutorService executorService) { + return new Workflow<>(dependentResourceNodes, executorService); + } +} diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java new file mode 100644 index 0000000000..5e66aa2b1e --- /dev/null +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java @@ -0,0 +1,29 @@ +package io.javaoperatorsdk.operator.processing.dependent.workflow; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class WorkflowTest { + + @Test + void reconcileTopLevelResources() { + + } + + @Test + void reconciliationWithSimpleDependsOn() { + + } + + @Test + void reconciliationWithTheDependsOns() { + + } + + @Test + void diamondShareWorkflowReconcile() { + + } + +} From 903edf4695db2876c82aaa499177ef55dcbb7bbb Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 11 Apr 2022 18:32:58 +0200 Subject: [PATCH 13/51] wip --- .../workflow/WorkflowReconcileExecutor.java | 2 +- .../workflow/builder/DependentBuilder.java | 11 +++-- .../workflow/builder/WorkflowBuilder.java | 2 +- .../workflow/ExecutionListAssert.java | 22 ++++++++++ .../dependent/workflow/WorkflowTest.java | 44 +++++++++++++++++++ 5 files changed, 76 insertions(+), 5 deletions(-) create mode 100644 operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionListAssert.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index 0ffa76a794..6582d7b77b 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -33,7 +33,7 @@ public WorkflowReconcileExecutor(Workflow

workflow, P primary, Context

con this.workflow = workflow; } - public void reconcile() { + public synchronized void reconcile() { for (DependentResourceNode dependentResourceNode : workflow.getTopLevelDependentResources()) { submitForReconcile(dependentResourceNode); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java index 5c41a3080d..c41b726aa5 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java @@ -1,15 +1,16 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow.builder; +import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.processing.dependent.workflow.DependentResourceNode; import io.javaoperatorsdk.operator.processing.dependent.workflow.DependsOnRelation; -public class DependentBuilder { +public class DependentBuilder

{ - private final WorkflowBuilder workflowBuilder; + private final WorkflowBuilder

workflowBuilder; private final DependentResourceNode node; - public DependentBuilder(WorkflowBuilder workflowBuilder, DependentResourceNode node) { + public DependentBuilder(WorkflowBuilder

workflowBuilder, DependentResourceNode node) { this.workflowBuilder = workflowBuilder; this.node = node; } @@ -20,4 +21,8 @@ public DependentBuilder dependsOn(DependentResource dependentResource) { return this; } + public WorkflowBuilder

build() { + return workflowBuilder; + } + } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java index 578fcd21e9..87b184ec07 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java @@ -14,7 +14,7 @@ public class WorkflowBuilder

{ private List dependentResourceNodes = new ArrayList<>(); - public DependentBuilder addDependent(DependentResource dependentResource) { + public DependentBuilder addDependent(DependentResource dependentResource) { DependentResourceNode node = new DependentResourceNode(dependentResource); dependentResourceNodes.add(node); return new DependentBuilder(this, node); diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionListAssert.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionListAssert.java new file mode 100644 index 0000000000..527623edab --- /dev/null +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionListAssert.java @@ -0,0 +1,22 @@ +package io.javaoperatorsdk.operator.processing.dependent.workflow; + +import java.util.List; + +import org.assertj.core.api.AbstractAssert; + +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; + +public class ExecutionListAssert + extends AbstractAssert>> { + + public ExecutionListAssert(List> dependentResources) { + super(dependentResources, ExecutionListAssert.class); + } + + public static ExecutionListAssert assertThat(List> actual) { + return new ExecutionListAssert(actual); + } + + + +} diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java index 5e66aa2b1e..546be89a1a 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java @@ -1,14 +1,39 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + import org.junit.jupiter.api.Test; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; +import io.javaoperatorsdk.operator.processing.dependent.workflow.builder.WorkflowBuilder; +import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; + +import static io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult.resourceCreated; import static org.junit.jupiter.api.Assertions.*; class WorkflowTest { + public static final String VALUE = "value"; + private List> dependentResourceExecutions = + Collections.synchronizedList(new ArrayList<>()); + @Test void reconcileTopLevelResources() { + var dr1 = new TestDependent(); + var dr2 = new TestDependent(); + Workflow workflow = new WorkflowBuilder() + .addDependent(dr1).build() + .addDependent(dr2).build() + .build(); + + workflow.reconcile(new TestCustomResource(), null); + // assertThat(dependentResourceExecutions).hasSize(2); } @Test @@ -26,4 +51,23 @@ void diamondShareWorkflowReconcile() { } + private class TestDependent implements DependentResource { + @Override + public ReconcileResult reconcile(TestCustomResource primary, + Context context) { + dependentResourceExecutions.add(this); + return ReconcileResult.resourceCreated(VALUE); + } + + @Override + public Class resourceType() { + return String.class; + } + + @Override + public Optional getSecondaryResource(TestCustomResource primary) { + return Optional.of(VALUE); + } + } + } From ff322fea946ea34b6fe264cbf2db5da8e00787b6 Mon Sep 17 00:00:00 2001 From: csviri Date: Tue, 12 Apr 2022 11:00:06 +0200 Subject: [PATCH 14/51] fix: basic depends on works --- .../dependent/workflow/Workflow.java | 25 ++++++++++++++----- .../workflow/WorkflowReconcileExecutor.java | 9 ++++--- .../dependent/workflow/WorkflowTest.java | 3 ++- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java index 8d8efe9f61..c9bb84e3a4 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java @@ -8,18 +8,31 @@ import java.util.concurrent.Executors; import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.config.ConfigurationServiceProvider; import io.javaoperatorsdk.operator.api.reconciler.Context; +/** + * Dependents definition: so if B depends on A, the B is dependent of A. + * + * @param

primary resource + */ +// todo build time graph creation for quarkus public class Workflow

{ private final List dependentResourceNodes; private List topLevelResources = new ArrayList<>(); - private Map> reverseDependsOn; + private Map> dependents; // it's "global" executor service shared between multiple reconciliations running parallel private ExecutorService executorService; + public Workflow(List dependentResourceNodes) { + this.executorService = ConfigurationServiceProvider.instance().getExecutorService(); + this.dependentResourceNodes = dependentResourceNodes; + preprocessForReconcile(); + } + public Workflow(List dependentResourceNodes, ExecutorService executorService) { this.executorService = executorService; @@ -43,14 +56,14 @@ public void cleanup(P resource, Context

context) { // add cycle detection? private void preprocessForReconcile() { - reverseDependsOn = new ConcurrentHashMap<>(dependentResourceNodes.size()); + dependents = new ConcurrentHashMap<>(dependentResourceNodes.size()); for (DependentResourceNode node : dependentResourceNodes) { if (node.getDependsOnRelations().isEmpty()) { topLevelResources.add(node); } else { for (DependsOnRelation relation : node.getDependsOnRelations()) { - reverseDependsOn.computeIfAbsent(relation.getDependsOn(), dr -> new ArrayList<>()); - reverseDependsOn.get(relation.getDependsOn()).add(relation.getOwner()); + dependents.computeIfAbsent(relation.getDependsOn(), dr -> new ArrayList<>()); + dependents.get(relation.getDependsOn()).add(relation.getOwner()); } } } @@ -64,8 +77,8 @@ List getTopLevelDependentResources() { return topLevelResources; } - Map> getReverseDependsOn() { - return reverseDependsOn; + Map> getDependents() { + return dependents; } ExecutorService getExecutorService() { diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index 6582d7b77b..71055f9a23 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -61,7 +61,7 @@ private synchronized boolean alreadyReconciled(DependentResourceNode dependentRe } private synchronized boolean allDependOnsReconciled(DependentResourceNode dependentResourceNode) { - return dependentResourceNode.getDependsOnRelations().stream() + return dependentResourceNode.getDependsOnRelations().isEmpty() || dependentResourceNode.getDependsOnRelations().stream() .allMatch(relation -> alreadyReconciled(relation.getDependsOn())); } @@ -105,7 +105,10 @@ private synchronized void submitForReconcile(DependentResourceNode dependentReso private synchronized void executeDependents(DependentResourceNode dependentResourceNode) { if (!exceptionsPresent()) { - workflow.getReverseDependsOn().get(dependentResourceNode).forEach(this::submitForReconcile); + var dependents = workflow.getDependents().get(dependentResourceNode); + if (dependents != null) { + dependents.forEach(this::submitForReconcile); + } } } @@ -146,7 +149,7 @@ private NodeExecutor(DependentResourceNode dependentResourceNode) { public void run() { try { if (alreadyReconciled(dependentResourceNode) - || allDependOnsReconciled(dependentResourceNode) + || !allDependOnsReconciled(dependentResourceNode) || exceptionsPresent()) { return; } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java index 546be89a1a..336ad342ff 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java @@ -14,6 +14,7 @@ import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; import static io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult.resourceCreated; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; class WorkflowTest { @@ -33,7 +34,7 @@ void reconcileTopLevelResources() { workflow.reconcile(new TestCustomResource(), null); - // assertThat(dependentResourceExecutions).hasSize(2); + assertThat(dependentResourceExecutions).hasSize(2); } @Test From 0bb48436d2f2ca87d2321c274c49883ccfda62ff Mon Sep 17 00:00:00 2001 From: csviri Date: Tue, 12 Apr 2022 13:35:07 +0200 Subject: [PATCH 15/51] fix: tests --- .../workflow/WorkflowReconcileExecutor.java | 53 ++++++----- .../workflow/builder/WorkflowBuilder.java | 4 +- .../dependent/workflow/ExecutionAssert.java | 50 +++++++++++ .../workflow/ExecutionListAssert.java | 22 ----- .../dependent/workflow/WorkflowTest.java | 87 +++++++++++++++++-- 5 files changed, 163 insertions(+), 53 deletions(-) create mode 100644 operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionAssert.java delete mode 100644 operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionListAssert.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index 71055f9a23..799f4fad73 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -42,12 +42,15 @@ public synchronized void reconcile() { this.wait(); if (exceptionsPresent()) { throw createFinalException(); - } else if (noMoreExecutionsScheduled()) { + } + if (noMoreExecutionsScheduled()) { break; + } else { + log.warn("Notified but still resources under execution. This should not happen."); } } catch (InterruptedException e) { - // todo check this better - throw new IllegalStateException(e); + log.warn("Thread interrupted", e); + Thread.currentThread().interrupt(); } } } @@ -61,12 +64,12 @@ private synchronized boolean alreadyReconciled(DependentResourceNode dependentRe } private synchronized boolean allDependOnsReconciled(DependentResourceNode dependentResourceNode) { - return dependentResourceNode.getDependsOnRelations().isEmpty() || dependentResourceNode.getDependsOnRelations().stream() - .allMatch(relation -> alreadyReconciled(relation.getDependsOn())); + return dependentResourceNode.getDependsOnRelations().isEmpty() + || dependentResourceNode.getDependsOnRelations().stream() + .allMatch(relation -> alreadyReconciled(relation.getDependsOn())); } private synchronized void handleNodeExecutionFinish(DependentResourceNode dependentResourceNode) { - var future = nodeToFuture.remove(dependentResourceNode); actualExecutions.remove(future); @@ -76,14 +79,16 @@ private synchronized void handleNodeExecutionFinish(DependentResourceNode depend } return; } - if (markedToReconcileAgain.contains(dependentResourceNode) && alreadyReconciled(dependentResourceNode)) { - log.warn("Something happened, this never should be the case."); + log.warn("Marked to reconcile but already reconciled, this should not happen. DR: {}", + dependentResourceNode); } + if (markedToReconcileAgain.contains(dependentResourceNode) && !alreadyReconciled(dependentResourceNode)) { markedToReconcileAgain.remove(dependentResourceNode); + log.debug("Submitting marked resource to reconcile: {}", dependentResourceNode); submitForReconcile(dependentResourceNode); } if (actualExecutions.isEmpty()) { @@ -92,18 +97,31 @@ && alreadyReconciled(dependentResourceNode)) { } private synchronized void submitForReconcile(DependentResourceNode dependentResourceNode) { + log.debug("Submitting for reconcile: {}", dependentResourceNode); + + if (alreadyReconciled(dependentResourceNode) + || !allDependOnsReconciled(dependentResourceNode) + || exceptionsPresent()) { + log.debug("Skipping submit of: {}, ", dependentResourceNode); + return; + } + if (nodeToFuture.containsKey(dependentResourceNode)) { + log.debug("The same dependent resource already bein reconciled," + + " marking it for future reconciliation: {}", dependentResourceNode); markedToReconcileAgain.add(dependentResourceNode); return; } + Future nodeFuture = workflow.getExecutorService().submit(new NodeExecutor(dependentResourceNode)); actualExecutions.add(nodeFuture); nodeToFuture.put(dependentResourceNode, nodeFuture); + log.debug("Submitted to reconcile: {}", dependentResourceNode); } - private synchronized void executeDependents(DependentResourceNode dependentResourceNode) { + private synchronized void submitDependents(DependentResourceNode dependentResourceNode) { if (!exceptionsPresent()) { var dependents = workflow.getDependents().get(dependentResourceNode); if (dependents != null) { @@ -112,15 +130,9 @@ private synchronized void executeDependents(DependentResourceNode dependentResou } } - private synchronized void handleExceptionInExecutor( - DependentResourceNode dependentResourceNode, RuntimeException e) { + private synchronized void handleExceptionInExecutor(RuntimeException e) { exceptionsDuringExecution.add(e); markedToReconcileAgain.clear(); - // todo optimize to cancel futures? - // actualExecutions.forEach(actualExecution -> actualExecution.cancel(false)); - // var doneExecutions = - // actualExecutions.stream().filter(Future::isDone).collect(Collectors.toSet()); - // actualExecutions.removeAll(doneExecutions); } private boolean exceptionsPresent() { @@ -143,21 +155,18 @@ private NodeExecutor(DependentResourceNode dependentResourceNode) { this.dependentResourceNode = dependentResourceNode; } - // todo conditions @Override @SuppressWarnings("unchecked") public void run() { try { - if (alreadyReconciled(dependentResourceNode) - || !allDependOnsReconciled(dependentResourceNode) - || exceptionsPresent()) { + if (exceptionsPresent()) { return; } dependentResourceNode.getDependentResource().reconcile(primary, context); alreadyReconciled.add(dependentResourceNode); - executeDependents(dependentResourceNode); + submitDependents(dependentResourceNode); } catch (RuntimeException e) { - handleExceptionInExecutor(dependentResourceNode, e); + handleExceptionInExecutor(e); } finally { handleNodeExecutionFinish(dependentResourceNode); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java index 87b184ec07..8e650e5001 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java @@ -14,10 +14,10 @@ public class WorkflowBuilder

{ private List dependentResourceNodes = new ArrayList<>(); - public DependentBuilder addDependent(DependentResource dependentResource) { + public DependentBuilder

addDependent(DependentResource dependentResource) { DependentResourceNode node = new DependentResourceNode(dependentResource); dependentResourceNodes.add(node); - return new DependentBuilder(this, node); + return new DependentBuilder<>(this, node); } void addDependentResourceNode(DependentResourceNode node) { diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionAssert.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionAssert.java new file mode 100644 index 0000000000..996e387363 --- /dev/null +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionAssert.java @@ -0,0 +1,50 @@ +package io.javaoperatorsdk.operator.processing.dependent.workflow; + +import java.util.List; + +import org.assertj.core.api.AbstractAssert; + +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; + +public class ExecutionAssert + extends AbstractAssert>> { + + public ExecutionAssert(List> dependentResources) { + super(dependentResources, ExecutionAssert.class); + } + + public static ExecutionAssert assertThat(List> actual) { + return new ExecutionAssert(actual); + } + + ExecutionAssert reconciledAll(DependentResource... dependentResources) { + for (int i = 0; i < dependentResources.length; i++) { + if (!actual.contains(dependentResources[i])) { + failWithMessage("Resource not reconciled: %s with index %d", dependentResources, i); + } + } + return this; + } + + ExecutionAssert reconciledInOrder(DependentResource... dependentResources) { + if (dependentResources.length < 2) { + throw new IllegalArgumentException("At least two dependent resource needs to be specified"); + } + for (int i = 0; i < dependentResources.length - 1; i++) { + checkIfReconciled(i, dependentResources); + checkIfReconciled(i + 1, dependentResources); + if (actual.indexOf(dependentResources[i]) > actual.indexOf(dependentResources[i + 1])) { + failWithMessage( + "Dependent resource on index %d reconciled after the one on index %d", i, i + 1); + } + } + + return this; + } + + private void checkIfReconciled(int i, DependentResource[] dependentResources) { + if (!actual.contains(dependentResources[i])) { + failWithMessage("Dependent resource not reconciled on place %i", i); + } + } +} diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionListAssert.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionListAssert.java deleted file mode 100644 index 527623edab..0000000000 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionListAssert.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.javaoperatorsdk.operator.processing.dependent.workflow; - -import java.util.List; - -import org.assertj.core.api.AbstractAssert; - -import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; - -public class ExecutionListAssert - extends AbstractAssert>> { - - public ExecutionListAssert(List> dependentResources) { - super(dependentResources, ExecutionListAssert.class); - } - - public static ExecutionListAssert assertThat(List> actual) { - return new ExecutionListAssert(actual); - } - - - -} diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java index 336ad342ff..c1f47168ac 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java @@ -13,9 +13,7 @@ import io.javaoperatorsdk.operator.processing.dependent.workflow.builder.WorkflowBuilder; import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; -import static io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult.resourceCreated; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; class WorkflowTest { @@ -23,36 +21,76 @@ class WorkflowTest { private List> dependentResourceExecutions = Collections.synchronizedList(new ArrayList<>()); + TestDependent dr1 = new TestDependent("DR_1"); + TestDependent dr2 = new TestDependent("DR_2"); + @Test void reconcileTopLevelResources() { - var dr1 = new TestDependent(); - var dr2 = new TestDependent(); - Workflow workflow = new WorkflowBuilder() + var workflow = new WorkflowBuilder() .addDependent(dr1).build() .addDependent(dr2).build() .build(); workflow.reconcile(new TestCustomResource(), null); - assertThat(dependentResourceExecutions).hasSize(2); + ExecutionAssert.assertThat(dependentResourceExecutions).reconciledAll(dr1, dr2); } @Test void reconciliationWithSimpleDependsOn() { + var workflow = new WorkflowBuilder() + .addDependent(dr1).build() + .addDependent(dr2).dependsOn(dr1).build() + .build(); + workflow.reconcile(new TestCustomResource(), null); + + ExecutionAssert.assertThat(dependentResourceExecutions).reconciledInOrder(dr1, dr2); } @Test - void reconciliationWithTheDependsOns() { + void reconciliationWithTwoTheDependsOns() { + TestDependent dr3 = new TestDependent("DR_3"); + + var workflow = new WorkflowBuilder() + .addDependent(dr1).build() + .addDependent(dr2).dependsOn(dr1).build() + .addDependent(dr3).dependsOn(dr1).build() + .build(); + + workflow.reconcile(new TestCustomResource(), null); + ExecutionAssert.assertThat(dependentResourceExecutions) + .reconciledInOrder(dr1, dr2).reconciledInOrder(dr1, dr3); } @Test void diamondShareWorkflowReconcile() { + TestDependent dr3 = new TestDependent("DR_3"); + TestDependent dr4 = new TestDependent("DR_4"); + var workflow = new WorkflowBuilder() + .addDependent(dr1).build() + .addDependent(dr2).dependsOn(dr1).build() + .addDependent(dr3).dependsOn(dr1).build() + .addDependent(dr4).dependsOn(dr3).dependsOn(dr2).build() + .build(); + + workflow.reconcile(new TestCustomResource(), null); + + ExecutionAssert.assertThat(dependentResourceExecutions) + .reconciledInOrder(dr1, dr2, dr4) + .reconciledInOrder(dr1, dr3, dr4); } private class TestDependent implements DependentResource { + + private String name; + + public TestDependent(String name) { + this.name = name; + } + @Override public ReconcileResult reconcile(TestCustomResource primary, Context context) { @@ -69,6 +107,41 @@ public Class resourceType() { public Optional getSecondaryResource(TestCustomResource primary) { return Optional.of(VALUE); } + + @Override + public String toString() { + return name; + } + } + + private class TestErrorDependent implements DependentResource { + private String name; + + public TestErrorDependent(String name) { + this.name = name; + } + + @Override + public ReconcileResult reconcile(TestCustomResource primary, + Context context) { + dependentResourceExecutions.add(this); + return ReconcileResult.resourceCreated(VALUE); + } + + @Override + public Class resourceType() { + return String.class; + } + + @Override + public Optional getSecondaryResource(TestCustomResource primary) { + return Optional.of(VALUE); + } + + @Override + public String toString() { + return name; + } } } From aadd3a4a863d47e2493ebf747be6fa328a0e2398 Mon Sep 17 00:00:00 2001 From: csviri Date: Tue, 12 Apr 2022 13:56:42 +0200 Subject: [PATCH 16/51] tests for exception handling --- .../workflow/builder/DependentBuilder.java | 2 +- .../dependent/workflow/ExecutionAssert.java | 15 ++++- .../dependent/workflow/WorkflowTest.java | 62 ++++++++++++++++--- 3 files changed, 65 insertions(+), 14 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java index c41b726aa5..d2b9f02003 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java @@ -15,7 +15,7 @@ public DependentBuilder(WorkflowBuilder

workflowBuilder, DependentResourceNod this.node = node; } - public DependentBuilder dependsOn(DependentResource dependentResource) { + public DependentBuilder

dependsOn(DependentResource dependentResource) { var dependsOn = workflowBuilder.getNodeByDependentResource(dependentResource); node.addDependsOnRelation(new DependsOnRelation(node, dependsOn, null)); return this; diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionAssert.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionAssert.java index 996e387363..abe87867e2 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionAssert.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionAssert.java @@ -17,7 +17,7 @@ public static ExecutionAssert assertThat(List> actual) { return new ExecutionAssert(actual); } - ExecutionAssert reconciledAll(DependentResource... dependentResources) { + public ExecutionAssert reconciled(DependentResource... dependentResources) { for (int i = 0; i < dependentResources.length; i++) { if (!actual.contains(dependentResources[i])) { failWithMessage("Resource not reconciled: %s with index %d", dependentResources, i); @@ -26,7 +26,7 @@ ExecutionAssert reconciledAll(DependentResource... dependentResources) { return this; } - ExecutionAssert reconciledInOrder(DependentResource... dependentResources) { + public ExecutionAssert reconciledInOrder(DependentResource... dependentResources) { if (dependentResources.length < 2) { throw new IllegalArgumentException("At least two dependent resource needs to be specified"); } @@ -42,7 +42,16 @@ ExecutionAssert reconciledInOrder(DependentResource... dependentResources) { return this; } - private void checkIfReconciled(int i, DependentResource[] dependentResources) { + public ExecutionAssert notReconciled(DependentResource... dependentResources) { + for (int i = 0; i < dependentResources.length - 1; i++) { + if (!actual.contains(dependentResources[i])) { + failWithMessage("Resource was reconciled: %s with index %d", dependentResources, i); + } + } + return this; + } + + private void checkIfReconciled(int i, DependentResource[] dependentResources) { if (!actual.contains(dependentResources[i])) { failWithMessage("Dependent resource not reconciled on place %i", i); } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java index c1f47168ac..9d468f9a19 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java @@ -7,22 +7,25 @@ import org.junit.jupiter.api.Test; +import io.javaoperatorsdk.operator.AggregatedOperatorException; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; import io.javaoperatorsdk.operator.processing.dependent.workflow.builder.WorkflowBuilder; import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; -import static org.assertj.core.api.Assertions.assertThat; +import static io.javaoperatorsdk.operator.processing.dependent.workflow.ExecutionAssert.*; +import static org.junit.jupiter.api.Assertions.assertThrows; class WorkflowTest { public static final String VALUE = "value"; - private List> dependentResourceExecutions = + private List> executionHistory = Collections.synchronizedList(new ArrayList<>()); TestDependent dr1 = new TestDependent("DR_1"); TestDependent dr2 = new TestDependent("DR_2"); + TestErrorDependent errorDR2 = new TestErrorDependent("ERROR_1"); @Test void reconcileTopLevelResources() { @@ -33,7 +36,7 @@ void reconcileTopLevelResources() { workflow.reconcile(new TestCustomResource(), null); - ExecutionAssert.assertThat(dependentResourceExecutions).reconciledAll(dr1, dr2); + assertThat(executionHistory).reconciled(dr1, dr2); } @Test @@ -45,7 +48,7 @@ void reconciliationWithSimpleDependsOn() { workflow.reconcile(new TestCustomResource(), null); - ExecutionAssert.assertThat(dependentResourceExecutions).reconciledInOrder(dr1, dr2); + assertThat(executionHistory).reconciledInOrder(dr1, dr2); } @Test @@ -60,7 +63,7 @@ void reconciliationWithTwoTheDependsOns() { workflow.reconcile(new TestCustomResource(), null); - ExecutionAssert.assertThat(dependentResourceExecutions) + assertThat(executionHistory) .reconciledInOrder(dr1, dr2).reconciledInOrder(dr1, dr3); } @@ -78,11 +81,50 @@ void diamondShareWorkflowReconcile() { workflow.reconcile(new TestCustomResource(), null); - ExecutionAssert.assertThat(dependentResourceExecutions) + assertThat(executionHistory) .reconciledInOrder(dr1, dr2, dr4) .reconciledInOrder(dr1, dr3, dr4); } + @Test + void exceptionHandlingSimpleCases() { + var workflow = new WorkflowBuilder() + .addDependent(errorDR2).build() + .build(); + assertThrows(AggregatedOperatorException.class, () -> { + workflow.reconcile(new TestCustomResource(), null); + }); + assertThat(executionHistory).notReconciled(errorDR2); + } + + @Test + void dependentsOnErroredResourceNotReconciled() { + var workflow = new WorkflowBuilder() + .addDependent(dr1).build() + .addDependent(errorDR2).dependsOn(dr1).build() + .addDependent(dr2).dependsOn(errorDR2).build() + .build(); + assertThrows(AggregatedOperatorException.class, () -> { + workflow.reconcile(new TestCustomResource(), null); + }); + + assertThat(executionHistory).reconciled(dr1).notReconciled(errorDR2, dr2); + } + + @Test + void onlyOneDependsOnErroredResourceNotReconciled() { + var workflow = new WorkflowBuilder() + .addDependent(dr1).build() + .addDependent(errorDR2).build() + .addDependent(dr2).dependsOn(errorDR2).dependsOn(dr1).build() + .build(); + assertThrows(AggregatedOperatorException.class, () -> { + workflow.reconcile(new TestCustomResource(), null); + }); + + assertThat(executionHistory).notReconciled(dr2); + } + private class TestDependent implements DependentResource { private String name; @@ -94,7 +136,7 @@ public TestDependent(String name) { @Override public ReconcileResult reconcile(TestCustomResource primary, Context context) { - dependentResourceExecutions.add(this); + executionHistory.add(this); return ReconcileResult.resourceCreated(VALUE); } @@ -123,9 +165,9 @@ public TestErrorDependent(String name) { @Override public ReconcileResult reconcile(TestCustomResource primary, - Context context) { - dependentResourceExecutions.add(this); - return ReconcileResult.resourceCreated(VALUE); + Context context) { + executionHistory.add(this); + throw new IllegalStateException("Test exception"); } @Override From f2bb2369774a5c5c606d78770a23453418198329 Mon Sep 17 00:00:00 2001 From: csviri Date: Tue, 12 Apr 2022 14:15:07 +0200 Subject: [PATCH 17/51] some smell fixes --- .../workflow/DependentResourceNode.java | 13 ++++---- .../dependent/workflow/DependsOnRelation.java | 6 ++-- .../dependent/workflow/Workflow.java | 20 ++++++------ .../workflow/WorkflowReconcileExecutor.java | 31 +++++++++++-------- .../workflow/builder/WorkflowBuilder.java | 2 +- 5 files changed, 38 insertions(+), 34 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java index cbc5176456..e33b7f7013 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java @@ -4,34 +4,35 @@ import java.util.List; import java.util.Optional; +import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.CleanupCondition; import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReconcileCondition; -public class DependentResourceNode { +public class DependentResourceNode { - private final DependentResource dependentResource; + private final DependentResource dependentResource; private ReconcileCondition reconcileCondition; private CleanupCondition cleanupCondition; private List dependsOnRelations = new ArrayList<>(1); - public DependentResourceNode(DependentResource dependentResource) { + public DependentResourceNode(DependentResource dependentResource) { this(dependentResource, null, null); } - public DependentResourceNode(DependentResource dependentResource, + public DependentResourceNode(DependentResource dependentResource, ReconcileCondition reconcileCondition) { this(dependentResource, reconcileCondition, null); } - public DependentResourceNode(DependentResource dependentResource, + public DependentResourceNode(DependentResource dependentResource, ReconcileCondition reconcileCondition, CleanupCondition cleanupCondition) { this.dependentResource = dependentResource; this.reconcileCondition = reconcileCondition; this.cleanupCondition = cleanupCondition; } - public DependentResource getDependentResource() { + public DependentResource getDependentResource() { return dependentResource; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependsOnRelation.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependsOnRelation.java index 00ba210b5c..f05795b305 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependsOnRelation.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependsOnRelation.java @@ -4,8 +4,8 @@ public class DependsOnRelation { - private DependentResourceNode owner; - private DependentResourceNode dependsOn; + private DependentResourceNode owner; + private DependentResourceNode dependsOn; private WaitCondition waitCondition; public DependsOnRelation() {} @@ -34,7 +34,7 @@ public void setWaitCondition(WaitCondition waitCondition) { this.waitCondition = waitCondition; } - public DependentResourceNode getOwner() { + public DependentResourceNode getOwner() { return owner; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java index c9bb84e3a4..037ceb034b 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java @@ -16,31 +16,29 @@ * * @param

primary resource */ -// todo build time graph creation for quarkus public class Workflow

{ - private final List dependentResourceNodes; - - private List topLevelResources = new ArrayList<>(); - private Map> dependents; + private final List> dependentResourceNodes; + private final List> topLevelResources = new ArrayList<>(); + private Map, List>> dependents; // it's "global" executor service shared between multiple reconciliations running parallel private ExecutorService executorService; - public Workflow(List dependentResourceNodes) { + public Workflow(List> dependentResourceNodes) { this.executorService = ConfigurationServiceProvider.instance().getExecutorService(); this.dependentResourceNodes = dependentResourceNodes; preprocessForReconcile(); } - public Workflow(List dependentResourceNodes, + public Workflow(List> dependentResourceNodes, ExecutorService executorService) { this.executorService = executorService; this.dependentResourceNodes = dependentResourceNodes; preprocessForReconcile(); } - public Workflow(List dependentResourceNodes, int globalParallelism) { + public Workflow(List> dependentResourceNodes, int globalParallelism) { this(dependentResourceNodes, Executors.newFixedThreadPool(globalParallelism)); } @@ -57,7 +55,7 @@ public void cleanup(P resource, Context

context) { // add cycle detection? private void preprocessForReconcile() { dependents = new ConcurrentHashMap<>(dependentResourceNodes.size()); - for (DependentResourceNode node : dependentResourceNodes) { + for (DependentResourceNode node : dependentResourceNodes) { if (node.getDependsOnRelations().isEmpty()) { topLevelResources.add(node); } else { @@ -73,11 +71,11 @@ public void setExecutorService(ExecutorService executorService) { this.executorService = executorService; } - List getTopLevelDependentResources() { + List> getTopLevelDependentResources() { return topLevelResources; } - Map> getDependents() { + Map, List>> getDependents() { return dependents; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index 799f4fad73..e03bd81693 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -15,14 +15,16 @@ public class WorkflowReconcileExecutor

{ // todo add log messages private static final Logger log = LoggerFactory.getLogger(WorkflowReconcileExecutor.class); - private Workflow

workflow; + private final Workflow

workflow; - private Set alreadyReconciled = ConcurrentHashMap.newKeySet(); - private Set> actualExecutions = ConcurrentHashMap.newKeySet(); - private Map> nodeToFuture = new ConcurrentHashMap<>(); - private List exceptionsDuringExecution = + private final Set> alreadyReconciled = ConcurrentHashMap.newKeySet(); + private final Set> actualExecutions = ConcurrentHashMap.newKeySet(); + private final Map, Future> nodeToFuture = + new ConcurrentHashMap<>(); + private final List exceptionsDuringExecution = Collections.synchronizedList(new ArrayList<>()); - private Set markedToReconcileAgain = ConcurrentHashMap.newKeySet(); + private final Set> markedToReconcileAgain = + ConcurrentHashMap.newKeySet(); private final P primary; private final Context

context; @@ -34,13 +36,15 @@ public WorkflowReconcileExecutor(Workflow

workflow, P primary, Context

con } public synchronized void reconcile() { - for (DependentResourceNode dependentResourceNode : workflow.getTopLevelDependentResources()) { + for (DependentResourceNode dependentResourceNode : workflow + .getTopLevelDependentResources()) { submitForReconcile(dependentResourceNode); } while (true) { try { this.wait(); if (exceptionsPresent()) { + log.debug("Exception during re"); throw createFinalException(); } if (noMoreExecutionsScheduled()) { @@ -59,11 +63,13 @@ private AggregatedOperatorException createFinalException() { return new AggregatedOperatorException("Exception during workflow.", exceptionsDuringExecution); } - private synchronized boolean alreadyReconciled(DependentResourceNode dependentResourceNode) { + private synchronized boolean alreadyReconciled( + DependentResourceNode dependentResourceNode) { return alreadyReconciled.contains(dependentResourceNode); } - private synchronized boolean allDependOnsReconciled(DependentResourceNode dependentResourceNode) { + private synchronized boolean allDependOnsReconciled( + DependentResourceNode dependentResourceNode) { return dependentResourceNode.getDependsOnRelations().isEmpty() || dependentResourceNode.getDependsOnRelations().stream() .allMatch(relation -> alreadyReconciled(relation.getDependsOn())); @@ -96,7 +102,7 @@ && alreadyReconciled(dependentResourceNode)) { } } - private synchronized void submitForReconcile(DependentResourceNode dependentResourceNode) { + private synchronized void submitForReconcile(DependentResourceNode dependentResourceNode) { log.debug("Submitting for reconcile: {}", dependentResourceNode); if (alreadyReconciled(dependentResourceNode) @@ -113,7 +119,6 @@ private synchronized void submitForReconcile(DependentResourceNode dependentReso return; } - Future nodeFuture = workflow.getExecutorService().submit(new NodeExecutor(dependentResourceNode)); actualExecutions.add(nodeFuture); @@ -121,7 +126,7 @@ private synchronized void submitForReconcile(DependentResourceNode dependentReso log.debug("Submitted to reconcile: {}", dependentResourceNode); } - private synchronized void submitDependents(DependentResourceNode dependentResourceNode) { + private synchronized void submitDependents(DependentResourceNode dependentResourceNode) { if (!exceptionsPresent()) { var dependents = workflow.getDependents().get(dependentResourceNode); if (dependents != null) { @@ -151,7 +156,7 @@ private class NodeExecutor implements Runnable { private final DependentResourceNode dependentResourceNode; - private NodeExecutor(DependentResourceNode dependentResourceNode) { + private NodeExecutor(DependentResourceNode dependentResourceNode) { this.dependentResourceNode = dependentResourceNode; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java index 8e650e5001..c59966dead 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java @@ -12,7 +12,7 @@ public class WorkflowBuilder

{ - private List dependentResourceNodes = new ArrayList<>(); + private List> dependentResourceNodes = new ArrayList<>(); public DependentBuilder

addDependent(DependentResource dependentResource) { DependentResourceNode node = new DependentResourceNode(dependentResource); From ee88a6b7c6196d3deb97526b58fb58caac98bafb Mon Sep 17 00:00:00 2001 From: csviri Date: Tue, 12 Apr 2022 14:28:33 +0200 Subject: [PATCH 18/51] fix: smells --- .../dependent/workflow/DependsOnRelation.java | 20 ++++--------------- .../dependent/workflow/WorkflowTest.java | 15 ++++++-------- 2 files changed, 10 insertions(+), 25 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependsOnRelation.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependsOnRelation.java index f05795b305..52e52d7b24 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependsOnRelation.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependsOnRelation.java @@ -10,35 +10,23 @@ public class DependsOnRelation { public DependsOnRelation() {} - public DependsOnRelation(DependentResourceNode owner, DependentResourceNode dependsOn) { + public DependsOnRelation(DependentResourceNode owner, + DependentResourceNode dependsOn) { this(owner, dependsOn, null); } - public DependsOnRelation(DependentResourceNode owner, DependentResourceNode dependsOn, + public DependsOnRelation(DependentResourceNode owner, DependentResourceNode dependsOn, WaitCondition waitCondition) { this.owner = owner; this.dependsOn = dependsOn; this.waitCondition = waitCondition; } - - public void setOwner(DependentResourceNode owner) { - this.owner = owner; - } - - public void setDependsOn(DependentResourceNode dependsOn) { - this.dependsOn = dependsOn; - } - - public void setWaitCondition(WaitCondition waitCondition) { - this.waitCondition = waitCondition; - } - public DependentResourceNode getOwner() { return owner; } - public DependentResourceNode getDependsOn() { + public DependentResourceNode getDependsOn() { return dependsOn; } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java index 9d468f9a19..1e48f7d8f7 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java @@ -91,9 +91,8 @@ void exceptionHandlingSimpleCases() { var workflow = new WorkflowBuilder() .addDependent(errorDR2).build() .build(); - assertThrows(AggregatedOperatorException.class, () -> { - workflow.reconcile(new TestCustomResource(), null); - }); + assertThrows(AggregatedOperatorException.class, + () -> workflow.reconcile(new TestCustomResource(), null)); assertThat(executionHistory).notReconciled(errorDR2); } @@ -104,9 +103,8 @@ void dependentsOnErroredResourceNotReconciled() { .addDependent(errorDR2).dependsOn(dr1).build() .addDependent(dr2).dependsOn(errorDR2).build() .build(); - assertThrows(AggregatedOperatorException.class, () -> { - workflow.reconcile(new TestCustomResource(), null); - }); + assertThrows(AggregatedOperatorException.class, + () -> workflow.reconcile(new TestCustomResource(), null)); assertThat(executionHistory).reconciled(dr1).notReconciled(errorDR2, dr2); } @@ -118,9 +116,8 @@ void onlyOneDependsOnErroredResourceNotReconciled() { .addDependent(errorDR2).build() .addDependent(dr2).dependsOn(errorDR2).dependsOn(dr1).build() .build(); - assertThrows(AggregatedOperatorException.class, () -> { - workflow.reconcile(new TestCustomResource(), null); - }); + assertThrows(AggregatedOperatorException.class, + () -> workflow.reconcile(new TestCustomResource(), null)); assertThat(executionHistory).notReconciled(dr2); } From e2aba175f14007cf714575586434d195f8d89805 Mon Sep 17 00:00:00 2001 From: csviri Date: Tue, 12 Apr 2022 16:37:30 +0200 Subject: [PATCH 19/51] wip --- .../dependent/workflow/builder/WorkflowBuilder.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java index c59966dead..3f287abf6c 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java @@ -3,9 +3,9 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.config.ConfigurationServiceProvider; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.processing.dependent.workflow.DependentResourceNode; import io.javaoperatorsdk.operator.processing.dependent.workflow.Workflow; @@ -14,8 +14,8 @@ public class WorkflowBuilder

{ private List> dependentResourceNodes = new ArrayList<>(); - public DependentBuilder

addDependent(DependentResource dependentResource) { - DependentResourceNode node = new DependentResourceNode(dependentResource); + public DependentBuilder

addDependent(DependentResource dependentResource) { + DependentResourceNode node = new DependentResourceNode<>(dependentResource); dependentResourceNodes.add(node); return new DependentBuilder<>(this, node); } @@ -32,7 +32,8 @@ DependentResourceNode getNodeByDependentResource(DependentResource depende } public Workflow

build() { - return new Workflow<>(dependentResourceNodes, Executors.newCachedThreadPool()); + return new Workflow<>(dependentResourceNodes, + ConfigurationServiceProvider.instance().getExecutorService()); } public Workflow

build(int parallelism) { From 362f077a386a9aabd69e90b59d2ad7f5840e88d9 Mon Sep 17 00:00:00 2001 From: csviri Date: Wed, 13 Apr 2022 13:31:36 +0200 Subject: [PATCH 20/51] feat: reconcile condition --- .../api/reconciler/UpdateControl.java | 8 +- .../workflow/DependentResourceNode.java | 42 +++- .../dependent/workflow/DependsOnRelation.java | 12 +- .../dependent/workflow/Workflow.java | 8 +- .../workflow/WorkflowReconcileExecutor.java | 188 ++++++++++-------- .../workflow/builder/DependentBuilder.java | 19 +- .../workflow/condition/ReadyCondition.java | 11 + .../condition/ReconcileCondition.java | 9 +- .../workflow/condition/WaitCondition.java | 4 - .../dependent/workflow/ExecutionAssert.java | 47 ++++- .../dependent/workflow/ReconcileRecord.java | 26 +++ .../dependent/workflow/WorkflowTest.java | 112 ++++++++++- 12 files changed, 346 insertions(+), 140 deletions(-) create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReadyCondition.java delete mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/WaitCondition.java create mode 100644 operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ReconcileRecord.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/UpdateControl.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/UpdateControl.java index 62f9bc0cd2..12f392901d 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/UpdateControl.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/UpdateControl.java @@ -3,15 +3,15 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.client.CustomResource; -public class UpdateControl extends BaseControl> { +public class UpdateControl

extends BaseControl> { - private final T resource; + private final P resource; private final boolean updateStatus; private final boolean updateResource; private final boolean patch; private UpdateControl( - T resource, boolean updateStatus, boolean updateResource, boolean patch) { + P resource, boolean updateStatus, boolean updateResource, boolean patch) { if ((updateResource || updateStatus) && resource == null) { throw new IllegalArgumentException("CustomResource cannot be null in case of update"); } @@ -92,7 +92,7 @@ public static UpdateControl noUpdate() { return new UpdateControl<>(null, false, false, false); } - public T getResource() { + public P getResource() { return resource; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java index e33b7f7013..d021d2a16b 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java @@ -7,14 +7,16 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.CleanupCondition; +import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReadyCondition; import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReconcileCondition; public class DependentResourceNode { private final DependentResource dependentResource; - private ReconcileCondition reconcileCondition; + private ReconcileCondition

reconcileCondition; private CleanupCondition cleanupCondition; - private List dependsOnRelations = new ArrayList<>(1); + private ReadyCondition readyCondition; + private List dependsOn = new ArrayList<>(1); public DependentResourceNode(DependentResource dependentResource) { this(dependentResource, null, null); @@ -26,7 +28,7 @@ public DependentResourceNode(DependentResource dependentResource, } public DependentResourceNode(DependentResource dependentResource, - ReconcileCondition reconcileCondition, CleanupCondition cleanupCondition) { + ReconcileCondition

reconcileCondition, CleanupCondition cleanupCondition) { this.dependentResource = dependentResource; this.reconcileCondition = reconcileCondition; this.cleanupCondition = cleanupCondition; @@ -36,7 +38,7 @@ public DependentResource getDependentResource() { return dependentResource; } - public Optional getReconcileCondition() { + public Optional> getReconcileCondition() { return Optional.ofNullable(reconcileCondition); } @@ -44,16 +46,16 @@ public Optional getCleanupCondition() { return Optional.ofNullable(cleanupCondition); } - public void setDependsOnRelations(List dependsOnRelations) { - this.dependsOnRelations = dependsOnRelations; + public void setDependsOn(List dependsOn) { + this.dependsOn = dependsOn; } - public List getDependsOnRelations() { - return dependsOnRelations; + public List getDependsOn() { + return dependsOn; } - public void addDependsOnRelation(DependsOnRelation dependsOnRelation) { - dependsOnRelations.add(dependsOnRelation); + public void addDependsOnRelation(DependentResourceNode node) { + dependsOn.add(node); } @Override @@ -62,4 +64,24 @@ public String toString() { "dependentResource=" + dependentResource + '}'; } + + public DependentResourceNode setReconcileCondition( + ReconcileCondition

reconcileCondition) { + this.reconcileCondition = reconcileCondition; + return this; + } + + public DependentResourceNode setCleanupCondition(CleanupCondition cleanupCondition) { + this.cleanupCondition = cleanupCondition; + return this; + } + + public ReadyCondition getReadyCondition() { + return readyCondition; + } + + public DependentResourceNode setReadyCondition(ReadyCondition readyCondition) { + this.readyCondition = readyCondition; + return this; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependsOnRelation.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependsOnRelation.java index 52e52d7b24..2e6a8d97f8 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependsOnRelation.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependsOnRelation.java @@ -1,12 +1,12 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow; -import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.WaitCondition; +import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReadyCondition; public class DependsOnRelation { private DependentResourceNode owner; private DependentResourceNode dependsOn; - private WaitCondition waitCondition; + private ReadyCondition readyCondition; public DependsOnRelation() {} @@ -16,10 +16,10 @@ public DependsOnRelation(DependentResourceNode owner, } public DependsOnRelation(DependentResourceNode owner, DependentResourceNode dependsOn, - WaitCondition waitCondition) { + ReadyCondition readyCondition) { this.owner = owner; this.dependsOn = dependsOn; - this.waitCondition = waitCondition; + this.readyCondition = readyCondition; } public DependentResourceNode getOwner() { @@ -30,7 +30,7 @@ public DependsOnRelation(DependentResourceNode owner, DependentResourceNod return dependsOn; } - public WaitCondition getWaitCondition() { - return waitCondition; + public ReadyCondition getWaitCondition() { + return readyCondition; } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java index 037ceb034b..69374eb6dc 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java @@ -56,12 +56,12 @@ public void cleanup(P resource, Context

context) { private void preprocessForReconcile() { dependents = new ConcurrentHashMap<>(dependentResourceNodes.size()); for (DependentResourceNode node : dependentResourceNodes) { - if (node.getDependsOnRelations().isEmpty()) { + if (node.getDependsOn().isEmpty()) { topLevelResources.add(node); } else { - for (DependsOnRelation relation : node.getDependsOnRelations()) { - dependents.computeIfAbsent(relation.getDependsOn(), dr -> new ArrayList<>()); - dependents.get(relation.getDependsOn()).add(relation.getOwner()); + for (DependentResourceNode dependsOn : node.getDependsOn()) { + dependents.computeIfAbsent(dependsOn, dr -> new ArrayList<>()); + dependents.get(dependsOn).add(node); } } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index e03bd81693..de05e6d103 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -9,22 +9,24 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.AggregatedOperatorException; import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; +import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReconcileCondition; public class WorkflowReconcileExecutor

{ - // todo add log messages private static final Logger log = LoggerFactory.getLogger(WorkflowReconcileExecutor.class); private final Workflow

workflow; private final Set> alreadyReconciled = ConcurrentHashMap.newKeySet(); - private final Set> actualExecutions = ConcurrentHashMap.newKeySet(); - private final Map, Future> nodeToFuture = + private final Set> errored = ConcurrentHashMap.newKeySet(); + private final Set> reconcileConditionOrParentsConditionNotMet = + ConcurrentHashMap.newKeySet(); + + private final Map, Future> actualExecutions = new ConcurrentHashMap<>(); private final List exceptionsDuringExecution = Collections.synchronizedList(new ArrayList<>()); - private final Set> markedToReconcileAgain = - ConcurrentHashMap.newKeySet(); private final P primary; private final Context

context; @@ -35,16 +37,17 @@ public WorkflowReconcileExecutor(Workflow

workflow, P primary, Context

con this.workflow = workflow; } + // add reconcile results public synchronized void reconcile() { for (DependentResourceNode dependentResourceNode : workflow .getTopLevelDependentResources()) { - submitForReconcile(dependentResourceNode); + handleReconcileOrDelete(dependentResourceNode, false); } while (true) { try { this.wait(); - if (exceptionsPresent()) { - log.debug("Exception during re"); + if (!exceptionsDuringExecution.isEmpty()) { + log.debug("Exception during reconciliation for: {}", primary); throw createFinalException(); } if (noMoreExecutionsScheduled()) { @@ -59,122 +62,135 @@ public synchronized void reconcile() { } } - private AggregatedOperatorException createFinalException() { - return new AggregatedOperatorException("Exception during workflow.", exceptionsDuringExecution); - } - - private synchronized boolean alreadyReconciled( - DependentResourceNode dependentResourceNode) { - return alreadyReconciled.contains(dependentResourceNode); - } - - private synchronized boolean allDependOnsReconciled( - DependentResourceNode dependentResourceNode) { - return dependentResourceNode.getDependsOnRelations().isEmpty() - || dependentResourceNode.getDependsOnRelations().stream() - .allMatch(relation -> alreadyReconciled(relation.getDependsOn())); - } - - private synchronized void handleNodeExecutionFinish(DependentResourceNode dependentResourceNode) { - var future = nodeToFuture.remove(dependentResourceNode); - actualExecutions.remove(future); - - if (exceptionsPresent()) { - if (actualExecutions.isEmpty()) { - notifyMainReconcile(); - } - return; - } - if (markedToReconcileAgain.contains(dependentResourceNode) - && alreadyReconciled(dependentResourceNode)) { - log.warn("Marked to reconcile but already reconciled, this should not happen. DR: {}", - dependentResourceNode); - } - - if (markedToReconcileAgain.contains(dependentResourceNode) - && !alreadyReconciled(dependentResourceNode)) { - markedToReconcileAgain.remove(dependentResourceNode); - log.debug("Submitting marked resource to reconcile: {}", dependentResourceNode); - submitForReconcile(dependentResourceNode); - } - if (actualExecutions.isEmpty()) { - notifyMainReconcile(); - } - } - - private synchronized void submitForReconcile(DependentResourceNode dependentResourceNode) { + private synchronized void handleReconcileOrDelete( + DependentResourceNode dependentResourceNode, + boolean onlyReconcileForPossibleDelete) { log.debug("Submitting for reconcile: {}", dependentResourceNode); if (alreadyReconciled(dependentResourceNode) - || !allDependOnsReconciled(dependentResourceNode) - || exceptionsPresent()) { + || isReconcilingNow(dependentResourceNode) + || !allDependsReconciled(dependentResourceNode) + || hasErroredDependOn(dependentResourceNode)) { log.debug("Skipping submit of: {}, ", dependentResourceNode); return; } - if (nodeToFuture.containsKey(dependentResourceNode)) { - log.debug("The same dependent resource already bein reconciled," + - " marking it for future reconciliation: {}", dependentResourceNode); - markedToReconcileAgain.add(dependentResourceNode); - return; + if (onlyReconcileForPossibleDelete) { + reconcileConditionOrParentsConditionNotMet.add(dependentResourceNode); + } else if (dependentResourceNode.getReconcileCondition().isPresent()) { + handleReconcileCondition(dependentResourceNode); } Future nodeFuture = - workflow.getExecutorService().submit(new NodeExecutor(dependentResourceNode)); - actualExecutions.add(nodeFuture); - nodeToFuture.put(dependentResourceNode, nodeFuture); + workflow.getExecutorService().submit( + new NodeExecutor(dependentResourceNode, + ownOrParentsReconcileConditionNotMet(dependentResourceNode))); + actualExecutions.put(dependentResourceNode, nodeFuture); log.debug("Submitted to reconcile: {}", dependentResourceNode); } - private synchronized void submitDependents(DependentResourceNode dependentResourceNode) { - if (!exceptionsPresent()) { - var dependents = workflow.getDependents().get(dependentResourceNode); - if (dependents != null) { - dependents.forEach(this::submitForReconcile); - } - } - } - private synchronized void handleExceptionInExecutor(RuntimeException e) { + private synchronized void handleExceptionInExecutor(DependentResourceNode dependentResourceNode, + RuntimeException e) { exceptionsDuringExecution.add(e); - markedToReconcileAgain.clear(); - } - - private boolean exceptionsPresent() { - return !exceptionsDuringExecution.isEmpty(); + errored.add(dependentResourceNode); } - private boolean noMoreExecutionsScheduled() { - return actualExecutions.isEmpty() && markedToReconcileAgain.isEmpty(); + private synchronized void handleNodeExecutionFinish(DependentResourceNode dependentResourceNode) { + actualExecutions.remove(dependentResourceNode); + if (actualExecutions.isEmpty()) { + this.notifyAll(); + } } - private synchronized void notifyMainReconcile() { - this.notifyAll(); + private boolean ownOrParentsReconcileConditionNotMet( + DependentResourceNode dependentResourceNode) { + return reconcileConditionOrParentsConditionNotMet.contains(dependentResourceNode) || + dependentResourceNode.getDependsOn().stream() + .anyMatch(dependsOnRelation -> reconcileConditionOrParentsConditionNotMet + .contains(dependsOnRelation.getDependsOn())); } private class NodeExecutor implements Runnable { private final DependentResourceNode dependentResourceNode; + private final boolean onlyReconcileForPossibleDelete; - private NodeExecutor(DependentResourceNode dependentResourceNode) { + private NodeExecutor(DependentResourceNode dependentResourceNode, + boolean onlyReconcileForDelete) { this.dependentResourceNode = dependentResourceNode; + this.onlyReconcileForPossibleDelete = onlyReconcileForDelete; } @Override @SuppressWarnings("unchecked") public void run() { try { - if (exceptionsPresent()) { - return; + var dependentResource = dependentResourceNode.getDependentResource(); + if (onlyReconcileForPossibleDelete) { + if (dependentResource instanceof Deleter) { + ((Deleter

) dependentResource).delete(primary, context); + } + } else { + dependentResource.reconcile(primary, context); } - dependentResourceNode.getDependentResource().reconcile(primary, context); alreadyReconciled.add(dependentResourceNode); - submitDependents(dependentResourceNode); + handleDependentsReconcile(dependentResourceNode, onlyReconcileForPossibleDelete); } catch (RuntimeException e) { - handleExceptionInExecutor(e); + handleExceptionInExecutor(dependentResourceNode, e); } finally { handleNodeExecutionFinish(dependentResourceNode); } } } + + private boolean isReconcilingNow(DependentResourceNode dependentResourceNode) { + return actualExecutions.containsKey(dependentResourceNode); + } + + private synchronized void handleDependentsReconcile( + DependentResourceNode dependentResourceNode, boolean onlyReconcileForPossibleDelete) { + var dependents = workflow.getDependents().get(dependentResourceNode); + if (dependents != null) { + dependents.forEach(d -> handleReconcileOrDelete(d, onlyReconcileForPossibleDelete)); + } + } + + private boolean noMoreExecutionsScheduled() { + return actualExecutions.isEmpty(); + } + + private AggregatedOperatorException createFinalException() { + return new AggregatedOperatorException("Exception during workflow.", exceptionsDuringExecution); + } + + private boolean alreadyReconciled( + DependentResourceNode dependentResourceNode) { + return alreadyReconciled.contains(dependentResourceNode); + } + + + private void handleReconcileCondition(DependentResourceNode dependentResourceNode) { + ReconcileCondition

reconcileCondition = + (ReconcileCondition

) dependentResourceNode.getReconcileCondition().get(); + boolean conditionMet = + reconcileCondition.isMet(dependentResourceNode.getDependentResource(), primary, context); + if (!conditionMet) { + reconcileConditionOrParentsConditionNotMet.add(dependentResourceNode); + } + } + + private boolean allDependsReconciled( + DependentResourceNode dependentResourceNode) { + return dependentResourceNode.getDependsOn().isEmpty() + || dependentResourceNode.getDependsOn().stream() + .allMatch(this::alreadyReconciled); + } + + private boolean hasErroredDependOn( + DependentResourceNode dependentResourceNode) { + return !dependentResourceNode.getDependsOn().isEmpty() + && dependentResourceNode.getDependsOn().stream() + .anyMatch(dependsOnRelation -> errored.contains(dependsOnRelation.getDependsOn())); + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java index d2b9f02003..53c70cfa91 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java @@ -3,21 +3,28 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.processing.dependent.workflow.DependentResourceNode; -import io.javaoperatorsdk.operator.processing.dependent.workflow.DependsOnRelation; +import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReconcileCondition; public class DependentBuilder

{ private final WorkflowBuilder

workflowBuilder; - private final DependentResourceNode node; + private final DependentResourceNode node; - public DependentBuilder(WorkflowBuilder

workflowBuilder, DependentResourceNode node) { + public DependentBuilder(WorkflowBuilder

workflowBuilder, DependentResourceNode node) { this.workflowBuilder = workflowBuilder; this.node = node; } - public DependentBuilder

dependsOn(DependentResource dependentResource) { - var dependsOn = workflowBuilder.getNodeByDependentResource(dependentResource); - node.addDependsOnRelation(new DependsOnRelation(node, dependsOn, null)); + public DependentBuilder

dependsOn(DependentResource... dependentResources) { + for (var dependentResource : dependentResources) { + var dependsOn = workflowBuilder.getNodeByDependentResource(dependentResource); + node.addDependsOnRelation(dependsOn); + } + return this; + } + + public DependentBuilder

withReconcileCondition(ReconcileCondition reconcileCondition) { + node.setReconcileCondition(reconcileCondition); return this; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReadyCondition.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReadyCondition.java new file mode 100644 index 0000000000..0cd14dfa45 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReadyCondition.java @@ -0,0 +1,11 @@ +package io.javaoperatorsdk.operator.processing.dependent.workflow.condition; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; + +public interface ReadyCondition { + + void isMet(DependentResource dependentResource, P primary, Context

context); + +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReconcileCondition.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReconcileCondition.java index 23235a8977..bafb2516a6 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReconcileCondition.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReconcileCondition.java @@ -1,4 +1,11 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow.condition; -public class ReconcileCondition { +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; + +public interface ReconcileCondition

{ + + boolean isMet(DependentResource dependentResource, P primary, Context

context); + } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/WaitCondition.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/WaitCondition.java deleted file mode 100644 index 54206ae22f..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/WaitCondition.java +++ /dev/null @@ -1,4 +0,0 @@ -package io.javaoperatorsdk.operator.processing.dependent.workflow.condition; - -public class WaitCondition { -} diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionAssert.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionAssert.java index abe87867e2..4669a753c1 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionAssert.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionAssert.java @@ -1,31 +1,60 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow; import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; import org.assertj.core.api.AbstractAssert; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; public class ExecutionAssert - extends AbstractAssert>> { + extends AbstractAssert> { - public ExecutionAssert(List> dependentResources) { - super(dependentResources, ExecutionAssert.class); + public ExecutionAssert(List reconcileRecords) { + super(reconcileRecords, ExecutionAssert.class); } - public static ExecutionAssert assertThat(List> actual) { + public static ExecutionAssert assertThat(List actual) { return new ExecutionAssert(actual); } public ExecutionAssert reconciled(DependentResource... dependentResources) { for (int i = 0; i < dependentResources.length; i++) { - if (!actual.contains(dependentResources[i])) { + var rr = getReconcileRecordFor(dependentResources[i]); + if (rr.isEmpty()) { failWithMessage("Resource not reconciled: %s with index %d", dependentResources, i); + } else { + if (rr.get().isDeleted()) { + failWithMessage("Resource deleted: %s with index %d", dependentResources, i); + } } } return this; } + public ExecutionAssert deleted(DependentResource... dependentResources) { + for (int i = 0; i < dependentResources.length; i++) { + var rr = getReconcileRecordFor(dependentResources[i]); + if (rr.isEmpty()) { + failWithMessage("Resource not reconciled: %s with index %d", dependentResources, i); + } else { + if (!rr.get().isDeleted()) { + failWithMessage("Resource not deleted: %s with index %d", dependentResources, i); + } + } + } + return this; + } + + private List getActualDependentResources() { + return actual.stream().map(rr -> rr.getDependentResource()).collect(Collectors.toList()); + } + + private Optional getReconcileRecordFor(DependentResource dependentResource) { + return actual.stream().filter(rr -> rr.getDependentResource() == dependentResource).findFirst(); + } + public ExecutionAssert reconciledInOrder(DependentResource... dependentResources) { if (dependentResources.length < 2) { throw new IllegalArgumentException("At least two dependent resource needs to be specified"); @@ -33,7 +62,9 @@ public ExecutionAssert reconciledInOrder(DependentResource... dependentRes for (int i = 0; i < dependentResources.length - 1; i++) { checkIfReconciled(i, dependentResources); checkIfReconciled(i + 1, dependentResources); - if (actual.indexOf(dependentResources[i]) > actual.indexOf(dependentResources[i + 1])) { + if (getActualDependentResources() + .indexOf(dependentResources[i]) > getActualDependentResources() + .indexOf(dependentResources[i + 1])) { failWithMessage( "Dependent resource on index %d reconciled after the one on index %d", i, i + 1); } @@ -44,7 +75,7 @@ public ExecutionAssert reconciledInOrder(DependentResource... dependentRes public ExecutionAssert notReconciled(DependentResource... dependentResources) { for (int i = 0; i < dependentResources.length - 1; i++) { - if (!actual.contains(dependentResources[i])) { + if (!getActualDependentResources().contains(dependentResources[i])) { failWithMessage("Resource was reconciled: %s with index %d", dependentResources, i); } } @@ -52,7 +83,7 @@ public ExecutionAssert notReconciled(DependentResource... dependentResourc } private void checkIfReconciled(int i, DependentResource[] dependentResources) { - if (!actual.contains(dependentResources[i])) { + if (!getActualDependentResources().contains(dependentResources[i])) { failWithMessage("Dependent resource not reconciled on place %i", i); } } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ReconcileRecord.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ReconcileRecord.java new file mode 100644 index 0000000000..66e0b82d59 --- /dev/null +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ReconcileRecord.java @@ -0,0 +1,26 @@ +package io.javaoperatorsdk.operator.processing.dependent.workflow; + +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; + +public class ReconcileRecord { + + private DependentResource dependentResource; + private final boolean deleted; + + public ReconcileRecord(DependentResource dependentResource) { + this(dependentResource, false); + } + + public ReconcileRecord(DependentResource dependentResource, boolean deleted) { + this.dependentResource = dependentResource; + this.deleted = deleted; + } + + public DependentResource getDependentResource() { + return dependentResource; + } + + public boolean isDeleted() { + return deleted; + } +} diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java index 1e48f7d8f7..cf0e258177 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java @@ -9,9 +9,11 @@ import io.javaoperatorsdk.operator.AggregatedOperatorException; import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; import io.javaoperatorsdk.operator.processing.dependent.workflow.builder.WorkflowBuilder; +import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReconcileCondition; import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; import static io.javaoperatorsdk.operator.processing.dependent.workflow.ExecutionAssert.*; @@ -19,13 +21,19 @@ class WorkflowTest { + private ReconcileCondition met_reconcile_condition = + (dependentResource, primary, context) -> true; + private ReconcileCondition not_met_reconcile_condition = + (dependentResource, primary, context) -> false; + public static final String VALUE = "value"; - private List> executionHistory = + private List executionHistory = Collections.synchronizedList(new ArrayList<>()); TestDependent dr1 = new TestDependent("DR_1"); TestDependent dr2 = new TestDependent("DR_2"); - TestErrorDependent errorDR2 = new TestErrorDependent("ERROR_1"); + TestDeleterDependent drDeleter = new TestDeleterDependent("DR_DELETER"); + TestErrorDependent drError = new TestErrorDependent("ERROR_1"); @Test void reconcileTopLevelResources() { @@ -89,32 +97,32 @@ void diamondShareWorkflowReconcile() { @Test void exceptionHandlingSimpleCases() { var workflow = new WorkflowBuilder() - .addDependent(errorDR2).build() + .addDependent(drError).build() .build(); assertThrows(AggregatedOperatorException.class, () -> workflow.reconcile(new TestCustomResource(), null)); - assertThat(executionHistory).notReconciled(errorDR2); + assertThat(executionHistory).notReconciled(drError); } @Test void dependentsOnErroredResourceNotReconciled() { var workflow = new WorkflowBuilder() .addDependent(dr1).build() - .addDependent(errorDR2).dependsOn(dr1).build() - .addDependent(dr2).dependsOn(errorDR2).build() + .addDependent(drError).dependsOn(dr1).build() + .addDependent(dr2).dependsOn(drError).build() .build(); assertThrows(AggregatedOperatorException.class, () -> workflow.reconcile(new TestCustomResource(), null)); - assertThat(executionHistory).reconciled(dr1).notReconciled(errorDR2, dr2); + assertThat(executionHistory).reconciled(dr1).notReconciled(drError, dr2); } @Test void onlyOneDependsOnErroredResourceNotReconciled() { var workflow = new WorkflowBuilder() .addDependent(dr1).build() - .addDependent(errorDR2).build() - .addDependent(dr2).dependsOn(errorDR2).dependsOn(dr1).build() + .addDependent(drError).build() + .addDependent(dr2).dependsOn(drError).dependsOn(dr1).build() .build(); assertThrows(AggregatedOperatorException.class, () -> workflow.reconcile(new TestCustomResource(), null)); @@ -122,6 +130,76 @@ void onlyOneDependsOnErroredResourceNotReconciled() { assertThat(executionHistory).notReconciled(dr2); } + @Test + void simpleReconcileCondition() { + var workflow = new WorkflowBuilder() + .addDependent(dr1).withReconcileCondition(not_met_reconcile_condition).build() + .addDependent(dr2).withReconcileCondition(met_reconcile_condition).build() + .addDependent(drDeleter).withReconcileCondition(not_met_reconcile_condition).build() + .build(); + + workflow.reconcile(new TestCustomResource(), null); + + assertThat(executionHistory).notReconciled(dr1); + assertThat(executionHistory).reconciled(dr2); + assertThat(executionHistory).deleted(drDeleter); + } + + @Test + void reconcileConditionTransitiveDelete() { + TestDeleterDependent drDeleter2 = new TestDeleterDependent("DR_DELETER_2"); + + var workflow = new WorkflowBuilder() + .addDependent(dr1).build() + .addDependent(dr2).withReconcileCondition(not_met_reconcile_condition).dependsOn(dr1) + .build() + .addDependent(drDeleter).withReconcileCondition(met_reconcile_condition).dependsOn(dr2) + .build() + .addDependent(drDeleter2).withReconcileCondition(met_reconcile_condition) + .dependsOn(drDeleter).build() + .build(); + + workflow.reconcile(new TestCustomResource(), null); + + assertThat(executionHistory).notReconciled(dr2); + assertThat(executionHistory).reconciledInOrder(dr1, drDeleter, drDeleter2); + assertThat(executionHistory).deleted(drDeleter, drDeleter2); + } + + @Test + void reconcileConditionAlsoErrorDependsOn() { + TestDeleterDependent drDeleter2 = new TestDeleterDependent("DR_DELETER_2"); + + var workflow = new WorkflowBuilder() + .addDependent(drError).build() + .addDependent(drDeleter).withReconcileCondition(not_met_reconcile_condition).build() + .addDependent(drDeleter2).withReconcileCondition(met_reconcile_condition) + .dependsOn(drError, drDeleter).build() + .build(); + + assertThrows(AggregatedOperatorException.class, + () -> workflow.reconcile(new TestCustomResource(), null)); + + assertThat(executionHistory).deleted(drDeleter); + assertThat(executionHistory).reconciled(drError); + assertThat(executionHistory).notReconciled(drDeleter2); + } + + @Test + void oneDependsOnConditionNotMet() { + var workflow = new WorkflowBuilder() + .addDependent(dr1).build() + .addDependent(dr2).withReconcileCondition(not_met_reconcile_condition).build() + .addDependent(drDeleter).dependsOn(dr1, dr2).build() + .build(); + + workflow.reconcile(new TestCustomResource(), null); + + assertThat(executionHistory).deleted(drDeleter); + assertThat(executionHistory).reconciledInOrder(dr1, drDeleter); + assertThat(executionHistory).notReconciled(dr2); + } + private class TestDependent implements DependentResource { private String name; @@ -133,7 +211,7 @@ public TestDependent(String name) { @Override public ReconcileResult reconcile(TestCustomResource primary, Context context) { - executionHistory.add(this); + executionHistory.add(new ReconcileRecord(this)); return ReconcileResult.resourceCreated(VALUE); } @@ -153,6 +231,18 @@ public String toString() { } } + private class TestDeleterDependent extends TestDependent implements Deleter { + + public TestDeleterDependent(String name) { + super(name); + } + + @Override + public void delete(TestCustomResource primary, Context context) { + executionHistory.add(new ReconcileRecord(this, true)); + } + } + private class TestErrorDependent implements DependentResource { private String name; @@ -163,7 +253,7 @@ public TestErrorDependent(String name) { @Override public ReconcileResult reconcile(TestCustomResource primary, Context context) { - executionHistory.add(this); + executionHistory.add(new ReconcileRecord(this)); throw new IllegalStateException("Test exception"); } From b10bc3900afb1901804c783d370e9f0d170b64b4 Mon Sep 17 00:00:00 2001 From: csviri Date: Wed, 13 Apr 2022 13:50:23 +0200 Subject: [PATCH 21/51] fix: tests --- .../workflow/WorkflowReconcileExecutor.java | 16 ++++---- .../dependent/workflow/ExecutionAssert.java | 4 +- .../dependent/workflow/WorkflowTest.java | 37 ++++++++++++++++++- 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index de05e6d103..972ea6da53 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -77,8 +77,10 @@ private synchronized void handleReconcileOrDelete( if (onlyReconcileForPossibleDelete) { reconcileConditionOrParentsConditionNotMet.add(dependentResourceNode); - } else if (dependentResourceNode.getReconcileCondition().isPresent()) { - handleReconcileCondition(dependentResourceNode); + } else { + dependentResourceNode.getReconcileCondition() + .ifPresent(reconcileCondition -> handleReconcileCondition(dependentResourceNode, + reconcileCondition)); } Future nodeFuture = @@ -107,8 +109,7 @@ private boolean ownOrParentsReconcileConditionNotMet( DependentResourceNode dependentResourceNode) { return reconcileConditionOrParentsConditionNotMet.contains(dependentResourceNode) || dependentResourceNode.getDependsOn().stream() - .anyMatch(dependsOnRelation -> reconcileConditionOrParentsConditionNotMet - .contains(dependsOnRelation.getDependsOn())); + .anyMatch(reconcileConditionOrParentsConditionNotMet::contains); } private class NodeExecutor implements Runnable { @@ -170,9 +171,8 @@ private boolean alreadyReconciled( } - private void handleReconcileCondition(DependentResourceNode dependentResourceNode) { - ReconcileCondition

reconcileCondition = - (ReconcileCondition

) dependentResourceNode.getReconcileCondition().get(); + private void handleReconcileCondition(DependentResourceNode dependentResourceNode, + ReconcileCondition reconcileCondition) { boolean conditionMet = reconcileCondition.isMet(dependentResourceNode.getDependentResource(), primary, context); if (!conditionMet) { @@ -191,6 +191,6 @@ private boolean hasErroredDependOn( DependentResourceNode dependentResourceNode) { return !dependentResourceNode.getDependsOn().isEmpty() && dependentResourceNode.getDependsOn().stream() - .anyMatch(dependsOnRelation -> errored.contains(dependsOnRelation.getDependsOn())); + .anyMatch(errored::contains); } } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionAssert.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionAssert.java index 4669a753c1..c963419102 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionAssert.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionAssert.java @@ -74,8 +74,8 @@ public ExecutionAssert reconciledInOrder(DependentResource... dependentRes } public ExecutionAssert notReconciled(DependentResource... dependentResources) { - for (int i = 0; i < dependentResources.length - 1; i++) { - if (!getActualDependentResources().contains(dependentResources[i])) { + for (int i = 0; i < dependentResources.length; i++) { + if (getActualDependentResources().contains(dependentResources[i])) { failWithMessage("Resource was reconciled: %s with index %d", dependentResources, i); } } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java index cf0e258177..1793de38c5 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java @@ -101,7 +101,7 @@ void exceptionHandlingSimpleCases() { .build(); assertThrows(AggregatedOperatorException.class, () -> workflow.reconcile(new TestCustomResource(), null)); - assertThat(executionHistory).notReconciled(drError); + assertThat(executionHistory).reconciled(drError); } @Test @@ -114,7 +114,25 @@ void dependentsOnErroredResourceNotReconciled() { assertThrows(AggregatedOperatorException.class, () -> workflow.reconcile(new TestCustomResource(), null)); - assertThat(executionHistory).reconciled(dr1).notReconciled(drError, dr2); + assertThat(executionHistory).reconciled(dr1, drError).notReconciled(dr2); + } + + @Test + void oneBranchErrorsOtherCompletes() { + TestDependent dr3 = new TestDependent("DR_3"); + + var workflow = new WorkflowBuilder() + .addDependent(dr1).build() + .addDependent(drError).dependsOn(dr1).build() + .addDependent(dr2).dependsOn(dr1).build() + .addDependent(dr3).dependsOn(dr2).build() + .build(); + + assertThrows(AggregatedOperatorException.class, + () -> workflow.reconcile(new TestCustomResource(), null)); + + assertThat(executionHistory).reconciledInOrder(dr1, dr2, dr3); + assertThat(executionHistory).reconciledInOrder(dr1, drError); } @Test @@ -145,6 +163,21 @@ void simpleReconcileCondition() { assertThat(executionHistory).deleted(drDeleter); } + @Test + void triangleOnceConditionNotMet() { + var workflow = new WorkflowBuilder() + .addDependent(dr1).build() + .addDependent(dr2).dependsOn(dr1).build() + .addDependent(drDeleter).withReconcileCondition(not_met_reconcile_condition).dependsOn(dr1) + .build() + .build(); + + workflow.reconcile(new TestCustomResource(), null); + + assertThat(executionHistory).reconciledInOrder(dr1, dr2); + assertThat(executionHistory).deleted(drDeleter); + } + @Test void reconcileConditionTransitiveDelete() { TestDeleterDependent drDeleter2 = new TestDeleterDependent("DR_DELETER_2"); From 3de8f8dea428d7d6482924585ed19ce9a2cdebb3 Mon Sep 17 00:00:00 2001 From: csviri Date: Wed, 13 Apr 2022 15:36:29 +0200 Subject: [PATCH 22/51] fix: ready impl no tests --- .../workflow/DependentResourceNode.java | 6 +-- .../dependent/workflow/Workflow.java | 20 +++++----- .../workflow/WorkflowReconcileExecutor.java | 39 ++++++++++++------- .../workflow/builder/DependentBuilder.java | 6 +++ .../workflow/builder/WorkflowBuilder.java | 6 +-- .../workflow/condition/ReadyCondition.java | 2 +- 6 files changed, 49 insertions(+), 30 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java index d021d2a16b..ba1b94846b 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java @@ -23,7 +23,7 @@ public DependentResourceNode(DependentResource dependentResource) { } public DependentResourceNode(DependentResource dependentResource, - ReconcileCondition reconcileCondition) { + ReconcileCondition

reconcileCondition) { this(dependentResource, reconcileCondition, null); } @@ -76,8 +76,8 @@ public DependentResourceNode setCleanupCondition(CleanupCondition cleanupC return this; } - public ReadyCondition getReadyCondition() { - return readyCondition; + public Optional> getReadyCondition() { + return Optional.ofNullable(readyCondition); } public DependentResourceNode setReadyCondition(ReadyCondition readyCondition) { diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java index 69374eb6dc..9ee603a821 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java @@ -18,27 +18,27 @@ */ public class Workflow

{ - private final List> dependentResourceNodes; - private final List> topLevelResources = new ArrayList<>(); - private Map, List>> dependents; + private final List> dependentResourceNodes; + private final List> topLevelResources = new ArrayList<>(); + private Map, List>> dependents; // it's "global" executor service shared between multiple reconciliations running parallel private ExecutorService executorService; - public Workflow(List> dependentResourceNodes) { + public Workflow(List> dependentResourceNodes) { this.executorService = ConfigurationServiceProvider.instance().getExecutorService(); this.dependentResourceNodes = dependentResourceNodes; preprocessForReconcile(); } - public Workflow(List> dependentResourceNodes, + public Workflow(List> dependentResourceNodes, ExecutorService executorService) { this.executorService = executorService; this.dependentResourceNodes = dependentResourceNodes; preprocessForReconcile(); } - public Workflow(List> dependentResourceNodes, int globalParallelism) { + public Workflow(List> dependentResourceNodes, int globalParallelism) { this(dependentResourceNodes, Executors.newFixedThreadPool(globalParallelism)); } @@ -55,11 +55,11 @@ public void cleanup(P resource, Context

context) { // add cycle detection? private void preprocessForReconcile() { dependents = new ConcurrentHashMap<>(dependentResourceNodes.size()); - for (DependentResourceNode node : dependentResourceNodes) { + for (DependentResourceNode node : dependentResourceNodes) { if (node.getDependsOn().isEmpty()) { topLevelResources.add(node); } else { - for (DependentResourceNode dependsOn : node.getDependsOn()) { + for (DependentResourceNode dependsOn : node.getDependsOn()) { dependents.computeIfAbsent(dependsOn, dr -> new ArrayList<>()); dependents.get(dependsOn).add(node); } @@ -71,11 +71,11 @@ public void setExecutorService(ExecutorService executorService) { this.executorService = executorService; } - List> getTopLevelDependentResources() { + List> getTopLevelDependentResources() { return topLevelResources; } - Map, List>> getDependents() { + Map, List>> getDependents() { return dependents; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index 972ea6da53..457ce67cd4 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -10,6 +10,7 @@ import io.javaoperatorsdk.operator.AggregatedOperatorException; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReconcileCondition; public class WorkflowReconcileExecutor

{ @@ -20,6 +21,8 @@ public class WorkflowReconcileExecutor

{ private final Set> alreadyReconciled = ConcurrentHashMap.newKeySet(); private final Set> errored = ConcurrentHashMap.newKeySet(); + private final Set> reconciledButNotReady = + ConcurrentHashMap.newKeySet(); private final Set> reconcileConditionOrParentsConditionNotMet = ConcurrentHashMap.newKeySet(); @@ -39,9 +42,9 @@ public WorkflowReconcileExecutor(Workflow

workflow, P primary, Context

con // add reconcile results public synchronized void reconcile() { - for (DependentResourceNode dependentResourceNode : workflow + for (DependentResourceNode dependentResourceNode : workflow .getTopLevelDependentResources()) { - handleReconcileOrDelete(dependentResourceNode, false); + handleReconcile(dependentResourceNode, false); } while (true) { try { @@ -62,14 +65,14 @@ public synchronized void reconcile() { } } - private synchronized void handleReconcileOrDelete( - DependentResourceNode dependentResourceNode, + private synchronized void handleReconcile( + DependentResourceNode dependentResourceNode, boolean onlyReconcileForPossibleDelete) { log.debug("Submitting for reconcile: {}", dependentResourceNode); if (alreadyReconciled(dependentResourceNode) || isReconcilingNow(dependentResourceNode) - || !allDependsReconciled(dependentResourceNode) + || !allDependsReconciledAndReady(dependentResourceNode) || hasErroredDependOn(dependentResourceNode)) { log.debug("Skipping submit of: {}, ", dependentResourceNode); return; @@ -114,10 +117,10 @@ private boolean ownOrParentsReconcileConditionNotMet( private class NodeExecutor implements Runnable { - private final DependentResourceNode dependentResourceNode; + private final DependentResourceNode dependentResourceNode; private final boolean onlyReconcileForPossibleDelete; - private NodeExecutor(DependentResourceNode dependentResourceNode, + private NodeExecutor(DependentResourceNode dependentResourceNode, boolean onlyReconcileForDelete) { this.dependentResourceNode = dependentResourceNode; this.onlyReconcileForPossibleDelete = onlyReconcileForDelete; @@ -127,16 +130,25 @@ private NodeExecutor(DependentResourceNode dependentResourceNode, @SuppressWarnings("unchecked") public void run() { try { - var dependentResource = dependentResourceNode.getDependentResource(); + DependentResource dependentResource = dependentResourceNode.getDependentResource(); + boolean handleDependents = true; if (onlyReconcileForPossibleDelete) { if (dependentResource instanceof Deleter) { ((Deleter

) dependentResource).delete(primary, context); } } else { dependentResource.reconcile(primary, context); + if (dependentResourceNode.getReadyCondition().isPresent() + && !dependentResourceNode.getReadyCondition().get() + .isMet(dependentResource, primary, context)) { + handleDependents = false; + reconciledButNotReady.add(dependentResourceNode); + } } alreadyReconciled.add(dependentResourceNode); - handleDependentsReconcile(dependentResourceNode, onlyReconcileForPossibleDelete); + if (handleDependents) { + handleDependentsReconcile(dependentResourceNode, onlyReconcileForPossibleDelete); + } } catch (RuntimeException e) { handleExceptionInExecutor(dependentResourceNode, e); } finally { @@ -150,10 +162,10 @@ private boolean isReconcilingNow(DependentResourceNode dependentResourceNo } private synchronized void handleDependentsReconcile( - DependentResourceNode dependentResourceNode, boolean onlyReconcileForPossibleDelete) { + DependentResourceNode dependentResourceNode, boolean onlyReconcileForPossibleDelete) { var dependents = workflow.getDependents().get(dependentResourceNode); if (dependents != null) { - dependents.forEach(d -> handleReconcileOrDelete(d, onlyReconcileForPossibleDelete)); + dependents.forEach(d -> handleReconcile(d, onlyReconcileForPossibleDelete)); } } @@ -180,11 +192,12 @@ private void handleReconcileCondition(DependentResourceNode dependentResou } } - private boolean allDependsReconciled( + private boolean allDependsReconciledAndReady( DependentResourceNode dependentResourceNode) { return dependentResourceNode.getDependsOn().isEmpty() || dependentResourceNode.getDependsOn().stream() - .allMatch(this::alreadyReconciled); + .allMatch(d -> alreadyReconciled(d) + && !reconciledButNotReady.contains(dependentResourceNode)); } private boolean hasErroredDependOn( diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java index 53c70cfa91..204532653d 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java @@ -3,6 +3,7 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.processing.dependent.workflow.DependentResourceNode; +import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReadyCondition; import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReconcileCondition; public class DependentBuilder

{ @@ -28,6 +29,11 @@ public DependentBuilder

withReconcileCondition(ReconcileCondition reconcileCo return this; } + public DependentBuilder

withReadyCondition(ReadyCondition readyCondition) { + node.setReadyCondition(readyCondition); + return this; + } + public WorkflowBuilder

build() { return workflowBuilder; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java index 3f287abf6c..d1b647befd 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java @@ -12,10 +12,10 @@ public class WorkflowBuilder

{ - private List> dependentResourceNodes = new ArrayList<>(); + private List> dependentResourceNodes = new ArrayList<>(); - public DependentBuilder

addDependent(DependentResource dependentResource) { - DependentResourceNode node = new DependentResourceNode<>(dependentResource); + public DependentBuilder

addDependent(DependentResource dependentResource) { + DependentResourceNode node = new DependentResourceNode<>(dependentResource); dependentResourceNodes.add(node); return new DependentBuilder<>(this, node); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReadyCondition.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReadyCondition.java index 0cd14dfa45..50592d8dca 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReadyCondition.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReadyCondition.java @@ -6,6 +6,6 @@ public interface ReadyCondition { - void isMet(DependentResource dependentResource, P primary, Context

context); + boolean isMet(DependentResource dependentResource, P primary, Context

context); } From def8cab9797d77fc4318edb6deca1f7ee9386041 Mon Sep 17 00:00:00 2001 From: csviri Date: Tue, 19 Apr 2022 10:10:58 +0200 Subject: [PATCH 23/51] tests for ready condition --- .../workflow/WorkflowReconcileExecutor.java | 7 ++ .../workflow/condition/ReadyCondition.java | 7 ++ .../dependent/workflow/WorkflowTest.java | 75 +++++++++++++++++++ 3 files changed, 89 insertions(+) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index 457ce67cd4..aa54a7b01a 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -11,6 +11,7 @@ import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReadyCondition; import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReconcileCondition; public class WorkflowReconcileExecutor

{ @@ -108,6 +109,10 @@ private synchronized void handleNodeExecutionFinish(DependentResourceNode depend } } + private synchronized void updateStatusForNotReady(ReadyCondition readyCondition) { + readyCondition.addNotReadyStatusInfo(primary); + } + private boolean ownOrParentsReconcileConditionNotMet( DependentResourceNode dependentResourceNode) { return reconcileConditionOrParentsConditionNotMet.contains(dependentResourceNode) || @@ -143,6 +148,8 @@ public void run() { .isMet(dependentResource, primary, context)) { handleDependents = false; reconciledButNotReady.add(dependentResourceNode); + // needs to be synced + updateStatusForNotReady(dependentResourceNode.getReadyCondition().get()); } } alreadyReconciled.add(dependentResourceNode); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReadyCondition.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReadyCondition.java index 50592d8dca..37cd224d2b 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReadyCondition.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReadyCondition.java @@ -8,4 +8,11 @@ public interface ReadyCondition { boolean isMet(DependentResource dependentResource, P primary, Context

context); + /** + * If condition not met, the primary resource status might be updated by overriding this method. + * In case there are multiple conditions in a workflow it is updated multiple times. + * + * @param primary + */ + default void addNotReadyStatusInfo(P primary) {} } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java index 1793de38c5..30efb06066 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Optional; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import io.javaoperatorsdk.operator.AggregatedOperatorException; @@ -13,6 +14,7 @@ import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; import io.javaoperatorsdk.operator.processing.dependent.workflow.builder.WorkflowBuilder; +import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReadyCondition; import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReconcileCondition; import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; @@ -21,11 +23,31 @@ class WorkflowTest { + public static final String NOT_READY_YET = "NOT READY YET"; private ReconcileCondition met_reconcile_condition = (dependentResource, primary, context) -> true; private ReconcileCondition not_met_reconcile_condition = (dependentResource, primary, context) -> false; + private ReadyCondition metReadyCondition = + (dependentResource, primary, context) -> true; + private ReadyCondition notMetReadyCondition = + (dependentResource, primary, context) -> false; + + private ReadyCondition notMetReadyConditionWithStatusUpdate = + new ReadyCondition<>() { + @Override + public boolean isMet(DependentResource dependentResource, + TestCustomResource primary, Context context) { + return false; + } + + @Override + public void addNotReadyStatusInfo(TestCustomResource primary) { + primary.getStatus().setConfigMapStatus(NOT_READY_YET); + } + }; + public static final String VALUE = "value"; private List executionHistory = Collections.synchronizedList(new ArrayList<>()); @@ -233,6 +255,59 @@ void oneDependsOnConditionNotMet() { assertThat(executionHistory).notReconciled(dr2); } + @Test + void readyConditionTrivialCase() { + var workflow = new WorkflowBuilder() + .addDependent(dr1).withReadyCondition(metReadyCondition).build() + .addDependent(dr2).dependsOn(dr1).build() + .build(); + + workflow.reconcile(new TestCustomResource(), null); + + assertThat(executionHistory).reconciledInOrder(dr1, dr2); + } + + @Test + void readyConditionNotMetTrivialCase() { + var workflow = new WorkflowBuilder() + .addDependent(dr1).withReadyCondition(notMetReadyCondition).build() + .addDependent(dr2).dependsOn(dr1).build() + .build(); + + workflow.reconcile(new TestCustomResource(), null); + + assertThat(executionHistory).reconciled(dr1).notReconciled(dr2); + } + + @Test + void readyConditionNotMetStatusUpdates() { + var workflow = new WorkflowBuilder() + .addDependent(dr1).withReadyCondition(notMetReadyConditionWithStatusUpdate).build() + .addDependent(dr2).dependsOn(dr1).build() + .build(); + + var cr = new TestCustomResource(); + workflow.reconcile(cr, null); + + assertThat(executionHistory).reconciled(dr1).notReconciled(dr2); + Assertions.assertThat(cr.getStatus().getConfigMapStatus()).isEqualTo(NOT_READY_YET); + } + + @Test + void readyConditionNotMetInOneParent() { + TestDependent dr3 = new TestDependent("DR_3"); + + var workflow = new WorkflowBuilder() + .addDependent(dr1).withReadyCondition(notMetReadyCondition).build() + .addDependent(dr2).build() + .addDependent(dr3).dependsOn(dr1, dr2).build() + .build(); + + workflow.reconcile(new TestCustomResource(), null); + + assertThat(executionHistory).reconciled(dr1, dr2).notReconciled(dr3); + } + private class TestDependent implements DependentResource { private String name; From c55add1df429b708687b635b0f021bf4d6672093 Mon Sep 17 00:00:00 2001 From: csviri Date: Tue, 19 Apr 2022 10:35:51 +0200 Subject: [PATCH 24/51] smell remove --- .../operator/processing/dependent/workflow/WorkflowTest.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java index 30efb06066..a20fa4e793 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java @@ -180,9 +180,8 @@ void simpleReconcileCondition() { workflow.reconcile(new TestCustomResource(), null); - assertThat(executionHistory).notReconciled(dr1); - assertThat(executionHistory).reconciled(dr2); - assertThat(executionHistory).deleted(drDeleter); + assertThat(executionHistory).notReconciled(dr1).reconciled(dr2).deleted(drDeleter); + } @Test From 2f830d48d107c10c56045100e9698faa4f1b6177 Mon Sep 17 00:00:00 2001 From: csviri Date: Tue, 19 Apr 2022 10:50:25 +0200 Subject: [PATCH 25/51] addtional test --- .../dependent/workflow/WorkflowTest.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java index a20fa4e793..dc19379bca 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java @@ -307,6 +307,25 @@ void readyConditionNotMetInOneParent() { assertThat(executionHistory).reconciled(dr1, dr2).notReconciled(dr3); } + @Test + void diamondShareWithReadyCondition() { + TestDependent dr3 = new TestDependent("DR_3"); + TestDependent dr4 = new TestDependent("DR_4"); + + var workflow = new WorkflowBuilder() + .addDependent(dr1).build() + .addDependent(dr2).dependsOn(dr1).withReadyCondition(notMetReadyCondition).build() + .addDependent(dr3).dependsOn(dr1).build() + .addDependent(dr4).dependsOn(dr2,dr3).build() + .build(); + + workflow.reconcile(new TestCustomResource(), null); + + assertThat(executionHistory).reconciledInOrder(dr1, dr2) + .reconciledInOrder(dr1, dr3) + .notReconciled(dr4); + } + private class TestDependent implements DependentResource { private String name; From 8bd1256ea38866fad0aa4b396d1deb7354a0ba97 Mon Sep 17 00:00:00 2001 From: csviri Date: Tue, 19 Apr 2022 12:13:06 +0200 Subject: [PATCH 26/51] fix: bug with the ready --- .../workflow/WorkflowReconcileExecutor.java | 43 +++++++++++++------ .../dependent/workflow/WorkflowTest.java | 16 +++---- 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index aa54a7b01a..5aa9b9b44b 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -22,7 +22,7 @@ public class WorkflowReconcileExecutor

{ private final Set> alreadyReconciled = ConcurrentHashMap.newKeySet(); private final Set> errored = ConcurrentHashMap.newKeySet(); - private final Set> reconciledButNotReady = + private final Set> notReady = ConcurrentHashMap.newKeySet(); private final Set> reconcileConditionOrParentsConditionNotMet = ConcurrentHashMap.newKeySet(); @@ -73,8 +73,8 @@ private synchronized void handleReconcile( if (alreadyReconciled(dependentResourceNode) || isReconcilingNow(dependentResourceNode) - || !allDependsReconciledAndReady(dependentResourceNode) - || hasErroredDependOn(dependentResourceNode)) { + || !allParentsReconciledAndReady(dependentResourceNode) + || hasErroredParent(dependentResourceNode)) { log.debug("Skipping submit of: {}, ", dependentResourceNode); return; } @@ -103,16 +103,26 @@ private synchronized void handleExceptionInExecutor(DependentResourceNode depend } private synchronized void handleNodeExecutionFinish(DependentResourceNode dependentResourceNode) { + log.debug("Finished execution for: {}", dependentResourceNode); actualExecutions.remove(dependentResourceNode); if (actualExecutions.isEmpty()) { this.notifyAll(); } } + // needs to be synced private synchronized void updateStatusForNotReady(ReadyCondition readyCondition) { readyCondition.addNotReadyStatusInfo(primary); } + // needs to be in one step + private synchronized void setAlreadyReconciledButNotReady( + DependentResourceNode dependentResourceNode) { + log.debug("Setting already reconciled but not ready for: {}", dependentResourceNode); + alreadyReconciled.add(dependentResourceNode); + notReady.add(dependentResourceNode); + } + private boolean ownOrParentsReconcileConditionNotMet( DependentResourceNode dependentResourceNode) { return reconcileConditionOrParentsConditionNotMet.contains(dependentResourceNode) || @@ -136,7 +146,7 @@ private NodeExecutor(DependentResourceNode dependentResourceNode, public void run() { try { DependentResource dependentResource = dependentResourceNode.getDependentResource(); - boolean handleDependents = true; + boolean ready = true; if (onlyReconcileForPossibleDelete) { if (dependentResource instanceof Deleter) { ((Deleter

) dependentResource).delete(primary, context); @@ -146,15 +156,17 @@ public void run() { if (dependentResourceNode.getReadyCondition().isPresent() && !dependentResourceNode.getReadyCondition().get() .isMet(dependentResource, primary, context)) { - handleDependents = false; - reconciledButNotReady.add(dependentResourceNode); - // needs to be synced + ready = false; updateStatusForNotReady(dependentResourceNode.getReadyCondition().get()); } } - alreadyReconciled.add(dependentResourceNode); - if (handleDependents) { + + if (ready) { + log.debug("Setting already reconciled for: {}", dependentResourceNode); + alreadyReconciled.add(dependentResourceNode); handleDependentsReconcile(dependentResourceNode, onlyReconcileForPossibleDelete); + } else { + setAlreadyReconciledButNotReady(dependentResourceNode); } } catch (RuntimeException e) { handleExceptionInExecutor(dependentResourceNode, e); @@ -172,7 +184,11 @@ private synchronized void handleDependentsReconcile( DependentResourceNode dependentResourceNode, boolean onlyReconcileForPossibleDelete) { var dependents = workflow.getDependents().get(dependentResourceNode); if (dependents != null) { - dependents.forEach(d -> handleReconcile(d, onlyReconcileForPossibleDelete)); + + dependents.forEach(d -> { + log.debug("Handle reconcile for dependent: {} of parent:{}", d, dependentResourceNode); + handleReconcile(d, onlyReconcileForPossibleDelete); + }); } } @@ -199,15 +215,14 @@ private void handleReconcileCondition(DependentResourceNode dependentResou } } - private boolean allDependsReconciledAndReady( + private boolean allParentsReconciledAndReady( DependentResourceNode dependentResourceNode) { return dependentResourceNode.getDependsOn().isEmpty() || dependentResourceNode.getDependsOn().stream() - .allMatch(d -> alreadyReconciled(d) - && !reconciledButNotReady.contains(dependentResourceNode)); + .allMatch(d -> alreadyReconciled(d) && !notReady.contains(d)); } - private boolean hasErroredDependOn( + private boolean hasErroredParent( DependentResourceNode dependentResourceNode) { return !dependentResourceNode.getDependsOn().isEmpty() && dependentResourceNode.getDependsOn().stream() diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java index dc19379bca..19f6fb3b4a 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java @@ -162,7 +162,7 @@ void onlyOneDependsOnErroredResourceNotReconciled() { var workflow = new WorkflowBuilder() .addDependent(dr1).build() .addDependent(drError).build() - .addDependent(dr2).dependsOn(drError).dependsOn(dr1).build() + .addDependent(dr2).dependsOn(drError, dr1).build() .build(); assertThrows(AggregatedOperatorException.class, () -> workflow.reconcile(new TestCustomResource(), null)); @@ -313,17 +313,17 @@ void diamondShareWithReadyCondition() { TestDependent dr4 = new TestDependent("DR_4"); var workflow = new WorkflowBuilder() - .addDependent(dr1).build() - .addDependent(dr2).dependsOn(dr1).withReadyCondition(notMetReadyCondition).build() - .addDependent(dr3).dependsOn(dr1).build() - .addDependent(dr4).dependsOn(dr2,dr3).build() - .build(); + .addDependent(dr1).build() + .addDependent(dr2).dependsOn(dr1).withReadyCondition(notMetReadyCondition).build() + .addDependent(dr3).dependsOn(dr1).build() + .addDependent(dr4).dependsOn(dr2, dr3).build() + .build(); workflow.reconcile(new TestCustomResource(), null); assertThat(executionHistory).reconciledInOrder(dr1, dr2) - .reconciledInOrder(dr1, dr3) - .notReconciled(dr4); + .reconciledInOrder(dr1, dr3) + .notReconciled(dr4); } private class TestDependent implements DependentResource { From d2f9110c0c1a7f3596a89e04c25bdee853b9007a Mon Sep 17 00:00:00 2001 From: csviri Date: Tue, 19 Apr 2022 13:52:27 +0200 Subject: [PATCH 27/51] execution results --- .../dependent/workflow/Workflow.java | 6 +- .../workflow/WorkflowExecutionResult.java | 69 +++++++++++++++++++ .../workflow/WorkflowReconcileExecutor.java | 66 ++++++++++-------- .../dependent/workflow/WorkflowTest.java | 55 +++++++++------ 4 files changed, 146 insertions(+), 50 deletions(-) create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutionResult.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java index 9ee603a821..2fe3f50fa0 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java @@ -42,10 +42,10 @@ public Workflow(List> dependentResourceNodes, int gl this(dependentResourceNodes, Executors.newFixedThreadPool(globalParallelism)); } - public void reconcile(P primary, Context

context) { - WorkflowReconcileExecutor

workflowReconcileExecutor = + public WorkflowExecutionResult reconcile(P primary, Context

context) { + WorkflowReconcileExecutor workflowReconcileExecutor = new WorkflowReconcileExecutor<>(this, primary, context); - workflowReconcileExecutor.reconcile(); + return workflowReconcileExecutor.reconcile(); } public void cleanup(P resource, Context

context) { diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutionResult.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutionResult.java new file mode 100644 index 0000000000..dae966645a --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutionResult.java @@ -0,0 +1,69 @@ +package io.javaoperatorsdk.operator.processing.dependent.workflow; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.javaoperatorsdk.operator.AggregatedOperatorException; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; + +public class WorkflowExecutionResult { + + // this includes also possibly deleted ones + private List> reconciledDependents = new ArrayList<>(); + private List> notReconciledDependents = new ArrayList<>(); + private List> notReadyDependents = new ArrayList<>(); + private Map, Exception> erroredDependents = new HashMap<>(); + + public Map, Exception> getErroredDependents() { + return erroredDependents; + } + + public WorkflowExecutionResult setErroredDependents( + Map, Exception> erroredDependents) { + this.erroredDependents = erroredDependents; + return this; + } + + public List> getReconciledDependents() { + return reconciledDependents; + } + + public WorkflowExecutionResult setReconciledDependents( + List> reconciledDependents) { + this.reconciledDependents = reconciledDependents; + return this; + } + + public List> getNotReadyDependents() { + return notReadyDependents; + } + + public WorkflowExecutionResult setNotReadyDependents( + List> notReadyDependents) { + this.notReadyDependents = notReadyDependents; + return this; + } + + public List> getNotReconciledDependents() { + return notReconciledDependents; + } + + public WorkflowExecutionResult setNotReconciledDependents( + List> notReconciledDependents) { + this.notReconciledDependents = notReconciledDependents; + return this; + } + + public void throwAggregatedExceptionIfErrorsPresent() { + if (!erroredDependents.isEmpty()) { + throw createFinalException(); + } + } + + private AggregatedOperatorException createFinalException() { + return new AggregatedOperatorException("Exception during workflow.", + new ArrayList<>(erroredDependents.values())); + } +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index 5aa9b9b44b..e63b91d538 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -2,12 +2,12 @@ import java.util.*; import java.util.concurrent.*; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.AggregatedOperatorException; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; @@ -20,17 +20,14 @@ public class WorkflowReconcileExecutor

{ private final Workflow

workflow; - private final Set> alreadyReconciled = ConcurrentHashMap.newKeySet(); - private final Set> errored = ConcurrentHashMap.newKeySet(); - private final Set> notReady = - ConcurrentHashMap.newKeySet(); - private final Set> reconcileConditionOrParentsConditionNotMet = - ConcurrentHashMap.newKeySet(); - + private final Set> alreadyReconciled = new HashSet<>(); + private final Set> notReady = new HashSet<>(); + private final Set> ownOrAncestorReconcileConditionConditionNotMet = + new HashSet<>(); private final Map, Future> actualExecutions = - new ConcurrentHashMap<>(); - private final List exceptionsDuringExecution = - Collections.synchronizedList(new ArrayList<>()); + new HashMap<>(); + private final Map, Exception> exceptionsDuringExecution = + new HashMap<>(); private final P primary; private final Context

context; @@ -42,7 +39,7 @@ public WorkflowReconcileExecutor(Workflow

workflow, P primary, Context

con } // add reconcile results - public synchronized void reconcile() { + public synchronized WorkflowExecutionResult reconcile() { for (DependentResourceNode dependentResourceNode : workflow .getTopLevelDependentResources()) { handleReconcile(dependentResourceNode, false); @@ -50,10 +47,6 @@ public synchronized void reconcile() { while (true) { try { this.wait(); - if (!exceptionsDuringExecution.isEmpty()) { - log.debug("Exception during reconciliation for: {}", primary); - throw createFinalException(); - } if (noMoreExecutionsScheduled()) { break; } else { @@ -64,6 +57,7 @@ public synchronized void reconcile() { Thread.currentThread().interrupt(); } } + return createReconcileResult(); } private synchronized void handleReconcile( @@ -80,7 +74,7 @@ private synchronized void handleReconcile( } if (onlyReconcileForPossibleDelete) { - reconcileConditionOrParentsConditionNotMet.add(dependentResourceNode); + ownOrAncestorReconcileConditionConditionNotMet.add(dependentResourceNode); } else { dependentResourceNode.getReconcileCondition() .ifPresent(reconcileCondition -> handleReconcileCondition(dependentResourceNode, @@ -98,8 +92,7 @@ private synchronized void handleReconcile( private synchronized void handleExceptionInExecutor(DependentResourceNode dependentResourceNode, RuntimeException e) { - exceptionsDuringExecution.add(e); - errored.add(dependentResourceNode); + exceptionsDuringExecution.put(dependentResourceNode, e); } private synchronized void handleNodeExecutionFinish(DependentResourceNode dependentResourceNode) { @@ -125,9 +118,9 @@ private synchronized void setAlreadyReconciledButNotReady( private boolean ownOrParentsReconcileConditionNotMet( DependentResourceNode dependentResourceNode) { - return reconcileConditionOrParentsConditionNotMet.contains(dependentResourceNode) || + return ownOrAncestorReconcileConditionConditionNotMet.contains(dependentResourceNode) || dependentResourceNode.getDependsOn().stream() - .anyMatch(reconcileConditionOrParentsConditionNotMet::contains); + .anyMatch(ownOrAncestorReconcileConditionConditionNotMet::contains); } private class NodeExecutor implements Runnable { @@ -196,10 +189,6 @@ private boolean noMoreExecutionsScheduled() { return actualExecutions.isEmpty(); } - private AggregatedOperatorException createFinalException() { - return new AggregatedOperatorException("Exception during workflow.", exceptionsDuringExecution); - } - private boolean alreadyReconciled( DependentResourceNode dependentResourceNode) { return alreadyReconciled.contains(dependentResourceNode); @@ -211,7 +200,7 @@ private void handleReconcileCondition(DependentResourceNode dependentResou boolean conditionMet = reconcileCondition.isMet(dependentResourceNode.getDependentResource(), primary, context); if (!conditionMet) { - reconcileConditionOrParentsConditionNotMet.add(dependentResourceNode); + ownOrAncestorReconcileConditionConditionNotMet.add(dependentResourceNode); } } @@ -226,6 +215,29 @@ private boolean hasErroredParent( DependentResourceNode dependentResourceNode) { return !dependentResourceNode.getDependsOn().isEmpty() && dependentResourceNode.getDependsOn().stream() - .anyMatch(errored::contains); + .anyMatch(exceptionsDuringExecution::containsKey); } + + private WorkflowExecutionResult createReconcileResult() { + WorkflowExecutionResult workflowExecutionResult = new WorkflowExecutionResult(); + + workflowExecutionResult.setErroredDependents(exceptionsDuringExecution + .entrySet().stream() + .collect(Collectors.toMap(e -> e.getKey().getDependentResource(), Map.Entry::getValue))); + workflowExecutionResult.setNotReadyDependents(notReady.stream() + .map(DependentResourceNode::getDependentResource) + .collect(Collectors.toList())); + + workflowExecutionResult.setReconciledDependents(alreadyReconciled.stream() + .map(DependentResourceNode::getDependentResource).collect(Collectors.toList())); + + var notReconciledDependentResources = + new HashSet>(workflow.getDependents().keySet()); + notReconciledDependentResources.removeAll(alreadyReconciled); + workflowExecutionResult.setNotReconciledDependents(notReconciledDependentResources.stream() + .map(DependentResourceNode::getDependentResource).collect(Collectors.toList())); + + return workflowExecutionResult; + } + } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java index 19f6fb3b4a..5dc2a3be77 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java @@ -64,8 +64,9 @@ void reconcileTopLevelResources() { .addDependent(dr2).build() .build(); - workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), null); + Assertions.assertThat(res.getErroredDependents()).isEmpty(); assertThat(executionHistory).reconciled(dr1, dr2); } @@ -76,8 +77,9 @@ void reconciliationWithSimpleDependsOn() { .addDependent(dr2).dependsOn(dr1).build() .build(); - workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), null); + Assertions.assertThat(res.getErroredDependents()).isEmpty(); assertThat(executionHistory).reconciledInOrder(dr1, dr2); } @@ -91,8 +93,9 @@ void reconciliationWithTwoTheDependsOns() { .addDependent(dr3).dependsOn(dr1).build() .build(); - workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), null); + Assertions.assertThat(res.getErroredDependents()).isEmpty(); assertThat(executionHistory) .reconciledInOrder(dr1, dr2).reconciledInOrder(dr1, dr3); } @@ -109,8 +112,9 @@ void diamondShareWorkflowReconcile() { .addDependent(dr4).dependsOn(dr3).dependsOn(dr2).build() .build(); - workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), null); + Assertions.assertThat(res.getErroredDependents()).isEmpty(); assertThat(executionHistory) .reconciledInOrder(dr1, dr2, dr4) .reconciledInOrder(dr1, dr3, dr4); @@ -122,7 +126,8 @@ void exceptionHandlingSimpleCases() { .addDependent(drError).build() .build(); assertThrows(AggregatedOperatorException.class, - () -> workflow.reconcile(new TestCustomResource(), null)); + () -> workflow.reconcile(new TestCustomResource(), null) + .throwAggregatedExceptionIfErrorsPresent()); assertThat(executionHistory).reconciled(drError); } @@ -134,7 +139,8 @@ void dependentsOnErroredResourceNotReconciled() { .addDependent(dr2).dependsOn(drError).build() .build(); assertThrows(AggregatedOperatorException.class, - () -> workflow.reconcile(new TestCustomResource(), null)); + () -> workflow.reconcile(new TestCustomResource(), null) + .throwAggregatedExceptionIfErrorsPresent()); assertThat(executionHistory).reconciled(dr1, drError).notReconciled(dr2); } @@ -151,7 +157,8 @@ void oneBranchErrorsOtherCompletes() { .build(); assertThrows(AggregatedOperatorException.class, - () -> workflow.reconcile(new TestCustomResource(), null)); + () -> workflow.reconcile(new TestCustomResource(), null) + .throwAggregatedExceptionIfErrorsPresent()); assertThat(executionHistory).reconciledInOrder(dr1, dr2, dr3); assertThat(executionHistory).reconciledInOrder(dr1, drError); @@ -165,7 +172,8 @@ void onlyOneDependsOnErroredResourceNotReconciled() { .addDependent(dr2).dependsOn(drError, dr1).build() .build(); assertThrows(AggregatedOperatorException.class, - () -> workflow.reconcile(new TestCustomResource(), null)); + () -> workflow.reconcile(new TestCustomResource(), null) + .throwAggregatedExceptionIfErrorsPresent()); assertThat(executionHistory).notReconciled(dr2); } @@ -178,10 +186,10 @@ void simpleReconcileCondition() { .addDependent(drDeleter).withReconcileCondition(not_met_reconcile_condition).build() .build(); - workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), null); + Assertions.assertThat(res.getErroredDependents()).isEmpty(); assertThat(executionHistory).notReconciled(dr1).reconciled(dr2).deleted(drDeleter); - } @Test @@ -193,10 +201,10 @@ void triangleOnceConditionNotMet() { .build() .build(); - workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), null); - assertThat(executionHistory).reconciledInOrder(dr1, dr2); - assertThat(executionHistory).deleted(drDeleter); + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + assertThat(executionHistory).reconciledInOrder(dr1, dr2).deleted(drDeleter); } @Test @@ -213,8 +221,9 @@ void reconcileConditionTransitiveDelete() { .dependsOn(drDeleter).build() .build(); - workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), null); + Assertions.assertThat(res.getErroredDependents()).isEmpty(); assertThat(executionHistory).notReconciled(dr2); assertThat(executionHistory).reconciledInOrder(dr1, drDeleter, drDeleter2); assertThat(executionHistory).deleted(drDeleter, drDeleter2); @@ -232,7 +241,8 @@ void reconcileConditionAlsoErrorDependsOn() { .build(); assertThrows(AggregatedOperatorException.class, - () -> workflow.reconcile(new TestCustomResource(), null)); + () -> workflow.reconcile(new TestCustomResource(), null) + .throwAggregatedExceptionIfErrorsPresent()); assertThat(executionHistory).deleted(drDeleter); assertThat(executionHistory).reconciled(drError); @@ -247,8 +257,9 @@ void oneDependsOnConditionNotMet() { .addDependent(drDeleter).dependsOn(dr1, dr2).build() .build(); - workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), null); + Assertions.assertThat(res.getErroredDependents()).isEmpty(); assertThat(executionHistory).deleted(drDeleter); assertThat(executionHistory).reconciledInOrder(dr1, drDeleter); assertThat(executionHistory).notReconciled(dr2); @@ -273,8 +284,9 @@ void readyConditionNotMetTrivialCase() { .addDependent(dr2).dependsOn(dr1).build() .build(); - workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), null); + Assertions.assertThat(res.getErroredDependents()).isEmpty(); assertThat(executionHistory).reconciled(dr1).notReconciled(dr2); } @@ -286,8 +298,9 @@ void readyConditionNotMetStatusUpdates() { .build(); var cr = new TestCustomResource(); - workflow.reconcile(cr, null); + var res = workflow.reconcile(cr, null); + Assertions.assertThat(res.getErroredDependents()).isEmpty(); assertThat(executionHistory).reconciled(dr1).notReconciled(dr2); Assertions.assertThat(cr.getStatus().getConfigMapStatus()).isEqualTo(NOT_READY_YET); } @@ -302,8 +315,9 @@ void readyConditionNotMetInOneParent() { .addDependent(dr3).dependsOn(dr1, dr2).build() .build(); - workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), null); + Assertions.assertThat(res.getErroredDependents()).isEmpty(); assertThat(executionHistory).reconciled(dr1, dr2).notReconciled(dr3); } @@ -319,8 +333,9 @@ void diamondShareWithReadyCondition() { .addDependent(dr4).dependsOn(dr2, dr3).build() .build(); - workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), null); + Assertions.assertThat(res.getErroredDependents()).isEmpty(); assertThat(executionHistory).reconciledInOrder(dr1, dr2) .reconciledInOrder(dr1, dr3) .notReconciled(dr4); From 2b4c26542cadf183b526e7447e4dffc6f807c2ab Mon Sep 17 00:00:00 2001 From: csviri Date: Tue, 19 Apr 2022 13:58:03 +0200 Subject: [PATCH 28/51] comment --- .../dependent/workflow/WorkflowReconcileExecutor.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index e63b91d538..444aa60f72 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -38,7 +38,8 @@ public WorkflowReconcileExecutor(Workflow

workflow, P primary, Context

con this.workflow = workflow; } - // add reconcile results + // todo add reconcile results + // - reconciled in results should contain only truly reconciled public synchronized WorkflowExecutionResult reconcile() { for (DependentResourceNode dependentResourceNode : workflow .getTopLevelDependentResources()) { From 67f6ec996cb3df9a8f56825a33f8cfa9f2be1b44 Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 25 Apr 2022 12:22:49 +0200 Subject: [PATCH 29/51] test refactor, bottom resources --- .../dependent/workflow/Workflow.java | 31 +- .../workflow/WorkflowCleanupExecutor.java | 29 ++ .../workflow/WorkflowCleanupResult.java | 4 + .../workflow/WorkflowReconcileExecutor.java | 1 + .../workflow/builder/WorkflowBuilder.java | 6 +- .../WorkflowReconcileExecutorTest.java | 417 ++++++++++++++++++ .../dependent/workflow/WorkflowTest.java | 411 ++--------------- 7 files changed, 500 insertions(+), 399 deletions(-) create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutor.java create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupResult.java create mode 100644 operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java index 2fe3f50fa0..4d245b6073 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java @@ -1,8 +1,6 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -18,42 +16,46 @@ */ public class Workflow

{ - private final List> dependentResourceNodes; - private final List> topLevelResources = new ArrayList<>(); + private final Set> dependentResourceNodes; + private final Set> topLevelResources = new HashSet<>(); + private final Set> bottomLevelResource = new HashSet<>(); private Map, List>> dependents; // it's "global" executor service shared between multiple reconciliations running parallel private ExecutorService executorService; - public Workflow(List> dependentResourceNodes) { + public Workflow(Set> dependentResourceNodes) { this.executorService = ConfigurationServiceProvider.instance().getExecutorService(); this.dependentResourceNodes = dependentResourceNodes; preprocessForReconcile(); } - public Workflow(List> dependentResourceNodes, + public Workflow(Set> dependentResourceNodes, ExecutorService executorService) { this.executorService = executorService; this.dependentResourceNodes = dependentResourceNodes; preprocessForReconcile(); } - public Workflow(List> dependentResourceNodes, int globalParallelism) { + public Workflow(Set> dependentResourceNodes, int globalParallelism) { this(dependentResourceNodes, Executors.newFixedThreadPool(globalParallelism)); } public WorkflowExecutionResult reconcile(P primary, Context

context) { - WorkflowReconcileExecutor workflowReconcileExecutor = + WorkflowReconcileExecutor

workflowReconcileExecutor = new WorkflowReconcileExecutor<>(this, primary, context); return workflowReconcileExecutor.reconcile(); } - public void cleanup(P resource, Context

context) { - + public WorkflowCleanupResult cleanup(P primary, Context

context) { + WorkflowCleanupExecutor

workflowCleanupExecutor = + new WorkflowCleanupExecutor<>(this, primary, context); + return workflowCleanupExecutor.cleanup(); } // add cycle detection? private void preprocessForReconcile() { + bottomLevelResource.addAll(dependentResourceNodes); dependents = new ConcurrentHashMap<>(dependentResourceNodes.size()); for (DependentResourceNode node : dependentResourceNodes) { if (node.getDependsOn().isEmpty()) { @@ -62,6 +64,7 @@ private void preprocessForReconcile() { for (DependentResourceNode dependsOn : node.getDependsOn()) { dependents.computeIfAbsent(dependsOn, dr -> new ArrayList<>()); dependents.get(dependsOn).add(node); + bottomLevelResource.remove(dependsOn); } } } @@ -71,10 +74,14 @@ public void setExecutorService(ExecutorService executorService) { this.executorService = executorService; } - List> getTopLevelDependentResources() { + Set> getTopLevelDependentResources() { return topLevelResources; } + Set> getBottomLevelResource() { + return bottomLevelResource; + } + Map, List>> getDependents() { return dependents; } 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 new file mode 100644 index 0000000000..e367d6c735 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutor.java @@ -0,0 +1,29 @@ +package io.javaoperatorsdk.operator.processing.dependent.workflow; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.reconciler.Context; + +public class WorkflowCleanupExecutor

{ + + private static final Logger log = LoggerFactory.getLogger(WorkflowReconcileExecutor.class); + + private final Workflow

workflow; + private final P primary; + private final Context

context; + + public WorkflowCleanupExecutor(Workflow

workflow, P primary, Context

context) { + this.workflow = workflow; + this.primary = primary; + this.context = context; + } + + + public WorkflowCleanupResult cleanup() { + + return null; + } + +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupResult.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupResult.java new file mode 100644 index 0000000000..b6632a0286 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupResult.java @@ -0,0 +1,4 @@ +package io.javaoperatorsdk.operator.processing.dependent.workflow; + +public class WorkflowCleanupResult { +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index 444aa60f72..b132fc934b 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -106,6 +106,7 @@ private synchronized void handleNodeExecutionFinish(DependentResourceNode depend // needs to be synced private synchronized void updateStatusForNotReady(ReadyCondition readyCondition) { + // todo think through this, since more can't be not ready readyCondition.addNotReadyStatusInfo(primary); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java index d1b647befd..32f62d9f4b 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java @@ -1,7 +1,7 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow.builder; -import java.util.ArrayList; -import java.util.List; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.ExecutorService; import io.fabric8.kubernetes.api.model.HasMetadata; @@ -12,7 +12,7 @@ public class WorkflowBuilder

{ - private List> dependentResourceNodes = new ArrayList<>(); + private Set> dependentResourceNodes = new HashSet<>(); public DependentBuilder

addDependent(DependentResource dependentResource) { DependentResourceNode node = new DependentResourceNode<>(dependentResource); diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java new file mode 100644 index 0000000000..e507941a7c --- /dev/null +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java @@ -0,0 +1,417 @@ +package io.javaoperatorsdk.operator.processing.dependent.workflow; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.javaoperatorsdk.operator.AggregatedOperatorException; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; +import io.javaoperatorsdk.operator.processing.dependent.workflow.builder.WorkflowBuilder; +import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReadyCondition; +import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReconcileCondition; +import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; + +import static io.javaoperatorsdk.operator.processing.dependent.workflow.ExecutionAssert.*; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class WorkflowReconcileExecutorTest { + + public static final String NOT_READY_YET = "NOT READY YET"; + private ReconcileCondition met_reconcile_condition = + (dependentResource, primary, context) -> true; + private ReconcileCondition not_met_reconcile_condition = + (dependentResource, primary, context) -> false; + + private ReadyCondition metReadyCondition = + (dependentResource, primary, context) -> true; + private ReadyCondition notMetReadyCondition = + (dependentResource, primary, context) -> false; + + private ReadyCondition notMetReadyConditionWithStatusUpdate = + new ReadyCondition<>() { + @Override + public boolean isMet(DependentResource dependentResource, + TestCustomResource primary, Context context) { + return false; + } + + @Override + public void addNotReadyStatusInfo(TestCustomResource primary) { + primary.getStatus().setConfigMapStatus(NOT_READY_YET); + } + }; + + public static final String VALUE = "value"; + private List executionHistory = + Collections.synchronizedList(new ArrayList<>()); + + TestDependent dr1 = new TestDependent("DR_1"); + TestDependent dr2 = new TestDependent("DR_2"); + TestDeleterDependent drDeleter = new TestDeleterDependent("DR_DELETER"); + TestErrorDependent drError = new TestErrorDependent("ERROR_1"); + + @Test + void reconcileTopLevelResources() { + var workflow = new WorkflowBuilder() + .addDependent(dr1).build() + .addDependent(dr2).build() + .build(); + + var res = workflow.reconcile(new TestCustomResource(), null); + + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + assertThat(executionHistory).reconciled(dr1, dr2); + } + + @Test + void reconciliationWithSimpleDependsOn() { + var workflow = new WorkflowBuilder() + .addDependent(dr1).build() + .addDependent(dr2).dependsOn(dr1).build() + .build(); + + var res = workflow.reconcile(new TestCustomResource(), null); + + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + assertThat(executionHistory).reconciledInOrder(dr1, dr2); + } + + @Test + void reconciliationWithTwoTheDependsOns() { + TestDependent dr3 = new TestDependent("DR_3"); + + var workflow = new WorkflowBuilder() + .addDependent(dr1).build() + .addDependent(dr2).dependsOn(dr1).build() + .addDependent(dr3).dependsOn(dr1).build() + .build(); + + var res = workflow.reconcile(new TestCustomResource(), null); + + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + assertThat(executionHistory) + .reconciledInOrder(dr1, dr2).reconciledInOrder(dr1, dr3); + } + + @Test + void diamondShareWorkflowReconcile() { + TestDependent dr3 = new TestDependent("DR_3"); + TestDependent dr4 = new TestDependent("DR_4"); + + var workflow = new WorkflowBuilder() + .addDependent(dr1).build() + .addDependent(dr2).dependsOn(dr1).build() + .addDependent(dr3).dependsOn(dr1).build() + .addDependent(dr4).dependsOn(dr3).dependsOn(dr2).build() + .build(); + + var res = workflow.reconcile(new TestCustomResource(), null); + + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + assertThat(executionHistory) + .reconciledInOrder(dr1, dr2, dr4) + .reconciledInOrder(dr1, dr3, dr4); + } + + @Test + void exceptionHandlingSimpleCases() { + var workflow = new WorkflowBuilder() + .addDependent(drError).build() + .build(); + assertThrows(AggregatedOperatorException.class, + () -> workflow.reconcile(new TestCustomResource(), null) + .throwAggregatedExceptionIfErrorsPresent()); + assertThat(executionHistory).reconciled(drError); + } + + @Test + void dependentsOnErroredResourceNotReconciled() { + var workflow = new WorkflowBuilder() + .addDependent(dr1).build() + .addDependent(drError).dependsOn(dr1).build() + .addDependent(dr2).dependsOn(drError).build() + .build(); + assertThrows(AggregatedOperatorException.class, + () -> workflow.reconcile(new TestCustomResource(), null) + .throwAggregatedExceptionIfErrorsPresent()); + + assertThat(executionHistory).reconciled(dr1, drError).notReconciled(dr2); + } + + @Test + void oneBranchErrorsOtherCompletes() { + TestDependent dr3 = new TestDependent("DR_3"); + + var workflow = new WorkflowBuilder() + .addDependent(dr1).build() + .addDependent(drError).dependsOn(dr1).build() + .addDependent(dr2).dependsOn(dr1).build() + .addDependent(dr3).dependsOn(dr2).build() + .build(); + + assertThrows(AggregatedOperatorException.class, + () -> workflow.reconcile(new TestCustomResource(), null) + .throwAggregatedExceptionIfErrorsPresent()); + + assertThat(executionHistory).reconciledInOrder(dr1, dr2, dr3); + assertThat(executionHistory).reconciledInOrder(dr1, drError); + } + + @Test + void onlyOneDependsOnErroredResourceNotReconciled() { + var workflow = new WorkflowBuilder() + .addDependent(dr1).build() + .addDependent(drError).build() + .addDependent(dr2).dependsOn(drError, dr1).build() + .build(); + assertThrows(AggregatedOperatorException.class, + () -> workflow.reconcile(new TestCustomResource(), null) + .throwAggregatedExceptionIfErrorsPresent()); + + assertThat(executionHistory).notReconciled(dr2); + } + + @Test + void simpleReconcileCondition() { + var workflow = new WorkflowBuilder() + .addDependent(dr1).withReconcileCondition(not_met_reconcile_condition).build() + .addDependent(dr2).withReconcileCondition(met_reconcile_condition).build() + .addDependent(drDeleter).withReconcileCondition(not_met_reconcile_condition).build() + .build(); + + var res = workflow.reconcile(new TestCustomResource(), null); + + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + assertThat(executionHistory).notReconciled(dr1).reconciled(dr2).deleted(drDeleter); + } + + @Test + void triangleOnceConditionNotMet() { + var workflow = new WorkflowBuilder() + .addDependent(dr1).build() + .addDependent(dr2).dependsOn(dr1).build() + .addDependent(drDeleter).withReconcileCondition(not_met_reconcile_condition).dependsOn(dr1) + .build() + .build(); + + var res = workflow.reconcile(new TestCustomResource(), null); + + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + assertThat(executionHistory).reconciledInOrder(dr1, dr2).deleted(drDeleter); + } + + @Test + void reconcileConditionTransitiveDelete() { + TestDeleterDependent drDeleter2 = new TestDeleterDependent("DR_DELETER_2"); + + var workflow = new WorkflowBuilder() + .addDependent(dr1).build() + .addDependent(dr2).withReconcileCondition(not_met_reconcile_condition).dependsOn(dr1) + .build() + .addDependent(drDeleter).withReconcileCondition(met_reconcile_condition).dependsOn(dr2) + .build() + .addDependent(drDeleter2).withReconcileCondition(met_reconcile_condition) + .dependsOn(drDeleter).build() + .build(); + + var res = workflow.reconcile(new TestCustomResource(), null); + + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + assertThat(executionHistory).notReconciled(dr2); + assertThat(executionHistory).reconciledInOrder(dr1, drDeleter, drDeleter2); + assertThat(executionHistory).deleted(drDeleter, drDeleter2); + } + + @Test + void reconcileConditionAlsoErrorDependsOn() { + TestDeleterDependent drDeleter2 = new TestDeleterDependent("DR_DELETER_2"); + + var workflow = new WorkflowBuilder() + .addDependent(drError).build() + .addDependent(drDeleter).withReconcileCondition(not_met_reconcile_condition).build() + .addDependent(drDeleter2).withReconcileCondition(met_reconcile_condition) + .dependsOn(drError, drDeleter).build() + .build(); + + assertThrows(AggregatedOperatorException.class, + () -> workflow.reconcile(new TestCustomResource(), null) + .throwAggregatedExceptionIfErrorsPresent()); + + assertThat(executionHistory).deleted(drDeleter); + assertThat(executionHistory).reconciled(drError); + assertThat(executionHistory).notReconciled(drDeleter2); + } + + @Test + void oneDependsOnConditionNotMet() { + var workflow = new WorkflowBuilder() + .addDependent(dr1).build() + .addDependent(dr2).withReconcileCondition(not_met_reconcile_condition).build() + .addDependent(drDeleter).dependsOn(dr1, dr2).build() + .build(); + + var res = workflow.reconcile(new TestCustomResource(), null); + + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + assertThat(executionHistory).deleted(drDeleter); + assertThat(executionHistory).reconciledInOrder(dr1, drDeleter); + assertThat(executionHistory).notReconciled(dr2); + } + + @Test + void readyConditionTrivialCase() { + var workflow = new WorkflowBuilder() + .addDependent(dr1).withReadyCondition(metReadyCondition).build() + .addDependent(dr2).dependsOn(dr1).build() + .build(); + + workflow.reconcile(new TestCustomResource(), null); + + assertThat(executionHistory).reconciledInOrder(dr1, dr2); + } + + @Test + void readyConditionNotMetTrivialCase() { + var workflow = new WorkflowBuilder() + .addDependent(dr1).withReadyCondition(notMetReadyCondition).build() + .addDependent(dr2).dependsOn(dr1).build() + .build(); + + var res = workflow.reconcile(new TestCustomResource(), null); + + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + assertThat(executionHistory).reconciled(dr1).notReconciled(dr2); + } + + @Test + void readyConditionNotMetStatusUpdates() { + var workflow = new WorkflowBuilder() + .addDependent(dr1).withReadyCondition(notMetReadyConditionWithStatusUpdate).build() + .addDependent(dr2).dependsOn(dr1).build() + .build(); + + var cr = new TestCustomResource(); + var res = workflow.reconcile(cr, null); + + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + assertThat(executionHistory).reconciled(dr1).notReconciled(dr2); + Assertions.assertThat(cr.getStatus().getConfigMapStatus()).isEqualTo(NOT_READY_YET); + } + + @Test + void readyConditionNotMetInOneParent() { + TestDependent dr3 = new TestDependent("DR_3"); + + var workflow = new WorkflowBuilder() + .addDependent(dr1).withReadyCondition(notMetReadyCondition).build() + .addDependent(dr2).build() + .addDependent(dr3).dependsOn(dr1, dr2).build() + .build(); + + var res = workflow.reconcile(new TestCustomResource(), null); + + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + assertThat(executionHistory).reconciled(dr1, dr2).notReconciled(dr3); + } + + @Test + void diamondShareWithReadyCondition() { + TestDependent dr3 = new TestDependent("DR_3"); + TestDependent dr4 = new TestDependent("DR_4"); + + var workflow = new WorkflowBuilder() + .addDependent(dr1).build() + .addDependent(dr2).dependsOn(dr1).withReadyCondition(notMetReadyCondition).build() + .addDependent(dr3).dependsOn(dr1).build() + .addDependent(dr4).dependsOn(dr2, dr3).build() + .build(); + + var res = workflow.reconcile(new TestCustomResource(), null); + + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + assertThat(executionHistory).reconciledInOrder(dr1, dr2) + .reconciledInOrder(dr1, dr3) + .notReconciled(dr4); + } + + public class TestDependent implements DependentResource { + + private String name; + + public TestDependent(String name) { + this.name = name; + } + + @Override + public ReconcileResult reconcile(TestCustomResource primary, + Context context) { + executionHistory.add(new ReconcileRecord(this)); + return ReconcileResult.resourceCreated(VALUE); + } + + @Override + public Class resourceType() { + return String.class; + } + + @Override + public Optional getSecondaryResource(TestCustomResource primary) { + return Optional.of(VALUE); + } + + @Override + public String toString() { + return name; + } + } + + private class TestDeleterDependent extends TestDependent implements Deleter { + + public TestDeleterDependent(String name) { + super(name); + } + + @Override + public void delete(TestCustomResource primary, Context context) { + executionHistory.add(new ReconcileRecord(this, true)); + } + } + + private class TestErrorDependent implements DependentResource { + private String name; + + public TestErrorDependent(String name) { + this.name = name; + } + + @Override + public ReconcileResult reconcile(TestCustomResource primary, + Context context) { + executionHistory.add(new ReconcileRecord(this)); + throw new IllegalStateException("Test exception"); + } + + @Override + public Class resourceType() { + return String.class; + } + + @Override + public Optional getSecondaryResource(TestCustomResource primary) { + return Optional.of(VALUE); + } + + @Override + public String toString() { + return name; + } + } + +} diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java index 5dc2a3be77..b721ec3b27 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java @@ -1,417 +1,60 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; -import io.javaoperatorsdk.operator.AggregatedOperatorException; -import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; -import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; import io.javaoperatorsdk.operator.processing.dependent.workflow.builder.WorkflowBuilder; -import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReadyCondition; -import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReconcileCondition; import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; -import static io.javaoperatorsdk.operator.processing.dependent.workflow.ExecutionAssert.*; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; class WorkflowTest { - public static final String NOT_READY_YET = "NOT READY YET"; - private ReconcileCondition met_reconcile_condition = - (dependentResource, primary, context) -> true; - private ReconcileCondition not_met_reconcile_condition = - (dependentResource, primary, context) -> false; - private ReadyCondition metReadyCondition = - (dependentResource, primary, context) -> true; - private ReadyCondition notMetReadyCondition = - (dependentResource, primary, context) -> false; - - private ReadyCondition notMetReadyConditionWithStatusUpdate = - new ReadyCondition<>() { - @Override - public boolean isMet(DependentResource dependentResource, - TestCustomResource primary, Context context) { - return false; - } - - @Override - public void addNotReadyStatusInfo(TestCustomResource primary) { - primary.getStatus().setConfigMapStatus(NOT_READY_YET); - } - }; - - public static final String VALUE = "value"; - private List executionHistory = - Collections.synchronizedList(new ArrayList<>()); - - TestDependent dr1 = new TestDependent("DR_1"); - TestDependent dr2 = new TestDependent("DR_2"); - TestDeleterDependent drDeleter = new TestDeleterDependent("DR_DELETER"); - TestErrorDependent drError = new TestErrorDependent("ERROR_1"); - - @Test - void reconcileTopLevelResources() { - var workflow = new WorkflowBuilder() - .addDependent(dr1).build() - .addDependent(dr2).build() - .build(); - - var res = workflow.reconcile(new TestCustomResource(), null); - - Assertions.assertThat(res.getErroredDependents()).isEmpty(); - assertThat(executionHistory).reconciled(dr1, dr2); - } - - @Test - void reconciliationWithSimpleDependsOn() { - var workflow = new WorkflowBuilder() - .addDependent(dr1).build() - .addDependent(dr2).dependsOn(dr1).build() - .build(); - - var res = workflow.reconcile(new TestCustomResource(), null); - - Assertions.assertThat(res.getErroredDependents()).isEmpty(); - assertThat(executionHistory).reconciledInOrder(dr1, dr2); - } - - @Test - void reconciliationWithTwoTheDependsOns() { - TestDependent dr3 = new TestDependent("DR_3"); - - var workflow = new WorkflowBuilder() - .addDependent(dr1).build() - .addDependent(dr2).dependsOn(dr1).build() - .addDependent(dr3).dependsOn(dr1).build() - .build(); - - var res = workflow.reconcile(new TestCustomResource(), null); - - Assertions.assertThat(res.getErroredDependents()).isEmpty(); - assertThat(executionHistory) - .reconciledInOrder(dr1, dr2).reconciledInOrder(dr1, dr3); - } - - @Test - void diamondShareWorkflowReconcile() { - TestDependent dr3 = new TestDependent("DR_3"); - TestDependent dr4 = new TestDependent("DR_4"); - - var workflow = new WorkflowBuilder() - .addDependent(dr1).build() - .addDependent(dr2).dependsOn(dr1).build() - .addDependent(dr3).dependsOn(dr1).build() - .addDependent(dr4).dependsOn(dr3).dependsOn(dr2).build() - .build(); - - var res = workflow.reconcile(new TestCustomResource(), null); - - Assertions.assertThat(res.getErroredDependents()).isEmpty(); - assertThat(executionHistory) - .reconciledInOrder(dr1, dr2, dr4) - .reconciledInOrder(dr1, dr3, dr4); - } @Test - void exceptionHandlingSimpleCases() { - var workflow = new WorkflowBuilder() - .addDependent(drError).build() - .build(); - assertThrows(AggregatedOperatorException.class, - () -> workflow.reconcile(new TestCustomResource(), null) - .throwAggregatedExceptionIfErrorsPresent()); - assertThat(executionHistory).reconciled(drError); - } + public void calculatesTopLevelResources() { + var dr1 = mock(DependentResource.class); + var dr2 = mock(DependentResource.class); + var independentDR = mock(DependentResource.class); - @Test - void dependentsOnErroredResourceNotReconciled() { - var workflow = new WorkflowBuilder() + Workflow workflow = new WorkflowBuilder<>() + .addDependent(independentDR).build() .addDependent(dr1).build() - .addDependent(drError).dependsOn(dr1).build() - .addDependent(dr2).dependsOn(drError).build() - .build(); - assertThrows(AggregatedOperatorException.class, - () -> workflow.reconcile(new TestCustomResource(), null) - .throwAggregatedExceptionIfErrorsPresent()); - - assertThat(executionHistory).reconciled(dr1, drError).notReconciled(dr2); - } - - @Test - void oneBranchErrorsOtherCompletes() { - TestDependent dr3 = new TestDependent("DR_3"); - - var workflow = new WorkflowBuilder() - .addDependent(dr1).build() - .addDependent(drError).dependsOn(dr1).build() .addDependent(dr2).dependsOn(dr1).build() - .addDependent(dr3).dependsOn(dr2).build() .build(); - assertThrows(AggregatedOperatorException.class, - () -> workflow.reconcile(new TestCustomResource(), null) - .throwAggregatedExceptionIfErrorsPresent()); - - assertThat(executionHistory).reconciledInOrder(dr1, dr2, dr3); - assertThat(executionHistory).reconciledInOrder(dr1, drError); - } - - @Test - void onlyOneDependsOnErroredResourceNotReconciled() { - var workflow = new WorkflowBuilder() - .addDependent(dr1).build() - .addDependent(drError).build() - .addDependent(dr2).dependsOn(drError, dr1).build() - .build(); - assertThrows(AggregatedOperatorException.class, - () -> workflow.reconcile(new TestCustomResource(), null) - .throwAggregatedExceptionIfErrorsPresent()); + Set> topResources = + workflow.getTopLevelDependentResources().stream() + .map(DependentResourceNode::getDependentResource) + .collect(Collectors.toSet()); - assertThat(executionHistory).notReconciled(dr2); + assertThat(topResources).containsExactlyInAnyOrder(dr1, independentDR); } @Test - void simpleReconcileCondition() { - var workflow = new WorkflowBuilder() - .addDependent(dr1).withReconcileCondition(not_met_reconcile_condition).build() - .addDependent(dr2).withReconcileCondition(met_reconcile_condition).build() - .addDependent(drDeleter).withReconcileCondition(not_met_reconcile_condition).build() - .build(); + public void calculatesBottomLevelResources() { + var dr1 = mock(DependentResource.class); + var dr2 = mock(DependentResource.class); + var independentDR = mock(DependentResource.class); - var res = workflow.reconcile(new TestCustomResource(), null); - - Assertions.assertThat(res.getErroredDependents()).isEmpty(); - assertThat(executionHistory).notReconciled(dr1).reconciled(dr2).deleted(drDeleter); - } - - @Test - void triangleOnceConditionNotMet() { - var workflow = new WorkflowBuilder() + Workflow workflow = new WorkflowBuilder<>() + .addDependent(independentDR).build() .addDependent(dr1).build() .addDependent(dr2).dependsOn(dr1).build() - .addDependent(drDeleter).withReconcileCondition(not_met_reconcile_condition).dependsOn(dr1) - .build() .build(); - var res = workflow.reconcile(new TestCustomResource(), null); - - Assertions.assertThat(res.getErroredDependents()).isEmpty(); - assertThat(executionHistory).reconciledInOrder(dr1, dr2).deleted(drDeleter); - } - - @Test - void reconcileConditionTransitiveDelete() { - TestDeleterDependent drDeleter2 = new TestDeleterDependent("DR_DELETER_2"); - - var workflow = new WorkflowBuilder() - .addDependent(dr1).build() - .addDependent(dr2).withReconcileCondition(not_met_reconcile_condition).dependsOn(dr1) - .build() - .addDependent(drDeleter).withReconcileCondition(met_reconcile_condition).dependsOn(dr2) - .build() - .addDependent(drDeleter2).withReconcileCondition(met_reconcile_condition) - .dependsOn(drDeleter).build() - .build(); - - var res = workflow.reconcile(new TestCustomResource(), null); - - Assertions.assertThat(res.getErroredDependents()).isEmpty(); - assertThat(executionHistory).notReconciled(dr2); - assertThat(executionHistory).reconciledInOrder(dr1, drDeleter, drDeleter2); - assertThat(executionHistory).deleted(drDeleter, drDeleter2); - } - - @Test - void reconcileConditionAlsoErrorDependsOn() { - TestDeleterDependent drDeleter2 = new TestDeleterDependent("DR_DELETER_2"); - - var workflow = new WorkflowBuilder() - .addDependent(drError).build() - .addDependent(drDeleter).withReconcileCondition(not_met_reconcile_condition).build() - .addDependent(drDeleter2).withReconcileCondition(met_reconcile_condition) - .dependsOn(drError, drDeleter).build() - .build(); - - assertThrows(AggregatedOperatorException.class, - () -> workflow.reconcile(new TestCustomResource(), null) - .throwAggregatedExceptionIfErrorsPresent()); - - assertThat(executionHistory).deleted(drDeleter); - assertThat(executionHistory).reconciled(drError); - assertThat(executionHistory).notReconciled(drDeleter2); - } - - @Test - void oneDependsOnConditionNotMet() { - var workflow = new WorkflowBuilder() - .addDependent(dr1).build() - .addDependent(dr2).withReconcileCondition(not_met_reconcile_condition).build() - .addDependent(drDeleter).dependsOn(dr1, dr2).build() - .build(); - - var res = workflow.reconcile(new TestCustomResource(), null); - - Assertions.assertThat(res.getErroredDependents()).isEmpty(); - assertThat(executionHistory).deleted(drDeleter); - assertThat(executionHistory).reconciledInOrder(dr1, drDeleter); - assertThat(executionHistory).notReconciled(dr2); - } - - @Test - void readyConditionTrivialCase() { - var workflow = new WorkflowBuilder() - .addDependent(dr1).withReadyCondition(metReadyCondition).build() - .addDependent(dr2).dependsOn(dr1).build() - .build(); - - workflow.reconcile(new TestCustomResource(), null); - - assertThat(executionHistory).reconciledInOrder(dr1, dr2); - } - - @Test - void readyConditionNotMetTrivialCase() { - var workflow = new WorkflowBuilder() - .addDependent(dr1).withReadyCondition(notMetReadyCondition).build() - .addDependent(dr2).dependsOn(dr1).build() - .build(); - - var res = workflow.reconcile(new TestCustomResource(), null); - - Assertions.assertThat(res.getErroredDependents()).isEmpty(); - assertThat(executionHistory).reconciled(dr1).notReconciled(dr2); - } - - @Test - void readyConditionNotMetStatusUpdates() { - var workflow = new WorkflowBuilder() - .addDependent(dr1).withReadyCondition(notMetReadyConditionWithStatusUpdate).build() - .addDependent(dr2).dependsOn(dr1).build() - .build(); - - var cr = new TestCustomResource(); - var res = workflow.reconcile(cr, null); - - Assertions.assertThat(res.getErroredDependents()).isEmpty(); - assertThat(executionHistory).reconciled(dr1).notReconciled(dr2); - Assertions.assertThat(cr.getStatus().getConfigMapStatus()).isEqualTo(NOT_READY_YET); - } - - @Test - void readyConditionNotMetInOneParent() { - TestDependent dr3 = new TestDependent("DR_3"); - - var workflow = new WorkflowBuilder() - .addDependent(dr1).withReadyCondition(notMetReadyCondition).build() - .addDependent(dr2).build() - .addDependent(dr3).dependsOn(dr1, dr2).build() - .build(); - - var res = workflow.reconcile(new TestCustomResource(), null); - - Assertions.assertThat(res.getErroredDependents()).isEmpty(); - assertThat(executionHistory).reconciled(dr1, dr2).notReconciled(dr3); - } - - @Test - void diamondShareWithReadyCondition() { - TestDependent dr3 = new TestDependent("DR_3"); - TestDependent dr4 = new TestDependent("DR_4"); - - var workflow = new WorkflowBuilder() - .addDependent(dr1).build() - .addDependent(dr2).dependsOn(dr1).withReadyCondition(notMetReadyCondition).build() - .addDependent(dr3).dependsOn(dr1).build() - .addDependent(dr4).dependsOn(dr2, dr3).build() - .build(); - - var res = workflow.reconcile(new TestCustomResource(), null); - - Assertions.assertThat(res.getErroredDependents()).isEmpty(); - assertThat(executionHistory).reconciledInOrder(dr1, dr2) - .reconciledInOrder(dr1, dr3) - .notReconciled(dr4); - } - - private class TestDependent implements DependentResource { - - private String name; - - public TestDependent(String name) { - this.name = name; - } - - @Override - public ReconcileResult reconcile(TestCustomResource primary, - Context context) { - executionHistory.add(new ReconcileRecord(this)); - return ReconcileResult.resourceCreated(VALUE); - } - - @Override - public Class resourceType() { - return String.class; - } - - @Override - public Optional getSecondaryResource(TestCustomResource primary) { - return Optional.of(VALUE); - } - - @Override - public String toString() { - return name; - } - } - - private class TestDeleterDependent extends TestDependent implements Deleter { - - public TestDeleterDependent(String name) { - super(name); - } - - @Override - public void delete(TestCustomResource primary, Context context) { - executionHistory.add(new ReconcileRecord(this, true)); - } - } - - private class TestErrorDependent implements DependentResource { - private String name; - - public TestErrorDependent(String name) { - this.name = name; - } - - @Override - public ReconcileResult reconcile(TestCustomResource primary, - Context context) { - executionHistory.add(new ReconcileRecord(this)); - throw new IllegalStateException("Test exception"); - } - - @Override - public Class resourceType() { - return String.class; - } - - @Override - public Optional getSecondaryResource(TestCustomResource primary) { - return Optional.of(VALUE); - } + Set> bottomResources = + workflow.getBottomLevelResource().stream() + .map(DependentResourceNode::getDependentResource) + .collect(Collectors.toSet()); - @Override - public String toString() { - return name; - } + assertThat(bottomResources).containsExactlyInAnyOrder(dr2, independentDR); } } From 90535c10bd8b1c215680264f47358a88d0ea81ef Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 25 Apr 2022 13:47:52 +0200 Subject: [PATCH 30/51] wip --- .../dependent/workflow/WorkflowCleanupExecutor.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) 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 e367d6c735..0436bb19e4 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 @@ -21,9 +21,15 @@ public WorkflowCleanupExecutor(Workflow

workflow, P primary, Context

conte } - public WorkflowCleanupResult cleanup() { - + public synchronized WorkflowCleanupResult cleanup() { + for (DependentResourceNode dependentResourceNode : workflow + .getTopLevelDependentResources()) { + handleCleanup(dependentResourceNode, false); + } return null; } + private void handleCleanup(DependentResourceNode dependentResourceNode, boolean b) { + + } } From 44e1ef1cac1ec44fd6e8d3d177aead056e0f1662 Mon Sep 17 00:00:00 2001 From: csviri Date: Wed, 4 May 2022 13:32:30 +0200 Subject: [PATCH 31/51] wip on cleanup --- .../workflow/WorkflowCleanupExecutor.java | 110 +++++++++++++++++- .../workflow/WorkflowReconcileExecutor.java | 3 +- 2 files changed, 110 insertions(+), 3 deletions(-) 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 0436bb19e4..c2695c45be 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 @@ -1,5 +1,11 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Future; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -10,6 +16,15 @@ public class WorkflowCleanupExecutor

{ private static final Logger log = LoggerFactory.getLogger(WorkflowReconcileExecutor.class); + private final Map, Future> actualExecutions = + new HashMap<>(); + private final Map, Exception> exceptionsDuringExecution = + new HashMap<>(); + private final Set> alreadyReconciled = new HashSet<>(); + private final Set> notReady = new HashSet<>(); + private final Set> ownOrAncestorReconcileConditionConditionNotMet = + new HashSet<>(); + private final Workflow

workflow; private final P primary; private final Context

context; @@ -23,13 +38,104 @@ public WorkflowCleanupExecutor(Workflow

workflow, P primary, Context

conte public synchronized WorkflowCleanupResult cleanup() { for (DependentResourceNode dependentResourceNode : workflow - .getTopLevelDependentResources()) { + .getBottomLevelResource()) { handleCleanup(dependentResourceNode, false); } - return null; + while (true) { + try { + this.wait(); + if (noMoreExecutionsScheduled()) { + break; + } else { + log.warn("Notified but still resources under execution. This should not happen."); + } + } catch (InterruptedException e) { + log.warn("Thread interrupted", e); + Thread.currentThread().interrupt(); + } + } + return createCleanupResult(); + } + + private WorkflowCleanupResult createCleanupResult() { + return new WorkflowCleanupResult(); + } + + private synchronized boolean noMoreExecutionsScheduled() { + return actualExecutions.isEmpty(); } private void handleCleanup(DependentResourceNode dependentResourceNode, boolean b) { + log.debug("Submitting for cleanup: {}", dependentResourceNode); + + if (alreadyVisited(dependentResourceNode) + || isCleaningNow(dependentResourceNode) + || !allParentsCleaned(dependentResourceNode) + || hasErroredParent(dependentResourceNode)) { + log.debug("Skipping submit of: {}, ", dependentResourceNode); + return; + } + + } + + private class NodeExecutor implements Runnable { + + private final DependentResourceNode dependentResourceNode; + private final boolean onlyReconcileForPossibleDelete; + + private NodeExecutor(DependentResourceNode dependentResourceNode, + boolean onlyReconcileForDelete) { + this.dependentResourceNode = dependentResourceNode; + this.onlyReconcileForPossibleDelete = onlyReconcileForDelete; + } + + @Override + @SuppressWarnings("unchecked") + public void run() { + try { + + } catch (RuntimeException e) { + handleExceptionInExecutor(dependentResourceNode, e); + } finally { + handleNodeExecutionFinish(dependentResourceNode); + } + } + } + + private synchronized void handleExceptionInExecutor(DependentResourceNode dependentResourceNode, + RuntimeException e) { + exceptionsDuringExecution.put(dependentResourceNode, e); + } + + private synchronized void handleNodeExecutionFinish(DependentResourceNode dependentResourceNode) { + log.debug("Finished execution for: {}", dependentResourceNode); + actualExecutions.remove(dependentResourceNode); + if (actualExecutions.isEmpty()) { + this.notifyAll(); + } + } + + private boolean isCleaningNow(DependentResourceNode dependentResourceNode) { + return actualExecutions.containsKey(dependentResourceNode); + } + + + private boolean alreadyVisited( + DependentResourceNode dependentResourceNode) { + return alreadyReconciled.contains(dependentResourceNode); + } + + private boolean allParentsCleaned( + DependentResourceNode dependentResourceNode) { + return dependentResourceNode.getDependsOn().isEmpty() + || dependentResourceNode.getDependsOn().stream() + .allMatch(d -> alreadyVisited(d) && !notReady.contains(d)); + } + private boolean hasErroredParent( + DependentResourceNode dependentResourceNode) { + return !dependentResourceNode.getDependsOn().isEmpty() + && dependentResourceNode.getDependsOn().stream() + .anyMatch(exceptionsDuringExecution::containsKey); } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index b132fc934b..29c7da5d6f 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -38,6 +38,8 @@ public WorkflowReconcileExecutor(Workflow

workflow, P primary, Context

con this.workflow = workflow; } + // todo reverse delete order + // todo add reconcile results // - reconciled in results should contain only truly reconciled public synchronized WorkflowExecutionResult reconcile() { @@ -106,7 +108,6 @@ private synchronized void handleNodeExecutionFinish(DependentResourceNode depend // needs to be synced private synchronized void updateStatusForNotReady(ReadyCondition readyCondition) { - // todo think through this, since more can't be not ready readyCondition.addNotReadyStatusInfo(primary); } From e5e939ca7dd8bea1dbb0ff1573994d3acc0abbf5 Mon Sep 17 00:00:00 2001 From: csviri Date: Wed, 4 May 2022 15:35:49 +0200 Subject: [PATCH 32/51] cleanup wip --- .../workflow/WorkflowCleanupExecutor.java | 67 ++++++++----- .../AbstractWorkflowExecutorTest.java | 97 +++++++++++++++++++ .../workflow/WorkflowCleanupExecutorTest.java | 35 +++++++ .../WorkflowReconcileExecutorTest.java | 91 +---------------- 4 files changed, 177 insertions(+), 113 deletions(-) create mode 100644 operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutorTest.java create mode 100644 operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java 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 c2695c45be..41fe0303d3 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 @@ -11,6 +11,7 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; public class WorkflowCleanupExecutor

{ @@ -20,10 +21,8 @@ public class WorkflowCleanupExecutor

{ new HashMap<>(); private final Map, Exception> exceptionsDuringExecution = new HashMap<>(); - private final Set> alreadyReconciled = new HashSet<>(); + private final Set> alreadyVisited = new HashSet<>(); private final Set> notReady = new HashSet<>(); - private final Set> ownOrAncestorReconcileConditionConditionNotMet = - new HashSet<>(); private final Workflow

workflow; private final P primary; @@ -35,11 +34,13 @@ public WorkflowCleanupExecutor(Workflow

workflow, P primary, Context

conte this.context = context; } + // todo cleanup condition + // todo error handling public synchronized WorkflowCleanupResult cleanup() { for (DependentResourceNode dependentResourceNode : workflow .getBottomLevelResource()) { - handleCleanup(dependentResourceNode, false); + handleCleanup(dependentResourceNode); } while (true) { try { @@ -57,15 +58,11 @@ public synchronized WorkflowCleanupResult cleanup() { return createCleanupResult(); } - private WorkflowCleanupResult createCleanupResult() { - return new WorkflowCleanupResult(); - } - private synchronized boolean noMoreExecutionsScheduled() { return actualExecutions.isEmpty(); } - private void handleCleanup(DependentResourceNode dependentResourceNode, boolean b) { + private synchronized void handleCleanup(DependentResourceNode dependentResourceNode) { log.debug("Submitting for cleanup: {}", dependentResourceNode); if (alreadyVisited(dependentResourceNode) @@ -76,24 +73,30 @@ private void handleCleanup(DependentResourceNode dependentResourceNode, bo return; } + Future nodeFuture = + workflow.getExecutorService().submit( + new NodeExecutor(dependentResourceNode)); + actualExecutions.put(dependentResourceNode, nodeFuture); + log.debug("Submitted to reconcile: {}", dependentResourceNode); } private class NodeExecutor implements Runnable { private final DependentResourceNode dependentResourceNode; - private final boolean onlyReconcileForPossibleDelete; - private NodeExecutor(DependentResourceNode dependentResourceNode, - boolean onlyReconcileForDelete) { + private NodeExecutor(DependentResourceNode dependentResourceNode) { this.dependentResourceNode = dependentResourceNode; - this.onlyReconcileForPossibleDelete = onlyReconcileForDelete; } @Override @SuppressWarnings("unchecked") public void run() { try { - + if (dependentResourceNode.getDependentResource() instanceof Deleter) { + // todo check if not garbage collected + ((Deleter

) dependentResourceNode.getDependentResource()).delete(primary, context); + } + handleDependentCleaned(dependentResourceNode); } catch (RuntimeException e) { handleExceptionInExecutor(dependentResourceNode, e); } finally { @@ -102,12 +105,26 @@ public void run() { } } - private synchronized void handleExceptionInExecutor(DependentResourceNode dependentResourceNode, + @SuppressWarnings("unchecked") + private synchronized void handleDependentCleaned( + DependentResourceNode dependentResourceNode) { + var dependOns = dependentResourceNode.getDependsOn(); + if (dependOns != null) { + dependOns.forEach(d -> { + log.debug("Handle cleanup for dependent: {} of parent:{}", d, dependentResourceNode); + handleCleanup(d); + }); + } + } + + private synchronized void handleExceptionInExecutor( + DependentResourceNode dependentResourceNode, RuntimeException e) { exceptionsDuringExecution.put(dependentResourceNode, e); } - private synchronized void handleNodeExecutionFinish(DependentResourceNode dependentResourceNode) { + private synchronized void handleNodeExecutionFinish( + DependentResourceNode dependentResourceNode) { log.debug("Finished execution for: {}", dependentResourceNode); actualExecutions.remove(dependentResourceNode); if (actualExecutions.isEmpty()) { @@ -119,23 +136,27 @@ private boolean isCleaningNow(DependentResourceNode dependentResourceNode) return actualExecutions.containsKey(dependentResourceNode); } - private boolean alreadyVisited( DependentResourceNode dependentResourceNode) { - return alreadyReconciled.contains(dependentResourceNode); + return alreadyVisited.contains(dependentResourceNode); } private boolean allParentsCleaned( DependentResourceNode dependentResourceNode) { - return dependentResourceNode.getDependsOn().isEmpty() - || dependentResourceNode.getDependsOn().stream() + var parents = workflow.getDependents().get(dependentResourceNode); + return parents.isEmpty() + || parents.stream() .allMatch(d -> alreadyVisited(d) && !notReady.contains(d)); } private boolean hasErroredParent( DependentResourceNode dependentResourceNode) { - return !dependentResourceNode.getDependsOn().isEmpty() - && dependentResourceNode.getDependsOn().stream() - .anyMatch(exceptionsDuringExecution::containsKey); + var parents = workflow.getDependents().get(dependentResourceNode); + return !parents.isEmpty() + && parents.stream().anyMatch(exceptionsDuringExecution::containsKey); + } + + private WorkflowCleanupResult createCleanupResult() { + return new WorkflowCleanupResult(); } } 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 new file mode 100644 index 0000000000..5a47eaaa71 --- /dev/null +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutorTest.java @@ -0,0 +1,97 @@ +package io.javaoperatorsdk.operator.processing.dependent.workflow; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; +import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; + +public class AbstractWorkflowExecutorTest { + public static final String VALUE = "value"; + + protected TestDependent dr1 = new TestDependent("DR_1"); + protected TestDependent dr2 = new TestDependent("DR_2"); + protected TestDeleterDependent drDeleter = new TestDeleterDependent("DR_DELETER"); + protected TestErrorDependent drError = new TestErrorDependent("ERROR_1"); + + protected List executionHistory = + Collections.synchronizedList(new ArrayList<>()); + + public class TestDependent implements DependentResource { + + private String name; + + public TestDependent(String name) { + this.name = name; + } + + @Override + public ReconcileResult reconcile(TestCustomResource primary, + Context context) { + executionHistory.add(new ReconcileRecord(this)); + return ReconcileResult.resourceCreated(VALUE); + } + + @Override + public Class resourceType() { + return String.class; + } + + @Override + public Optional getSecondaryResource(TestCustomResource primary) { + return Optional.of(VALUE); + } + + @Override + public String toString() { + return name; + } + } + + public class TestDeleterDependent extends TestDependent implements Deleter { + + public TestDeleterDependent(String name) { + super(name); + } + + @Override + public void delete(TestCustomResource primary, Context context) { + executionHistory.add(new ReconcileRecord(this, true)); + } + } + + public class TestErrorDependent implements DependentResource { + private String name; + + public TestErrorDependent(String name) { + this.name = name; + } + + @Override + public ReconcileResult reconcile(TestCustomResource primary, + Context context) { + executionHistory.add(new ReconcileRecord(this)); + throw new IllegalStateException("Test exception"); + } + + @Override + public Class resourceType() { + return String.class; + } + + @Override + public Optional getSecondaryResource(TestCustomResource primary) { + return Optional.of(VALUE); + } + + @Override + public String toString() { + return name; + } + } +} diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java new file mode 100644 index 0000000000..e15c73d4e9 --- /dev/null +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java @@ -0,0 +1,35 @@ +package io.javaoperatorsdk.operator.processing.dependent.workflow; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.javaoperatorsdk.operator.processing.dependent.workflow.builder.WorkflowBuilder; +import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; + +import static io.javaoperatorsdk.operator.processing.dependent.workflow.ExecutionAssert.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +class WorkflowCleanupExecutorTest extends AbstractWorkflowExecutorTest { + + protected TestDeleterDependent dd1 = new TestDeleterDependent("DR_DELETER_1"); + protected TestDeleterDependent dd2 = new TestDeleterDependent("DR_DELETER_2"); + protected TestDeleterDependent dd3 = new TestDeleterDependent("DR_DELETER_3"); + + @Test + void cleanUpDiamondWorkflow() { + var workflow = new WorkflowBuilder() + .addDependent(dd1).build() + .addDependent(dr1).dependsOn(dd1).build() + .addDependent(dd2).dependsOn(dd1).build() + .addDependent(dd3).dependsOn(dr1,dd2).build() + .build(); + + var res = workflow.cleanup(new TestCustomResource(), null); + + + assertThat(executionHistory).reconciledInOrder(dd1, dd2, dd3).reconciledInOrder(); + } + + + +} diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java index e507941a7c..ca466c7a58 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java @@ -1,18 +1,11 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import io.javaoperatorsdk.operator.AggregatedOperatorException; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; -import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; import io.javaoperatorsdk.operator.processing.dependent.workflow.builder.WorkflowBuilder; import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReadyCondition; import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReconcileCondition; @@ -21,7 +14,7 @@ import static io.javaoperatorsdk.operator.processing.dependent.workflow.ExecutionAssert.*; import static org.junit.jupiter.api.Assertions.assertThrows; -class WorkflowReconcileExecutorTest { +class WorkflowReconcileExecutorTest extends AbstractWorkflowExecutorTest { public static final String NOT_READY_YET = "NOT READY YET"; private ReconcileCondition met_reconcile_condition = @@ -48,15 +41,6 @@ public void addNotReadyStatusInfo(TestCustomResource primary) { } }; - public static final String VALUE = "value"; - private List executionHistory = - Collections.synchronizedList(new ArrayList<>()); - - TestDependent dr1 = new TestDependent("DR_1"); - TestDependent dr2 = new TestDependent("DR_2"); - TestDeleterDependent drDeleter = new TestDeleterDependent("DR_DELETER"); - TestErrorDependent drError = new TestErrorDependent("ERROR_1"); - @Test void reconcileTopLevelResources() { var workflow = new WorkflowBuilder() @@ -341,77 +325,4 @@ void diamondShareWithReadyCondition() { .notReconciled(dr4); } - public class TestDependent implements DependentResource { - - private String name; - - public TestDependent(String name) { - this.name = name; - } - - @Override - public ReconcileResult reconcile(TestCustomResource primary, - Context context) { - executionHistory.add(new ReconcileRecord(this)); - return ReconcileResult.resourceCreated(VALUE); - } - - @Override - public Class resourceType() { - return String.class; - } - - @Override - public Optional getSecondaryResource(TestCustomResource primary) { - return Optional.of(VALUE); - } - - @Override - public String toString() { - return name; - } - } - - private class TestDeleterDependent extends TestDependent implements Deleter { - - public TestDeleterDependent(String name) { - super(name); - } - - @Override - public void delete(TestCustomResource primary, Context context) { - executionHistory.add(new ReconcileRecord(this, true)); - } - } - - private class TestErrorDependent implements DependentResource { - private String name; - - public TestErrorDependent(String name) { - this.name = name; - } - - @Override - public ReconcileResult reconcile(TestCustomResource primary, - Context context) { - executionHistory.add(new ReconcileRecord(this)); - throw new IllegalStateException("Test exception"); - } - - @Override - public Class resourceType() { - return String.class; - } - - @Override - public Optional getSecondaryResource(TestCustomResource primary) { - return Optional.of(VALUE); - } - - @Override - public String toString() { - return name; - } - } - } From 6c9d3c85103f3962de247f6217c9f35307c51130 Mon Sep 17 00:00:00 2001 From: csviri Date: Wed, 4 May 2022 16:17:29 +0200 Subject: [PATCH 33/51] wip --- .../processing/dependent/workflow/Workflow.java | 9 +++++++++ .../dependent/workflow/WorkflowCleanupExecutor.java | 8 ++++---- .../workflow/WorkflowReconcileExecutor.java | 13 +++++-------- .../dependent/workflow/ExecutionAssert.java | 2 +- .../workflow/WorkflowCleanupExecutorTest.java | 10 +++------- 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java index 4d245b6073..c0e431274d 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java @@ -82,6 +82,15 @@ Set> getBottomLevelResource() { return bottomLevelResource; } + List> getDependents(DependentResourceNode node) { + var deps = dependents.get(node); + if (deps == null) { + return Collections.emptyList(); + } else { + return deps; + } + } + Map, List>> getDependents() { return dependents; } 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 41fe0303d3..d60e7a8a7f 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 @@ -142,16 +142,16 @@ private boolean alreadyVisited( } private boolean allParentsCleaned( - DependentResourceNode dependentResourceNode) { - var parents = workflow.getDependents().get(dependentResourceNode); + DependentResourceNode dependentResourceNode) { + var parents = workflow.getDependents(dependentResourceNode); return parents.isEmpty() || parents.stream() .allMatch(d -> alreadyVisited(d) && !notReady.contains(d)); } private boolean hasErroredParent( - DependentResourceNode dependentResourceNode) { - var parents = workflow.getDependents().get(dependentResourceNode); + DependentResourceNode dependentResourceNode) { + var parents = workflow.getDependents(dependentResourceNode); return !parents.isEmpty() && parents.stream().anyMatch(exceptionsDuringExecution::containsKey); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index 29c7da5d6f..6401f68e46 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -178,14 +178,11 @@ private boolean isReconcilingNow(DependentResourceNode dependentResourceNo private synchronized void handleDependentsReconcile( DependentResourceNode dependentResourceNode, boolean onlyReconcileForPossibleDelete) { - var dependents = workflow.getDependents().get(dependentResourceNode); - if (dependents != null) { - - dependents.forEach(d -> { - log.debug("Handle reconcile for dependent: {} of parent:{}", d, dependentResourceNode); - handleReconcile(d, onlyReconcileForPossibleDelete); - }); - } + var dependents = workflow.getDependents(dependentResourceNode); + dependents.forEach(d -> { + log.debug("Handle reconcile for dependent: {} of parent:{}", d, dependentResourceNode); + handleReconcile(d, onlyReconcileForPossibleDelete); + }); } private boolean noMoreExecutionsScheduled() { diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionAssert.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionAssert.java index c963419102..0180131d2a 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionAssert.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionAssert.java @@ -84,7 +84,7 @@ public ExecutionAssert notReconciled(DependentResource... dependentResourc private void checkIfReconciled(int i, DependentResource[] dependentResources) { if (!getActualDependentResources().contains(dependentResources[i])) { - failWithMessage("Dependent resource not reconciled on place %i", i); + failWithMessage("Dependent resource not reconciled on place %d", i); } } } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java index e15c73d4e9..ae0d3b626d 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java @@ -1,8 +1,5 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; - import io.javaoperatorsdk.operator.processing.dependent.workflow.builder.WorkflowBuilder; import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; @@ -15,17 +12,16 @@ class WorkflowCleanupExecutorTest extends AbstractWorkflowExecutorTest { protected TestDeleterDependent dd2 = new TestDeleterDependent("DR_DELETER_2"); protected TestDeleterDependent dd3 = new TestDeleterDependent("DR_DELETER_3"); - @Test + // @Test void cleanUpDiamondWorkflow() { var workflow = new WorkflowBuilder() .addDependent(dd1).build() .addDependent(dr1).dependsOn(dd1).build() .addDependent(dd2).dependsOn(dd1).build() - .addDependent(dd3).dependsOn(dr1,dd2).build() + .addDependent(dd3).dependsOn(dr1, dd2).build() .build(); - var res = workflow.cleanup(new TestCustomResource(), null); - + workflow.cleanup(new TestCustomResource(), null); assertThat(executionHistory).reconciledInOrder(dd1, dd2, dd3).reconciledInOrder(); } From ca3ab563d73b36508d0c7a49fb1e302b5ada8f43 Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 16 May 2022 12:11:10 +0200 Subject: [PATCH 34/51] fix: test --- .../dependent/workflow/WorkflowCleanupExecutor.java | 1 + .../processing/dependent/workflow/ExecutionAssert.java | 3 ++- .../dependent/workflow/WorkflowCleanupExecutorTest.java | 6 ++++-- 3 files changed, 7 insertions(+), 3 deletions(-) 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 d60e7a8a7f..c6caa364e9 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 @@ -96,6 +96,7 @@ public void run() { // todo check if not garbage collected ((Deleter

) dependentResourceNode.getDependentResource()).delete(primary, context); } + alreadyVisited.add(dependentResourceNode); handleDependentCleaned(dependentResourceNode); } catch (RuntimeException e) { handleExceptionInExecutor(dependentResourceNode, e); diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionAssert.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionAssert.java index 0180131d2a..b928071bd7 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionAssert.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ExecutionAssert.java @@ -84,7 +84,8 @@ public ExecutionAssert notReconciled(DependentResource... dependentResourc private void checkIfReconciled(int i, DependentResource[] dependentResources) { if (!getActualDependentResources().contains(dependentResources[i])) { - failWithMessage("Dependent resource not reconciled on place %d", i); + failWithMessage("Dependent resource: %s, not reconciled on place %d", dependentResources[i], + i); } } } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java index ae0d3b626d..7fbd8f07b6 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java @@ -1,5 +1,7 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow; +import org.junit.jupiter.api.Test; + import io.javaoperatorsdk.operator.processing.dependent.workflow.builder.WorkflowBuilder; import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; @@ -12,7 +14,7 @@ class WorkflowCleanupExecutorTest extends AbstractWorkflowExecutorTest { protected TestDeleterDependent dd2 = new TestDeleterDependent("DR_DELETER_2"); protected TestDeleterDependent dd3 = new TestDeleterDependent("DR_DELETER_3"); - // @Test + @Test void cleanUpDiamondWorkflow() { var workflow = new WorkflowBuilder() .addDependent(dd1).build() @@ -23,7 +25,7 @@ void cleanUpDiamondWorkflow() { workflow.cleanup(new TestCustomResource(), null); - assertThat(executionHistory).reconciledInOrder(dd1, dd2, dd3).reconciledInOrder(); + assertThat(executionHistory).reconciledInOrder(dd3, dd2, dd1).notReconciled(dr1); } From 540e22a5d6e73ed1d577b19d0fff76a738fc2c20 Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 16 May 2022 14:37:24 +0200 Subject: [PATCH 35/51] cleanup workflow process --- .../workflow/WorkflowCleanupExecutor.java | 30 ++++--- .../workflow/WorkflowReconcileExecutor.java | 9 +-- .../workflow/builder/DependentBuilder.java | 6 ++ .../workflow/condition/CleanupCondition.java | 8 +- .../workflow/condition/ReadyCondition.java | 7 -- .../AbstractWorkflowExecutorTest.java | 23 ++++++ .../workflow/WorkflowCleanupExecutorTest.java | 78 ++++++++++++++++++- .../WorkflowReconcileExecutorTest.java | 30 +------ 8 files changed, 135 insertions(+), 56 deletions(-) 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 c6caa364e9..d238d88bb2 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 @@ -12,6 +12,7 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; +import io.javaoperatorsdk.operator.api.reconciler.dependent.GarbageCollected; public class WorkflowCleanupExecutor

{ @@ -22,7 +23,7 @@ public class WorkflowCleanupExecutor

{ private final Map, Exception> exceptionsDuringExecution = new HashMap<>(); private final Set> alreadyVisited = new HashSet<>(); - private final Set> notReady = new HashSet<>(); + private final Set> cleanupConditionNotMet = new HashSet<>(); private final Workflow

workflow; private final P primary; @@ -35,7 +36,6 @@ public WorkflowCleanupExecutor(Workflow

workflow, P primary, Context

conte } // todo cleanup condition - // todo error handling public synchronized WorkflowCleanupResult cleanup() { for (DependentResourceNode dependentResourceNode : workflow @@ -67,8 +67,8 @@ private synchronized void handleCleanup(DependentResourceNode dependentRes if (alreadyVisited(dependentResourceNode) || isCleaningNow(dependentResourceNode) - || !allParentsCleaned(dependentResourceNode) - || hasErroredParent(dependentResourceNode)) { + || !allDependentsCleaned(dependentResourceNode) + || hasErroredDependent(dependentResourceNode)) { log.debug("Skipping submit of: {}, ", dependentResourceNode); return; } @@ -92,12 +92,22 @@ private NodeExecutor(DependentResourceNode dependentResourceNode) { @SuppressWarnings("unchecked") public void run() { try { - if (dependentResourceNode.getDependentResource() instanceof Deleter) { - // todo check if not garbage collected + var dependentResource = dependentResourceNode.getDependentResource(); + + var cleanupCondition = dependentResourceNode.getCleanupCondition(); + + if (dependentResource instanceof Deleter + && !(dependentResource instanceof GarbageCollected)) { ((Deleter

) dependentResourceNode.getDependentResource()).delete(primary, context); } alreadyVisited.add(dependentResourceNode); - handleDependentCleaned(dependentResourceNode); + boolean cleanupConditionMet = + cleanupCondition.map(c -> c.isMet(dependentResource, primary, context)).orElse(true); + if (cleanupConditionMet) { + handleDependentCleaned(dependentResourceNode); + } else { + cleanupConditionNotMet.add(dependentResourceNode); + } } catch (RuntimeException e) { handleExceptionInExecutor(dependentResourceNode, e); } finally { @@ -142,15 +152,15 @@ private boolean alreadyVisited( return alreadyVisited.contains(dependentResourceNode); } - private boolean allParentsCleaned( + private boolean allDependentsCleaned( DependentResourceNode dependentResourceNode) { var parents = workflow.getDependents(dependentResourceNode); return parents.isEmpty() || parents.stream() - .allMatch(d -> alreadyVisited(d) && !notReady.contains(d)); + .allMatch(d -> alreadyVisited(d) && !cleanupConditionNotMet.contains(d)); } - private boolean hasErroredParent( + private boolean hasErroredDependent( DependentResourceNode dependentResourceNode) { var parents = workflow.getDependents(dependentResourceNode); return !parents.isEmpty() diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index 6401f68e46..2fa494e48b 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -11,7 +11,6 @@ import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; -import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReadyCondition; import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReconcileCondition; public class WorkflowReconcileExecutor

{ @@ -106,11 +105,6 @@ private synchronized void handleNodeExecutionFinish(DependentResourceNode depend } } - // needs to be synced - private synchronized void updateStatusForNotReady(ReadyCondition readyCondition) { - readyCondition.addNotReadyStatusInfo(primary); - } - // needs to be in one step private synchronized void setAlreadyReconciledButNotReady( DependentResourceNode dependentResourceNode) { @@ -148,12 +142,11 @@ public void run() { ((Deleter

) dependentResource).delete(primary, context); } } else { - dependentResource.reconcile(primary, context); + var reconcileResult = dependentResource.reconcile(primary, context); if (dependentResourceNode.getReadyCondition().isPresent() && !dependentResourceNode.getReadyCondition().get() .isMet(dependentResource, primary, context)) { ready = false; - updateStatusForNotReady(dependentResourceNode.getReadyCondition().get()); } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java index 204532653d..58264e274f 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java @@ -3,6 +3,7 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.processing.dependent.workflow.DependentResourceNode; +import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.CleanupCondition; import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReadyCondition; import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReconcileCondition; @@ -34,6 +35,11 @@ public DependentBuilder

withReadyCondition(ReadyCondition readyCondition) { return this; } + public DependentBuilder

withCleanupCondition(CleanupCondition readyCondition) { + node.setCleanupCondition(readyCondition); + return this; + } + public WorkflowBuilder

build() { return workflowBuilder; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/CleanupCondition.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/CleanupCondition.java index cb506c1187..e8fd391b01 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/CleanupCondition.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/CleanupCondition.java @@ -1,4 +1,10 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow.condition; -public class CleanupCondition { +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; + +public interface CleanupCondition { + + boolean isMet(DependentResource dependentResource, P primary, Context

context); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReadyCondition.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReadyCondition.java index 37cd224d2b..50592d8dca 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReadyCondition.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReadyCondition.java @@ -8,11 +8,4 @@ public interface ReadyCondition { boolean isMet(DependentResource dependentResource, P primary, Context

context); - /** - * If condition not met, the primary resource status might be updated by overriding this method. - * In case there are multiple conditions in a workflow it is updated multiple times. - * - * @param primary - */ - default void addNotReadyStatusInfo(P primary) {} } 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 5a47eaaa71..7b153702dc 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 @@ -8,6 +8,7 @@ import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.api.reconciler.dependent.GarbageCollected; import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; @@ -65,6 +66,28 @@ public void delete(TestCustomResource primary, Context conte } } + public class GarbageCollectedDeleter extends TestDeleterDependent + implements GarbageCollected { + + public GarbageCollectedDeleter(String name) { + super(name); + } + } + + public class TestErrorDeleterDependent extends TestDependent + implements Deleter { + + public TestErrorDeleterDependent(String name) { + super(name); + } + + @Override + public void delete(TestCustomResource primary, Context context) { + executionHistory.add(new ReconcileRecord(this, true)); + throw new IllegalStateException("Test exception"); + } + } + public class TestErrorDependent implements DependentResource { private String name; diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java index 7fbd8f07b6..b962ab1760 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java @@ -3,10 +3,10 @@ import org.junit.jupiter.api.Test; import io.javaoperatorsdk.operator.processing.dependent.workflow.builder.WorkflowBuilder; +import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.CleanupCondition; import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; import static io.javaoperatorsdk.operator.processing.dependent.workflow.ExecutionAssert.assertThat; -import static org.junit.jupiter.api.Assertions.*; class WorkflowCleanupExecutorTest extends AbstractWorkflowExecutorTest { @@ -14,6 +14,13 @@ class WorkflowCleanupExecutorTest extends AbstractWorkflowExecutorTest { protected TestDeleterDependent dd2 = new TestDeleterDependent("DR_DELETER_2"); protected TestDeleterDependent dd3 = new TestDeleterDependent("DR_DELETER_3"); + protected TestErrorDeleterDependent errorDD = new TestErrorDeleterDependent("ERROR_DELETER"); + + private final CleanupCondition noMetCleanupCondition = + (dependentResource, primary, context) -> false; + private final CleanupCondition metCleanupCondition = + (dependentResource, primary, context) -> true; + @Test void cleanUpDiamondWorkflow() { var workflow = new WorkflowBuilder() @@ -28,6 +35,75 @@ void cleanUpDiamondWorkflow() { assertThat(executionHistory).reconciledInOrder(dd3, dd2, dd1).notReconciled(dr1); } + @Test + void dontDeleteIfDependentErrored() { + var workflow = new WorkflowBuilder() + .addDependent(dd1).build() + .addDependent(dd2).dependsOn(dd1).build() + .addDependent(dd3).dependsOn(dd2).build() + .addDependent(errorDD).dependsOn(dd2).build() + .build(); + + workflow.cleanup(new TestCustomResource(), null); + + assertThat(executionHistory).deleted(dd3, errorDD).notReconciled(dd1, dd2); + } + + + @Test + void cleanupConditionTrivialCase() { + var workflow = new WorkflowBuilder() + .addDependent(dd1).build() + .addDependent(dd2).dependsOn(dd1).withCleanupCondition(noMetCleanupCondition).build() + .build(); + + workflow.cleanup(new TestCustomResource(), null); + + assertThat(executionHistory).deleted(dd2).notReconciled(dd1); + } + + @Test + void cleanupConditionMet() { + var workflow = new WorkflowBuilder() + .addDependent(dd1).build() + .addDependent(dd2).dependsOn(dd1).withCleanupCondition(metCleanupCondition).build() + .build(); + + workflow.cleanup(new TestCustomResource(), null); + + assertThat(executionHistory).deleted(dd2, dd1); + } + + @Test + void cleanupConditionDiamondWorkflow() { + TestDeleterDependent dd4 = new TestDeleterDependent("DR_DELETER_4"); + + var workflow = new WorkflowBuilder() + .addDependent(dd1).build() + .addDependent(dd2).dependsOn(dd1).build() + .addDependent(dd3).dependsOn(dd1).withCleanupCondition(noMetCleanupCondition).build() + .addDependent(dd4).dependsOn(dd2, dd3).build() + .build(); + + workflow.cleanup(new TestCustomResource(), null); + + assertThat(executionHistory) + .reconciledInOrder(dd4, dd2) + .reconciledInOrder(dd4, dd3) + .notReconciled(dr1); + } + + @Test + void dontDeleteIfGarbageCollected() { + GarbageCollectedDeleter gcDel = new GarbageCollectedDeleter("GC_DELETER"); + var workflow = new WorkflowBuilder() + .addDependent(gcDel).build() + .build(); + workflow.cleanup(new TestCustomResource(), null); + + assertThat(executionHistory) + .notReconciled(gcDel); + } } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java index ca466c7a58..cc352186db 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java @@ -4,8 +4,6 @@ import org.junit.jupiter.api.Test; import io.javaoperatorsdk.operator.AggregatedOperatorException; -import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.processing.dependent.workflow.builder.WorkflowBuilder; import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReadyCondition; import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReconcileCondition; @@ -28,18 +26,7 @@ class WorkflowReconcileExecutorTest extends AbstractWorkflowExecutorTest { (dependentResource, primary, context) -> false; private ReadyCondition notMetReadyConditionWithStatusUpdate = - new ReadyCondition<>() { - @Override - public boolean isMet(DependentResource dependentResource, - TestCustomResource primary, Context context) { - return false; - } - - @Override - public void addNotReadyStatusInfo(TestCustomResource primary) { - primary.getStatus().setConfigMapStatus(NOT_READY_YET); - } - }; + (dependentResource, primary, context) -> false; @Test void reconcileTopLevelResources() { @@ -274,21 +261,6 @@ void readyConditionNotMetTrivialCase() { assertThat(executionHistory).reconciled(dr1).notReconciled(dr2); } - @Test - void readyConditionNotMetStatusUpdates() { - var workflow = new WorkflowBuilder() - .addDependent(dr1).withReadyCondition(notMetReadyConditionWithStatusUpdate).build() - .addDependent(dr2).dependsOn(dr1).build() - .build(); - - var cr = new TestCustomResource(); - var res = workflow.reconcile(cr, null); - - Assertions.assertThat(res.getErroredDependents()).isEmpty(); - assertThat(executionHistory).reconciled(dr1).notReconciled(dr2); - Assertions.assertThat(cr.getStatus().getConfigMapStatus()).isEqualTo(NOT_READY_YET); - } - @Test void readyConditionNotMetInOneParent() { TestDependent dr3 = new TestDependent("DR_3"); From 0475c2f1ffe03cc363be1ede3d7b65904b7c320d Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 16 May 2022 15:29:19 +0200 Subject: [PATCH 36/51] Only one Condition type --- .../ReadyCondition.java => Condition.java} | 5 ++- .../workflow/DependentResourceNode.java | 25 ++++++------- .../dependent/workflow/DependsOnRelation.java | 36 ------------------- .../workflow/WorkflowReconcileExecutor.java | 3 +- .../workflow/builder/DependentBuilder.java | 10 +++--- .../workflow/condition/CleanupCondition.java | 10 ------ .../condition/ReconcileCondition.java | 11 ------ .../workflow/WorkflowCleanupExecutorTest.java | 5 ++- .../WorkflowReconcileExecutorTest.java | 12 +++---- 9 files changed, 25 insertions(+), 92 deletions(-) rename operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/{condition/ReadyCondition.java => Condition.java} (82%) delete mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependsOnRelation.java delete mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/CleanupCondition.java delete mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReconcileCondition.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReadyCondition.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Condition.java similarity index 82% rename from operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReadyCondition.java rename to operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Condition.java index 50592d8dca..222433118e 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReadyCondition.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Condition.java @@ -1,11 +1,10 @@ -package io.javaoperatorsdk.operator.processing.dependent.workflow.condition; +package io.javaoperatorsdk.operator.processing.dependent.workflow; import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; -public interface ReadyCondition { +public interface Condition { boolean isMet(DependentResource dependentResource, P primary, Context

context); - } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java index ba1b94846b..a7f1d6c9a1 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java @@ -6,16 +6,13 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; -import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.CleanupCondition; -import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReadyCondition; -import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReconcileCondition; public class DependentResourceNode { private final DependentResource dependentResource; - private ReconcileCondition

reconcileCondition; - private CleanupCondition cleanupCondition; - private ReadyCondition readyCondition; + private Condition reconcileCondition; + private Condition cleanupCondition; + private Condition readyCondition; private List dependsOn = new ArrayList<>(1); public DependentResourceNode(DependentResource dependentResource) { @@ -23,12 +20,12 @@ public DependentResourceNode(DependentResource dependentResource) { } public DependentResourceNode(DependentResource dependentResource, - ReconcileCondition

reconcileCondition) { + Condition reconcileCondition) { this(dependentResource, reconcileCondition, null); } public DependentResourceNode(DependentResource dependentResource, - ReconcileCondition

reconcileCondition, CleanupCondition cleanupCondition) { + Condition reconcileCondition, Condition cleanupCondition) { this.dependentResource = dependentResource; this.reconcileCondition = reconcileCondition; this.cleanupCondition = cleanupCondition; @@ -38,11 +35,11 @@ public DependentResource getDependentResource() { return dependentResource; } - public Optional> getReconcileCondition() { + public Optional getReconcileCondition() { return Optional.ofNullable(reconcileCondition); } - public Optional getCleanupCondition() { + public Optional getCleanupCondition() { return Optional.ofNullable(cleanupCondition); } @@ -66,21 +63,21 @@ public String toString() { } public DependentResourceNode setReconcileCondition( - ReconcileCondition

reconcileCondition) { + Condition reconcileCondition) { this.reconcileCondition = reconcileCondition; return this; } - public DependentResourceNode setCleanupCondition(CleanupCondition cleanupCondition) { + public DependentResourceNode setCleanupCondition(Condition cleanupCondition) { this.cleanupCondition = cleanupCondition; return this; } - public Optional> getReadyCondition() { + public Optional> getReadyCondition() { return Optional.ofNullable(readyCondition); } - public DependentResourceNode setReadyCondition(ReadyCondition readyCondition) { + public DependentResourceNode setReadyCondition(Condition readyCondition) { this.readyCondition = readyCondition; return this; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependsOnRelation.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependsOnRelation.java deleted file mode 100644 index 2e6a8d97f8..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependsOnRelation.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.javaoperatorsdk.operator.processing.dependent.workflow; - -import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReadyCondition; - -public class DependsOnRelation { - - private DependentResourceNode owner; - private DependentResourceNode dependsOn; - private ReadyCondition readyCondition; - - public DependsOnRelation() {} - - public DependsOnRelation(DependentResourceNode owner, - DependentResourceNode dependsOn) { - this(owner, dependsOn, null); - } - - public DependsOnRelation(DependentResourceNode owner, DependentResourceNode dependsOn, - ReadyCondition readyCondition) { - this.owner = owner; - this.dependsOn = dependsOn; - this.readyCondition = readyCondition; - } - - public DependentResourceNode getOwner() { - return owner; - } - - public DependentResourceNode getDependsOn() { - return dependsOn; - } - - public ReadyCondition getWaitCondition() { - return readyCondition; - } -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index 2fa494e48b..ce17c07505 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -11,7 +11,6 @@ import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; -import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReconcileCondition; public class WorkflowReconcileExecutor

{ @@ -189,7 +188,7 @@ private boolean alreadyReconciled( private void handleReconcileCondition(DependentResourceNode dependentResourceNode, - ReconcileCondition reconcileCondition) { + Condition reconcileCondition) { boolean conditionMet = reconcileCondition.isMet(dependentResourceNode.getDependentResource(), primary, context); if (!conditionMet) { diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java index 58264e274f..5e38ab5b15 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java @@ -2,10 +2,8 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition; import io.javaoperatorsdk.operator.processing.dependent.workflow.DependentResourceNode; -import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.CleanupCondition; -import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReadyCondition; -import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReconcileCondition; public class DependentBuilder

{ @@ -25,17 +23,17 @@ public DependentBuilder

dependsOn(DependentResource... dependentResourc return this; } - public DependentBuilder

withReconcileCondition(ReconcileCondition reconcileCondition) { + public DependentBuilder

withReconcileCondition(Condition reconcileCondition) { node.setReconcileCondition(reconcileCondition); return this; } - public DependentBuilder

withReadyCondition(ReadyCondition readyCondition) { + public DependentBuilder

withReadyCondition(Condition readyCondition) { node.setReadyCondition(readyCondition); return this; } - public DependentBuilder

withCleanupCondition(CleanupCondition readyCondition) { + public DependentBuilder

withCleanupCondition(Condition readyCondition) { node.setCleanupCondition(readyCondition); return this; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/CleanupCondition.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/CleanupCondition.java deleted file mode 100644 index e8fd391b01..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/CleanupCondition.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.javaoperatorsdk.operator.processing.dependent.workflow.condition; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; - -public interface CleanupCondition { - - boolean isMet(DependentResource dependentResource, P primary, Context

context); -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReconcileCondition.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReconcileCondition.java deleted file mode 100644 index bafb2516a6..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/condition/ReconcileCondition.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.javaoperatorsdk.operator.processing.dependent.workflow.condition; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; - -public interface ReconcileCondition

{ - - boolean isMet(DependentResource dependentResource, P primary, Context

context); - -} diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java index b962ab1760..6731e5063c 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java @@ -3,7 +3,6 @@ import org.junit.jupiter.api.Test; import io.javaoperatorsdk.operator.processing.dependent.workflow.builder.WorkflowBuilder; -import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.CleanupCondition; import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; import static io.javaoperatorsdk.operator.processing.dependent.workflow.ExecutionAssert.assertThat; @@ -16,9 +15,9 @@ class WorkflowCleanupExecutorTest extends AbstractWorkflowExecutorTest { protected TestErrorDeleterDependent errorDD = new TestErrorDeleterDependent("ERROR_DELETER"); - private final CleanupCondition noMetCleanupCondition = + private final Condition noMetCleanupCondition = (dependentResource, primary, context) -> false; - private final CleanupCondition metCleanupCondition = + private final Condition metCleanupCondition = (dependentResource, primary, context) -> true; @Test diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java index cc352186db..5d83830bda 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java @@ -5,8 +5,6 @@ import io.javaoperatorsdk.operator.AggregatedOperatorException; import io.javaoperatorsdk.operator.processing.dependent.workflow.builder.WorkflowBuilder; -import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReadyCondition; -import io.javaoperatorsdk.operator.processing.dependent.workflow.condition.ReconcileCondition; import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; import static io.javaoperatorsdk.operator.processing.dependent.workflow.ExecutionAssert.*; @@ -15,17 +13,17 @@ class WorkflowReconcileExecutorTest extends AbstractWorkflowExecutorTest { public static final String NOT_READY_YET = "NOT READY YET"; - private ReconcileCondition met_reconcile_condition = + private Condition met_reconcile_condition = (dependentResource, primary, context) -> true; - private ReconcileCondition not_met_reconcile_condition = + private Condition not_met_reconcile_condition = (dependentResource, primary, context) -> false; - private ReadyCondition metReadyCondition = + private Condition metReadyCondition = (dependentResource, primary, context) -> true; - private ReadyCondition notMetReadyCondition = + private Condition notMetReadyCondition = (dependentResource, primary, context) -> false; - private ReadyCondition notMetReadyConditionWithStatusUpdate = + private Condition notMetReadyConditionWithStatusUpdate = (dependentResource, primary, context) -> false; @Test From a7f4fa0b06c54ee1ff0140f9377f5bab99992234 Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 16 May 2022 16:44:09 +0200 Subject: [PATCH 37/51] unused var --- .../dependent/workflow/WorkflowReconcileExecutor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index ce17c07505..a515096d70 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -141,7 +141,7 @@ public void run() { ((Deleter

) dependentResource).delete(primary, context); } } else { - var reconcileResult = dependentResource.reconcile(primary, context); + dependentResource.reconcile(primary, context); if (dependentResourceNode.getReadyCondition().isPresent() && !dependentResourceNode.getReadyCondition().get() .isMet(dependentResource, primary, context)) { From 9cf0bd8444f8e44812624033ac9c04b0f15e7416 Mon Sep 17 00:00:00 2001 From: csviri Date: Tue, 17 May 2022 12:13:41 +0200 Subject: [PATCH 38/51] progress on delete during reconcile --- .../workflow/DependentResourceNode.java | 2 +- .../dependent/workflow/Workflow.java | 3 +- .../workflow/WorkflowCleanupExecutor.java | 15 +- .../workflow/WorkflowReconcileExecutor.java | 158 ++++++++++++------ 4 files changed, 121 insertions(+), 57 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java index a7f1d6c9a1..5cd3330dab 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java @@ -39,7 +39,7 @@ public Optional getReconcileCondition() { return Optional.ofNullable(reconcileCondition); } - public Optional getCleanupCondition() { + public Optional getDeletePostCondition() { return Optional.ofNullable(cleanupCondition); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java index c0e431274d..0dbd18ff7c 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java @@ -82,7 +82,8 @@ Set> getBottomLevelResource() { return bottomLevelResource; } - List> getDependents(DependentResourceNode node) { + @SuppressWarnings("rawtypes") + List> getDependents(DependentResourceNode node) { var deps = dependents.get(node); if (deps == null) { return Collections.emptyList(); 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 d238d88bb2..2b81da117a 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 @@ -23,7 +23,7 @@ public class WorkflowCleanupExecutor

{ private final Map, Exception> exceptionsDuringExecution = new HashMap<>(); private final Set> alreadyVisited = new HashSet<>(); - private final Set> cleanupConditionNotMet = new HashSet<>(); + private final Set> deleteConditionNotMet = new HashSet<>(); private final Workflow

workflow; private final P primary; @@ -93,20 +93,19 @@ private NodeExecutor(DependentResourceNode dependentResourceNode) { public void run() { try { var dependentResource = dependentResourceNode.getDependentResource(); - - var cleanupCondition = dependentResourceNode.getCleanupCondition(); + var deletePostCondition = dependentResourceNode.getDeletePostCondition(); if (dependentResource instanceof Deleter && !(dependentResource instanceof GarbageCollected)) { ((Deleter

) dependentResourceNode.getDependentResource()).delete(primary, context); } alreadyVisited.add(dependentResourceNode); - boolean cleanupConditionMet = - cleanupCondition.map(c -> c.isMet(dependentResource, primary, context)).orElse(true); - if (cleanupConditionMet) { + boolean deletePostConditionMet = + deletePostCondition.map(c -> c.isMet(dependentResource, primary, context)).orElse(true); + if (deletePostConditionMet) { handleDependentCleaned(dependentResourceNode); } else { - cleanupConditionNotMet.add(dependentResourceNode); + deleteConditionNotMet.add(dependentResourceNode); } } catch (RuntimeException e) { handleExceptionInExecutor(dependentResourceNode, e); @@ -157,7 +156,7 @@ private boolean allDependentsCleaned( var parents = workflow.getDependents(dependentResourceNode); return parents.isEmpty() || parents.stream() - .allMatch(d -> alreadyVisited(d) && !cleanupConditionNotMet.contains(d)); + .allMatch(d -> alreadyVisited(d) && !deleteConditionNotMet.contains(d)); } private boolean hasErroredDependent( diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index a515096d70..21849d92f2 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -11,6 +11,7 @@ import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.api.reconciler.dependent.GarbageCollected; public class WorkflowReconcileExecutor

{ @@ -18,15 +19,17 @@ public class WorkflowReconcileExecutor

{ private final Workflow

workflow; + /** Covers both deleted and reconciled */ private final Set> alreadyReconciled = new HashSet<>(); private final Set> notReady = new HashSet<>(); - private final Set> ownOrAncestorReconcileConditionConditionNotMet = - new HashSet<>(); private final Map, Future> actualExecutions = new HashMap<>(); private final Map, Exception> exceptionsDuringExecution = new HashMap<>(); + private final Set> markedForDelete = new HashSet<>(); + private final Set> deleteConditionNotMet = new HashSet<>(); + private final P primary; private final Context

context; @@ -43,7 +46,7 @@ public WorkflowReconcileExecutor(Workflow

workflow, P primary, Context

con public synchronized WorkflowExecutionResult reconcile() { for (DependentResourceNode dependentResourceNode : workflow .getTopLevelDependentResources()) { - handleReconcile(dependentResourceNode, false); + handleReconcile(dependentResourceNode); } while (true) { try { @@ -62,32 +65,58 @@ public synchronized WorkflowExecutionResult reconcile() { } private synchronized void handleReconcile( - DependentResourceNode dependentResourceNode, - boolean onlyReconcileForPossibleDelete) { + DependentResourceNode dependentResourceNode) { log.debug("Submitting for reconcile: {}", dependentResourceNode); if (alreadyReconciled(dependentResourceNode) || isReconcilingNow(dependentResourceNode) || !allParentsReconciledAndReady(dependentResourceNode) + || markedForDelete.contains(dependentResourceNode) || hasErroredParent(dependentResourceNode)) { log.debug("Skipping submit of: {}, ", dependentResourceNode); return; } - if (onlyReconcileForPossibleDelete) { - ownOrAncestorReconcileConditionConditionNotMet.add(dependentResourceNode); + boolean reconcileConditionMet = dependentResourceNode.getReconcileCondition().map( + rc -> rc.isMet(dependentResourceNode.getDependentResource(), primary, context)) + .orElse(true); + + if (!reconcileConditionMet) { + handleReconcileConditionNotMet(dependentResourceNode); } else { - dependentResourceNode.getReconcileCondition() - .ifPresent(reconcileCondition -> handleReconcileCondition(dependentResourceNode, - reconcileCondition)); + Future nodeFuture = + workflow + .getExecutorService() + .submit( + new NodeReconcileExecutor( + dependentResourceNode)); + actualExecutions.put(dependentResourceNode, nodeFuture); + log.debug("Submitted to reconcile: {}", dependentResourceNode); + } + } + + private void handleDelete(DependentResourceNode dependentResourceNode) { + log.debug("Submitting for delete: {}", dependentResourceNode); + + if (alreadyReconciled(dependentResourceNode) + || isReconcilingNow(dependentResourceNode) + || !markedForDelete.contains(dependentResourceNode) + || !allDependentsDeletedAlready(dependentResourceNode)) { + log.debug("Skipping submit for delete of: {}, ", dependentResourceNode); + return; } Future nodeFuture = - workflow.getExecutorService().submit( - new NodeExecutor(dependentResourceNode, - ownOrParentsReconcileConditionNotMet(dependentResourceNode))); + workflow.getExecutorService() + .submit(new NodeDeleteExecutor(dependentResourceNode)); actualExecutions.put(dependentResourceNode, nodeFuture); - log.debug("Submitted to reconcile: {}", dependentResourceNode); + log.debug("Submitted to delete: {}", dependentResourceNode); + } + + private boolean allDependentsDeletedAlready(DependentResourceNode dependentResourceNode) { + var dependents = workflow.getDependents(dependentResourceNode); + return dependents.stream().allMatch(d -> alreadyReconciled.contains(d) && !notReady.contains(d) + && !exceptionsDuringExecution.containsKey(d)); } @@ -112,22 +141,12 @@ private synchronized void setAlreadyReconciledButNotReady( notReady.add(dependentResourceNode); } - private boolean ownOrParentsReconcileConditionNotMet( - DependentResourceNode dependentResourceNode) { - return ownOrAncestorReconcileConditionConditionNotMet.contains(dependentResourceNode) || - dependentResourceNode.getDependsOn().stream() - .anyMatch(ownOrAncestorReconcileConditionConditionNotMet::contains); - } - - private class NodeExecutor implements Runnable { + private class NodeReconcileExecutor implements Runnable { private final DependentResourceNode dependentResourceNode; - private final boolean onlyReconcileForPossibleDelete; - private NodeExecutor(DependentResourceNode dependentResourceNode, - boolean onlyReconcileForDelete) { + private NodeReconcileExecutor(DependentResourceNode dependentResourceNode) { this.dependentResourceNode = dependentResourceNode; - this.onlyReconcileForPossibleDelete = onlyReconcileForDelete; } @Override @@ -136,23 +155,17 @@ public void run() { try { DependentResource dependentResource = dependentResourceNode.getDependentResource(); boolean ready = true; - if (onlyReconcileForPossibleDelete) { - if (dependentResource instanceof Deleter) { - ((Deleter

) dependentResource).delete(primary, context); - } - } else { - dependentResource.reconcile(primary, context); - if (dependentResourceNode.getReadyCondition().isPresent() - && !dependentResourceNode.getReadyCondition().get() - .isMet(dependentResource, primary, context)) { - ready = false; - } - } + dependentResource.reconcile(primary, context); + if (dependentResourceNode.getReadyCondition().isPresent() + && !dependentResourceNode.getReadyCondition().get() + .isMet(dependentResource, primary, context)) { + ready = false; + } if (ready) { log.debug("Setting already reconciled for: {}", dependentResourceNode); alreadyReconciled.add(dependentResourceNode); - handleDependentsReconcile(dependentResourceNode, onlyReconcileForPossibleDelete); + handleDependentsReconcile(dependentResourceNode); } else { setAlreadyReconciledButNotReady(dependentResourceNode); } @@ -164,16 +177,59 @@ public void run() { } } + private class NodeDeleteExecutor implements Runnable { + + private final DependentResourceNode dependentResourceNode; + + private NodeDeleteExecutor(DependentResourceNode dependentResourceNode) { + this.dependentResourceNode = dependentResourceNode; + } + + @Override + @SuppressWarnings("unchecked") + public void run() { + try { + DependentResource dependentResource = dependentResourceNode.getDependentResource(); + var deletePostCondition = dependentResourceNode.getDeletePostCondition(); + + if (dependentResource instanceof Deleter + && !(dependentResource instanceof GarbageCollected)) { + ((Deleter

) dependentResourceNode.getDependentResource()).delete(primary, context); + } + alreadyReconciled.add(dependentResourceNode); + boolean deletePostConditionMet = + deletePostCondition.map(c -> c.isMet(dependentResource, primary, context)).orElse(true); + if (deletePostConditionMet) { + handleDependentDeleted(dependentResourceNode); + } else { + deleteConditionNotMet.add(dependentResourceNode); + } + } catch (RuntimeException e) { + handleExceptionInExecutor(dependentResourceNode, e); + } finally { + handleNodeExecutionFinish(dependentResourceNode); + } + } + } + + private synchronized void handleDependentDeleted( + DependentResourceNode dependentResourceNode) { + dependentResourceNode.getDependsOn().forEach(dr -> { + log.debug("Handle deleted for: {} with dependent: {}", dr, dependentResourceNode); + handleDelete(dr); + }); + } + private boolean isReconcilingNow(DependentResourceNode dependentResourceNode) { return actualExecutions.containsKey(dependentResourceNode); } private synchronized void handleDependentsReconcile( - DependentResourceNode dependentResourceNode, boolean onlyReconcileForPossibleDelete) { + DependentResourceNode dependentResourceNode) { var dependents = workflow.getDependents(dependentResourceNode); dependents.forEach(d -> { log.debug("Handle reconcile for dependent: {} of parent:{}", d, dependentResourceNode); - handleReconcile(d, onlyReconcileForPossibleDelete); + handleReconcile(d); }); } @@ -187,12 +243,20 @@ private boolean alreadyReconciled( } - private void handleReconcileCondition(DependentResourceNode dependentResourceNode, - Condition reconcileCondition) { - boolean conditionMet = - reconcileCondition.isMet(dependentResourceNode.getDependentResource(), primary, context); - if (!conditionMet) { - ownOrAncestorReconcileConditionConditionNotMet.add(dependentResourceNode); + private void handleReconcileConditionNotMet(DependentResourceNode dependentResourceNode) { + Set bottomNodes = new HashSet<>(); + markDependentsForDelete(dependentResourceNode, bottomNodes); + bottomNodes.forEach(bn -> handleDelete(bn)); + } + + private void markDependentsForDelete(DependentResourceNode dependentResourceNode, + Set bottomNodes) { + markedForDelete.add(dependentResourceNode); + var dependents = workflow.getDependents(dependentResourceNode); + if (dependents.isEmpty()) { + bottomNodes.add(dependentResourceNode); + } else { + dependents.forEach(d -> markDependentsForDelete(d, bottomNodes)); } } From 820bd65e0dfb12bc5380fd092061e71b63b2dce8 Mon Sep 17 00:00:00 2001 From: csviri Date: Thu, 19 May 2022 10:48:07 +0200 Subject: [PATCH 39/51] fix tests --- .../workflow/WorkflowReconcileExecutorTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java index 5d83830bda..2cc881446f 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java @@ -194,8 +194,8 @@ void reconcileConditionTransitiveDelete() { Assertions.assertThat(res.getErroredDependents()).isEmpty(); assertThat(executionHistory).notReconciled(dr2); - assertThat(executionHistory).reconciledInOrder(dr1, drDeleter, drDeleter2); - assertThat(executionHistory).deleted(drDeleter, drDeleter2); + assertThat(executionHistory).reconciledInOrder(dr1, drDeleter2, drDeleter); + assertThat(executionHistory).deleted(drDeleter2, drDeleter); } @Test @@ -206,16 +206,16 @@ void reconcileConditionAlsoErrorDependsOn() { .addDependent(drError).build() .addDependent(drDeleter).withReconcileCondition(not_met_reconcile_condition).build() .addDependent(drDeleter2).withReconcileCondition(met_reconcile_condition) - .dependsOn(drError, drDeleter).build() + .dependsOn(drError, drDeleter) + .build() .build(); assertThrows(AggregatedOperatorException.class, () -> workflow.reconcile(new TestCustomResource(), null) .throwAggregatedExceptionIfErrorsPresent()); - assertThat(executionHistory).deleted(drDeleter); + assertThat(executionHistory).deleted(drDeleter2, drDeleter); assertThat(executionHistory).reconciled(drError); - assertThat(executionHistory).notReconciled(drDeleter2); } @Test From 9d2bdcf2295b9ae1ad435868c018f5cefffff56d Mon Sep 17 00:00:00 2001 From: csviri Date: Thu, 19 May 2022 11:57:39 +0200 Subject: [PATCH 40/51] format --- .../dependent/workflow/WorkflowReconcileExecutorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java index 2cc881446f..fb0f03722c 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java @@ -230,8 +230,8 @@ void oneDependsOnConditionNotMet() { Assertions.assertThat(res.getErroredDependents()).isEmpty(); assertThat(executionHistory).deleted(drDeleter); - assertThat(executionHistory).reconciledInOrder(dr1, drDeleter); assertThat(executionHistory).notReconciled(dr2); + assertThat(executionHistory).reconciledInOrder(dr1, drDeleter); } @Test From 15e513745f930a93e2aafd8cbe422d81b49669b1 Mon Sep 17 00:00:00 2001 From: csviri Date: Thu, 19 May 2022 12:09:29 +0200 Subject: [PATCH 41/51] test fix --- .../dependent/workflow/WorkflowReconcileExecutorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java index fb0f03722c..d8eb30f541 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java @@ -231,7 +231,7 @@ void oneDependsOnConditionNotMet() { Assertions.assertThat(res.getErroredDependents()).isEmpty(); assertThat(executionHistory).deleted(drDeleter); assertThat(executionHistory).notReconciled(dr2); - assertThat(executionHistory).reconciledInOrder(dr1, drDeleter); + assertThat(executionHistory).reconciled(dr1); } @Test From df897eb50eca9dcc90a0d19a3ec2702b8addf270 Mon Sep 17 00:00:00 2001 From: csviri Date: Thu, 19 May 2022 13:53:10 +0200 Subject: [PATCH 42/51] unit tests --- .../workflow/DependentResourceNode.java | 12 +-- .../workflow/WorkflowReconcileExecutor.java | 2 +- .../workflow/builder/DependentBuilder.java | 4 +- .../AbstractWorkflowExecutorTest.java | 6 ++ .../workflow/WorkflowCleanupExecutorTest.java | 13 +-- .../WorkflowReconcileExecutorTest.java | 80 ++++++++++++++++++- 6 files changed, 97 insertions(+), 20 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java index 5cd3330dab..c2999aeb11 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java @@ -11,7 +11,7 @@ public class DependentResourceNode { private final DependentResource dependentResource; private Condition reconcileCondition; - private Condition cleanupCondition; + private Condition deletePostCondition; private Condition readyCondition; private List dependsOn = new ArrayList<>(1); @@ -25,10 +25,10 @@ public DependentResourceNode(DependentResource dependentResource, } public DependentResourceNode(DependentResource dependentResource, - Condition reconcileCondition, Condition cleanupCondition) { + Condition reconcileCondition, Condition deletePostCondition) { this.dependentResource = dependentResource; this.reconcileCondition = reconcileCondition; - this.cleanupCondition = cleanupCondition; + this.deletePostCondition = deletePostCondition; } public DependentResource getDependentResource() { @@ -40,7 +40,7 @@ public Optional getReconcileCondition() { } public Optional getDeletePostCondition() { - return Optional.ofNullable(cleanupCondition); + return Optional.ofNullable(deletePostCondition); } public void setDependsOn(List dependsOn) { @@ -68,8 +68,8 @@ public DependentResourceNode setReconcileCondition( return this; } - public DependentResourceNode setCleanupCondition(Condition cleanupCondition) { - this.cleanupCondition = cleanupCondition; + public DependentResourceNode setDeletePostCondition(Condition cleanupCondition) { + this.deletePostCondition = cleanupCondition; return this; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index 21849d92f2..a070cd3064 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -116,7 +116,7 @@ private void handleDelete(DependentResourceNode dependentResourceNode) { private boolean allDependentsDeletedAlready(DependentResourceNode dependentResourceNode) { var dependents = workflow.getDependents(dependentResourceNode); return dependents.stream().allMatch(d -> alreadyReconciled.contains(d) && !notReady.contains(d) - && !exceptionsDuringExecution.containsKey(d)); + && !exceptionsDuringExecution.containsKey(d) && !deleteConditionNotMet.contains(d)); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java index 5e38ab5b15..2ebe8dfc07 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java @@ -33,8 +33,8 @@ public DependentBuilder

withReadyCondition(Condition readyCondition) { return this; } - public DependentBuilder

withCleanupCondition(Condition readyCondition) { - node.setCleanupCondition(readyCondition); + public DependentBuilder

withDeletePostCondition(Condition readyCondition) { + node.setDeletePostCondition(readyCondition); return this; } 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 7b153702dc..0ad559b7ca 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 @@ -19,6 +19,12 @@ public class AbstractWorkflowExecutorTest { protected TestDependent dr2 = new TestDependent("DR_2"); protected TestDeleterDependent drDeleter = new TestDeleterDependent("DR_DELETER"); protected TestErrorDependent drError = new TestErrorDependent("ERROR_1"); + protected TestErrorDeleterDependent errorDD = new TestErrorDeleterDependent("ERROR_DELETER"); + + protected final Condition noMetDeletePostCondition = + (dependentResource, primary, context) -> false; + protected final Condition metDeletePostCondition = + (dependentResource, primary, context) -> true; protected List executionHistory = Collections.synchronizedList(new ArrayList<>()); diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java index 6731e5063c..3f250398ba 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java @@ -13,13 +13,6 @@ class WorkflowCleanupExecutorTest extends AbstractWorkflowExecutorTest { protected TestDeleterDependent dd2 = new TestDeleterDependent("DR_DELETER_2"); protected TestDeleterDependent dd3 = new TestDeleterDependent("DR_DELETER_3"); - protected TestErrorDeleterDependent errorDD = new TestErrorDeleterDependent("ERROR_DELETER"); - - private final Condition noMetCleanupCondition = - (dependentResource, primary, context) -> false; - private final Condition metCleanupCondition = - (dependentResource, primary, context) -> true; - @Test void cleanUpDiamondWorkflow() { var workflow = new WorkflowBuilder() @@ -53,7 +46,7 @@ void dontDeleteIfDependentErrored() { void cleanupConditionTrivialCase() { var workflow = new WorkflowBuilder() .addDependent(dd1).build() - .addDependent(dd2).dependsOn(dd1).withCleanupCondition(noMetCleanupCondition).build() + .addDependent(dd2).dependsOn(dd1).withDeletePostCondition(noMetDeletePostCondition).build() .build(); workflow.cleanup(new TestCustomResource(), null); @@ -65,7 +58,7 @@ void cleanupConditionTrivialCase() { void cleanupConditionMet() { var workflow = new WorkflowBuilder() .addDependent(dd1).build() - .addDependent(dd2).dependsOn(dd1).withCleanupCondition(metCleanupCondition).build() + .addDependent(dd2).dependsOn(dd1).withDeletePostCondition(metDeletePostCondition).build() .build(); workflow.cleanup(new TestCustomResource(), null); @@ -80,7 +73,7 @@ void cleanupConditionDiamondWorkflow() { var workflow = new WorkflowBuilder() .addDependent(dd1).build() .addDependent(dd2).dependsOn(dd1).build() - .addDependent(dd3).dependsOn(dd1).withCleanupCondition(noMetCleanupCondition).build() + .addDependent(dd3).dependsOn(dd1).withDeletePostCondition(noMetDeletePostCondition).build() .addDependent(dd4).dependsOn(dd2, dd3).build() .build(); diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java index d8eb30f541..4d592e68a5 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java @@ -12,7 +12,6 @@ class WorkflowReconcileExecutorTest extends AbstractWorkflowExecutorTest { - public static final String NOT_READY_YET = "NOT READY YET"; private Condition met_reconcile_condition = (dependentResource, primary, context) -> true; private Condition not_met_reconcile_condition = @@ -234,6 +233,85 @@ void oneDependsOnConditionNotMet() { assertThat(executionHistory).reconciled(dr1); } + @Test + void deletedIfReconcileConditionNotMet() { + TestDeleterDependent drDeleter2 = new TestDeleterDependent("DR_DELETER_2"); + var workflow = new WorkflowBuilder() + .addDependent(dr1).build() + .addDependent(drDeleter).withReconcileCondition(not_met_reconcile_condition).dependsOn(dr1) + .build() + .addDependent(drDeleter2).dependsOn(dr1, drDeleter).build() + .build(); + + workflow.reconcile(new TestCustomResource(), null); + + assertThat(executionHistory) + .reconciledInOrder(dr1, drDeleter2, drDeleter) + .deleted(drDeleter2, drDeleter); + } + + @Test + void deleteDoneInReverseOrder() { + TestDeleterDependent drDeleter2 = new TestDeleterDependent("DR_DELETER_2"); + TestDeleterDependent drDeleter3 = new TestDeleterDependent("DR_DELETER_3"); + TestDeleterDependent drDeleter4 = new TestDeleterDependent("DR_DELETER_4"); + + var workflow = new WorkflowBuilder() + .addDependent(dr1).build() + .addDependent(drDeleter).withReconcileCondition(not_met_reconcile_condition).dependsOn(dr1) + .build() + .addDependent(drDeleter2).dependsOn(drDeleter).build() + .addDependent(drDeleter3).dependsOn(drDeleter).build() + .addDependent(drDeleter4).dependsOn(drDeleter3).build() + .build(); + + workflow.reconcile(new TestCustomResource(), null); + + assertThat(executionHistory) + .reconciledInOrder(dr1, drDeleter4, drDeleter3, drDeleter) + .reconciledInOrder(dr1, drDeleter2, drDeleter) + .deleted(drDeleter, drDeleter2, drDeleter3, drDeleter4); + } + + @Test + void diamondDeleteWithPostConditionInMiddle() { + TestDeleterDependent drDeleter2 = new TestDeleterDependent("DR_DELETER_2"); + TestDeleterDependent drDeleter3 = new TestDeleterDependent("DR_DELETER_3"); + TestDeleterDependent drDeleter4 = new TestDeleterDependent("DR_DELETER_4"); + + var workflow = new WorkflowBuilder() + .addDependent(drDeleter).withReconcileCondition(not_met_reconcile_condition).build() + .addDependent(drDeleter2).dependsOn(drDeleter).build() + .addDependent(drDeleter3).dependsOn(drDeleter) + .withDeletePostCondition(noMetDeletePostCondition).build() + .addDependent(drDeleter4).dependsOn(drDeleter3, drDeleter2).build() + .build(); + + workflow.reconcile(new TestCustomResource(), null); + + assertThat(executionHistory).notReconciled(drDeleter) + .reconciledInOrder(drDeleter4, drDeleter2) + .reconciledInOrder(drDeleter4, drDeleter3); + } + + @Test + void diamondDeleteErrorInMiddle() { + TestDeleterDependent drDeleter2 = new TestDeleterDependent("DR_DELETER_2"); + TestDeleterDependent drDeleter3 = new TestDeleterDependent("DR_DELETER_3"); + + var workflow = new WorkflowBuilder() + .addDependent(drDeleter).withReconcileCondition(not_met_reconcile_condition).build() + .addDependent(drDeleter2).dependsOn(drDeleter).build() + .addDependent(errorDD).dependsOn(drDeleter).build() + .addDependent(drDeleter3).dependsOn(errorDD, drDeleter2).build() + .build(); + + workflow.reconcile(new TestCustomResource(), null); + + assertThat(executionHistory).notReconciled(drDeleter, drError) + .reconciledInOrder(drDeleter3, drDeleter2); + } + @Test void readyConditionTrivialCase() { var workflow = new WorkflowBuilder() From c107eb03edfbcea98074d5c3dab125d61052f505 Mon Sep 17 00:00:00 2001 From: csviri Date: Thu, 19 May 2022 14:45:15 +0200 Subject: [PATCH 43/51] fixes --- .../workflow/DependentResourceNode.java | 1 + .../dependent/workflow/Workflow.java | 25 ++++---- .../workflow/WorkflowCleanupResult.java | 41 +++++++++++++ .../workflow/WorkflowExecutionResult.java | 47 ++++++++------- .../workflow/WorkflowReconcileExecutor.java | 58 ++++++++----------- .../workflow/builder/DependentBuilder.java | 6 +- .../workflow/builder/WorkflowBuilder.java | 9 +-- .../dependent/workflow/WorkflowTest.java | 9 ++- 8 files changed, 118 insertions(+), 78 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java index c2999aeb11..d0da19257f 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java @@ -7,6 +7,7 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +@SuppressWarnings("rawtypes") public class DependentResourceNode { private final DependentResource dependentResource; diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java index 0dbd18ff7c..c51eea9015 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java @@ -14,30 +14,31 @@ * * @param

primary resource */ +@SuppressWarnings("rawtypes") public class Workflow

{ - private final Set> dependentResourceNodes; - private final Set> topLevelResources = new HashSet<>(); - private final Set> bottomLevelResource = new HashSet<>(); - private Map, List>> dependents; + private final Set dependentResourceNodes; + private final Set topLevelResources = new HashSet<>(); + private final Set bottomLevelResource = new HashSet<>(); + private Map> dependents; // it's "global" executor service shared between multiple reconciliations running parallel private ExecutorService executorService; - public Workflow(Set> dependentResourceNodes) { + public Workflow(Set dependentResourceNodes) { this.executorService = ConfigurationServiceProvider.instance().getExecutorService(); this.dependentResourceNodes = dependentResourceNodes; preprocessForReconcile(); } - public Workflow(Set> dependentResourceNodes, + public Workflow(Set dependentResourceNodes, ExecutorService executorService) { this.executorService = executorService; this.dependentResourceNodes = dependentResourceNodes; preprocessForReconcile(); } - public Workflow(Set> dependentResourceNodes, int globalParallelism) { + public Workflow(Set dependentResourceNodes, int globalParallelism) { this(dependentResourceNodes, Executors.newFixedThreadPool(globalParallelism)); } @@ -61,7 +62,7 @@ private void preprocessForReconcile() { if (node.getDependsOn().isEmpty()) { topLevelResources.add(node); } else { - for (DependentResourceNode dependsOn : node.getDependsOn()) { + for (DependentResourceNode dependsOn : node.getDependsOn()) { dependents.computeIfAbsent(dependsOn, dr -> new ArrayList<>()); dependents.get(dependsOn).add(node); bottomLevelResource.remove(dependsOn); @@ -74,16 +75,16 @@ public void setExecutorService(ExecutorService executorService) { this.executorService = executorService; } - Set> getTopLevelDependentResources() { + Set getTopLevelDependentResources() { return topLevelResources; } - Set> getBottomLevelResource() { + Set getBottomLevelResource() { return bottomLevelResource; } @SuppressWarnings("rawtypes") - List> getDependents(DependentResourceNode node) { + List getDependents(DependentResourceNode node) { var deps = dependents.get(node); if (deps == null) { return Collections.emptyList(); @@ -92,7 +93,7 @@ List> getDependents(DependentResourceNode node) { } } - Map, List>> getDependents() { + Map> getDependents() { return dependents; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupResult.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupResult.java index b6632a0286..e1b43ad8d2 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupResult.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupResult.java @@ -1,4 +1,45 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; + +@SuppressWarnings("rawtypes") public class WorkflowCleanupResult { + + private List deletedDependents = new ArrayList<>(); + private List notDeletedDependents = new ArrayList<>(); + private Map erroredDependents = new HashMap<>(); + + public List getDeletedDependents() { + return deletedDependents; + } + + public WorkflowCleanupResult setDeletedDependents(List deletedDependents) { + this.deletedDependents = deletedDependents; + return this; + } + + public List getNotDeletedDependents() { + return notDeletedDependents; + } + + public WorkflowCleanupResult setNotDeletedDependents( + List notDeletedDependents) { + this.notDeletedDependents = notDeletedDependents; + return this; + } + + public Map getErroredDependents() { + return erroredDependents; + } + + public WorkflowCleanupResult setErroredDependents( + Map erroredDependents) { + this.erroredDependents = erroredDependents; + return this; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutionResult.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutionResult.java index dae966645a..5a66dcd2ac 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutionResult.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutionResult.java @@ -8,54 +8,43 @@ import io.javaoperatorsdk.operator.AggregatedOperatorException; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +@SuppressWarnings("rawtypes") public class WorkflowExecutionResult { - // this includes also possibly deleted ones - private List> reconciledDependents = new ArrayList<>(); - private List> notReconciledDependents = new ArrayList<>(); - private List> notReadyDependents = new ArrayList<>(); - private Map, Exception> erroredDependents = new HashMap<>(); + private List reconciledDependents = new ArrayList<>(); + private List notReadyDependents = new ArrayList<>(); + private Map erroredDependents = new HashMap<>(); - public Map, Exception> getErroredDependents() { + public Map getErroredDependents() { return erroredDependents; } public WorkflowExecutionResult setErroredDependents( - Map, Exception> erroredDependents) { + Map erroredDependents) { this.erroredDependents = erroredDependents; return this; } - public List> getReconciledDependents() { + public List getReconciledDependents() { return reconciledDependents; } public WorkflowExecutionResult setReconciledDependents( - List> reconciledDependents) { + List reconciledDependents) { this.reconciledDependents = reconciledDependents; return this; } - public List> getNotReadyDependents() { + public List getNotReadyDependents() { return notReadyDependents; } public WorkflowExecutionResult setNotReadyDependents( - List> notReadyDependents) { + List notReadyDependents) { this.notReadyDependents = notReadyDependents; return this; } - public List> getNotReconciledDependents() { - return notReconciledDependents; - } - - public WorkflowExecutionResult setNotReconciledDependents( - List> notReconciledDependents) { - this.notReconciledDependents = notReconciledDependents; - return this; - } - public void throwAggregatedExceptionIfErrorsPresent() { if (!erroredDependents.isEmpty()) { throw createFinalException(); @@ -66,4 +55,20 @@ private AggregatedOperatorException createFinalException() { return new AggregatedOperatorException("Exception during workflow.", new ArrayList<>(erroredDependents.values())); } + + public boolean notReadyDependentsExists() { + return !notReadyDependents.isEmpty(); + } + + public boolean erroredDependentsExists() { + return !erroredDependents.isEmpty(); + } + + public void throwAggregateExceptionIfErroredExists() { + if (erroredDependentsExists()) { + throw new AggregatedOperatorException("Exception(s) during workflow execution.", + new ArrayList<>(erroredDependents.values())); + } + } + } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index a070cd3064..8ee4507408 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -13,6 +13,7 @@ import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.api.reconciler.dependent.GarbageCollected; +@SuppressWarnings({"rawtypes", "unchecked"}) public class WorkflowReconcileExecutor

{ private static final Logger log = LoggerFactory.getLogger(WorkflowReconcileExecutor.class); @@ -20,15 +21,17 @@ public class WorkflowReconcileExecutor

{ private final Workflow

workflow; /** Covers both deleted and reconciled */ - private final Set> alreadyReconciled = new HashSet<>(); - private final Set> notReady = new HashSet<>(); - private final Map, Future> actualExecutions = + private final Set alreadyVisited = new HashSet<>(); + private final Set notReady = new HashSet<>(); + private final Map> actualExecutions = new HashMap<>(); - private final Map, Exception> exceptionsDuringExecution = + private final Map exceptionsDuringExecution = new HashMap<>(); - private final Set> markedForDelete = new HashSet<>(); - private final Set> deleteConditionNotMet = new HashSet<>(); + private final Set markedForDelete = new HashSet<>(); + private final Set deleteConditionNotMet = new HashSet<>(); + // used to remember reconciled (not deleted or errored) dependents + private final Set reconciled = new HashSet<>(); private final P primary; private final Context

context; @@ -39,12 +42,8 @@ public WorkflowReconcileExecutor(Workflow

workflow, P primary, Context

con this.workflow = workflow; } - // todo reverse delete order - - // todo add reconcile results - // - reconciled in results should contain only truly reconciled public synchronized WorkflowExecutionResult reconcile() { - for (DependentResourceNode dependentResourceNode : workflow + for (DependentResourceNode dependentResourceNode : workflow .getTopLevelDependentResources()) { handleReconcile(dependentResourceNode); } @@ -68,7 +67,7 @@ private synchronized void handleReconcile( DependentResourceNode dependentResourceNode) { log.debug("Submitting for reconcile: {}", dependentResourceNode); - if (alreadyReconciled(dependentResourceNode) + if (alreadyVisited(dependentResourceNode) || isReconcilingNow(dependentResourceNode) || !allParentsReconciledAndReady(dependentResourceNode) || markedForDelete.contains(dependentResourceNode) @@ -98,7 +97,7 @@ private synchronized void handleReconcile( private void handleDelete(DependentResourceNode dependentResourceNode) { log.debug("Submitting for delete: {}", dependentResourceNode); - if (alreadyReconciled(dependentResourceNode) + if (alreadyVisited(dependentResourceNode) || isReconcilingNow(dependentResourceNode) || !markedForDelete.contains(dependentResourceNode) || !allDependentsDeletedAlready(dependentResourceNode)) { @@ -115,7 +114,7 @@ private void handleDelete(DependentResourceNode dependentResourceNode) { private boolean allDependentsDeletedAlready(DependentResourceNode dependentResourceNode) { var dependents = workflow.getDependents(dependentResourceNode); - return dependents.stream().allMatch(d -> alreadyReconciled.contains(d) && !notReady.contains(d) + return dependents.stream().allMatch(d -> alreadyVisited.contains(d) && !notReady.contains(d) && !exceptionsDuringExecution.containsKey(d) && !deleteConditionNotMet.contains(d)); } @@ -137,7 +136,7 @@ private synchronized void handleNodeExecutionFinish(DependentResourceNode depend private synchronized void setAlreadyReconciledButNotReady( DependentResourceNode dependentResourceNode) { log.debug("Setting already reconciled but not ready for: {}", dependentResourceNode); - alreadyReconciled.add(dependentResourceNode); + alreadyVisited.add(dependentResourceNode); notReady.add(dependentResourceNode); } @@ -154,17 +153,16 @@ private NodeReconcileExecutor(DependentResourceNode dependentResourceNode) public void run() { try { DependentResource dependentResource = dependentResourceNode.getDependentResource(); - boolean ready = true; dependentResource.reconcile(primary, context); - if (dependentResourceNode.getReadyCondition().isPresent() - && !dependentResourceNode.getReadyCondition().get() - .isMet(dependentResource, primary, context)) { - ready = false; - } + reconciled.add(dependentResourceNode); + boolean ready = dependentResourceNode.getReadyCondition() + .map(rc -> rc.isMet(dependentResource, primary, context)) + .orElse(true); + if (ready) { log.debug("Setting already reconciled for: {}", dependentResourceNode); - alreadyReconciled.add(dependentResourceNode); + alreadyVisited.add(dependentResourceNode); handleDependentsReconcile(dependentResourceNode); } else { setAlreadyReconciledButNotReady(dependentResourceNode); @@ -196,7 +194,7 @@ public void run() { && !(dependentResource instanceof GarbageCollected)) { ((Deleter

) dependentResourceNode.getDependentResource()).delete(primary, context); } - alreadyReconciled.add(dependentResourceNode); + alreadyVisited.add(dependentResourceNode); boolean deletePostConditionMet = deletePostCondition.map(c -> c.isMet(dependentResource, primary, context)).orElse(true); if (deletePostConditionMet) { @@ -237,9 +235,9 @@ private boolean noMoreExecutionsScheduled() { return actualExecutions.isEmpty(); } - private boolean alreadyReconciled( + private boolean alreadyVisited( DependentResourceNode dependentResourceNode) { - return alreadyReconciled.contains(dependentResourceNode); + return alreadyVisited.contains(dependentResourceNode); } @@ -264,7 +262,7 @@ private boolean allParentsReconciledAndReady( DependentResourceNode dependentResourceNode) { return dependentResourceNode.getDependsOn().isEmpty() || dependentResourceNode.getDependsOn().stream() - .allMatch(d -> alreadyReconciled(d) && !notReady.contains(d)); + .allMatch(d -> alreadyVisited(d) && !notReady.contains(d)); } private boolean hasErroredParent( @@ -284,13 +282,7 @@ private WorkflowExecutionResult createReconcileResult() { .map(DependentResourceNode::getDependentResource) .collect(Collectors.toList())); - workflowExecutionResult.setReconciledDependents(alreadyReconciled.stream() - .map(DependentResourceNode::getDependentResource).collect(Collectors.toList())); - - var notReconciledDependentResources = - new HashSet>(workflow.getDependents().keySet()); - notReconciledDependentResources.removeAll(alreadyReconciled); - workflowExecutionResult.setNotReconciledDependents(notReconciledDependentResources.stream() + workflowExecutionResult.setReconciledDependents(reconciled.stream() .map(DependentResourceNode::getDependentResource).collect(Collectors.toList())); return workflowExecutionResult; diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java index 2ebe8dfc07..1207913905 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java @@ -23,17 +23,17 @@ public DependentBuilder

dependsOn(DependentResource... dependentResourc return this; } - public DependentBuilder

withReconcileCondition(Condition reconcileCondition) { + public DependentBuilder

withReconcileCondition(Condition reconcileCondition) { node.setReconcileCondition(reconcileCondition); return this; } - public DependentBuilder

withReadyCondition(Condition readyCondition) { + public DependentBuilder

withReadyCondition(Condition readyCondition) { node.setReadyCondition(readyCondition); return this; } - public DependentBuilder

withDeletePostCondition(Condition readyCondition) { + public DependentBuilder

withDeletePostCondition(Condition readyCondition) { node.setDeletePostCondition(readyCondition); return this; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java index 32f62d9f4b..c8a6f4f734 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java @@ -10,9 +10,10 @@ import io.javaoperatorsdk.operator.processing.dependent.workflow.DependentResourceNode; import io.javaoperatorsdk.operator.processing.dependent.workflow.Workflow; +@SuppressWarnings({"rawtypes", "unchecked"}) public class WorkflowBuilder

{ - private Set> dependentResourceNodes = new HashSet<>(); + private final Set> dependentResourceNodes = new HashSet<>(); public DependentBuilder

addDependent(DependentResource dependentResource) { DependentResourceNode node = new DependentResourceNode<>(dependentResource); @@ -32,15 +33,15 @@ DependentResourceNode getNodeByDependentResource(DependentResource depende } public Workflow

build() { - return new Workflow<>(dependentResourceNodes, + return new Workflow(dependentResourceNodes, ConfigurationServiceProvider.instance().getExecutorService()); } public Workflow

build(int parallelism) { - return new Workflow<>(dependentResourceNodes, parallelism); + return new Workflow(dependentResourceNodes, parallelism); } public Workflow

build(ExecutorService executorService) { - return new Workflow<>(dependentResourceNodes, executorService); + return new Workflow(dependentResourceNodes, executorService); } } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java index b721ec3b27..1dbd04a11d 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java @@ -13,10 +13,9 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.mock; +@SuppressWarnings("unchecked") class WorkflowTest { - - @Test public void calculatesTopLevelResources() { var dr1 = mock(DependentResource.class); @@ -29,7 +28,7 @@ public void calculatesTopLevelResources() { .addDependent(dr2).dependsOn(dr1).build() .build(); - Set> topResources = + Set topResources = workflow.getTopLevelDependentResources().stream() .map(DependentResourceNode::getDependentResource) .collect(Collectors.toSet()); @@ -43,13 +42,13 @@ public void calculatesBottomLevelResources() { var dr2 = mock(DependentResource.class); var independentDR = mock(DependentResource.class); - Workflow workflow = new WorkflowBuilder<>() + Workflow workflow = new WorkflowBuilder() .addDependent(independentDR).build() .addDependent(dr1).build() .addDependent(dr2).dependsOn(dr1).build() .build(); - Set> bottomResources = + Set bottomResources = workflow.getBottomLevelResource().stream() .map(DependentResourceNode::getDependentResource) .collect(Collectors.toSet()); From 9f8dc3124c67901104c226cb8a9385a9e67f29df Mon Sep 17 00:00:00 2001 From: csviri Date: Thu, 19 May 2022 15:13:12 +0200 Subject: [PATCH 44/51] cleanup result --- .../workflow/WorkflowCleanupExecutor.java | 39 ++++++++++++------- .../workflow/WorkflowCleanupResult.java | 11 +++--- .../workflow/builder/DependentBuilder.java | 9 +++-- .../workflow/builder/WorkflowBuilder.java | 4 +- .../WorkflowReconcileExecutorTest.java | 3 +- .../dependent/workflow/WorkflowTest.java | 8 ++-- 6 files changed, 44 insertions(+), 30 deletions(-) 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 2b81da117a..7df94a199e 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 @@ -5,6 +5,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.Future; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,16 +15,18 @@ import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; import io.javaoperatorsdk.operator.api.reconciler.dependent.GarbageCollected; +@SuppressWarnings("rawtypes") public class WorkflowCleanupExecutor

{ - private static final Logger log = LoggerFactory.getLogger(WorkflowReconcileExecutor.class); + private static final Logger log = LoggerFactory.getLogger(WorkflowCleanupExecutor.class); - private final Map, Future> actualExecutions = + private final Map> actualExecutions = new HashMap<>(); - private final Map, Exception> exceptionsDuringExecution = + private final Map exceptionsDuringExecution = new HashMap<>(); - private final Set> alreadyVisited = new HashSet<>(); - private final Set> deleteConditionNotMet = new HashSet<>(); + private final Set alreadyVisited = new HashSet<>(); + private final Set postDeleteConditionNotMet = new HashSet<>(); + private final Set deleteCalled = new HashSet<>(); private final Workflow

workflow; private final P primary; @@ -35,10 +38,8 @@ public WorkflowCleanupExecutor(Workflow

workflow, P primary, Context

conte this.context = context; } - // todo cleanup condition - public synchronized WorkflowCleanupResult cleanup() { - for (DependentResourceNode dependentResourceNode : workflow + for (DependentResourceNode dependentResourceNode : workflow .getBottomLevelResource()) { handleCleanup(dependentResourceNode); } @@ -62,7 +63,7 @@ private synchronized boolean noMoreExecutionsScheduled() { return actualExecutions.isEmpty(); } - private synchronized void handleCleanup(DependentResourceNode dependentResourceNode) { + private synchronized void handleCleanup(DependentResourceNode dependentResourceNode) { log.debug("Submitting for cleanup: {}", dependentResourceNode); if (alreadyVisited(dependentResourceNode) @@ -98,6 +99,7 @@ public void run() { if (dependentResource instanceof Deleter && !(dependentResource instanceof GarbageCollected)) { ((Deleter

) dependentResourceNode.getDependentResource()).delete(primary, context); + deleteCalled.add(dependentResourceNode); } alreadyVisited.add(dependentResourceNode); boolean deletePostConditionMet = @@ -105,7 +107,7 @@ public void run() { if (deletePostConditionMet) { handleDependentCleaned(dependentResourceNode); } else { - deleteConditionNotMet.add(dependentResourceNode); + postDeleteConditionNotMet.add(dependentResourceNode); } } catch (RuntimeException e) { handleExceptionInExecutor(dependentResourceNode, e); @@ -152,11 +154,11 @@ private boolean alreadyVisited( } private boolean allDependentsCleaned( - DependentResourceNode dependentResourceNode) { + DependentResourceNode dependentResourceNode) { var parents = workflow.getDependents(dependentResourceNode); return parents.isEmpty() || parents.stream() - .allMatch(d -> alreadyVisited(d) && !deleteConditionNotMet.contains(d)); + .allMatch(d -> alreadyVisited(d) && !postDeleteConditionNotMet.contains(d)); } private boolean hasErroredDependent( @@ -167,6 +169,17 @@ private boolean hasErroredDependent( } private WorkflowCleanupResult createCleanupResult() { - return new WorkflowCleanupResult(); + var result = new WorkflowCleanupResult(); + result.setErroredDependents(exceptionsDuringExecution + .entrySet().stream() + .collect(Collectors.toMap(e -> e.getKey().getDependentResource(), Map.Entry::getValue))); + + result.setNotDeletedDependents( + postDeleteConditionNotMet.stream().map(DependentResourceNode::getDependentResource) + .collect(Collectors.toList())); + result.setDeleteCalledOnDependents( + deleteCalled.stream().map(DependentResourceNode::getDependentResource) + .collect(Collectors.toList())); + return result; } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupResult.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupResult.java index e1b43ad8d2..7b72b1b8fd 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupResult.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupResult.java @@ -10,16 +10,17 @@ @SuppressWarnings("rawtypes") public class WorkflowCleanupResult { - private List deletedDependents = new ArrayList<>(); + private List deleteCalledOnDependents = new ArrayList<>(); private List notDeletedDependents = new ArrayList<>(); private Map erroredDependents = new HashMap<>(); - public List getDeletedDependents() { - return deletedDependents; + public List getDeleteCalledOnDependents() { + return deleteCalledOnDependents; } - public WorkflowCleanupResult setDeletedDependents(List deletedDependents) { - this.deletedDependents = deletedDependents; + public WorkflowCleanupResult setDeleteCalledOnDependents( + List deletedDependents) { + this.deleteCalledOnDependents = deletedDependents; return this; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java index 1207913905..56feec0ae1 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/DependentBuilder.java @@ -5,6 +5,7 @@ import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition; import io.javaoperatorsdk.operator.processing.dependent.workflow.DependentResourceNode; +@SuppressWarnings("rawtypes") public class DependentBuilder

{ private final WorkflowBuilder

workflowBuilder; @@ -15,7 +16,7 @@ public DependentBuilder(WorkflowBuilder

workflowBuilder, DependentResourceNod this.node = node; } - public DependentBuilder

dependsOn(DependentResource... dependentResources) { + public DependentBuilder

dependsOn(DependentResource... dependentResources) { for (var dependentResource : dependentResources) { var dependsOn = workflowBuilder.getNodeByDependentResource(dependentResource); node.addDependsOnRelation(dependsOn); @@ -23,17 +24,17 @@ public DependentBuilder

dependsOn(DependentResource... dependentResourc return this; } - public DependentBuilder

withReconcileCondition(Condition reconcileCondition) { + public DependentBuilder

withReconcileCondition(Condition reconcileCondition) { node.setReconcileCondition(reconcileCondition); return this; } - public DependentBuilder

withReadyCondition(Condition readyCondition) { + public DependentBuilder

withReadyCondition(Condition readyCondition) { node.setReadyCondition(readyCondition); return this; } - public DependentBuilder

withDeletePostCondition(Condition readyCondition) { + public DependentBuilder

withDeletePostCondition(Condition readyCondition) { node.setDeletePostCondition(readyCondition); return this; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java index c8a6f4f734..3a4d0e8116 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java @@ -15,8 +15,8 @@ public class WorkflowBuilder

{ private final Set> dependentResourceNodes = new HashSet<>(); - public DependentBuilder

addDependent(DependentResource dependentResource) { - DependentResourceNode node = new DependentResourceNode<>(dependentResource); + public DependentBuilder

addDependent(DependentResource dependentResource) { + DependentResourceNode node = new DependentResourceNode<>(dependentResource); dependentResourceNodes.add(node); return new DependentBuilder<>(this, node); } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java index 4d592e68a5..c615741f9a 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java @@ -128,8 +128,7 @@ void oneBranchErrorsOtherCompletes() { () -> workflow.reconcile(new TestCustomResource(), null) .throwAggregatedExceptionIfErrorsPresent()); - assertThat(executionHistory).reconciledInOrder(dr1, dr2, dr3); - assertThat(executionHistory).reconciledInOrder(dr1, drError); + assertThat(executionHistory).reconciledInOrder(dr1, dr2, dr3).reconciledInOrder(dr1, drError); } @Test diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java index 1dbd04a11d..72a27ed305 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java @@ -13,16 +13,16 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.mock; -@SuppressWarnings("unchecked") +@SuppressWarnings("rawtypes") class WorkflowTest { @Test - public void calculatesTopLevelResources() { + void calculatesTopLevelResources() { var dr1 = mock(DependentResource.class); var dr2 = mock(DependentResource.class); var independentDR = mock(DependentResource.class); - Workflow workflow = new WorkflowBuilder<>() + var workflow = new WorkflowBuilder() .addDependent(independentDR).build() .addDependent(dr1).build() .addDependent(dr2).dependsOn(dr1).build() @@ -37,7 +37,7 @@ public void calculatesTopLevelResources() { } @Test - public void calculatesBottomLevelResources() { + void calculatesBottomLevelResources() { var dr1 = mock(DependentResource.class); var dr2 = mock(DependentResource.class); var independentDR = mock(DependentResource.class); From e96ade0e1d53b23aa5692733a289d483c1bdc5f8 Mon Sep 17 00:00:00 2001 From: csviri Date: Thu, 19 May 2022 16:07:17 +0200 Subject: [PATCH 45/51] sample webpage reconciler --- .../dependent/workflow/Workflow.java | 1 + .../workflow/WorkflowCleanupExecutor.java | 2 +- .../workflow/WorkflowCleanupResult.java | 30 ++++- .../workflow/WorkflowExecutionResult.java | 9 +- .../WorkflowReconcileExecutorTest.java | 10 +- .../WebPageDependentsWorkflowReconciler.java | 103 ++++++++++++++++++ 6 files changed, 135 insertions(+), 20 deletions(-) create mode 100644 sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageDependentsWorkflowReconciler.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java index c51eea9015..bcfcc4b726 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java @@ -14,6 +14,7 @@ * * @param

primary resource */ +// todo results unit test @SuppressWarnings("rawtypes") public class Workflow

{ 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 7df94a199e..eb21004b1f 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 @@ -174,7 +174,7 @@ private WorkflowCleanupResult createCleanupResult() { .entrySet().stream() .collect(Collectors.toMap(e -> e.getKey().getDependentResource(), Map.Entry::getValue))); - result.setNotDeletedDependents( + result.setPostConditionNotMetDependents( postDeleteConditionNotMet.stream().map(DependentResourceNode::getDependentResource) .collect(Collectors.toList())); result.setDeleteCalledOnDependents( diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupResult.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupResult.java index 7b72b1b8fd..10741cd55f 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupResult.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupResult.java @@ -5,13 +5,14 @@ import java.util.List; import java.util.Map; +import io.javaoperatorsdk.operator.AggregatedOperatorException; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; @SuppressWarnings("rawtypes") public class WorkflowCleanupResult { private List deleteCalledOnDependents = new ArrayList<>(); - private List notDeletedDependents = new ArrayList<>(); + private List postConditionNotMetDependents = new ArrayList<>(); private Map erroredDependents = new HashMap<>(); public List getDeleteCalledOnDependents() { @@ -24,13 +25,13 @@ public WorkflowCleanupResult setDeleteCalledOnDependents( return this; } - public List getNotDeletedDependents() { - return notDeletedDependents; + public List getPostConditionNotMetDependents() { + return postConditionNotMetDependents; } - public WorkflowCleanupResult setNotDeletedDependents( - List notDeletedDependents) { - this.notDeletedDependents = notDeletedDependents; + public WorkflowCleanupResult setPostConditionNotMetDependents( + List postConditionNotMetDependents) { + this.postConditionNotMetDependents = postConditionNotMetDependents; return this; } @@ -43,4 +44,21 @@ public WorkflowCleanupResult setErroredDependents( this.erroredDependents = erroredDependents; return this; } + + public boolean postConditionsNotMet() { + return !postConditionNotMetDependents.isEmpty(); + } + + public boolean erroredDependentsExists() { + return !erroredDependents.isEmpty(); + } + + public void throwAggregateExceptionIfErroredExists() { + if (erroredDependentsExists()) { + throw new AggregatedOperatorException("Exception(s) during workflow execution.", + new ArrayList<>(erroredDependents.values())); + } + } + + } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutionResult.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutionResult.java index 5a66dcd2ac..32ae4946b8 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutionResult.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutionResult.java @@ -45,7 +45,7 @@ public WorkflowExecutionResult setNotReadyDependents( return this; } - public void throwAggregatedExceptionIfErrorsPresent() { + public void throwAggregateExceptionIfErrorsPresent() { if (!erroredDependents.isEmpty()) { throw createFinalException(); } @@ -64,11 +64,4 @@ public boolean erroredDependentsExists() { return !erroredDependents.isEmpty(); } - public void throwAggregateExceptionIfErroredExists() { - if (erroredDependentsExists()) { - throw new AggregatedOperatorException("Exception(s) during workflow execution.", - new ArrayList<>(erroredDependents.values())); - } - } - } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java index c615741f9a..5c6e6893d3 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java @@ -95,7 +95,7 @@ void exceptionHandlingSimpleCases() { .build(); assertThrows(AggregatedOperatorException.class, () -> workflow.reconcile(new TestCustomResource(), null) - .throwAggregatedExceptionIfErrorsPresent()); + .throwAggregateExceptionIfErrorsPresent()); assertThat(executionHistory).reconciled(drError); } @@ -108,7 +108,7 @@ void dependentsOnErroredResourceNotReconciled() { .build(); assertThrows(AggregatedOperatorException.class, () -> workflow.reconcile(new TestCustomResource(), null) - .throwAggregatedExceptionIfErrorsPresent()); + .throwAggregateExceptionIfErrorsPresent()); assertThat(executionHistory).reconciled(dr1, drError).notReconciled(dr2); } @@ -126,7 +126,7 @@ void oneBranchErrorsOtherCompletes() { assertThrows(AggregatedOperatorException.class, () -> workflow.reconcile(new TestCustomResource(), null) - .throwAggregatedExceptionIfErrorsPresent()); + .throwAggregateExceptionIfErrorsPresent()); assertThat(executionHistory).reconciledInOrder(dr1, dr2, dr3).reconciledInOrder(dr1, drError); } @@ -140,7 +140,7 @@ void onlyOneDependsOnErroredResourceNotReconciled() { .build(); assertThrows(AggregatedOperatorException.class, () -> workflow.reconcile(new TestCustomResource(), null) - .throwAggregatedExceptionIfErrorsPresent()); + .throwAggregateExceptionIfErrorsPresent()); assertThat(executionHistory).notReconciled(dr2); } @@ -210,7 +210,7 @@ void reconcileConditionAlsoErrorDependsOn() { assertThrows(AggregatedOperatorException.class, () -> workflow.reconcile(new TestCustomResource(), null) - .throwAggregatedExceptionIfErrorsPresent()); + .throwAggregateExceptionIfErrorsPresent()); assertThat(executionHistory).deleted(drDeleter2, drDeleter); assertThat(executionHistory).reconciled(drError); 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 new file mode 100644 index 0000000000..b2fae657b1 --- /dev/null +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageDependentsWorkflowReconciler.java @@ -0,0 +1,103 @@ +package io.javaoperatorsdk.operator.sample; + +import java.util.Arrays; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.fabric8.kubernetes.api.model.networking.v1.Ingress; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig; +import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition; +import io.javaoperatorsdk.operator.processing.dependent.workflow.Workflow; +import io.javaoperatorsdk.operator.processing.dependent.workflow.builder.WorkflowBuilder; +import io.javaoperatorsdk.operator.processing.event.source.EventSource; + +import static io.javaoperatorsdk.operator.sample.Utils.*; + +/** + * Shows how to implement reconciler using standalone dependent resources. + */ +@ControllerConfiguration( + labelSelector = WebPageDependentsWorkflowReconciler.DEPENDENT_RESOURCE_LABEL_SELECTOR) +public class WebPageDependentsWorkflowReconciler + implements Reconciler, ErrorStatusHandler, EventSourceInitializer { + + public static final String DEPENDENT_RESOURCE_LABEL_SELECTOR = "!low-level"; + private static final Logger log = + LoggerFactory.getLogger(WebPageDependentsWorkflowReconciler.class); + + private KubernetesDependentResource configMapDR; + private KubernetesDependentResource deploymentDR; + private KubernetesDependentResource serviceDR; + private KubernetesDependentResource ingressDR; + + private Workflow workflow; + + public WebPageDependentsWorkflowReconciler(KubernetesClient kubernetesClient) { + initDependentResources(kubernetesClient); + workflow = new WorkflowBuilder() + .addDependent(configMapDR).build() + .addDependent(deploymentDR).build() + .addDependent(serviceDR).build() + .addDependent(ingressDR).withReconcileCondition(new IngressCondition()).build() + .build(); + } + + @Override + public Map prepareEventSources(EventSourceContext context) { + return EventSourceInitializer.nameEventSources(configMapDR.initEventSource(context), + deploymentDR.initEventSource(context), serviceDR.initEventSource(context), + ingressDR.initEventSource(context)); + } + + @Override + public UpdateControl reconcile(WebPage webPage, Context context) + throws Exception { + simulateErrorIfRequested(webPage); + + var result = workflow.reconcile(webPage, context); + // todo default? + result.throwAggregateExceptionIfErrorsPresent(); + + webPage.setStatus( + createStatus( + configMapDR.getSecondaryResource(webPage).orElseThrow().getMetadata().getName())); + return UpdateControl.patchStatus(webPage); + } + + @Override + public ErrorStatusUpdateControl updateErrorStatus( + WebPage resource, Context retryInfo, Exception e) { + return handleError(resource, e); + } + + private void initDependentResources(KubernetesClient client) { + this.configMapDR = new ConfigMapDependentResource(); + this.deploymentDR = new DeploymentDependentResource(); + this.serviceDR = new ServiceDependentResource(); + this.ingressDR = new IngressDependentResource(); + + Arrays.asList(configMapDR, deploymentDR, serviceDR, ingressDR).forEach(dr -> { + dr.setKubernetesClient(client); + dr.configureWith(new KubernetesDependentResourceConfig() + .setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR)); + }); + } + + static class IngressCondition implements Condition { + @Override + public boolean isMet(DependentResource dependentResource, WebPage primary, + Context context) { + return primary.getSpec().getExposed(); + } + } + +} From 1460a01aa898b10e06a79754baa2a524ff35ba9d Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 23 May 2022 11:04:31 +0200 Subject: [PATCH 46/51] improved assertions --- .../dependent/workflow/Workflow.java | 27 +++- .../workflow/WorkflowCleanupExecutor.java | 14 +- .../workflow/WorkflowCleanupResult.java | 2 +- .../workflow/WorkflowReconcileExecutor.java | 14 +- .../workflow/builder/WorkflowBuilder.java | 16 +- .../workflow/WorkflowCleanupExecutorTest.java | 10 +- .../WorkflowReconcileExecutorTest.java | 148 ++++++++++++++---- .../WebPageDependentsWorkflowReconciler.java | 4 +- 8 files changed, 174 insertions(+), 61 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java index bcfcc4b726..ffa77d7159 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java @@ -14,45 +14,56 @@ * * @param

primary resource */ -// todo results unit test @SuppressWarnings("rawtypes") public class Workflow

{ + public static boolean THROW_EXCEPTION_AUTOMATICALLY_DEFAULT = true; + private final Set dependentResourceNodes; private final Set topLevelResources = new HashSet<>(); private final Set bottomLevelResource = new HashSet<>(); private Map> dependents; - + private final boolean throwExceptionAutomatically; // it's "global" executor service shared between multiple reconciliations running parallel private ExecutorService executorService; public Workflow(Set dependentResourceNodes) { this.executorService = ConfigurationServiceProvider.instance().getExecutorService(); this.dependentResourceNodes = dependentResourceNodes; + this.throwExceptionAutomatically = THROW_EXCEPTION_AUTOMATICALLY_DEFAULT; preprocessForReconcile(); } public Workflow(Set dependentResourceNodes, - ExecutorService executorService) { + ExecutorService executorService, boolean throwExceptionAutomatically) { this.executorService = executorService; this.dependentResourceNodes = dependentResourceNodes; + this.throwExceptionAutomatically = throwExceptionAutomatically; preprocessForReconcile(); } public Workflow(Set dependentResourceNodes, int globalParallelism) { - this(dependentResourceNodes, Executors.newFixedThreadPool(globalParallelism)); + this(dependentResourceNodes, Executors.newFixedThreadPool(globalParallelism), true); } public WorkflowExecutionResult reconcile(P primary, Context

context) { WorkflowReconcileExecutor

workflowReconcileExecutor = new WorkflowReconcileExecutor<>(this, primary, context); - return workflowReconcileExecutor.reconcile(); + var result = workflowReconcileExecutor.reconcile(); + if (throwExceptionAutomatically) { + result.throwAggregateExceptionIfErrorsPresent(); + } + return result; } public WorkflowCleanupResult cleanup(P primary, Context

context) { WorkflowCleanupExecutor

workflowCleanupExecutor = new WorkflowCleanupExecutor<>(this, primary, context); - return workflowCleanupExecutor.cleanup(); + var result = workflowCleanupExecutor.cleanup(); + if (throwExceptionAutomatically) { + result.throwAggregateExceptionIfErrorsPresent(); + } + return result; } // add cycle detection? @@ -72,6 +83,10 @@ private void preprocessForReconcile() { } } + public boolean isThrowExceptionAutomatically() { + return throwExceptionAutomatically; + } + public void setExecutorService(ExecutorService executorService) { this.executorService = executorService; } 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 eb21004b1f..61611817d0 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 @@ -1,9 +1,8 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow; -import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Future; import java.util.stream.Collectors; @@ -21,12 +20,13 @@ public class WorkflowCleanupExecutor

{ private static final Logger log = LoggerFactory.getLogger(WorkflowCleanupExecutor.class); private final Map> actualExecutions = - new HashMap<>(); + new ConcurrentHashMap<>(); private final Map exceptionsDuringExecution = - new HashMap<>(); - private final Set alreadyVisited = new HashSet<>(); - private final Set postDeleteConditionNotMet = new HashSet<>(); - private final Set deleteCalled = new HashSet<>(); + new ConcurrentHashMap<>(); + private final Set alreadyVisited = ConcurrentHashMap.newKeySet(); + private final Set postDeleteConditionNotMet = + ConcurrentHashMap.newKeySet(); + private final Set deleteCalled = ConcurrentHashMap.newKeySet(); private final Workflow

workflow; private final P primary; diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupResult.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupResult.java index 10741cd55f..c724376d78 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupResult.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupResult.java @@ -53,7 +53,7 @@ public boolean erroredDependentsExists() { return !erroredDependents.isEmpty(); } - public void throwAggregateExceptionIfErroredExists() { + public void throwAggregateExceptionIfErrorsPresent() { if (erroredDependentsExists()) { throw new AggregatedOperatorException("Exception(s) during workflow execution.", new ArrayList<>(erroredDependents.values())); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index 8ee4507408..c14ba4de03 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -21,17 +21,17 @@ public class WorkflowReconcileExecutor

{ private final Workflow

workflow; /** Covers both deleted and reconciled */ - private final Set alreadyVisited = new HashSet<>(); - private final Set notReady = new HashSet<>(); + private final Set alreadyVisited = ConcurrentHashMap.newKeySet(); + private final Set notReady = ConcurrentHashMap.newKeySet(); private final Map> actualExecutions = new HashMap<>(); private final Map exceptionsDuringExecution = - new HashMap<>(); + new ConcurrentHashMap<>(); - private final Set markedForDelete = new HashSet<>(); - private final Set deleteConditionNotMet = new HashSet<>(); + private final Set markedForDelete = ConcurrentHashMap.newKeySet(); + private final Set deleteConditionNotMet = ConcurrentHashMap.newKeySet(); // used to remember reconciled (not deleted or errored) dependents - private final Set reconciled = new HashSet<>(); + private final Set reconciled = ConcurrentHashMap.newKeySet(); private final P primary; private final Context

context; @@ -244,7 +244,7 @@ private boolean alreadyVisited( private void handleReconcileConditionNotMet(DependentResourceNode dependentResourceNode) { Set bottomNodes = new HashSet<>(); markDependentsForDelete(dependentResourceNode, bottomNodes); - bottomNodes.forEach(bn -> handleDelete(bn)); + bottomNodes.forEach(this::handleDelete); } private void markDependentsForDelete(DependentResourceNode dependentResourceNode, diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java index 3a4d0e8116..270dbd0261 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/builder/WorkflowBuilder.java @@ -10,10 +10,13 @@ import io.javaoperatorsdk.operator.processing.dependent.workflow.DependentResourceNode; import io.javaoperatorsdk.operator.processing.dependent.workflow.Workflow; +import static io.javaoperatorsdk.operator.processing.dependent.workflow.Workflow.THROW_EXCEPTION_AUTOMATICALLY_DEFAULT; + @SuppressWarnings({"rawtypes", "unchecked"}) public class WorkflowBuilder

{ private final Set> dependentResourceNodes = new HashSet<>(); + private boolean throwExceptionAutomatically = THROW_EXCEPTION_AUTOMATICALLY_DEFAULT; public DependentBuilder

addDependent(DependentResource dependentResource) { DependentResourceNode node = new DependentResourceNode<>(dependentResource); @@ -32,9 +35,18 @@ DependentResourceNode getNodeByDependentResource(DependentResource depende .orElseThrow(); } + public boolean isThrowExceptionAutomatically() { + return throwExceptionAutomatically; + } + + public WorkflowBuilder

withThrowExceptionFurther(boolean throwExceptionFurther) { + this.throwExceptionAutomatically = throwExceptionFurther; + return this; + } + public Workflow

build() { return new Workflow(dependentResourceNodes, - ConfigurationServiceProvider.instance().getExecutorService()); + ConfigurationServiceProvider.instance().getExecutorService(), throwExceptionAutomatically); } public Workflow

build(int parallelism) { @@ -42,6 +54,6 @@ public Workflow

build(int parallelism) { } public Workflow

build(ExecutorService executorService) { - return new Workflow(dependentResourceNodes, executorService); + return new Workflow(dependentResourceNodes, executorService, throwExceptionAutomatically); } } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java index 3f250398ba..2a4b4b1e40 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java @@ -2,10 +2,12 @@ import org.junit.jupiter.api.Test; +import io.javaoperatorsdk.operator.AggregatedOperatorException; import io.javaoperatorsdk.operator.processing.dependent.workflow.builder.WorkflowBuilder; import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; import static io.javaoperatorsdk.operator.processing.dependent.workflow.ExecutionAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; class WorkflowCleanupExecutorTest extends AbstractWorkflowExecutorTest { @@ -22,9 +24,10 @@ void cleanUpDiamondWorkflow() { .addDependent(dd3).dependsOn(dr1, dd2).build() .build(); - workflow.cleanup(new TestCustomResource(), null); + var res = workflow.cleanup(new TestCustomResource(), null); assertThat(executionHistory).reconciledInOrder(dd3, dd2, dd1).notReconciled(dr1); + } @Test @@ -34,9 +37,12 @@ void dontDeleteIfDependentErrored() { .addDependent(dd2).dependsOn(dd1).build() .addDependent(dd3).dependsOn(dd2).build() .addDependent(errorDD).dependsOn(dd2).build() + .withThrowExceptionFurther(false) .build(); - workflow.cleanup(new TestCustomResource(), null); + var res = workflow.cleanup(new TestCustomResource(), null); + assertThrows(AggregatedOperatorException.class, + res::throwAggregateExceptionIfErrorsPresent); assertThat(executionHistory).deleted(dd3, errorDD).notReconciled(dd1, dd2); } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java index 5c6e6893d3..6bc5cb90f1 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java @@ -34,8 +34,9 @@ void reconcileTopLevelResources() { var res = workflow.reconcile(new TestCustomResource(), null); - Assertions.assertThat(res.getErroredDependents()).isEmpty(); assertThat(executionHistory).reconciled(dr1, dr2); + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + Assertions.assertThat(res.getReconciledDependents()).containsExactly(dr1, dr2); } @Test @@ -49,6 +50,9 @@ void reconciliationWithSimpleDependsOn() { Assertions.assertThat(res.getErroredDependents()).isEmpty(); assertThat(executionHistory).reconciledInOrder(dr1, dr2); + Assertions.assertThat(res.getReconciledDependents()).containsExactlyInAnyOrder(dr1, dr2); + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + Assertions.assertThat(res.getNotReadyDependents()).isEmpty(); } @Test @@ -66,6 +70,9 @@ void reconciliationWithTwoTheDependsOns() { Assertions.assertThat(res.getErroredDependents()).isEmpty(); assertThat(executionHistory) .reconciledInOrder(dr1, dr2).reconciledInOrder(dr1, dr3); + Assertions.assertThat(res.getReconciledDependents()).containsExactlyInAnyOrder(dr1, dr2, dr3); + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + Assertions.assertThat(res.getNotReadyDependents()).isEmpty(); } @Test @@ -86,17 +93,29 @@ void diamondShareWorkflowReconcile() { assertThat(executionHistory) .reconciledInOrder(dr1, dr2, dr4) .reconciledInOrder(dr1, dr3, dr4); + + Assertions.assertThat(res.getReconciledDependents()).containsExactlyInAnyOrder(dr1, dr2, dr3, + dr4); + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + Assertions.assertThat(res.getNotReadyDependents()).isEmpty(); } @Test void exceptionHandlingSimpleCases() { var workflow = new WorkflowBuilder() .addDependent(drError).build() + .withThrowExceptionFurther(false) .build(); + + var res = workflow.reconcile(new TestCustomResource(), null); + assertThrows(AggregatedOperatorException.class, - () -> workflow.reconcile(new TestCustomResource(), null) - .throwAggregateExceptionIfErrorsPresent()); + res::throwAggregateExceptionIfErrorsPresent); + assertThat(executionHistory).reconciled(drError); + Assertions.assertThat(res.getErroredDependents()).containsOnlyKeys(drError); + Assertions.assertThat(res.getReconciledDependents()).isEmpty(); + Assertions.assertThat(res.getNotReadyDependents()).isEmpty(); } @Test @@ -105,12 +124,17 @@ void dependentsOnErroredResourceNotReconciled() { .addDependent(dr1).build() .addDependent(drError).dependsOn(dr1).build() .addDependent(dr2).dependsOn(drError).build() + .withThrowExceptionFurther(false) .build(); + + var res = workflow.reconcile(new TestCustomResource(), null); assertThrows(AggregatedOperatorException.class, - () -> workflow.reconcile(new TestCustomResource(), null) - .throwAggregateExceptionIfErrorsPresent()); + res::throwAggregateExceptionIfErrorsPresent); assertThat(executionHistory).reconciled(dr1, drError).notReconciled(dr2); + Assertions.assertThat(res.getErroredDependents()).containsOnlyKeys(drError); + Assertions.assertThat(res.getReconciledDependents()).containsExactlyInAnyOrder(dr1); + Assertions.assertThat(res.getNotReadyDependents()).isEmpty(); } @Test @@ -122,13 +146,17 @@ void oneBranchErrorsOtherCompletes() { .addDependent(drError).dependsOn(dr1).build() .addDependent(dr2).dependsOn(dr1).build() .addDependent(dr3).dependsOn(dr2).build() + .withThrowExceptionFurther(false) .build(); + var res = workflow.reconcile(new TestCustomResource(), null); assertThrows(AggregatedOperatorException.class, - () -> workflow.reconcile(new TestCustomResource(), null) - .throwAggregateExceptionIfErrorsPresent()); + res::throwAggregateExceptionIfErrorsPresent); assertThat(executionHistory).reconciledInOrder(dr1, dr2, dr3).reconciledInOrder(dr1, drError); + Assertions.assertThat(res.getErroredDependents()).containsOnlyKeys(drError); + Assertions.assertThat(res.getReconciledDependents()).containsExactlyInAnyOrder(dr1, dr2, dr3); + Assertions.assertThat(res.getNotReadyDependents()).isEmpty(); } @Test @@ -137,12 +165,17 @@ void onlyOneDependsOnErroredResourceNotReconciled() { .addDependent(dr1).build() .addDependent(drError).build() .addDependent(dr2).dependsOn(drError, dr1).build() + .withThrowExceptionFurther(false) .build(); + + var res = workflow.reconcile(new TestCustomResource(), null); assertThrows(AggregatedOperatorException.class, - () -> workflow.reconcile(new TestCustomResource(), null) - .throwAggregateExceptionIfErrorsPresent()); + res::throwAggregateExceptionIfErrorsPresent); assertThat(executionHistory).notReconciled(dr2); + Assertions.assertThat(res.getErroredDependents()).containsKey(drError); + Assertions.assertThat(res.getReconciledDependents()).containsExactlyInAnyOrder(dr1); + Assertions.assertThat(res.getNotReadyDependents()).isEmpty(); } @Test @@ -155,10 +188,13 @@ void simpleReconcileCondition() { var res = workflow.reconcile(new TestCustomResource(), null); - Assertions.assertThat(res.getErroredDependents()).isEmpty(); assertThat(executionHistory).notReconciled(dr1).reconciled(dr2).deleted(drDeleter); + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + Assertions.assertThat(res.getReconciledDependents()).containsExactlyInAnyOrder(dr2); + Assertions.assertThat(res.getNotReadyDependents()).isEmpty(); } + @Test void triangleOnceConditionNotMet() { var workflow = new WorkflowBuilder() @@ -170,8 +206,10 @@ void triangleOnceConditionNotMet() { var res = workflow.reconcile(new TestCustomResource(), null); - Assertions.assertThat(res.getErroredDependents()).isEmpty(); assertThat(executionHistory).reconciledInOrder(dr1, dr2).deleted(drDeleter); + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + Assertions.assertThat(res.getReconciledDependents()).containsExactlyInAnyOrder(dr1, dr2); + Assertions.assertThat(res.getNotReadyDependents()).isEmpty(); } @Test @@ -180,12 +218,12 @@ void reconcileConditionTransitiveDelete() { var workflow = new WorkflowBuilder() .addDependent(dr1).build() - .addDependent(dr2).withReconcileCondition(not_met_reconcile_condition).dependsOn(dr1) + .addDependent(dr2).dependsOn(dr1).withReconcileCondition(not_met_reconcile_condition) .build() - .addDependent(drDeleter).withReconcileCondition(met_reconcile_condition).dependsOn(dr2) + .addDependent(drDeleter).dependsOn(dr2).withReconcileCondition(met_reconcile_condition) .build() - .addDependent(drDeleter2).withReconcileCondition(met_reconcile_condition) - .dependsOn(drDeleter).build() + .addDependent(drDeleter2).dependsOn(drDeleter) + .withReconcileCondition(met_reconcile_condition).build() .build(); var res = workflow.reconcile(new TestCustomResource(), null); @@ -194,6 +232,10 @@ void reconcileConditionTransitiveDelete() { assertThat(executionHistory).notReconciled(dr2); assertThat(executionHistory).reconciledInOrder(dr1, drDeleter2, drDeleter); assertThat(executionHistory).deleted(drDeleter2, drDeleter); + + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + Assertions.assertThat(res.getReconciledDependents()).containsExactlyInAnyOrder(dr1); + Assertions.assertThat(res.getNotReadyDependents()).isEmpty(); } @Test @@ -203,17 +245,23 @@ void reconcileConditionAlsoErrorDependsOn() { var workflow = new WorkflowBuilder() .addDependent(drError).build() .addDependent(drDeleter).withReconcileCondition(not_met_reconcile_condition).build() - .addDependent(drDeleter2).withReconcileCondition(met_reconcile_condition) - .dependsOn(drError, drDeleter) + .addDependent(drDeleter2).dependsOn(drError, drDeleter) + .withReconcileCondition(met_reconcile_condition) .build() + .withThrowExceptionFurther(false) .build(); + var res = workflow.reconcile(new TestCustomResource(), null); assertThrows(AggregatedOperatorException.class, - () -> workflow.reconcile(new TestCustomResource(), null) - .throwAggregateExceptionIfErrorsPresent()); + res::throwAggregateExceptionIfErrorsPresent); - assertThat(executionHistory).deleted(drDeleter2, drDeleter); - assertThat(executionHistory).reconciled(drError); + assertThat(executionHistory) + .deleted(drDeleter2, drDeleter) + .reconciled(drError); + + Assertions.assertThat(res.getErroredDependents()).containsOnlyKeys(drError); + Assertions.assertThat(res.getReconciledDependents()).isEmpty(); + Assertions.assertThat(res.getNotReadyDependents()).isEmpty(); } @Test @@ -227,9 +275,11 @@ void oneDependsOnConditionNotMet() { var res = workflow.reconcile(new TestCustomResource(), null); Assertions.assertThat(res.getErroredDependents()).isEmpty(); - assertThat(executionHistory).deleted(drDeleter); - assertThat(executionHistory).notReconciled(dr2); - assertThat(executionHistory).reconciled(dr1); + + assertThat(executionHistory).deleted(drDeleter).notReconciled(dr2).reconciled(dr1); + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + Assertions.assertThat(res.getReconciledDependents()).containsExactlyInAnyOrder(dr1); + Assertions.assertThat(res.getNotReadyDependents()).isEmpty(); } @Test @@ -237,16 +287,20 @@ void deletedIfReconcileConditionNotMet() { TestDeleterDependent drDeleter2 = new TestDeleterDependent("DR_DELETER_2"); var workflow = new WorkflowBuilder() .addDependent(dr1).build() - .addDependent(drDeleter).withReconcileCondition(not_met_reconcile_condition).dependsOn(dr1) + .addDependent(drDeleter).dependsOn(dr1).withReconcileCondition(not_met_reconcile_condition) .build() .addDependent(drDeleter2).dependsOn(dr1, drDeleter).build() .build(); - workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), null); assertThat(executionHistory) .reconciledInOrder(dr1, drDeleter2, drDeleter) .deleted(drDeleter2, drDeleter); + + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + Assertions.assertThat(res.getReconciledDependents()).containsExactlyInAnyOrder(dr1); + Assertions.assertThat(res.getNotReadyDependents()).isEmpty(); } @Test @@ -264,12 +318,16 @@ void deleteDoneInReverseOrder() { .addDependent(drDeleter4).dependsOn(drDeleter3).build() .build(); - workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), null); assertThat(executionHistory) .reconciledInOrder(dr1, drDeleter4, drDeleter3, drDeleter) .reconciledInOrder(dr1, drDeleter2, drDeleter) .deleted(drDeleter, drDeleter2, drDeleter3, drDeleter4); + + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + Assertions.assertThat(res.getReconciledDependents()).containsExactlyInAnyOrder(dr1); + Assertions.assertThat(res.getNotReadyDependents()).isEmpty(); } @Test @@ -286,11 +344,15 @@ void diamondDeleteWithPostConditionInMiddle() { .addDependent(drDeleter4).dependsOn(drDeleter3, drDeleter2).build() .build(); - workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), null); assertThat(executionHistory).notReconciled(drDeleter) .reconciledInOrder(drDeleter4, drDeleter2) .reconciledInOrder(drDeleter4, drDeleter3); + + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + Assertions.assertThat(res.getReconciledDependents()).isEmpty(); + Assertions.assertThat(res.getNotReadyDependents()).isEmpty(); } @Test @@ -303,12 +365,18 @@ void diamondDeleteErrorInMiddle() { .addDependent(drDeleter2).dependsOn(drDeleter).build() .addDependent(errorDD).dependsOn(drDeleter).build() .addDependent(drDeleter3).dependsOn(errorDD, drDeleter2).build() + .withThrowExceptionFurther(false) .build(); - workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), null); - assertThat(executionHistory).notReconciled(drDeleter, drError) + assertThat(executionHistory) + .notReconciled(drDeleter, drError) .reconciledInOrder(drDeleter3, drDeleter2); + + Assertions.assertThat(res.getErroredDependents()).containsOnlyKeys(errorDD); + Assertions.assertThat(res.getReconciledDependents()).isEmpty(); + Assertions.assertThat(res.getNotReadyDependents()).isEmpty(); } @Test @@ -318,9 +386,13 @@ void readyConditionTrivialCase() { .addDependent(dr2).dependsOn(dr1).build() .build(); - workflow.reconcile(new TestCustomResource(), null); + var res = workflow.reconcile(new TestCustomResource(), null); assertThat(executionHistory).reconciledInOrder(dr1, dr2); + + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + Assertions.assertThat(res.getReconciledDependents()).containsExactlyInAnyOrder(dr1, dr2); + Assertions.assertThat(res.getNotReadyDependents()).isEmpty(); } @Test @@ -332,8 +404,12 @@ void readyConditionNotMetTrivialCase() { var res = workflow.reconcile(new TestCustomResource(), null); - Assertions.assertThat(res.getErroredDependents()).isEmpty(); + assertThat(executionHistory).reconciled(dr1).notReconciled(dr2); + + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + Assertions.assertThat(res.getReconciledDependents()).containsExactlyInAnyOrder(dr1); + Assertions.assertThat(res.getNotReadyDependents()).containsExactlyInAnyOrder(dr1); } @Test @@ -348,8 +424,10 @@ void readyConditionNotMetInOneParent() { var res = workflow.reconcile(new TestCustomResource(), null); - Assertions.assertThat(res.getErroredDependents()).isEmpty(); assertThat(executionHistory).reconciled(dr1, dr2).notReconciled(dr3); + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + Assertions.assertThat(res.getReconciledDependents()).containsExactlyInAnyOrder(dr1, dr2); + Assertions.assertThat(res.getNotReadyDependents()).containsExactlyInAnyOrder(dr1); } @Test @@ -370,6 +448,10 @@ void diamondShareWithReadyCondition() { assertThat(executionHistory).reconciledInOrder(dr1, dr2) .reconciledInOrder(dr1, dr3) .notReconciled(dr4); + + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + Assertions.assertThat(res.getReconciledDependents()).containsExactlyInAnyOrder(dr1, dr2, dr3); + Assertions.assertThat(res.getNotReadyDependents()).containsExactlyInAnyOrder(dr2); } } 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 b2fae657b1..2ced5a0f3d 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 @@ -63,9 +63,7 @@ public UpdateControl reconcile(WebPage webPage, Context contex throws Exception { simulateErrorIfRequested(webPage); - var result = workflow.reconcile(webPage, context); - // todo default? - result.throwAggregateExceptionIfErrorsPresent(); + workflow.reconcile(webPage, context); webPage.setStatus( createStatus( From 13c823ce98bf3e06e085669c0fe9408389f0e9de Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 23 May 2022 12:00:41 +0200 Subject: [PATCH 47/51] wip --- .../dependent/workflow/Workflow.java | 2 +- .../workflow/WorkflowCleanupExecutorTest.java | 31 ++++++++++++++++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java index ffa77d7159..8dbe13cb9e 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java @@ -17,7 +17,7 @@ @SuppressWarnings("rawtypes") public class Workflow

{ - public static boolean THROW_EXCEPTION_AUTOMATICALLY_DEFAULT = true; + public static final boolean THROW_EXCEPTION_AUTOMATICALLY_DEFAULT = true; private final Set dependentResourceNodes; private final Set topLevelResources = new HashSet<>(); diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java index 2a4b4b1e40..2488059beb 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutorTest.java @@ -1,5 +1,6 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import io.javaoperatorsdk.operator.AggregatedOperatorException; @@ -28,6 +29,10 @@ void cleanUpDiamondWorkflow() { assertThat(executionHistory).reconciledInOrder(dd3, dd2, dd1).notReconciled(dr1); + Assertions.assertThat(res.getDeleteCalledOnDependents()).containsExactlyInAnyOrder(dd1, dd2, + dd3); + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + Assertions.assertThat(res.getPostConditionNotMetDependents()).isEmpty(); } @Test @@ -45,6 +50,10 @@ void dontDeleteIfDependentErrored() { res::throwAggregateExceptionIfErrorsPresent); assertThat(executionHistory).deleted(dd3, errorDD).notReconciled(dd1, dd2); + + Assertions.assertThat(res.getDeleteCalledOnDependents()).containsExactlyInAnyOrder(dd3); + Assertions.assertThat(res.getErroredDependents()).containsOnlyKeys(errorDD); + Assertions.assertThat(res.getPostConditionNotMetDependents()).isEmpty(); } @@ -55,9 +64,12 @@ void cleanupConditionTrivialCase() { .addDependent(dd2).dependsOn(dd1).withDeletePostCondition(noMetDeletePostCondition).build() .build(); - workflow.cleanup(new TestCustomResource(), null); + var res = workflow.cleanup(new TestCustomResource(), null); assertThat(executionHistory).deleted(dd2).notReconciled(dd1); + Assertions.assertThat(res.getDeleteCalledOnDependents()).containsExactlyInAnyOrder(dd2); + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + Assertions.assertThat(res.getPostConditionNotMetDependents()).containsExactlyInAnyOrder(dd2); } @Test @@ -67,9 +79,13 @@ void cleanupConditionMet() { .addDependent(dd2).dependsOn(dd1).withDeletePostCondition(metDeletePostCondition).build() .build(); - workflow.cleanup(new TestCustomResource(), null); + var res = workflow.cleanup(new TestCustomResource(), null); assertThat(executionHistory).deleted(dd2, dd1); + + Assertions.assertThat(res.getDeleteCalledOnDependents()).containsExactlyInAnyOrder(dd1, dd2); + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + Assertions.assertThat(res.getPostConditionNotMetDependents()).isEmpty(); } @Test @@ -83,12 +99,17 @@ void cleanupConditionDiamondWorkflow() { .addDependent(dd4).dependsOn(dd2, dd3).build() .build(); - workflow.cleanup(new TestCustomResource(), null); + var res = workflow.cleanup(new TestCustomResource(), null); assertThat(executionHistory) .reconciledInOrder(dd4, dd2) .reconciledInOrder(dd4, dd3) .notReconciled(dr1); + + Assertions.assertThat(res.getDeleteCalledOnDependents()).containsExactlyInAnyOrder(dd4, dd3, + dd2); + Assertions.assertThat(res.getErroredDependents()).isEmpty(); + Assertions.assertThat(res.getPostConditionNotMetDependents()).containsExactlyInAnyOrder(dd3); } @Test @@ -98,10 +119,12 @@ void dontDeleteIfGarbageCollected() { .addDependent(gcDel).build() .build(); - workflow.cleanup(new TestCustomResource(), null); + var res = workflow.cleanup(new TestCustomResource(), null); assertThat(executionHistory) .notReconciled(gcDel); + + Assertions.assertThat(res.getDeleteCalledOnDependents()).isEmpty(); } } From 088665afd43689a26b1b74a35cb53bbd1ee53bcb Mon Sep 17 00:00:00 2001 From: csviri Date: Tue, 24 May 2022 10:02:43 +0200 Subject: [PATCH 48/51] reconcile result --- .../workflow/WorkflowExecutionResult.java | 20 +++++++++++++++---- .../workflow/WorkflowReconcileExecutor.java | 10 ++++++---- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutionResult.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutionResult.java index 32ae4946b8..87c23e422c 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutionResult.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowExecutionResult.java @@ -1,19 +1,20 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import io.javaoperatorsdk.operator.AggregatedOperatorException; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; @SuppressWarnings("rawtypes") public class WorkflowExecutionResult { - private List reconciledDependents = new ArrayList<>(); - private List notReadyDependents = new ArrayList<>(); - private Map erroredDependents = new HashMap<>(); + private List reconciledDependents; + private List notReadyDependents; + private Map erroredDependents; + private Map reconcileResults; public Map getErroredDependents() { return erroredDependents; @@ -45,6 +46,16 @@ public WorkflowExecutionResult setNotReadyDependents( return this; } + public Map getReconcileResults() { + return reconcileResults; + } + + public WorkflowExecutionResult setReconcileResults( + Map reconcileResults) { + this.reconcileResults = reconcileResults; + return this; + } + public void throwAggregateExceptionIfErrorsPresent() { if (!erroredDependents.isEmpty()) { throw createFinalException(); @@ -64,4 +75,5 @@ public boolean erroredDependentsExists() { return !erroredDependents.isEmpty(); } + } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index c14ba4de03..3d3ba6e786 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -12,6 +12,7 @@ import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.api.reconciler.dependent.GarbageCollected; +import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; @SuppressWarnings({"rawtypes", "unchecked"}) public class WorkflowReconcileExecutor

{ @@ -32,6 +33,8 @@ public class WorkflowReconcileExecutor

{ private final Set deleteConditionNotMet = ConcurrentHashMap.newKeySet(); // used to remember reconciled (not deleted or errored) dependents private final Set reconciled = ConcurrentHashMap.newKeySet(); + private final Map reconcileResults = + new ConcurrentHashMap<>(); private final P primary; private final Context

context; @@ -154,7 +157,8 @@ public void run() { try { DependentResource dependentResource = dependentResourceNode.getDependentResource(); - dependentResource.reconcile(primary, context); + ReconcileResult reconcileResult = dependentResource.reconcile(primary, context); + reconcileResults.put(dependentResource, reconcileResult); reconciled.add(dependentResourceNode); boolean ready = dependentResourceNode.getReadyCondition() .map(rc -> rc.isMet(dependentResource, primary, context)) @@ -274,17 +278,15 @@ private boolean hasErroredParent( private WorkflowExecutionResult createReconcileResult() { WorkflowExecutionResult workflowExecutionResult = new WorkflowExecutionResult(); - workflowExecutionResult.setErroredDependents(exceptionsDuringExecution .entrySet().stream() .collect(Collectors.toMap(e -> e.getKey().getDependentResource(), Map.Entry::getValue))); workflowExecutionResult.setNotReadyDependents(notReady.stream() .map(DependentResourceNode::getDependentResource) .collect(Collectors.toList())); - workflowExecutionResult.setReconciledDependents(reconciled.stream() .map(DependentResourceNode::getDependentResource).collect(Collectors.toList())); - + workflowExecutionResult.setReconcileResults(reconcileResults); return workflowExecutionResult; } From e45d3047816d3a4139de058ed5180bd14eaf0e16 Mon Sep 17 00:00:00 2001 From: csviri Date: Tue, 24 May 2022 11:40:57 +0200 Subject: [PATCH 49/51] fix test --- .../dependent/workflow/WorkflowReconcileExecutorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java index 6bc5cb90f1..b0d210b07e 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java @@ -36,7 +36,7 @@ void reconcileTopLevelResources() { assertThat(executionHistory).reconciled(dr1, dr2); Assertions.assertThat(res.getErroredDependents()).isEmpty(); - Assertions.assertThat(res.getReconciledDependents()).containsExactly(dr1, dr2); + Assertions.assertThat(res.getReconciledDependents()).containsExactlyInAnyOrder(dr1, dr2); } @Test From 7511b4dd4639c5a66dcfefeab11762f649261398 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Tue, 24 May 2022 21:31:36 +0200 Subject: [PATCH 50/51] refactor: remove need to maintain external list of parents --- .../workflow/DependentResourceNode.java | 11 +++++++-- .../dependent/workflow/Workflow.java | 23 +++--------------- .../workflow/WorkflowCleanupExecutor.java | 7 +++--- .../workflow/WorkflowReconcileExecutor.java | 24 +++++++++++-------- 4 files changed, 29 insertions(+), 36 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java index d0da19257f..d88a8f9f22 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java @@ -1,8 +1,9 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow; -import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; @@ -14,7 +15,8 @@ public class DependentResourceNode { private Condition reconcileCondition; private Condition deletePostCondition; private Condition readyCondition; - private List dependsOn = new ArrayList<>(1); + private final List dependsOn = new LinkedList<>(); + private final List parents = new LinkedList<>(); public DependentResourceNode(DependentResource dependentResource) { this(dependentResource, null, null); @@ -53,6 +55,7 @@ public List getDependsOn() { } public void addDependsOnRelation(DependentResourceNode node) { + node.parents.add(this); dependsOn.add(node); } @@ -82,4 +85,8 @@ public DependentResourceNode setReadyCondition(Condition readyCondition) { this.readyCondition = readyCondition; return this; } + + public List getParents() { + return parents; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java index 8dbe13cb9e..df7ca1bd31 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java @@ -1,7 +1,7 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -22,7 +22,7 @@ public class Workflow

{ private final Set dependentResourceNodes; private final Set topLevelResources = new HashSet<>(); private final Set bottomLevelResource = new HashSet<>(); - private Map> dependents; + private final boolean throwExceptionAutomatically; // it's "global" executor service shared between multiple reconciliations running parallel private ExecutorService executorService; @@ -69,14 +69,11 @@ public WorkflowCleanupResult cleanup(P primary, Context

context) { // add cycle detection? private void preprocessForReconcile() { bottomLevelResource.addAll(dependentResourceNodes); - dependents = new ConcurrentHashMap<>(dependentResourceNodes.size()); for (DependentResourceNode node : dependentResourceNodes) { if (node.getDependsOn().isEmpty()) { topLevelResources.add(node); } else { for (DependentResourceNode dependsOn : node.getDependsOn()) { - dependents.computeIfAbsent(dependsOn, dr -> new ArrayList<>()); - dependents.get(dependsOn).add(node); bottomLevelResource.remove(dependsOn); } } @@ -99,20 +96,6 @@ Set getBottomLevelResource() { return bottomLevelResource; } - @SuppressWarnings("rawtypes") - List getDependents(DependentResourceNode node) { - var deps = dependents.get(node); - if (deps == null) { - return Collections.emptyList(); - } else { - return deps; - } - } - - Map> getDependents() { - return dependents; - } - ExecutorService getExecutorService() { return executorService; } 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 61611817d0..e56a496cda 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 @@ -117,7 +117,6 @@ public void run() { } } - @SuppressWarnings("unchecked") private synchronized void handleDependentCleaned( DependentResourceNode dependentResourceNode) { var dependOns = dependentResourceNode.getDependsOn(); @@ -154,8 +153,8 @@ private boolean alreadyVisited( } private boolean allDependentsCleaned( - DependentResourceNode dependentResourceNode) { - var parents = workflow.getDependents(dependentResourceNode); + DependentResourceNode dependentResourceNode) { + var parents = dependentResourceNode.getParents(); return parents.isEmpty() || parents.stream() .allMatch(d -> alreadyVisited(d) && !postDeleteConditionNotMet.contains(d)); @@ -163,7 +162,7 @@ private boolean allDependentsCleaned( private boolean hasErroredDependent( DependentResourceNode dependentResourceNode) { - var parents = workflow.getDependents(dependentResourceNode); + var parents = dependentResourceNode.getParents(); return !parents.isEmpty() && parents.stream().anyMatch(exceptionsDuringExecution::containsKey); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index 3d3ba6e786..f32e3b83b8 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -1,7 +1,11 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow; -import java.util.*; -import java.util.concurrent.*; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Future; import java.util.stream.Collectors; import org.slf4j.Logger; @@ -115,8 +119,8 @@ private void handleDelete(DependentResourceNode dependentResourceNode) { log.debug("Submitted to delete: {}", dependentResourceNode); } - private boolean allDependentsDeletedAlready(DependentResourceNode dependentResourceNode) { - var dependents = workflow.getDependents(dependentResourceNode); + private boolean allDependentsDeletedAlready(DependentResourceNode dependentResourceNode) { + var dependents = dependentResourceNode.getParents(); return dependents.stream().allMatch(d -> alreadyVisited.contains(d) && !notReady.contains(d) && !exceptionsDuringExecution.containsKey(d) && !deleteConditionNotMet.contains(d)); } @@ -222,13 +226,13 @@ private synchronized void handleDependentDeleted( }); } - private boolean isReconcilingNow(DependentResourceNode dependentResourceNode) { + private boolean isReconcilingNow(DependentResourceNode dependentResourceNode) { return actualExecutions.containsKey(dependentResourceNode); } private synchronized void handleDependentsReconcile( DependentResourceNode dependentResourceNode) { - var dependents = workflow.getDependents(dependentResourceNode); + var dependents = dependentResourceNode.getParents(); dependents.forEach(d -> { log.debug("Handle reconcile for dependent: {} of parent:{}", d, dependentResourceNode); handleReconcile(d); @@ -240,21 +244,21 @@ private boolean noMoreExecutionsScheduled() { } private boolean alreadyVisited( - DependentResourceNode dependentResourceNode) { + DependentResourceNode dependentResourceNode) { return alreadyVisited.contains(dependentResourceNode); } - private void handleReconcileConditionNotMet(DependentResourceNode dependentResourceNode) { + private void handleReconcileConditionNotMet(DependentResourceNode dependentResourceNode) { Set bottomNodes = new HashSet<>(); markDependentsForDelete(dependentResourceNode, bottomNodes); bottomNodes.forEach(this::handleDelete); } - private void markDependentsForDelete(DependentResourceNode dependentResourceNode, + private void markDependentsForDelete(DependentResourceNode dependentResourceNode, Set bottomNodes) { markedForDelete.add(dependentResourceNode); - var dependents = workflow.getDependents(dependentResourceNode); + var dependents = dependentResourceNode.getParents(); if (dependents.isEmpty()) { bottomNodes.add(dependentResourceNode); } else { From dff7f39d0859e5b6bf520aaa01a7b250eb66ab68 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Tue, 24 May 2022 21:32:16 +0200 Subject: [PATCH 51/51] refactor: show parents and dependents in String representation --- .../dependent/workflow/DependentResourceNode.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java index d88a8f9f22..973afd4ff6 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java @@ -46,14 +46,11 @@ public Optional getDeletePostCondition() { return Optional.ofNullable(deletePostCondition); } - public void setDependsOn(List dependsOn) { - this.dependsOn = dependsOn; - } - public List getDependsOn() { return dependsOn; } + @SuppressWarnings("unchecked") public void addDependsOnRelation(DependentResourceNode node) { node.parents.add(this); dependsOn.add(node); @@ -61,9 +58,13 @@ public void addDependsOnRelation(DependentResourceNode node) { @Override public String toString() { - return "DependentResourceNode{" + - "dependentResource=" + dependentResource + - '}'; + return "{" + + parents.stream().map(p -> p.dependentResource.toString()) + .collect(Collectors.joining(", ", "[", "]->")) + + "(" + dependentResource + ")" + + dependsOn.stream().map(d -> d.dependentResource.toString()) + .collect(Collectors.joining(", ", "->[", "]")) + + '}'; } public DependentResourceNode setReconcileCondition(