From 0112e6f2a7e1c5d944b3c4f7d602f05c25fb8b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Wed, 13 Mar 2024 17:50:42 +0100 Subject: [PATCH 1/6] feat: explicit workflow invocation 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 | 2 +- .../api/config/workflow/WorkflowSpec.java | 9 ++++++- .../api/reconciler/DefaultContext.java | 5 ++-- .../operator/api/reconciler/Workflow.java | 9 +++++++ ...efaultManagedDependentResourceContext.java | 25 +++++++++++++++++-- .../ManagedDependentResourceContext.java | 3 +++ .../operator/processing/Controller.java | 20 ++++++++++----- .../workflow/ManagedWorkflowTest.java | 2 +- 8 files changed, 62 insertions(+), 13 deletions(-) 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 eab8e61f72..846c16046c 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 @@ -169,7 +169,7 @@ protected

ControllerConfiguration

configFor(Reconcile io.javaoperatorsdk.operator.api.reconciler.Workflow.class); if (workflowAnnotation != null) { List specs = dependentResources(workflowAnnotation, config); - WorkflowSpec workflowSpec = new WorkflowSpec(specs); + WorkflowSpec workflowSpec = new WorkflowSpec(specs, workflowAnnotation.explicitInvocation()); 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 f1eea3c5d3..ab89bb07db 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 @@ -8,12 +8,19 @@ public class WorkflowSpec { @SuppressWarnings("rawtypes") private final List dependentResourceSpecs; + private final boolean explicitInvocation; - public WorkflowSpec(List dependentResourceSpecs) { + public WorkflowSpec(List dependentResourceSpecs, + boolean explicitInvocation) { this.dependentResourceSpecs = dependentResourceSpecs; + this.explicitInvocation = explicitInvocation; } public List getDependentResourceSpecs() { return dependentResourceSpecs; } + + public boolean isExplicitInvocation() { + return explicitInvocation; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java index 633daea6aa..6dd13fe92b 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java @@ -21,14 +21,15 @@ public class DefaultContext

implements Context

{ private final Controller

controller; private final P primaryResource; private final ControllerConfiguration

controllerConfiguration; - private final DefaultManagedDependentResourceContext defaultManagedDependentResourceContext; + private final DefaultManagedDependentResourceContext

defaultManagedDependentResourceContext; public DefaultContext(RetryInfo retryInfo, Controller

controller, P primaryResource) { this.retryInfo = retryInfo; this.controller = controller; this.primaryResource = primaryResource; this.controllerConfiguration = controller.getConfiguration(); - this.defaultManagedDependentResourceContext = new DefaultManagedDependentResourceContext(); + this.defaultManagedDependentResourceContext = + new DefaultManagedDependentResourceContext<>(controller, primaryResource, this); } @Override 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 6726a1d32b..cde26d6ad1 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 @@ -2,6 +2,7 @@ import java.lang.annotation.*; +import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; @Inherited @@ -11,4 +12,12 @@ Dependent[] dependents(); + // todo maybe better naming? "explicitReconciliation" ? + /** + * 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. + */ + boolean explicitInvocation() default false; + } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DefaultManagedDependentResourceContext.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DefaultManagedDependentResourceContext.java index d6fa5c7b32..ebd9a50f97 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DefaultManagedDependentResourceContext.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DefaultManagedDependentResourceContext.java @@ -3,15 +3,30 @@ import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.Controller; import io.javaoperatorsdk.operator.processing.dependent.workflow.WorkflowCleanupResult; import io.javaoperatorsdk.operator.processing.dependent.workflow.WorkflowReconcileResult; @SuppressWarnings("rawtypes") -public class DefaultManagedDependentResourceContext implements ManagedDependentResourceContext { +public class DefaultManagedDependentResourceContext

+ implements ManagedDependentResourceContext { + private final ConcurrentHashMap attributes = new ConcurrentHashMap(); + private final Controller

controller; + private final P primaryResource; + private final Context

context; private WorkflowReconcileResult workflowReconcileResult; private WorkflowCleanupResult workflowCleanupResult; - private final ConcurrentHashMap attributes = new ConcurrentHashMap(); + + public DefaultManagedDependentResourceContext(Controller

controller, + P primaryResource, + Context

context) { + this.controller = controller; + this.primaryResource = primaryResource; + this.context = context; + } @Override public Optional get(Object key, Class expectedType) { @@ -58,4 +73,10 @@ public WorkflowReconcileResult getWorkflowReconcileResult() { public WorkflowCleanupResult getWorkflowCleanupResult() { return workflowCleanupResult; } + + @Override + public void invokeWorkflow() { + controller.invokeManagedWorkflow(primaryResource, context); + } + } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedDependentResourceContext.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedDependentResourceContext.java index 47534cc30d..74c6821b29 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedDependentResourceContext.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedDependentResourceContext.java @@ -55,4 +55,7 @@ public interface ManagedDependentResourceContext { WorkflowReconcileResult getWorkflowReconcileResult(); WorkflowCleanupResult getWorkflowCleanupResult(); + + void invokeWorkflow(); + } 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 78b3a64043..f0e7ff603b 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 @@ -130,12 +130,11 @@ public Map metadata() { @Override public UpdateControl

execute() throws Exception { initContextIfNeeded(resource, context); - if (!managedWorkflow.isEmpty()) { - var res = managedWorkflow.reconcile(resource, context); - ((DefaultManagedDependentResourceContext) context.managedDependentResourceContext()) - .setWorkflowExecutionResult(res); - res.throwAggregateExceptionIfErrorsPresent(); - } + configuration.getWorkflowSpec().ifPresent(ws -> { + if (!ws.isExplicitInvocation()) { + invokeManagedWorkflow(resource, context); + } + }); return reconciler.reconcile(resource, context); } }); @@ -429,4 +428,13 @@ public ExecutorServiceManager getExecutorServiceManager() { public EventSourceContext

eventSourceContext() { return eventSourceContext; } + + public void invokeManagedWorkflow(P primary, Context

context) { + if (!managedWorkflow.isEmpty()) { + var res = managedWorkflow.reconcile(primary, context); + ((DefaultManagedDependentResourceContext) context.managedDependentResourceContext()) + .setWorkflowExecutionResult(res); + res.throwAggregateExceptionIfErrorsPresent(); + } + } } 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 5327439a31..fc254aa217 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 @@ -64,7 +64,7 @@ ManagedWorkflow managedWorkflow(DependentResourceSpec... specs) { final var configuration = mock(ControllerConfiguration.class); final var specList = List.of(specs); - var ws = new WorkflowSpec(specList); + var ws = new WorkflowSpec(specList, false); when(configuration.getWorkflowSpec()).thenReturn(Optional.of(ws)); return new BaseConfigurationService().getWorkflowFactory() From 12f5d63be6b3faa5dda38cd8586c1d34cb3a0e46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 15 Mar 2024 11:39:01 +0100 Subject: [PATCH 2/6] IT, docs, impl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- docs/documentation/workflows.md | 34 ++++++++++ .../operator/api/reconciler/Context.java | 4 +- .../api/reconciler/DefaultContext.java | 10 +-- .../operator/api/reconciler/Workflow.java | 1 - ...dWorkflowAndDependentResourceContext.java} | 25 +++++-- ...dWorkflowAndDependentResourceContext.java} | 6 +- .../operator/processing/Controller.java | 42 +++++++++--- .../operator/WorkflowExplicitCleanupIT.java | 50 ++++++++++++++ .../WorkflowExplicitInvocationIT.java | 66 +++++++++++++++++++ ...DependentWithReadyConditionReconciler.java | 2 +- .../ComplexDependentReconciler.java | 2 +- .../WorkflowAllFeatureReconciler.java | 2 +- .../ConfigMapDependent.java | 29 ++++++++ ...WorkflowExplicitCleanupCustomResource.java | 15 +++++ .../WorkflowExplicitCleanupReconciler.java | 32 +++++++++ .../WorkflowExplicitCleanupSpec.java | 15 +++++ .../ConfigMapDependent.java | 29 ++++++++ ...kflowExplicitInvocationCustomResource.java | 15 +++++ .../WorkflowExplicitInvocationReconciler.java | 39 +++++++++++ .../WorkflowExplicitInvocationSpec.java | 15 +++++ 20 files changed, 403 insertions(+), 30 deletions(-) rename operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/{DefaultManagedDependentResourceContext.java => DefaultManagedWorkflowAndDependentResourceContext.java} (71%) rename operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/{ManagedDependentResourceContext.java => ManagedWorkflowAndDependentResourceContext.java} (95%) create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowExplicitCleanupIT.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowExplicitInvocationIT.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/ConfigMapDependent.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/WorkflowExplicitCleanupCustomResource.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/WorkflowExplicitCleanupReconciler.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/WorkflowExplicitCleanupSpec.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/ConfigMapDependent.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/WorkflowExplicitInvocationCustomResource.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/WorkflowExplicitInvocationReconciler.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/WorkflowExplicitInvocationSpec.java diff --git a/docs/documentation/workflows.md b/docs/documentation/workflows.md index d0d7e5b2c7..a10e2d2c2d 100644 --- a/docs/documentation/workflows.md +++ b/docs/documentation/workflows.md @@ -338,6 +338,40 @@ and NOT `CRUDKubernetesDependentResource` since otherwise the Kubernetes Garbage In other words if a Kubernetes Dependent Resource depends on another dependent resource, it should not implement `GargageCollected` interface, otherwise the deletion order won't be guaranteed. + +## Explicit Managed Workflow Invocation + +Managed workflow is execution before the `reconcile(Resource,Context)` is called. There are certain situations however +when before the invocation there is additional validation or other logic needs to be executed or in some cases +event the workflow execution skipped. For this reason managed workflow explicit invocation is possible. + +```java + +@Workflow(explicitInvocation = true, + dependents = @Dependent(type = ConfigMapDependent.class)) +@ControllerConfiguration +public class WorkflowExplicitInvocationReconciler + implements Reconciler { + + @Override + public UpdateControl reconcile( + WorkflowExplicitInvocationCustomResource resource, + Context context) { + + // additional logic before the workflow is executed + + context.managedWorkflowAndDependentResourceContext().reconcileManagedWorkflow(); + + return UpdateControl.noUpdate(); + } + +``` + +For `cleanup`, if the `Cleaner` interface is implemented, the `cleanupManageWorkflow()` needs to be called explicitly. +However, if `Cleaner` interface is not implemented, it will be called implicitly. + +Nothing prevents calling the workflow multiple times in a reconciler, however it is meant to be called at most once. + ## Notes and Caveats - Delete is almost always called on every resource during the cleanup. However, it might be the case diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java index 78592495ad..375d2035ad 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java @@ -8,7 +8,7 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.client.KubernetesClient; import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; -import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.ManagedDependentResourceContext; +import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.ManagedWorkflowAndDependentResourceContext; import io.javaoperatorsdk.operator.processing.event.EventSourceRetriever; import io.javaoperatorsdk.operator.processing.event.source.IndexerResourceCache; @@ -34,7 +34,7 @@ Optional getSecondaryResource(Class expectedType, ControllerConfiguration

getControllerConfiguration(); - ManagedDependentResourceContext managedDependentResourceContext(); + ManagedWorkflowAndDependentResourceContext managedWorkflowAndDependentResourceContext(); EventSourceRetriever

eventSourceRetriever(); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java index 6dd13fe92b..9ff7ddd7a3 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java @@ -9,8 +9,8 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.client.KubernetesClient; import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; -import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DefaultManagedDependentResourceContext; -import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.ManagedDependentResourceContext; +import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DefaultManagedWorkflowAndDependentResourceContext; +import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.ManagedWorkflowAndDependentResourceContext; import io.javaoperatorsdk.operator.processing.Controller; import io.javaoperatorsdk.operator.processing.event.EventSourceRetriever; import io.javaoperatorsdk.operator.processing.event.ResourceID; @@ -21,7 +21,7 @@ public class DefaultContext

implements Context

{ private final Controller

controller; private final P primaryResource; private final ControllerConfiguration

controllerConfiguration; - private final DefaultManagedDependentResourceContext

defaultManagedDependentResourceContext; + private final DefaultManagedWorkflowAndDependentResourceContext

defaultManagedDependentResourceContext; public DefaultContext(RetryInfo retryInfo, Controller

controller, P primaryResource) { this.retryInfo = retryInfo; @@ -29,7 +29,7 @@ public DefaultContext(RetryInfo retryInfo, Controller

controller, P primaryRe this.primaryResource = primaryResource; this.controllerConfiguration = controller.getConfiguration(); this.defaultManagedDependentResourceContext = - new DefaultManagedDependentResourceContext<>(controller, primaryResource, this); + new DefaultManagedWorkflowAndDependentResourceContext<>(controller, primaryResource, this); } @Override @@ -80,7 +80,7 @@ public ControllerConfiguration

getControllerConfiguration() { } @Override - public ManagedDependentResourceContext managedDependentResourceContext() { + public ManagedWorkflowAndDependentResourceContext managedWorkflowAndDependentResourceContext() { return defaultManagedDependentResourceContext; } 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 cde26d6ad1..04a5b21606 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 @@ -12,7 +12,6 @@ Dependent[] dependents(); - // todo maybe better naming? "explicitReconciliation" ? /** * 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)} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DefaultManagedDependentResourceContext.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DefaultManagedWorkflowAndDependentResourceContext.java similarity index 71% rename from operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DefaultManagedDependentResourceContext.java rename to operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DefaultManagedWorkflowAndDependentResourceContext.java index ebd9a50f97..f93f45ecf7 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DefaultManagedDependentResourceContext.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DefaultManagedWorkflowAndDependentResourceContext.java @@ -10,8 +10,8 @@ import io.javaoperatorsdk.operator.processing.dependent.workflow.WorkflowReconcileResult; @SuppressWarnings("rawtypes") -public class DefaultManagedDependentResourceContext

- implements ManagedDependentResourceContext { +public class DefaultManagedWorkflowAndDependentResourceContext

+ implements ManagedWorkflowAndDependentResourceContext { private final ConcurrentHashMap attributes = new ConcurrentHashMap(); private final Controller

controller; @@ -20,7 +20,7 @@ public class DefaultManagedDependentResourceContext

private WorkflowReconcileResult workflowReconcileResult; private WorkflowCleanupResult workflowCleanupResult; - public DefaultManagedDependentResourceContext(Controller

controller, + public DefaultManagedWorkflowAndDependentResourceContext(Controller

controller, P primaryResource, Context

context) { this.controller = controller; @@ -52,13 +52,13 @@ public T getMandatory(Object key, Class expectedType) { + ") is missing or not of the expected type")); } - public DefaultManagedDependentResourceContext setWorkflowExecutionResult( + public DefaultManagedWorkflowAndDependentResourceContext setWorkflowExecutionResult( WorkflowReconcileResult workflowReconcileResult) { this.workflowReconcileResult = workflowReconcileResult; return this; } - public DefaultManagedDependentResourceContext setWorkflowCleanupResult( + public DefaultManagedWorkflowAndDependentResourceContext setWorkflowCleanupResult( WorkflowCleanupResult workflowCleanupResult) { this.workflowCleanupResult = workflowCleanupResult; return this; @@ -75,8 +75,19 @@ public WorkflowCleanupResult getWorkflowCleanupResult() { } @Override - public void invokeWorkflow() { - controller.invokeManagedWorkflow(primaryResource, context); + public void reconcileManagedWorkflow() { + if (!controller.isWorkflowExplicitInvocation()) { + throw new IllegalStateException("Workflow explicit invocation is not set."); + } + controller.reconcileManagedWorkflow(primaryResource, context); + } + + @Override + public void cleanupManageWorkflow() { + if (!controller.isWorkflowExplicitInvocation()) { + throw new IllegalStateException("Workflow explicit invocation is not set."); + } + controller.cleanupManagedWorkflow(primaryResource, context); } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedDependentResourceContext.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedWorkflowAndDependentResourceContext.java similarity index 95% rename from operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedDependentResourceContext.java rename to operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedWorkflowAndDependentResourceContext.java index 74c6821b29..929330a9ed 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedDependentResourceContext.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedWorkflowAndDependentResourceContext.java @@ -10,7 +10,7 @@ * Contextual information related to {@link DependentResource} either to retrieve the actual * implementations to interact with them or to pass information between them and/or the reconciler */ -public interface ManagedDependentResourceContext { +public interface ManagedWorkflowAndDependentResourceContext { /** * Retrieve a contextual object, if it exists and is of the specified expected type, associated @@ -56,6 +56,8 @@ public interface ManagedDependentResourceContext { WorkflowCleanupResult getWorkflowCleanupResult(); - void invokeWorkflow(); + void reconcileManagedWorkflow(); + + void cleanupManageWorkflow(); } 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 f0e7ff603b..37e34efdc6 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 @@ -18,13 +18,14 @@ import io.javaoperatorsdk.operator.RegisteredController; import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; import io.javaoperatorsdk.operator.api.config.ExecutorServiceManager; +import io.javaoperatorsdk.operator.api.config.workflow.WorkflowSpec; import io.javaoperatorsdk.operator.api.monitoring.Metrics; import io.javaoperatorsdk.operator.api.monitoring.Metrics.ControllerExecution; import io.javaoperatorsdk.operator.api.reconciler.*; import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceNotFoundException; import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceProvider; import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceReferencer; -import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DefaultManagedDependentResourceContext; +import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DefaultManagedWorkflowAndDependentResourceContext; import io.javaoperatorsdk.operator.health.ControllerHealthInfo; import io.javaoperatorsdk.operator.processing.dependent.workflow.Workflow; import io.javaoperatorsdk.operator.processing.dependent.workflow.WorkflowCleanupResult; @@ -131,8 +132,8 @@ public Map metadata() { public UpdateControl

execute() throws Exception { initContextIfNeeded(resource, context); configuration.getWorkflowSpec().ifPresent(ws -> { - if (!ws.isExplicitInvocation()) { - invokeManagedWorkflow(resource, context); + if (!isWorkflowExplicitInvocation()) { + reconcileManagedWorkflow(resource, context); } }); return reconciler.reconcile(resource, context); @@ -174,12 +175,14 @@ public Map metadata() { public DeleteControl execute() { initContextIfNeeded(resource, context); WorkflowCleanupResult workflowCleanupResult = null; - if (managedWorkflow.hasCleaner()) { - workflowCleanupResult = managedWorkflow.cleanup(resource, context); - ((DefaultManagedDependentResourceContext) context.managedDependentResourceContext()) - .setWorkflowCleanupResult(workflowCleanupResult); - workflowCleanupResult.throwAggregateExceptionIfErrorsPresent(); + + // The cleanup is called also when explicit invocation is true, but the cleaner is not + // implemented + if (!isCleaner + || !isWorkflowExplicitInvocation()) { + workflowCleanupResult = cleanupManagedWorkflow(resource, context); } + if (isCleaner) { var cleanupResult = ((Cleaner

) reconciler).cleanup(resource, context); if (!cleanupResult.isRemoveFinalizer()) { @@ -429,12 +432,31 @@ public EventSourceContext

eventSourceContext() { return eventSourceContext; } - public void invokeManagedWorkflow(P primary, Context

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

context) { if (!managedWorkflow.isEmpty()) { var res = managedWorkflow.reconcile(primary, context); - ((DefaultManagedDependentResourceContext) context.managedDependentResourceContext()) + ((DefaultManagedWorkflowAndDependentResourceContext) context + .managedWorkflowAndDependentResourceContext()) .setWorkflowExecutionResult(res); res.throwAggregateExceptionIfErrorsPresent(); } } + + public WorkflowCleanupResult cleanupManagedWorkflow(P resource, Context

context) { + if (managedWorkflow.hasCleaner()) { + var workflowCleanupResult = managedWorkflow.cleanup(resource, context); + ((DefaultManagedWorkflowAndDependentResourceContext) context + .managedWorkflowAndDependentResourceContext()) + .setWorkflowCleanupResult(workflowCleanupResult); + workflowCleanupResult.throwAggregateExceptionIfErrorsPresent(); + return workflowCleanupResult; + } else { + return null; + } + } + + public boolean isWorkflowExplicitInvocation() { + return configuration.getWorkflowSpec().map(WorkflowSpec::isExplicitInvocation) + .orElse(false); + } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowExplicitCleanupIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowExplicitCleanupIT.java new file mode 100644 index 0000000000..b26bfdd443 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowExplicitCleanupIT.java @@ -0,0 +1,50 @@ +package io.javaoperatorsdk.operator; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; +import io.javaoperatorsdk.operator.sample.workflowexplicitcleanup.WorkflowExplicitCleanupCustomResource; +import io.javaoperatorsdk.operator.sample.workflowexplicitcleanup.WorkflowExplicitCleanupReconciler; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +public class WorkflowExplicitCleanupIT { + + public static final String RESOURCE_NAME = "test1"; + + @RegisterExtension + LocallyRunOperatorExtension extension = + LocallyRunOperatorExtension.builder() + .withReconciler(WorkflowExplicitCleanupReconciler.class) + .build(); + + @Test + void workflowInvokedExplicitly() { + var res = extension.create(testResource()); + + await().untilAsserted(() -> { + assertThat(extension.get(ConfigMap.class, RESOURCE_NAME)).isNotNull(); + }); + + extension.delete(res); + + // The ConfigMap is not garbage collected, this tests that even if the cleaner is not + // implemented the workflow cleanup still called even if there is explicit invocation + await().untilAsserted(() -> { + assertThat(extension.get(ConfigMap.class, RESOURCE_NAME)).isNull(); + }); + } + + WorkflowExplicitCleanupCustomResource testResource() { + var res = new WorkflowExplicitCleanupCustomResource(); + res.setMetadata(new ObjectMetaBuilder() + .withName(RESOURCE_NAME) + .build()); + return res; + } + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowExplicitInvocationIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowExplicitInvocationIT.java new file mode 100644 index 0000000000..dba08faba0 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowExplicitInvocationIT.java @@ -0,0 +1,66 @@ +package io.javaoperatorsdk.operator; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; +import io.javaoperatorsdk.operator.sample.workflowexplicitinvocation.WorkflowExplicitInvocationCustomResource; +import io.javaoperatorsdk.operator.sample.workflowexplicitinvocation.WorkflowExplicitInvocationReconciler; +import io.javaoperatorsdk.operator.sample.workflowexplicitinvocation.WorkflowExplicitInvocationSpec; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +public class WorkflowExplicitInvocationIT { + + public static final String RESOURCE_NAME = "test1"; + + @RegisterExtension + LocallyRunOperatorExtension extension = + LocallyRunOperatorExtension.builder() + .withReconciler(WorkflowExplicitInvocationReconciler.class) + .build(); + + @Test + void workflowInvokedExplicitly() { + var res = extension.create(testResource()); + var reconciler = extension.getReconcilerOfType(WorkflowExplicitInvocationReconciler.class); + + await().untilAsserted(() -> { + assertThat(reconciler.getNumberOfExecutions()).isEqualTo(1); + assertThat(extension.get(ConfigMap.class, RESOURCE_NAME)).isNull(); + }); + + reconciler.setInvokeWorkflow(true); + + // trigger reconciliation + res.getSpec().setValue("changed value"); + res = extension.replace(res); + + await().untilAsserted(() -> { + assertThat(reconciler.getNumberOfExecutions()).isEqualTo(2); + assertThat(extension.get(ConfigMap.class, RESOURCE_NAME)).isNotNull(); + }); + + extension.delete(res); + + // The ConfigMap is not garbage collected, this tests that even if the cleaner is not + // implemented the workflow cleanup still called even if there is explicit invocation + await().untilAsserted(() -> { + assertThat(extension.get(ConfigMap.class, RESOURCE_NAME)).isNull(); + }); + } + + WorkflowExplicitInvocationCustomResource testResource() { + var res = new WorkflowExplicitInvocationCustomResource(); + res.setMetadata(new ObjectMetaBuilder() + .withName(RESOURCE_NAME) + .build()); + res.setSpec(new WorkflowExplicitInvocationSpec()); + res.getSpec().setValue("initial value"); + return res; + } + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ManagedBulkDependentWithReadyConditionReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ManagedBulkDependentWithReadyConditionReconciler.java index 8da3ba944f..aca1e98c88 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ManagedBulkDependentWithReadyConditionReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ManagedBulkDependentWithReadyConditionReconciler.java @@ -19,7 +19,7 @@ public UpdateControl reconcile( Context context) throws Exception { numberOfExecutions.incrementAndGet(); - var ready = context.managedDependentResourceContext().getWorkflowReconcileResult() + var ready = context.managedWorkflowAndDependentResourceContext().getWorkflowReconcileResult() .allDependentResourcesReady(); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/ComplexDependentReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/ComplexDependentReconciler.java index e8fa40c63e..81bcb7e153 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/ComplexDependentReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/ComplexDependentReconciler.java @@ -40,7 +40,7 @@ public class ComplexDependentReconciler implements Reconciler reconcile( ComplexDependentCustomResource resource, Context context) throws Exception { - var ready = context.managedDependentResourceContext().getWorkflowReconcileResult() + var ready = context.managedWorkflowAndDependentResourceContext().getWorkflowReconcileResult() .allDependentResourcesReady(); var status = Objects.requireNonNullElseGet(resource.getStatus(), ComplexDependentStatus::new); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowallfeature/WorkflowAllFeatureReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowallfeature/WorkflowAllFeatureReconciler.java index 1fadcdad66..f5c14d6f96 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowallfeature/WorkflowAllFeatureReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowallfeature/WorkflowAllFeatureReconciler.java @@ -35,7 +35,7 @@ public UpdateControl reconcile( } resource.getStatus() .setReady( - context.managedDependentResourceContext() + context.managedWorkflowAndDependentResourceContext() .getWorkflowReconcileResult() .allDependentResourcesReady()); return UpdateControl.patchStatus(resource); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/ConfigMapDependent.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/ConfigMapDependent.java new file mode 100644 index 0000000000..1c0c9b4e9e --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/ConfigMapDependent.java @@ -0,0 +1,29 @@ +package io.javaoperatorsdk.operator.sample.workflowexplicitcleanup; + +import java.util.Map; + +import io.fabric8.kubernetes.api.model.ConfigMap; +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.CRUDKubernetesDependentResource; + +public class ConfigMapDependent extends + CRUDKubernetesDependentResource { + + public ConfigMapDependent() { + super(ConfigMap.class); + } + + @Override + protected ConfigMap desired(WorkflowExplicitCleanupCustomResource primary, + Context context) { + return new ConfigMapBuilder() + .withMetadata(new ObjectMetaBuilder() + .withName(primary.getMetadata().getName()) + .withNamespace(primary.getMetadata().getNamespace()) + .build()) + .withData(Map.of("key", "val")) + .build(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/WorkflowExplicitCleanupCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/WorkflowExplicitCleanupCustomResource.java new file mode 100644 index 0000000000..b2057a54dd --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/WorkflowExplicitCleanupCustomResource.java @@ -0,0 +1,15 @@ +package io.javaoperatorsdk.operator.sample.workflowexplicitcleanup; + +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("wec") +public class WorkflowExplicitCleanupCustomResource + extends CustomResource + implements Namespaced { +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/WorkflowExplicitCleanupReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/WorkflowExplicitCleanupReconciler.java new file mode 100644 index 0000000000..128bb9629c --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/WorkflowExplicitCleanupReconciler.java @@ -0,0 +1,32 @@ +package io.javaoperatorsdk.operator.sample.workflowexplicitcleanup; + +import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; + +@Workflow(explicitInvocation = true, + dependents = @Dependent(type = ConfigMapDependent.class)) +@ControllerConfiguration +public class WorkflowExplicitCleanupReconciler + implements Reconciler, + Cleaner { + + @Override + public UpdateControl reconcile( + WorkflowExplicitCleanupCustomResource resource, + Context context) { + + context.managedWorkflowAndDependentResourceContext().reconcileManagedWorkflow(); + + return UpdateControl.noUpdate(); + } + + @Override + public DeleteControl cleanup(WorkflowExplicitCleanupCustomResource resource, + Context context) { + + context.managedWorkflowAndDependentResourceContext().cleanupManageWorkflow(); + // this can be checked + // context.managedWorkflowAndDependentResourceContext().getWorkflowCleanupResult() + return DeleteControl.defaultDelete(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/WorkflowExplicitCleanupSpec.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/WorkflowExplicitCleanupSpec.java new file mode 100644 index 0000000000..d8da8797f5 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/WorkflowExplicitCleanupSpec.java @@ -0,0 +1,15 @@ +package io.javaoperatorsdk.operator.sample.workflowexplicitcleanup; + +public class WorkflowExplicitCleanupSpec { + + private String value; + + public String getValue() { + return value; + } + + public WorkflowExplicitCleanupSpec setValue(String value) { + this.value = value; + return this; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/ConfigMapDependent.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/ConfigMapDependent.java new file mode 100644 index 0000000000..fe940efc5a --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/ConfigMapDependent.java @@ -0,0 +1,29 @@ +package io.javaoperatorsdk.operator.sample.workflowexplicitinvocation; + +import java.util.Map; + +import io.fabric8.kubernetes.api.model.ConfigMap; +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.CRUDKubernetesDependentResource; + +public class ConfigMapDependent extends + CRUDKubernetesDependentResource { + + public ConfigMapDependent() { + super(ConfigMap.class); + } + + @Override + protected ConfigMap desired(WorkflowExplicitInvocationCustomResource primary, + Context context) { + return new ConfigMapBuilder() + .withMetadata(new ObjectMetaBuilder() + .withName(primary.getMetadata().getName()) + .withNamespace(primary.getMetadata().getNamespace()) + .build()) + .withData(Map.of("key", primary.getSpec().getValue())) + .build(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/WorkflowExplicitInvocationCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/WorkflowExplicitInvocationCustomResource.java new file mode 100644 index 0000000000..827a17ddaf --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/WorkflowExplicitInvocationCustomResource.java @@ -0,0 +1,15 @@ +package io.javaoperatorsdk.operator.sample.workflowexplicitinvocation; + +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("wei") +public class WorkflowExplicitInvocationCustomResource + extends CustomResource + implements Namespaced { +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/WorkflowExplicitInvocationReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/WorkflowExplicitInvocationReconciler.java new file mode 100644 index 0000000000..dc7bce4296 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/WorkflowExplicitInvocationReconciler.java @@ -0,0 +1,39 @@ +package io.javaoperatorsdk.operator.sample.workflowexplicitinvocation; + +import java.util.concurrent.atomic.AtomicInteger; + +import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; + +@Workflow(explicitInvocation = true, + dependents = @Dependent(type = ConfigMapDependent.class)) +@ControllerConfiguration +public class WorkflowExplicitInvocationReconciler + implements Reconciler { + + private final AtomicInteger numberOfExecutions = new AtomicInteger(0); + + private volatile boolean invokeWorkflow = false; + + @Override + public UpdateControl reconcile( + WorkflowExplicitInvocationCustomResource resource, + Context context) { + + numberOfExecutions.addAndGet(1); + if (invokeWorkflow) { + context.managedWorkflowAndDependentResourceContext().reconcileManagedWorkflow(); + } + + + return UpdateControl.noUpdate(); + } + + public int getNumberOfExecutions() { + return numberOfExecutions.get(); + } + + public void setInvokeWorkflow(boolean invokeWorkflow) { + this.invokeWorkflow = invokeWorkflow; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/WorkflowExplicitInvocationSpec.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/WorkflowExplicitInvocationSpec.java new file mode 100644 index 0000000000..2112d348e2 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/WorkflowExplicitInvocationSpec.java @@ -0,0 +1,15 @@ +package io.javaoperatorsdk.operator.sample.workflowexplicitinvocation; + +public class WorkflowExplicitInvocationSpec { + + private String value; + + public String getValue() { + return value; + } + + public WorkflowExplicitInvocationSpec setValue(String value) { + this.value = value; + return this; + } +} From 687ead7db56b3f7b6040b2f65f10b75ffb3f9ca7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 15 Mar 2024 11:41:16 +0100 Subject: [PATCH 3/6] docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- docs/documentation/workflows.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/documentation/workflows.md b/docs/documentation/workflows.md index a10e2d2c2d..995d6bb637 100644 --- a/docs/documentation/workflows.md +++ b/docs/documentation/workflows.md @@ -366,9 +366,12 @@ public class WorkflowExplicitInvocationReconciler } ``` +See related [integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowExplicitInvocationIT.java). + For `cleanup`, if the `Cleaner` interface is implemented, the `cleanupManageWorkflow()` needs to be called explicitly. -However, if `Cleaner` interface is not implemented, it will be called implicitly. +However, if `Cleaner` interface is not implemented, it will be called implicitly. +See related [integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowExplicitCleanupIT.java). Nothing prevents calling the workflow multiple times in a reconciler, however it is meant to be called at most once. From 883c3ad1cdb3dba1a00234eb3cc06da9dbb9b80a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 15 Mar 2024 12:54:29 +0100 Subject: [PATCH 4/6] test fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../sample/workflowexplicitcleanup/ConfigMapDependent.java | 4 ++-- .../sample/workflowexplicitinvocation/ConfigMapDependent.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/ConfigMapDependent.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/ConfigMapDependent.java index 1c0c9b4e9e..91bf73906f 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/ConfigMapDependent.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/ConfigMapDependent.java @@ -6,10 +6,10 @@ 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.CRUDKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDNoGCKubernetesDependentResource; public class ConfigMapDependent extends - CRUDKubernetesDependentResource { + CRUDNoGCKubernetesDependentResource { public ConfigMapDependent() { super(ConfigMap.class); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/ConfigMapDependent.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/ConfigMapDependent.java index fe940efc5a..e26fcfcf11 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/ConfigMapDependent.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/ConfigMapDependent.java @@ -6,10 +6,10 @@ 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.CRUDKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDNoGCKubernetesDependentResource; public class ConfigMapDependent extends - CRUDKubernetesDependentResource { + CRUDNoGCKubernetesDependentResource { public ConfigMapDependent() { super(ConfigMap.class); From d7432a600a876edb3126ce70a5fb0fab1964f363 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Mon, 18 Mar 2024 10:45:34 +0100 Subject: [PATCH 5/6] docs: improve Signed-off-by: Chris Laprun --- docs/documentation/v5-0-migration.md | 2 + docs/documentation/workflows.md | 49 ++++++++----------- .../operator/api/reconciler/Context.java | 7 +++ ...edWorkflowAndDependentResourceContext.java | 16 +++++- .../operator/processing/Controller.java | 20 ++++++-- 5 files changed, 60 insertions(+), 34 deletions(-) diff --git a/docs/documentation/v5-0-migration.md b/docs/documentation/v5-0-migration.md index f4ec51f4fb..f69625afad 100644 --- a/docs/documentation/v5-0-migration.md +++ b/docs/documentation/v5-0-migration.md @@ -17,3 +17,5 @@ permalink: /docs/v5-0-migration [`EventSourceUtils`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceUtils.java#L11-L11) now contains all the utility methods used for event sources naming that were previously defined in the `EventSourceInitializer` interface. +3. `ManagedDependentResourceContext` has been renamed to `ManagedWorkflowAndDependentResourceContext` and is accessed + via the accordingly renamed `managedWorkflowAndDependentResourceContext` method. diff --git a/docs/documentation/workflows.md b/docs/documentation/workflows.md index 995d6bb637..ea83016430 100644 --- a/docs/documentation/workflows.md +++ b/docs/documentation/workflows.md @@ -341,39 +341,30 @@ In other words if a Kubernetes Dependent Resource depends on another dependent r ## Explicit Managed Workflow Invocation -Managed workflow is execution before the `reconcile(Resource,Context)` is called. There are certain situations however -when before the invocation there is additional validation or other logic needs to be executed or in some cases -event the workflow execution skipped. For this reason managed workflow explicit invocation is possible. - -```java - -@Workflow(explicitInvocation = true, - dependents = @Dependent(type = ConfigMapDependent.class)) -@ControllerConfiguration -public class WorkflowExplicitInvocationReconciler - implements Reconciler { - - @Override - public UpdateControl reconcile( - WorkflowExplicitInvocationCustomResource resource, - Context context) { - - // additional logic before the workflow is executed - - context.managedWorkflowAndDependentResourceContext().reconcileManagedWorkflow(); - - return UpdateControl.noUpdate(); - } - -``` -See related [integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowExplicitInvocationIT.java). - +Managed workflows, i.e. ones that are declared via annotations and therefore completely managed by JOSDK, are reconciled +before the primary resource. Each dependent resource that can be reconciled (according to the workflow configuration) +will therefore be reconciled before the primary reconciler is called to reconcile the primary resource. There are, +however, situations where it would be be useful to perform additional steps before the workflow is reconciled, for +example to validate the current state, execute arbitrary logic or even skip reconciliation altogether. Explicit +invocation of managed workflow was therefore introduced to solve these issues. + +To use this feature, you need to set the `explicitInvocation` field to `true` on the `@Workflow` annotation and then +call the `reconcileManagedWorkflow` method from the ` +ManagedWorkflowAndDependentResourceContext` retrieved from the reconciliation `Context` provided as part of your primary +resource reconciler `reconcile` method arguments. + +See +related [integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowExplicitInvocationIT.java) +for more details. For `cleanup`, if the `Cleaner` interface is implemented, the `cleanupManageWorkflow()` needs to be called explicitly. However, if `Cleaner` interface is not implemented, it will be called implicitly. -See related [integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowExplicitCleanupIT.java). +See +related [integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowExplicitCleanupIT.java). -Nothing prevents calling the workflow multiple times in a reconciler, however it is meant to be called at most once. +While nothing prevents calling the workflow multiple times in a reconciler, it isn't typical or even recommended to do +so. Conversely, if explicit invocation is requested but `reconcileManagedWorkflow` is not called in the primary resource +reconciler, the workflow won't be reconciled at all. ## Notes and Caveats diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java index 375d2035ad..85ff545e61 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java @@ -34,6 +34,13 @@ Optional getSecondaryResource(Class expectedType, ControllerConfiguration

getControllerConfiguration(); + /** + * Retrieve the {@link ManagedWorkflowAndDependentResourceContext} used to interact with + * {@link io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource}s and associated + * {@link io.javaoperatorsdk.operator.processing.dependent.workflow.Workflow} + * + * @return the {@link ManagedWorkflowAndDependentResourceContext} + */ ManagedWorkflowAndDependentResourceContext managedWorkflowAndDependentResourceContext(); EventSourceRetriever

eventSourceRetriever(); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedWorkflowAndDependentResourceContext.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedWorkflowAndDependentResourceContext.java index 929330a9ed..8ddcb7abe5 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedWorkflowAndDependentResourceContext.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedWorkflowAndDependentResourceContext.java @@ -37,7 +37,6 @@ public interface ManagedWorkflowAndDependentResourceContext { * @return an Optional containing the previous value associated with the key or * {@link Optional#empty()} if none existed */ - @SuppressWarnings("unchecked") T put(Object key, T value); /** @@ -54,10 +53,25 @@ public interface ManagedWorkflowAndDependentResourceContext { WorkflowReconcileResult getWorkflowReconcileResult(); + @SuppressWarnings("unused") WorkflowCleanupResult getWorkflowCleanupResult(); + /** + * Explicitly reconcile the declared workflow for the associated + * {@link io.javaoperatorsdk.operator.api.reconciler.Reconciler} + * + * @throws IllegalStateException if called when explicit invocation is not requested + */ void reconcileManagedWorkflow(); + /** + * Explicitly clean-up dependent resources in the declared workflow for the associated + * {@link io.javaoperatorsdk.operator.api.reconciler.Reconciler}. Note that calling this method is + * only needed if the associated reconciler implements the + * {@link io.javaoperatorsdk.operator.api.reconciler.Cleaner} interface. + * + * @throws IllegalStateException if called when explicit invocation is not requested + */ void cleanupManageWorkflow(); } 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 37e34efdc6..7ab1eca457 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java @@ -1,6 +1,11 @@ package io.javaoperatorsdk.operator.processing; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,7 +26,15 @@ import io.javaoperatorsdk.operator.api.config.workflow.WorkflowSpec; import io.javaoperatorsdk.operator.api.monitoring.Metrics; import io.javaoperatorsdk.operator.api.monitoring.Metrics.ControllerExecution; -import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.api.reconciler.Cleaner; +import io.javaoperatorsdk.operator.api.reconciler.Constants; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ContextInitializer; +import io.javaoperatorsdk.operator.api.reconciler.DeleteControl; +import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; +import io.javaoperatorsdk.operator.api.reconciler.Ignore; +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceNotFoundException; import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceProvider; import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceReferencer; @@ -178,8 +191,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 || !isWorkflowExplicitInvocation()) { workflowCleanupResult = cleanupManagedWorkflow(resource, context); } From c19e06612c1d74351be92350198547b151a9ece6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Mon, 18 Mar 2024 13:02:11 +0100 Subject: [PATCH 6/6] format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../io/javaoperatorsdk/operator/api/reconciler/Context.java | 2 +- .../managed/ManagedWorkflowAndDependentResourceContext.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java index 85ff545e61..a997835822 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java @@ -38,7 +38,7 @@ Optional getSecondaryResource(Class expectedType, * Retrieve the {@link ManagedWorkflowAndDependentResourceContext} used to interact with * {@link io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource}s and associated * {@link io.javaoperatorsdk.operator.processing.dependent.workflow.Workflow} - * + * * @return the {@link ManagedWorkflowAndDependentResourceContext} */ ManagedWorkflowAndDependentResourceContext managedWorkflowAndDependentResourceContext(); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedWorkflowAndDependentResourceContext.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedWorkflowAndDependentResourceContext.java index 8ddcb7abe5..f5a25a6d9c 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedWorkflowAndDependentResourceContext.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedWorkflowAndDependentResourceContext.java @@ -59,7 +59,7 @@ public interface ManagedWorkflowAndDependentResourceContext { /** * Explicitly reconcile the declared workflow for the associated * {@link io.javaoperatorsdk.operator.api.reconciler.Reconciler} - * + * * @throws IllegalStateException if called when explicit invocation is not requested */ void reconcileManagedWorkflow(); @@ -69,7 +69,7 @@ public interface ManagedWorkflowAndDependentResourceContext { * {@link io.javaoperatorsdk.operator.api.reconciler.Reconciler}. Note that calling this method is * only needed if the associated reconciler implements the * {@link io.javaoperatorsdk.operator.api.reconciler.Cleaner} interface. - * + * * @throws IllegalStateException if called when explicit invocation is not requested */ void cleanupManageWorkflow();