From 78105520b7604e0a4e068843d0cac9ab23efb922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Wed, 24 Apr 2024 12:52:29 +0200 Subject: [PATCH 1/2] feat: silent exception handling in managed workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../api/config/BaseConfigurationService.java | 6 +++ .../api/config/workflow/WorkflowSpec.java | 2 + .../operator/api/reconciler/Workflow.java | 9 ++++ .../operator/processing/Controller.java | 14 +++--- .../workflow/DefaultManagedWorkflow.java | 4 +- .../dependent/workflow/WorkflowResult.java | 1 - .../workflow/ManagedWorkflowTest.java | 5 ++ .../WorkflowSilentExceptionHandlingIT.java | 46 +++++++++++++++++++ .../ConfigMapDependent.java | 27 +++++++++++ ...SilentExceptionHandlingCustomResource.java | 15 ++++++ ...flowSilentExceptionHandlingReconciler.java | 44 ++++++++++++++++++ 11 files changed, 164 insertions(+), 9 deletions(-) create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowSilentExceptionHandlingIT.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/ConfigMapDependent.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/WorkflowSilentExceptionHandlingCustomResource.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/WorkflowSilentExceptionHandlingReconciler.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java index 488f3dc9ce..f4e8b0d775 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java @@ -178,6 +178,12 @@ public List getDependentResourceSpecs() { public boolean isExplicitInvocation() { return workflowAnnotation.explicitInvocation(); } + + @Override + public boolean silentExceptionHandling() { + return workflowAnnotation.silentExceptionHandling(); + } + }; config.setWorkflowSpec(workflowSpec); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/workflow/WorkflowSpec.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/workflow/WorkflowSpec.java index 1b1c9da668..d1fc58c33a 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/workflow/WorkflowSpec.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/workflow/WorkflowSpec.java @@ -10,4 +10,6 @@ public interface WorkflowSpec { List getDependentResourceSpecs(); boolean isExplicitInvocation(); + + boolean silentExceptionHandling(); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Workflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Workflow.java index 04a5b21606..6010fab820 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Workflow.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Workflow.java @@ -19,4 +19,13 @@ */ boolean explicitInvocation() default false; + /** + * if true and an exception(s) is thrown in the managed workflow, when the workflow reconciliation + * finished the controller won't throw an exception, instead will call the reconcile method where + * the reconcile result is accessible through + * {@link Context#managedWorkflowAndDependentResourceContext()}. If false, the exception is thrown + * by the controller before executing the reconcile method. + */ + boolean silentExceptionHandling() default false; + } 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 c0bed7acdb..0371cfcac6 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 @@ -72,6 +72,7 @@ public class Controller

private final boolean isCleaner; private final Metrics metrics; private final Workflow

managedWorkflow; + private final boolean explicitWorkflowInvocation; private final GroupVersionKind associatedGVK; private final EventProcessor

