Skip to content

Commit

Permalink
feat: return result when calling workflow explicitly (#2601)
Browse files Browse the repository at this point in the history
Signed-off-by: Chris Laprun <claprun@redhat.com>
  • Loading branch information
metacosm authored Nov 27, 2024
1 parent e86e295 commit bb3910b
Show file tree
Hide file tree
Showing 13 changed files with 315 additions and 230 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,19 +83,18 @@ public WorkflowCleanupResult getWorkflowCleanupResult() {
}

@Override
public void reconcileManagedWorkflow() {
public WorkflowReconcileResult reconcileManagedWorkflow() {
if (!controller.isWorkflowExplicitInvocation()) {
throw new IllegalStateException("Workflow explicit invocation is not set.");
}
controller.reconcileManagedWorkflow(primaryResource, context);
return controller.reconcileManagedWorkflow(primaryResource, context);
}

@Override
public void cleanupManageWorkflow() {
public WorkflowCleanupResult cleanupManageWorkflow() {
if (!controller.isWorkflowExplicitInvocation()) {
throw new IllegalStateException("Workflow explicit invocation is not set.");
}
controller.cleanupManagedWorkflow(primaryResource, context);
return controller.cleanupManagedWorkflow(primaryResource, context);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -70,18 +70,20 @@ public interface ManagedWorkflowAndDependentResourceContext {
* Explicitly reconcile the declared workflow for the associated
* {@link io.javaoperatorsdk.operator.api.reconciler.Reconciler}
*
* @return the result of the workflow reconciliation
* @throws IllegalStateException if called when explicit invocation is not requested
*/
void reconcileManagedWorkflow();
WorkflowReconcileResult 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.
*
* @return the result of the workflow reconciliation on cleanup
* @throws IllegalStateException if called when explicit invocation is not requested
*/
void cleanupManageWorkflow();
WorkflowCleanupResult cleanupManageWorkflow();

}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import io.javaoperatorsdk.operator.health.ControllerHealthInfo;
import io.javaoperatorsdk.operator.processing.dependent.workflow.Workflow;
import io.javaoperatorsdk.operator.processing.dependent.workflow.WorkflowCleanupResult;
import io.javaoperatorsdk.operator.processing.dependent.workflow.WorkflowReconcileResult;
import io.javaoperatorsdk.operator.processing.event.EventProcessor;
import io.javaoperatorsdk.operator.processing.event.EventSourceManager;
import io.javaoperatorsdk.operator.processing.event.ResourceID;
Expand Down Expand Up @@ -449,16 +450,18 @@ public EventSourceContext<P> eventSourceContext() {
return eventSourceContext;
}

public void reconcileManagedWorkflow(P primary, Context<P> context) {
public WorkflowReconcileResult reconcileManagedWorkflow(P primary, Context<P> context) {
if (!managedWorkflow.isEmpty()) {
managedWorkflow.reconcile(primary, context);
return managedWorkflow.reconcile(primary, context);
}
return WorkflowReconcileResult.EMPTY;
}

public void cleanupManagedWorkflow(P resource, Context<P> context) {
public WorkflowCleanupResult cleanupManagedWorkflow(P resource, Context<P> context) {
if (managedWorkflow.hasCleaner()) {
managedWorkflow.cleanup(resource, context);
return managedWorkflow.cleanup(resource, context);
}
return WorkflowCleanupResult.EMPTY;
}

public boolean isWorkflowExplicitInvocation() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ abstract class AbstractWorkflowExecutor<P extends HasMetadata> {
protected final P primary;
protected final ResourceID primaryID;
protected final Context<P> context;
protected final Map<DependentResourceNode<?, P>, WorkflowResult.DetailBuilder<?>> results;
protected final Map<DependentResourceNode<?, P>, BaseWorkflowResult.DetailBuilder<?>> results;
/**
* Covers both deleted and reconciled
*/
Expand Down Expand Up @@ -74,30 +74,30 @@ protected boolean noMoreExecutionsScheduled() {
}

protected boolean alreadyVisited(DependentResourceNode<?, P> dependentResourceNode) {
return getResultFlagFor(dependentResourceNode, WorkflowResult.DetailBuilder::isVisited);
return getResultFlagFor(dependentResourceNode, BaseWorkflowResult.DetailBuilder::isVisited);
}

protected boolean postDeleteConditionNotMet(DependentResourceNode<?, P> drn) {
return getResultFlagFor(drn, WorkflowResult.DetailBuilder::hasPostDeleteConditionNotMet);
return getResultFlagFor(drn, BaseWorkflowResult.DetailBuilder::hasPostDeleteConditionNotMet);
}

protected boolean isMarkedForDelete(DependentResourceNode<?, P> drn) {
return getResultFlagFor(drn, WorkflowResult.DetailBuilder::isMarkedForDelete);
return getResultFlagFor(drn, BaseWorkflowResult.DetailBuilder::isMarkedForDelete);
}

protected synchronized WorkflowResult.DetailBuilder createOrGetResultFor(
protected synchronized BaseWorkflowResult.DetailBuilder createOrGetResultFor(
DependentResourceNode<?, P> dependentResourceNode) {
return results.computeIfAbsent(dependentResourceNode,
unused -> new WorkflowResult.DetailBuilder());
unused -> new BaseWorkflowResult.DetailBuilder());
}

protected synchronized Optional<WorkflowResult.DetailBuilder<?>> getResultFor(
protected synchronized Optional<BaseWorkflowResult.DetailBuilder<?>> getResultFor(
DependentResourceNode<?, P> dependentResourceNode) {
return Optional.ofNullable(results.get(dependentResourceNode));
}

protected boolean getResultFlagFor(DependentResourceNode<?, P> dependentResourceNode,
Function<WorkflowResult.DetailBuilder<?>, Boolean> flag) {
Function<BaseWorkflowResult.DetailBuilder<?>, Boolean> flag) {
return getResultFor(dependentResourceNode).map(flag).orElse(false);
}

Expand All @@ -117,11 +117,11 @@ protected synchronized void handleExceptionInExecutor(
}

protected boolean isReady(DependentResourceNode<?, P> dependentResourceNode) {
return getResultFlagFor(dependentResourceNode, WorkflowResult.DetailBuilder::isReady);
return getResultFlagFor(dependentResourceNode, BaseWorkflowResult.DetailBuilder::isReady);
}

protected boolean isInError(DependentResourceNode<?, P> dependentResourceNode) {
return getResultFlagFor(dependentResourceNode, WorkflowResult.DetailBuilder::hasError);
return getResultFlagFor(dependentResourceNode, BaseWorkflowResult.DetailBuilder::hasError);
}

protected synchronized void handleNodeExecutionFinish(
Expand All @@ -141,7 +141,7 @@ protected <R> boolean isConditionMet(
return condition.map(c -> {
final DetailedCondition.Result<?> r = c.detailedIsMet(dr, primary, context);
synchronized (this) {
results.computeIfAbsent(dependentResource, unused -> new WorkflowResult.DetailBuilder())
results.computeIfAbsent(dependentResource, unused -> new BaseWorkflowResult.DetailBuilder())
.withResultForCondition(c, r);
}
return r;
Expand Down Expand Up @@ -173,7 +173,7 @@ protected <R> void registerOrDeregisterEventSourceBasedOnActivation(
}
}

protected synchronized Map<DependentResource, WorkflowResult.Detail<?>> asDetails() {
protected synchronized Map<DependentResource, BaseWorkflowResult.Detail<?>> asDetails() {
return results.entrySet().stream()
.collect(
Collectors.toMap(e -> e.getKey().getDependentResource(), e -> e.getValue().build()));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package io.javaoperatorsdk.operator.processing.dependent.workflow;

import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import io.javaoperatorsdk.operator.AggregatedOperatorException;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult;

@SuppressWarnings("rawtypes")
class BaseWorkflowResult implements WorkflowResult {
private final Map<DependentResource, Detail<?>> results;
private Boolean hasErroredDependents;

BaseWorkflowResult(Map<DependentResource, Detail<?>> results) {
this.results = results;
}

@Override
public Map<DependentResource, Exception> getErroredDependents() {
return getErroredDependentsStream()
.collect(Collectors.toMap(Entry::getKey, entry -> entry.getValue().error));
}

private Stream<Entry<DependentResource, Detail<?>>> getErroredDependentsStream() {
return results.entrySet().stream().filter(entry -> entry.getValue().error != null);
}

protected Map<DependentResource, Detail<?>> results() {
return results;
}

@Override
public Optional<DependentResource> getDependentResourceByName(String name) {
if (name == null || name.isEmpty()) {
return Optional.empty();
}
return results.keySet().stream().filter(dr -> dr.name().equals(name)).findFirst();
}

@Override
public <T> Optional<T> getDependentConditionResult(DependentResource dependentResource,
Condition.Type conditionType, Class<T> expectedResultType) {
if (dependentResource == null) {
return Optional.empty();
}

final var result = new Object[1];
try {
return Optional.ofNullable(results().get(dependentResource))
.flatMap(detail -> detail.getResultForConditionWithType(conditionType))
.map(r -> result[0] = r.getDetail())
.map(expectedResultType::cast);
} catch (Exception e) {
throw new IllegalArgumentException("Condition " +
"result " + result[0] +
" for Dependent " + dependentResource.name() + " doesn't match expected type "
+ expectedResultType.getSimpleName(), e);
}
}

protected List<DependentResource> listFilteredBy(
Function<Detail, Boolean> filter) {
return results.entrySet().stream()
.filter(e -> filter.apply(e.getValue()))
.map(Map.Entry::getKey)
.toList();
}

@Override
public boolean erroredDependentsExist() {
if (hasErroredDependents == null) {
hasErroredDependents = !getErroredDependents().isEmpty();
}
return hasErroredDependents;
}

@Override
public void throwAggregateExceptionIfErrorsPresent() {
if (erroredDependentsExist()) {
throw new AggregatedOperatorException("Exception(s) during workflow execution.",
getErroredDependentsStream()
.collect(Collectors.toMap(e -> e.getKey().name(), e -> e.getValue().error)));
}
}

@SuppressWarnings("UnusedReturnValue")
static class DetailBuilder<R> {
private Exception error;
private ReconcileResult<R> reconcileResult;
private DetailedCondition.Result activationConditionResult;
private DetailedCondition.Result deletePostconditionResult;
private DetailedCondition.Result readyPostconditionResult;
private DetailedCondition.Result reconcilePostconditionResult;
private boolean deleted;
private boolean visited;
private boolean markedForDelete;

Detail<R> build() {
return new Detail<>(error, reconcileResult, activationConditionResult,
deletePostconditionResult, readyPostconditionResult, reconcilePostconditionResult,
deleted, visited, markedForDelete);
}

DetailBuilder<R> withResultForCondition(
ConditionWithType conditionWithType,
DetailedCondition.Result conditionResult) {
switch (conditionWithType.type()) {
case ACTIVATION -> activationConditionResult = conditionResult;
case DELETE -> deletePostconditionResult = conditionResult;
case READY -> readyPostconditionResult = conditionResult;
case RECONCILE -> reconcilePostconditionResult = conditionResult;
default ->
throw new IllegalStateException("Unexpected condition type: " + conditionWithType);
}
return this;
}

DetailBuilder<R> withError(Exception error) {
this.error = error;
return this;
}

DetailBuilder<R> withReconcileResult(ReconcileResult<R> reconcileResult) {
this.reconcileResult = reconcileResult;
return this;
}

DetailBuilder<R> markAsDeleted() {
this.deleted = true;
return this;
}

public boolean hasError() {
return error != null;
}

public boolean hasPostDeleteConditionNotMet() {
return deletePostconditionResult != null && !deletePostconditionResult.isSuccess();
}

public boolean isReady() {
return readyPostconditionResult == null || readyPostconditionResult.isSuccess();
}

DetailBuilder<R> markAsVisited() {
visited = true;
return this;
}

public boolean isVisited() {
return visited;
}

public boolean isMarkedForDelete() {
return markedForDelete;
}

DetailBuilder<R> markForDelete() {
markedForDelete = true;
return this;
}
}


record Detail<R>(Exception error, ReconcileResult<R> reconcileResult,
DetailedCondition.Result activationConditionResult,
DetailedCondition.Result deletePostconditionResult,
DetailedCondition.Result readyPostconditionResult,
DetailedCondition.Result reconcilePostconditionResult,
boolean deleted, boolean visited, boolean markedForDelete) {

boolean isConditionWithTypeMet(Condition.Type conditionType) {
return getResultForConditionWithType(conditionType).map(DetailedCondition.Result::isSuccess)
.orElse(true);
}

Optional<DetailedCondition.Result<?>> getResultForConditionWithType(
Condition.Type conditionType) {
return switch (conditionType) {
case ACTIVATION -> Optional.ofNullable(activationConditionResult);
case DELETE -> Optional.ofNullable(deletePostconditionResult);
case READY -> Optional.ofNullable(readyPostconditionResult);
case RECONCILE -> Optional.ofNullable(reconcilePostconditionResult);
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.javaoperatorsdk.operator.processing.dependent.workflow;

import java.util.List;
import java.util.Map;

import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;

@SuppressWarnings("rawtypes")
class DefaultWorkflowCleanupResult extends BaseWorkflowResult implements WorkflowCleanupResult {
private Boolean allPostConditionsMet;

DefaultWorkflowCleanupResult(Map<DependentResource, BaseWorkflowResult.Detail<?>> results) {
super(results);
}

public List<DependentResource> getDeleteCalledOnDependents() {
return listFilteredBy(BaseWorkflowResult.Detail::deleted);
}

public List<DependentResource> getPostConditionNotMetDependents() {
return listFilteredBy(detail -> !detail.isConditionWithTypeMet(Condition.Type.DELETE));
}

public boolean allPostConditionsMet() {
if (allPostConditionsMet == null) {
allPostConditionsMet = getPostConditionNotMetDependents().isEmpty();
}
return allPostConditionsMet;
}
}
Loading

0 comments on commit bb3910b

Please sign in to comment.