Skip to content

feat: explicit workflow invocation #2289

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/documentation/v5-0-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
49 changes: 20 additions & 29 deletions docs/documentation/workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<WorkflowExplicitInvocationCustomResource> {

@Override
public UpdateControl<WorkflowExplicitInvocationCustomResource> reconcile(
WorkflowExplicitInvocationCustomResource resource,
Context<WorkflowExplicitInvocationCustomResource> context) {

// additional logic before the workflow is executed

context.managedWorkflowAndDependentResourceContext().reconcileManagedWorkflow();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@metacosm why did you delete sample code from here?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it doesn't add much, imo, and would need to be kept up to date (as are the other samples)…

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will merge this now as it is, when we migrate to docsy, would be nice to revisit the principles for the dox though


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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ <R> Optional<R> getSecondaryResource(Class<R> expectedType,

ControllerConfiguration<P> 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<P> eventSourceRetriever();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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> T put(Object key, T value);

/**
Expand All @@ -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();

}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down