eventProcessor; @@ -94,6 +95,9 @@ public Controller(Reconciler

reconciler, final var managed = configurationService.getWorkflowFactory().workflowFor(configuration); managedWorkflow = managed.resolve(kubernetesClient, configuration); + explicitWorkflowInvocation = + configuration.getWorkflowSpec().map(WorkflowSpec::isExplicitInvocation) + .orElse(false); eventSourceManager = new EventSourceManager<>(this); eventProcessor = new EventProcessor<>(eventSourceManager, configurationService); @@ -145,7 +149,7 @@ public Map metadata() { public UpdateControl

execute() throws Exception { initContextIfNeeded(resource, context); configuration.getWorkflowSpec().ifPresent(ws -> { - if (!isWorkflowExplicitInvocation()) { + if (!explicitWorkflowInvocation) { reconcileManagedWorkflow(resource, context); } }); @@ -191,7 +195,7 @@ public DeleteControl execute() { // The cleanup is called also when explicit invocation is true, but the cleaner is not // implemented - if (!isCleaner || !isWorkflowExplicitInvocation()) { + if (!isCleaner || !explicitWorkflowInvocation) { workflowCleanupResult = cleanupManagedWorkflow(resource, context); } @@ -449,7 +453,6 @@ public void reconcileManagedWorkflow(P primary, Context

context) { ((DefaultManagedWorkflowAndDependentResourceContext) context .managedWorkflowAndDependentResourceContext()) .setWorkflowExecutionResult(res); - res.throwAggregateExceptionIfErrorsPresent(); } } @@ -459,7 +462,7 @@ public WorkflowCleanupResult cleanupManagedWorkflow(P resource, Context

conte ((DefaultManagedWorkflowAndDependentResourceContext) context .managedWorkflowAndDependentResourceContext()) .setWorkflowCleanupResult(workflowCleanupResult); - workflowCleanupResult.throwAggregateExceptionIfErrorsPresent(); + return workflowCleanupResult; } else { return null; @@ -467,7 +470,6 @@ public WorkflowCleanupResult cleanupManagedWorkflow(P resource, Context

conte } public boolean isWorkflowExplicitInvocation() { - return configuration.getWorkflowSpec().map(WorkflowSpec::isExplicitInvocation) - .orElse(false); + return explicitWorkflowInvocation; } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultManagedWorkflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultManagedWorkflow.java index fb0b733c32..acec9353d2 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultManagedWorkflow.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultManagedWorkflow.java @@ -16,7 +16,6 @@ import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.KubernetesClientAware; import static io.javaoperatorsdk.operator.api.reconciler.Constants.NO_VALUE_SET; -import static io.javaoperatorsdk.operator.processing.dependent.workflow.Workflow.THROW_EXCEPTION_AUTOMATICALLY_DEFAULT; @SuppressWarnings("rawtypes") public class DefaultManagedWorkflow

implements ManagedWorkflow

{ @@ -96,7 +95,8 @@ public Workflow

resolve(KubernetesClient client, final var top = topLevelResources.stream().map(alreadyResolved::get).collect(Collectors.toSet()); return new DefaultWorkflow<>(alreadyResolved, bottom, top, - THROW_EXCEPTION_AUTOMATICALLY_DEFAULT, hasCleaner); + configuration.getWorkflowSpec().map(w -> !w.silentExceptionHandling()).orElseThrow(), + hasCleaner); } @SuppressWarnings({"rawtypes", "unchecked"}) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowResult.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowResult.java index 75366925bd..e282c70527 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowResult.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowResult.java @@ -10,7 +10,6 @@ @SuppressWarnings("rawtypes") class WorkflowResult { - private static final String NUMBER_DELIMITER = "_"; private final Map erroredDependents; WorkflowResult(Map erroredDependents) { diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowTest.java index e618e44e46..e677a33339 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowTest.java @@ -73,6 +73,11 @@ public List getDependentResourceSpecs() { public boolean isExplicitInvocation() { return false; } + + @Override + public boolean silentExceptionHandling() { + return false; + } }; when(configuration.getWorkflowSpec()).thenReturn(Optional.of(ws)); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowSilentExceptionHandlingIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowSilentExceptionHandlingIT.java new file mode 100644 index 0000000000..f10d66edc1 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowSilentExceptionHandlingIT.java @@ -0,0 +1,46 @@ +package io.javaoperatorsdk.operator; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; +import io.javaoperatorsdk.operator.sample.workflowsilentexceptionhandling.WorkflowSilentExceptionHandlingCustomResource; +import io.javaoperatorsdk.operator.sample.workflowsilentexceptionhandling.WorkflowSilentExceptionHandlingReconciler; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +public class WorkflowSilentExceptionHandlingIT { + + @RegisterExtension + LocallyRunOperatorExtension extension = + LocallyRunOperatorExtension.builder() + .withReconciler(WorkflowSilentExceptionHandlingReconciler.class) + .build(); + + @Test + void silentExceptionHandling() { + extension.create(testResource()); + var reconciler = extension.getReconcilerOfType(WorkflowSilentExceptionHandlingReconciler.class); + + await().untilAsserted(() -> { + assertThat(reconciler.isErrorsFoundInReconcilerResult()).isTrue(); + }); + + extension.delete(testResource()); + + await().untilAsserted(() -> { + assertThat(reconciler.isErrorsFoundInCleanupResult()).isTrue(); + }); + } + + WorkflowSilentExceptionHandlingCustomResource testResource() { + var res = new WorkflowSilentExceptionHandlingCustomResource(); + res.setMetadata(new ObjectMetaBuilder() + .withName("test1") + .build()); + return res; + } + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/ConfigMapDependent.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/ConfigMapDependent.java new file mode 100644 index 0000000000..508f588543 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/ConfigMapDependent.java @@ -0,0 +1,27 @@ +package io.javaoperatorsdk.operator.sample.workflowsilentexceptionhandling; + + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDNoGCKubernetesDependentResource; + +public class ConfigMapDependent extends + CRUDNoGCKubernetesDependentResource { + + public ConfigMapDependent() { + super(ConfigMap.class); + } + + @Override + public ReconcileResult reconcile(WorkflowSilentExceptionHandlingCustomResource primary, + Context context) { + throw new RuntimeException("Exception thrown on purpose"); + } + + @Override + public void delete(WorkflowSilentExceptionHandlingCustomResource primary, + Context context) { + throw new RuntimeException("Exception thrown on purpose"); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/WorkflowSilentExceptionHandlingCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/WorkflowSilentExceptionHandlingCustomResource.java new file mode 100644 index 0000000000..81ffa6141e --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/WorkflowSilentExceptionHandlingCustomResource.java @@ -0,0 +1,15 @@ +package io.javaoperatorsdk.operator.sample.workflowsilentexceptionhandling; + +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("wse") +public class WorkflowSilentExceptionHandlingCustomResource + extends CustomResource + implements Namespaced { +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/WorkflowSilentExceptionHandlingReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/WorkflowSilentExceptionHandlingReconciler.java new file mode 100644 index 0000000000..abd855e508 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/WorkflowSilentExceptionHandlingReconciler.java @@ -0,0 +1,44 @@ +package io.javaoperatorsdk.operator.sample.workflowsilentexceptionhandling; + +import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; + +@Workflow(silentExceptionHandling = true, + dependents = @Dependent(type = ConfigMapDependent.class)) +@ControllerConfiguration +public class WorkflowSilentExceptionHandlingReconciler + implements Reconciler, + Cleaner { + + private volatile boolean errorsFoundInReconcilerResult = false; + private volatile boolean errorsFoundInCleanupResult = false; + + @Override + public UpdateControl reconcile( + WorkflowSilentExceptionHandlingCustomResource resource, + Context context) { + + errorsFoundInReconcilerResult = context.managedWorkflowAndDependentResourceContext() + .getWorkflowReconcileResult().erroredDependentsExist(); + + + return UpdateControl.noUpdate(); + } + + @Override + public DeleteControl cleanup(WorkflowSilentExceptionHandlingCustomResource resource, + Context context) { + + errorsFoundInCleanupResult = context.managedWorkflowAndDependentResourceContext() + .getWorkflowCleanupResult().erroredDependentsExist(); + return DeleteControl.defaultDelete(); + } + + public boolean isErrorsFoundInReconcilerResult() { + return errorsFoundInReconcilerResult; + } + + public boolean isErrorsFoundInCleanupResult() { + return errorsFoundInCleanupResult; + } +} From 3a46a2e13882c02ac314fe718a4b872f113b8947 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Mon, 29 Apr 2024 13:11:49 +0200 Subject: [PATCH 2/2] refactor: rename silentExceptionHandling -> handleExceptionsInReconciler Signed-off-by: Chris Laprun --- .../api/config/BaseConfigurationService.java | 4 +- .../api/config/workflow/WorkflowSpec.java | 2 +- .../operator/api/reconciler/Workflow.java | 30 +++++++---- .../workflow/DefaultManagedWorkflow.java | 2 +- .../dependent/workflow/WorkflowResult.java | 3 +- .../workflow/ManagedWorkflowTest.java | 2 +- .../WorkflowSilentExceptionHandlingIT.java | 15 +++--- .../ConfigMapDependent.java | 11 ++-- ...ExceptionsInReconcilerCustomResource.java} | 4 +- ...kflowExceptionsInReconcilerReconciler.java | 50 +++++++++++++++++++ ...flowSilentExceptionHandlingReconciler.java | 44 ---------------- 11 files changed, 93 insertions(+), 74 deletions(-) rename operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/{WorkflowSilentExceptionHandlingCustomResource.java => HandleWorkflowExceptionsInReconcilerCustomResource.java} (84%) create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/HandleWorkflowExceptionsInReconcilerReconciler.java delete mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/WorkflowSilentExceptionHandlingReconciler.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java index f4e8b0d775..b496a9b1b7 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java @@ -180,8 +180,8 @@ public boolean isExplicitInvocation() { } @Override - public boolean silentExceptionHandling() { - return workflowAnnotation.silentExceptionHandling(); + public boolean handleExceptionsInReconciler() { + return workflowAnnotation.handleExceptionsInReconciler(); } }; diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/workflow/WorkflowSpec.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/workflow/WorkflowSpec.java index d1fc58c33a..72d50f8050 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/workflow/WorkflowSpec.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/workflow/WorkflowSpec.java @@ -11,5 +11,5 @@ public interface WorkflowSpec { boolean isExplicitInvocation(); - boolean silentExceptionHandling(); + boolean handleExceptionsInReconciler(); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Workflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Workflow.java index 6010fab820..a9497a9749 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Workflow.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Workflow.java @@ -1,6 +1,10 @@ package io.javaoperatorsdk.operator.api.reconciler; -import java.lang.annotation.*; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; @@ -13,19 +17,25 @@ Dependent[] dependents(); /** - * If true, managed workflow should be explicitly invoked within the reconciler implementation. If - * false workflow is invoked just before the {@link Reconciler#reconcile(HasMetadata, Context)} - * method. + * If {@code true}, the managed workflow should be explicitly invoked within the reconciler + * implementation. If {@code false}, the workflow is invoked just before the + * {@link Reconciler#reconcile(HasMetadata, Context)} method. */ boolean explicitInvocation() default false; /** - * if true and an exception(s) is thrown in the managed workflow, when the workflow reconciliation - * finished the controller won't throw an exception, instead will call the reconcile method where - * the reconcile result is accessible through - * {@link Context#managedWorkflowAndDependentResourceContext()}. If false, the exception is thrown - * by the controller before executing the reconcile method. + * If {@code true} and exceptions are thrown during the workflow's execution, the reconciler won't + * throw an {@link io.javaoperatorsdk.operator.AggregatedOperatorException} at the end of the + * execution as would normally be the case. Instead, it will proceed to its + * {@link Reconciler#reconcile(HasMetadata, Context)} method as if no error occurred. It is then + * up to the developer to decide how to proceed by retrieving the errored dependents (and their + * associated exception) via + * {@link io.javaoperatorsdk.operator.processing.dependent.workflow.WorkflowResult#erroredDependents}, + * the workflow result itself being accessed from + * {@link Context#managedWorkflowAndDependentResourceContext()}. If {@code false}, an exception + * will be automatically thrown at the end of the workflow execution, presenting an aggregated + * view of what happened. */ - boolean silentExceptionHandling() default false; + boolean handleExceptionsInReconciler() default false; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultManagedWorkflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultManagedWorkflow.java index acec9353d2..03ed24d6df 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultManagedWorkflow.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultManagedWorkflow.java @@ -95,7 +95,7 @@ public Workflow

resolve(KubernetesClient client, final var top = topLevelResources.stream().map(alreadyResolved::get).collect(Collectors.toSet()); return new DefaultWorkflow<>(alreadyResolved, bottom, top, - configuration.getWorkflowSpec().map(w -> !w.silentExceptionHandling()).orElseThrow(), + configuration.getWorkflowSpec().map(w -> !w.handleExceptionsInReconciler()).orElseThrow(), hasCleaner); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowResult.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowResult.java index e282c70527..d442c75c09 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowResult.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowResult.java @@ -1,5 +1,6 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow; +import java.util.Collections; import java.util.Map; import java.util.Map.Entry; import java.util.stream.Collectors; @@ -13,7 +14,7 @@ class WorkflowResult { private final Map erroredDependents; WorkflowResult(Map erroredDependents) { - this.erroredDependents = erroredDependents; + this.erroredDependents = erroredDependents != null ? erroredDependents : Collections.emptyMap(); } public Map getErroredDependents() { diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowTest.java index e677a33339..e634a368d7 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowTest.java @@ -75,7 +75,7 @@ public boolean isExplicitInvocation() { } @Override - public boolean silentExceptionHandling() { + public boolean handleExceptionsInReconciler() { return false; } }; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowSilentExceptionHandlingIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowSilentExceptionHandlingIT.java index f10d66edc1..cd79283585 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowSilentExceptionHandlingIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowSilentExceptionHandlingIT.java @@ -5,8 +5,8 @@ import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; -import io.javaoperatorsdk.operator.sample.workflowsilentexceptionhandling.WorkflowSilentExceptionHandlingCustomResource; -import io.javaoperatorsdk.operator.sample.workflowsilentexceptionhandling.WorkflowSilentExceptionHandlingReconciler; +import io.javaoperatorsdk.operator.sample.workflowsilentexceptionhandling.HandleWorkflowExceptionsInReconcilerCustomResource; +import io.javaoperatorsdk.operator.sample.workflowsilentexceptionhandling.HandleWorkflowExceptionsInReconcilerReconciler; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; @@ -16,13 +16,14 @@ public class WorkflowSilentExceptionHandlingIT { @RegisterExtension LocallyRunOperatorExtension extension = LocallyRunOperatorExtension.builder() - .withReconciler(WorkflowSilentExceptionHandlingReconciler.class) + .withReconciler(HandleWorkflowExceptionsInReconcilerReconciler.class) .build(); @Test - void silentExceptionHandling() { + void handleExceptionsInReconciler() { extension.create(testResource()); - var reconciler = extension.getReconcilerOfType(WorkflowSilentExceptionHandlingReconciler.class); + var reconciler = + extension.getReconcilerOfType(HandleWorkflowExceptionsInReconcilerReconciler.class); await().untilAsserted(() -> { assertThat(reconciler.isErrorsFoundInReconcilerResult()).isTrue(); @@ -35,8 +36,8 @@ void silentExceptionHandling() { }); } - WorkflowSilentExceptionHandlingCustomResource testResource() { - var res = new WorkflowSilentExceptionHandlingCustomResource(); + HandleWorkflowExceptionsInReconcilerCustomResource testResource() { + var res = new HandleWorkflowExceptionsInReconcilerCustomResource(); res.setMetadata(new ObjectMetaBuilder() .withName("test1") .build()); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/ConfigMapDependent.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/ConfigMapDependent.java index 508f588543..a418e8787e 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/ConfigMapDependent.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/ConfigMapDependent.java @@ -7,21 +7,22 @@ import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDNoGCKubernetesDependentResource; public class ConfigMapDependent extends - CRUDNoGCKubernetesDependentResource { + CRUDNoGCKubernetesDependentResource { public ConfigMapDependent() { super(ConfigMap.class); } @Override - public ReconcileResult reconcile(WorkflowSilentExceptionHandlingCustomResource primary, - Context context) { + public ReconcileResult reconcile( + HandleWorkflowExceptionsInReconcilerCustomResource primary, + Context context) { throw new RuntimeException("Exception thrown on purpose"); } @Override - public void delete(WorkflowSilentExceptionHandlingCustomResource primary, - Context context) { + public void delete(HandleWorkflowExceptionsInReconcilerCustomResource primary, + Context context) { throw new RuntimeException("Exception thrown on purpose"); } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/WorkflowSilentExceptionHandlingCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/HandleWorkflowExceptionsInReconcilerCustomResource.java similarity index 84% rename from operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/WorkflowSilentExceptionHandlingCustomResource.java rename to operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/HandleWorkflowExceptionsInReconcilerCustomResource.java index 81ffa6141e..3d4283e182 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/WorkflowSilentExceptionHandlingCustomResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/HandleWorkflowExceptionsInReconcilerCustomResource.java @@ -8,8 +8,8 @@ @Group("sample.javaoperatorsdk") @Version("v1") -@ShortNames("wse") -public class WorkflowSilentExceptionHandlingCustomResource +@ShortNames("hweir") +public class HandleWorkflowExceptionsInReconcilerCustomResource extends CustomResource implements Namespaced { } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/HandleWorkflowExceptionsInReconcilerReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/HandleWorkflowExceptionsInReconcilerReconciler.java new file mode 100644 index 0000000000..2519ccfe8d --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/HandleWorkflowExceptionsInReconcilerReconciler.java @@ -0,0 +1,50 @@ +package io.javaoperatorsdk.operator.sample.workflowsilentexceptionhandling; + +import io.javaoperatorsdk.operator.api.reconciler.Cleaner; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.DeleteControl; +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; +import io.javaoperatorsdk.operator.api.reconciler.Workflow; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; + +@Workflow(handleExceptionsInReconciler = true, + dependents = @Dependent(type = ConfigMapDependent.class)) +@ControllerConfiguration +public class HandleWorkflowExceptionsInReconcilerReconciler + implements Reconciler, + Cleaner { + + private volatile boolean errorsFoundInReconcilerResult = false; + private volatile boolean errorsFoundInCleanupResult = false; + + @Override + public UpdateControl reconcile( + HandleWorkflowExceptionsInReconcilerCustomResource resource, + Context context) { + + errorsFoundInReconcilerResult = context.managedWorkflowAndDependentResourceContext() + .getWorkflowReconcileResult().erroredDependentsExist(); + + + return UpdateControl.noUpdate(); + } + + @Override + public DeleteControl cleanup(HandleWorkflowExceptionsInReconcilerCustomResource resource, + Context context) { + + errorsFoundInCleanupResult = context.managedWorkflowAndDependentResourceContext() + .getWorkflowCleanupResult().erroredDependentsExist(); + return DeleteControl.defaultDelete(); + } + + public boolean isErrorsFoundInReconcilerResult() { + return errorsFoundInReconcilerResult; + } + + public boolean isErrorsFoundInCleanupResult() { + return errorsFoundInCleanupResult; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/WorkflowSilentExceptionHandlingReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/WorkflowSilentExceptionHandlingReconciler.java deleted file mode 100644 index abd855e508..0000000000 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/WorkflowSilentExceptionHandlingReconciler.java +++ /dev/null @@ -1,44 +0,0 @@ -package io.javaoperatorsdk.operator.sample.workflowsilentexceptionhandling; - -import io.javaoperatorsdk.operator.api.reconciler.*; -import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; - -@Workflow(silentExceptionHandling = true, - dependents = @Dependent(type = ConfigMapDependent.class)) -@ControllerConfiguration -public class WorkflowSilentExceptionHandlingReconciler - implements Reconciler, - Cleaner { - - private volatile boolean errorsFoundInReconcilerResult = false; - private volatile boolean errorsFoundInCleanupResult = false; - - @Override - public UpdateControl reconcile( - WorkflowSilentExceptionHandlingCustomResource resource, - Context context) { - - errorsFoundInReconcilerResult = context.managedWorkflowAndDependentResourceContext() - .getWorkflowReconcileResult().erroredDependentsExist(); - - - return UpdateControl.noUpdate(); - } - - @Override - public DeleteControl cleanup(WorkflowSilentExceptionHandlingCustomResource resource, - Context context) { - - errorsFoundInCleanupResult = context.managedWorkflowAndDependentResourceContext() - .getWorkflowCleanupResult().erroredDependentsExist(); - return DeleteControl.defaultDelete(); - } - - public boolean isErrorsFoundInReconcilerResult() { - return errorsFoundInReconcilerResult; - } - - public boolean isErrorsFoundInCleanupResult() { - return errorsFoundInCleanupResult; - } -}