diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DefaultManagedWorkflowAndDependentResourceContext.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DefaultManagedWorkflowAndDependentResourceContext.java index 1e9e10ca20..0102b58d80 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DefaultManagedWorkflowAndDependentResourceContext.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DefaultManagedWorkflowAndDependentResourceContext.java @@ -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); } - } 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 6bb1e60b0e..617505b387 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 @@ -70,9 +70,10 @@ 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 @@ -80,8 +81,9 @@ public interface ManagedWorkflowAndDependentResourceContext { * 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(); } 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 76c8401249..da9843ec40 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 @@ -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; @@ -449,16 +450,18 @@ public EventSourceContext

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

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

context) { if (!managedWorkflow.isEmpty()) { - managedWorkflow.reconcile(primary, context); + return managedWorkflow.reconcile(primary, context); } + return WorkflowReconcileResult.EMPTY; } - public void cleanupManagedWorkflow(P resource, Context

context) { + public WorkflowCleanupResult cleanupManagedWorkflow(P resource, Context

context) { if (managedWorkflow.hasCleaner()) { - managedWorkflow.cleanup(resource, context); + return managedWorkflow.cleanup(resource, context); } + return WorkflowCleanupResult.EMPTY; } public boolean isWorkflowExplicitInvocation() { diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutor.java index eb0f3f7e83..60d137fa1a 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutor.java @@ -24,7 +24,7 @@ abstract class AbstractWorkflowExecutor

{ protected final P primary; protected final ResourceID primaryID; protected final Context

context; - protected final Map, WorkflowResult.DetailBuilder> results; + protected final Map, BaseWorkflowResult.DetailBuilder> results; /** * Covers both deleted and reconciled */ @@ -74,30 +74,30 @@ protected boolean noMoreExecutionsScheduled() { } protected boolean alreadyVisited(DependentResourceNode dependentResourceNode) { - return getResultFlagFor(dependentResourceNode, WorkflowResult.DetailBuilder::isVisited); + return getResultFlagFor(dependentResourceNode, BaseWorkflowResult.DetailBuilder::isVisited); } protected boolean postDeleteConditionNotMet(DependentResourceNode drn) { - return getResultFlagFor(drn, WorkflowResult.DetailBuilder::hasPostDeleteConditionNotMet); + return getResultFlagFor(drn, BaseWorkflowResult.DetailBuilder::hasPostDeleteConditionNotMet); } protected boolean isMarkedForDelete(DependentResourceNode drn) { - return getResultFlagFor(drn, WorkflowResult.DetailBuilder::isMarkedForDelete); + return getResultFlagFor(drn, BaseWorkflowResult.DetailBuilder::isMarkedForDelete); } - protected synchronized WorkflowResult.DetailBuilder createOrGetResultFor( + protected synchronized BaseWorkflowResult.DetailBuilder createOrGetResultFor( DependentResourceNode dependentResourceNode) { return results.computeIfAbsent(dependentResourceNode, - unused -> new WorkflowResult.DetailBuilder()); + unused -> new BaseWorkflowResult.DetailBuilder()); } - protected synchronized Optional> getResultFor( + protected synchronized Optional> getResultFor( DependentResourceNode dependentResourceNode) { return Optional.ofNullable(results.get(dependentResourceNode)); } protected boolean getResultFlagFor(DependentResourceNode dependentResourceNode, - Function, Boolean> flag) { + Function, Boolean> flag) { return getResultFor(dependentResourceNode).map(flag).orElse(false); } @@ -117,11 +117,11 @@ protected synchronized void handleExceptionInExecutor( } protected boolean isReady(DependentResourceNode dependentResourceNode) { - return getResultFlagFor(dependentResourceNode, WorkflowResult.DetailBuilder::isReady); + return getResultFlagFor(dependentResourceNode, BaseWorkflowResult.DetailBuilder::isReady); } protected boolean isInError(DependentResourceNode dependentResourceNode) { - return getResultFlagFor(dependentResourceNode, WorkflowResult.DetailBuilder::hasError); + return getResultFlagFor(dependentResourceNode, BaseWorkflowResult.DetailBuilder::hasError); } protected synchronized void handleNodeExecutionFinish( @@ -141,7 +141,7 @@ protected 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; @@ -173,7 +173,7 @@ protected void registerOrDeregisterEventSourceBasedOnActivation( } } - protected synchronized Map> asDetails() { + protected synchronized Map> asDetails() { return results.entrySet().stream() .collect( Collectors.toMap(e -> e.getKey().getDependentResource(), e -> e.getValue().build())); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/BaseWorkflowResult.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/BaseWorkflowResult.java new file mode 100644 index 0000000000..cf759022dc --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/BaseWorkflowResult.java @@ -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> results; + private Boolean hasErroredDependents; + + BaseWorkflowResult(Map> results) { + this.results = results; + } + + @Override + public Map getErroredDependents() { + return getErroredDependentsStream() + .collect(Collectors.toMap(Entry::getKey, entry -> entry.getValue().error)); + } + + private Stream>> getErroredDependentsStream() { + return results.entrySet().stream().filter(entry -> entry.getValue().error != null); + } + + protected Map> results() { + return results; + } + + @Override + public Optional getDependentResourceByName(String name) { + if (name == null || name.isEmpty()) { + return Optional.empty(); + } + return results.keySet().stream().filter(dr -> dr.name().equals(name)).findFirst(); + } + + @Override + public Optional getDependentConditionResult(DependentResource dependentResource, + Condition.Type conditionType, Class 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 listFilteredBy( + Function 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 { + private Exception error; + private ReconcileResult 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 build() { + return new Detail<>(error, reconcileResult, activationConditionResult, + deletePostconditionResult, readyPostconditionResult, reconcilePostconditionResult, + deleted, visited, markedForDelete); + } + + DetailBuilder 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 withError(Exception error) { + this.error = error; + return this; + } + + DetailBuilder withReconcileResult(ReconcileResult reconcileResult) { + this.reconcileResult = reconcileResult; + return this; + } + + DetailBuilder 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 markAsVisited() { + visited = true; + return this; + } + + public boolean isVisited() { + return visited; + } + + public boolean isMarkedForDelete() { + return markedForDelete; + } + + DetailBuilder markForDelete() { + markedForDelete = true; + return this; + } + } + + + record Detail(Exception error, ReconcileResult 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> 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); + }; + } + } +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultWorkflowCleanupResult.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultWorkflowCleanupResult.java new file mode 100644 index 0000000000..951ff98bca --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultWorkflowCleanupResult.java @@ -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> results) { + super(results); + } + + public List getDeleteCalledOnDependents() { + return listFilteredBy(BaseWorkflowResult.Detail::deleted); + } + + public List getPostConditionNotMetDependents() { + return listFilteredBy(detail -> !detail.isConditionWithTypeMet(Condition.Type.DELETE)); + } + + public boolean allPostConditionsMet() { + if (allPostConditionsMet == null) { + allPostConditionsMet = getPostConditionNotMetDependents().isEmpty(); + } + return allPostConditionsMet; + } +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultWorkflowReconcileResult.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultWorkflowReconcileResult.java new file mode 100644 index 0000000000..3221308312 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultWorkflowReconcileResult.java @@ -0,0 +1,28 @@ +package io.javaoperatorsdk.operator.processing.dependent.workflow; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; + +@SuppressWarnings("rawtypes") +class DefaultWorkflowReconcileResult extends BaseWorkflowResult implements WorkflowReconcileResult { + DefaultWorkflowReconcileResult(Map> results) { + super(results); + } + + + public List getReconciledDependents() { + return listFilteredBy(detail -> detail.reconcileResult() != null); + } + + public List getNotReadyDependents() { + return listFilteredBy(detail -> !detail.isConditionWithTypeMet(Condition.Type.READY)); + } + + public Optional getNotReadyDependentResult(DependentResource dependentResource, + Class expectedResultType) { + return getDependentConditionResult(dependentResource, Condition.Type.READY, expectedResultType); + } +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutor.java index 73633adb82..4681502a3b 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupExecutor.java @@ -127,6 +127,6 @@ private boolean hasErroredDependent(DependentResourceNode dependentResourceNode) } private WorkflowCleanupResult createCleanupResult() { - return new WorkflowCleanupResult(asDetails()); + return new DefaultWorkflowCleanupResult(asDetails()); } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupResult.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupResult.java index b93741ef56..1333270b47 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupResult.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowCleanupResult.java @@ -1,30 +1,22 @@ 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") -public class WorkflowCleanupResult extends WorkflowResult { - private Boolean allPostConditionsMet; +public interface WorkflowCleanupResult extends WorkflowResult { + WorkflowCleanupResult EMPTY = new WorkflowCleanupResult() {}; - WorkflowCleanupResult(Map> results) { - super(results); + default List getDeleteCalledOnDependents() { + return List.of(); } - public List getDeleteCalledOnDependents() { - return listFilteredBy(Detail::deleted); + default List getPostConditionNotMetDependents() { + return List.of(); } - public List getPostConditionNotMetDependents() { - return listFilteredBy(detail -> !detail.isConditionWithTypeMet(Condition.Type.DELETE)); - } - - public boolean allPostConditionsMet() { - if (allPostConditionsMet == null) { - allPostConditionsMet = getPostConditionNotMetDependents().isEmpty(); - } - return allPostConditionsMet; + default boolean allPostConditionsMet() { + return true; } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index 962d01c8e5..432a168ad7 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -241,6 +241,6 @@ private boolean hasErroredParent(DependentResourceNode dependentResourceNo } private WorkflowReconcileResult createReconcileResult() { - return new WorkflowReconcileResult(asDetails()); + return new DefaultWorkflowReconcileResult(asDetails()); } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileResult.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileResult.java index 055fca3bfe..dd05e3f8e3 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileResult.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileResult.java @@ -1,32 +1,28 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow; import java.util.List; -import java.util.Map; import java.util.Optional; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; @SuppressWarnings("rawtypes") -public class WorkflowReconcileResult extends WorkflowResult { +public interface WorkflowReconcileResult extends WorkflowResult { + WorkflowReconcileResult EMPTY = new WorkflowReconcileResult() {}; - WorkflowReconcileResult(Map> results) { - super(results); + default List getReconciledDependents() { + return List.of(); } - public List getReconciledDependents() { - return listFilteredBy(detail -> detail.reconcileResult() != null); + default List getNotReadyDependents() { + return List.of(); } - public List getNotReadyDependents() { - return listFilteredBy(detail -> !detail.isConditionWithTypeMet(Condition.Type.READY)); - } - - public Optional getNotReadyDependentResult(DependentResource dependentResource, + default Optional getNotReadyDependentResult(DependentResource dependentResource, Class expectedResultType) { - return getDependentConditionResult(dependentResource, Condition.Type.READY, expectedResultType); + return Optional.empty(); } - public boolean allDependentResourcesReady() { + default boolean allDependentResourcesReady() { return getNotReadyDependents().isEmpty(); } } 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 1b278fed77..0d7e74fa78 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,52 +1,26 @@ 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 WorkflowResult { - private final Map> results; - private Boolean hasErroredDependents; - - WorkflowResult(Map> results) { - this.results = results; - } - - public Map getErroredDependents() { - return getErroredDependentsStream() - .collect(Collectors.toMap(Entry::getKey, entry -> entry.getValue().error)); - } - - private Stream>> getErroredDependentsStream() { - return results.entrySet().stream().filter(entry -> entry.getValue().error != null); - } - - protected Map> results() { - return results; +public interface WorkflowResult { + default Map getErroredDependents() { + return Map.of(); } /** * Retrieves the {@link DependentResource} associated with the specified name if it exists, * {@link Optional#empty()} otherwise. - * + * * @param name the name of the {@link DependentResource} to retrieve * @return the {@link DependentResource} associated with the specified name if it exists, * {@link Optional#empty()} otherwise */ - public Optional getDependentResourceByName(String name) { - if (name == null || name.isEmpty()) { - return Optional.empty(); - } - return results.keySet().stream().filter(dr -> dr.name().equals(name)).findFirst(); + default Optional getDependentResourceByName(String name) { + return Optional.empty(); } /** @@ -61,7 +35,7 @@ public Optional getDependentResourceByName(String name) { * @return the dependent condition result if it exists or {@link Optional#empty()} otherwise * @throws IllegalArgumentException if a result exists but is not of the expected type */ - public Optional getDependentConditionResult(String dependentResourceName, + default Optional getDependentConditionResult(String dependentResourceName, Condition.Type conditionType, Class expectedResultType) { return getDependentConditionResult( getDependentResourceByName(dependentResourceName).orElse(null), conditionType, @@ -80,148 +54,16 @@ public Optional getDependentConditionResult(String dependentResourceName, * @return the dependent condition result if it exists or {@link Optional#empty()} otherwise * @throws IllegalArgumentException if a result exists but is not of the expected type */ - public Optional getDependentConditionResult(DependentResource dependentResource, + default Optional getDependentConditionResult(DependentResource dependentResource, Condition.Type conditionType, Class 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 listFilteredBy( - Function filter) { - return results.entrySet().stream() - .filter(e -> filter.apply(e.getValue())) - .map(Map.Entry::getKey) - .toList(); - } - - public boolean erroredDependentsExist() { - if (hasErroredDependents == null) { - hasErroredDependents = !getErroredDependents().isEmpty(); - } - return hasErroredDependents; - } - - 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))); - } + return Optional.empty(); } - @SuppressWarnings("UnusedReturnValue") - static class DetailBuilder { - private Exception error; - private ReconcileResult 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 build() { - return new Detail<>(error, reconcileResult, activationConditionResult, - deletePostconditionResult, readyPostconditionResult, reconcilePostconditionResult, - deleted, visited, markedForDelete); - } - - DetailBuilder 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 withError(Exception error) { - this.error = error; - return this; - } - - DetailBuilder withReconcileResult(ReconcileResult reconcileResult) { - this.reconcileResult = reconcileResult; - return this; - } - - DetailBuilder 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 markAsVisited() { - visited = true; - return this; - } - - public boolean isVisited() { - return visited; - } - - public boolean isMarkedForDelete() { - return markedForDelete; - } - - DetailBuilder markForDelete() { - markedForDelete = true; - return this; - } + default boolean erroredDependentsExist() { + return false; } - - record Detail(Exception error, ReconcileResult 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> 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); - }; - } + default void throwAggregateExceptionIfErrorsPresent() { + throw new UnsupportedOperationException("Implement this method"); } } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowResultTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/BaseWorkflowResultTest.java similarity index 85% rename from operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowResultTest.java rename to operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/BaseWorkflowResultTest.java index ca0b883e99..8503e402f1 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowResultTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/BaseWorkflowResultTest.java @@ -12,14 +12,14 @@ import static org.assertj.core.api.Assertions.assertThat; -class WorkflowResultTest { - private final static WorkflowResult.Detail detail = - new WorkflowResult.Detail<>(new RuntimeException(), null, null, null, null, null, false, +class BaseWorkflowResultTest { + private final static BaseWorkflowResult.Detail detail = + new BaseWorkflowResult.Detail<>(new RuntimeException(), null, null, null, null, null, false, false, false); @Test void throwsExceptionWithoutNumberingIfAllDifferentClass() { - var res = new WorkflowResult(Map.of(new DependentA(), detail, + var res = new BaseWorkflowResult(Map.of(new DependentA(), detail, new DependentB(), detail)); try { res.throwAggregateExceptionIfErrorsPresent(); @@ -31,7 +31,7 @@ void throwsExceptionWithoutNumberingIfAllDifferentClass() { @Test void numbersDependentClassNamesIfMoreOfSameType() { - var res = new WorkflowResult(Map.of(new DependentA("name1"), detail, + var res = new BaseWorkflowResult(Map.of(new DependentA("name1"), detail, new DependentA("name2"), detail)); try { res.throwAggregateExceptionIfErrorsPresent();