Skip to content

Commit 75c724d

Browse files
metacosmcsviri
authored andcommitted
feat: return result when calling workflow explicitly (#2601)
Signed-off-by: Chris Laprun <[email protected]>
1 parent 93bc9c4 commit 75c724d

13 files changed

+315
-230
lines changed

Diff for: operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DefaultManagedWorkflowAndDependentResourceContext.java

+4-5
Original file line numberDiff line numberDiff line change
@@ -83,19 +83,18 @@ public WorkflowCleanupResult getWorkflowCleanupResult() {
8383
}
8484

8585
@Override
86-
public void reconcileManagedWorkflow() {
86+
public WorkflowReconcileResult reconcileManagedWorkflow() {
8787
if (!controller.isWorkflowExplicitInvocation()) {
8888
throw new IllegalStateException("Workflow explicit invocation is not set.");
8989
}
90-
controller.reconcileManagedWorkflow(primaryResource, context);
90+
return controller.reconcileManagedWorkflow(primaryResource, context);
9191
}
9292

9393
@Override
94-
public void cleanupManageWorkflow() {
94+
public WorkflowCleanupResult cleanupManageWorkflow() {
9595
if (!controller.isWorkflowExplicitInvocation()) {
9696
throw new IllegalStateException("Workflow explicit invocation is not set.");
9797
}
98-
controller.cleanupManagedWorkflow(primaryResource, context);
98+
return controller.cleanupManagedWorkflow(primaryResource, context);
9999
}
100-
101100
}

Diff for: operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedWorkflowAndDependentResourceContext.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -70,18 +70,20 @@ public interface ManagedWorkflowAndDependentResourceContext {
7070
* Explicitly reconcile the declared workflow for the associated
7171
* {@link io.javaoperatorsdk.operator.api.reconciler.Reconciler}
7272
*
73+
* @return the result of the workflow reconciliation
7374
* @throws IllegalStateException if called when explicit invocation is not requested
7475
*/
75-
void reconcileManagedWorkflow();
76+
WorkflowReconcileResult reconcileManagedWorkflow();
7677

7778
/**
7879
* Explicitly clean-up dependent resources in the declared workflow for the associated
7980
* {@link io.javaoperatorsdk.operator.api.reconciler.Reconciler}. Note that calling this method is
8081
* only needed if the associated reconciler implements the
8182
* {@link io.javaoperatorsdk.operator.api.reconciler.Cleaner} interface.
8283
*
84+
* @return the result of the workflow reconciliation on cleanup
8385
* @throws IllegalStateException if called when explicit invocation is not requested
8486
*/
85-
void cleanupManageWorkflow();
87+
WorkflowCleanupResult cleanupManageWorkflow();
8688

8789
}

Diff for: operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java

+7-4
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import io.javaoperatorsdk.operator.health.ControllerHealthInfo;
4141
import io.javaoperatorsdk.operator.processing.dependent.workflow.Workflow;
4242
import io.javaoperatorsdk.operator.processing.dependent.workflow.WorkflowCleanupResult;
43+
import io.javaoperatorsdk.operator.processing.dependent.workflow.WorkflowReconcileResult;
4344
import io.javaoperatorsdk.operator.processing.event.EventProcessor;
4445
import io.javaoperatorsdk.operator.processing.event.EventSourceManager;
4546
import io.javaoperatorsdk.operator.processing.event.ResourceID;
@@ -449,16 +450,18 @@ public EventSourceContext<P> eventSourceContext() {
449450
return eventSourceContext;
450451
}
451452

452-
public void reconcileManagedWorkflow(P primary, Context<P> context) {
453+
public WorkflowReconcileResult reconcileManagedWorkflow(P primary, Context<P> context) {
453454
if (!managedWorkflow.isEmpty()) {
454-
managedWorkflow.reconcile(primary, context);
455+
return managedWorkflow.reconcile(primary, context);
455456
}
457+
return WorkflowReconcileResult.EMPTY;
456458
}
457459

458-
public void cleanupManagedWorkflow(P resource, Context<P> context) {
460+
public WorkflowCleanupResult cleanupManagedWorkflow(P resource, Context<P> context) {
459461
if (managedWorkflow.hasCleaner()) {
460-
managedWorkflow.cleanup(resource, context);
462+
return managedWorkflow.cleanup(resource, context);
461463
}
464+
return WorkflowCleanupResult.EMPTY;
462465
}
463466

464467
public boolean isWorkflowExplicitInvocation() {

Diff for: operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutor.java

+12-12
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ abstract class AbstractWorkflowExecutor<P extends HasMetadata> {
2424
protected final P primary;
2525
protected final ResourceID primaryID;
2626
protected final Context<P> context;
27-
protected final Map<DependentResourceNode<?, P>, WorkflowResult.DetailBuilder<?>> results;
27+
protected final Map<DependentResourceNode<?, P>, BaseWorkflowResult.DetailBuilder<?>> results;
2828
/**
2929
* Covers both deleted and reconciled
3030
*/
@@ -74,30 +74,30 @@ protected boolean noMoreExecutionsScheduled() {
7474
}
7575

7676
protected boolean alreadyVisited(DependentResourceNode<?, P> dependentResourceNode) {
77-
return getResultFlagFor(dependentResourceNode, WorkflowResult.DetailBuilder::isVisited);
77+
return getResultFlagFor(dependentResourceNode, BaseWorkflowResult.DetailBuilder::isVisited);
7878
}
7979

8080
protected boolean postDeleteConditionNotMet(DependentResourceNode<?, P> drn) {
81-
return getResultFlagFor(drn, WorkflowResult.DetailBuilder::hasPostDeleteConditionNotMet);
81+
return getResultFlagFor(drn, BaseWorkflowResult.DetailBuilder::hasPostDeleteConditionNotMet);
8282
}
8383

8484
protected boolean isMarkedForDelete(DependentResourceNode<?, P> drn) {
85-
return getResultFlagFor(drn, WorkflowResult.DetailBuilder::isMarkedForDelete);
85+
return getResultFlagFor(drn, BaseWorkflowResult.DetailBuilder::isMarkedForDelete);
8686
}
8787

88-
protected synchronized WorkflowResult.DetailBuilder createOrGetResultFor(
88+
protected synchronized BaseWorkflowResult.DetailBuilder createOrGetResultFor(
8989
DependentResourceNode<?, P> dependentResourceNode) {
9090
return results.computeIfAbsent(dependentResourceNode,
91-
unused -> new WorkflowResult.DetailBuilder());
91+
unused -> new BaseWorkflowResult.DetailBuilder());
9292
}
9393

94-
protected synchronized Optional<WorkflowResult.DetailBuilder<?>> getResultFor(
94+
protected synchronized Optional<BaseWorkflowResult.DetailBuilder<?>> getResultFor(
9595
DependentResourceNode<?, P> dependentResourceNode) {
9696
return Optional.ofNullable(results.get(dependentResourceNode));
9797
}
9898

9999
protected boolean getResultFlagFor(DependentResourceNode<?, P> dependentResourceNode,
100-
Function<WorkflowResult.DetailBuilder<?>, Boolean> flag) {
100+
Function<BaseWorkflowResult.DetailBuilder<?>, Boolean> flag) {
101101
return getResultFor(dependentResourceNode).map(flag).orElse(false);
102102
}
103103

@@ -117,11 +117,11 @@ protected synchronized void handleExceptionInExecutor(
117117
}
118118

119119
protected boolean isReady(DependentResourceNode<?, P> dependentResourceNode) {
120-
return getResultFlagFor(dependentResourceNode, WorkflowResult.DetailBuilder::isReady);
120+
return getResultFlagFor(dependentResourceNode, BaseWorkflowResult.DetailBuilder::isReady);
121121
}
122122

123123
protected boolean isInError(DependentResourceNode<?, P> dependentResourceNode) {
124-
return getResultFlagFor(dependentResourceNode, WorkflowResult.DetailBuilder::hasError);
124+
return getResultFlagFor(dependentResourceNode, BaseWorkflowResult.DetailBuilder::hasError);
125125
}
126126

127127
protected synchronized void handleNodeExecutionFinish(
@@ -141,7 +141,7 @@ protected <R> boolean isConditionMet(
141141
return condition.map(c -> {
142142
final DetailedCondition.Result<?> r = c.detailedIsMet(dr, primary, context);
143143
synchronized (this) {
144-
results.computeIfAbsent(dependentResource, unused -> new WorkflowResult.DetailBuilder())
144+
results.computeIfAbsent(dependentResource, unused -> new BaseWorkflowResult.DetailBuilder())
145145
.withResultForCondition(c, r);
146146
}
147147
return r;
@@ -173,7 +173,7 @@ protected <R> void registerOrDeregisterEventSourceBasedOnActivation(
173173
}
174174
}
175175

176-
protected synchronized Map<DependentResource, WorkflowResult.Detail<?>> asDetails() {
176+
protected synchronized Map<DependentResource, BaseWorkflowResult.Detail<?>> asDetails() {
177177
return results.entrySet().stream()
178178
.collect(
179179
Collectors.toMap(e -> e.getKey().getDependentResource(), e -> e.getValue().build()));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
package io.javaoperatorsdk.operator.processing.dependent.workflow;
2+
3+
import java.util.List;
4+
import java.util.Map;
5+
import java.util.Map.Entry;
6+
import java.util.Optional;
7+
import java.util.function.Function;
8+
import java.util.stream.Collectors;
9+
import java.util.stream.Stream;
10+
11+
import io.javaoperatorsdk.operator.AggregatedOperatorException;
12+
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
13+
import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult;
14+
15+
@SuppressWarnings("rawtypes")
16+
class BaseWorkflowResult implements WorkflowResult {
17+
private final Map<DependentResource, Detail<?>> results;
18+
private Boolean hasErroredDependents;
19+
20+
BaseWorkflowResult(Map<DependentResource, Detail<?>> results) {
21+
this.results = results;
22+
}
23+
24+
@Override
25+
public Map<DependentResource, Exception> getErroredDependents() {
26+
return getErroredDependentsStream()
27+
.collect(Collectors.toMap(Entry::getKey, entry -> entry.getValue().error));
28+
}
29+
30+
private Stream<Entry<DependentResource, Detail<?>>> getErroredDependentsStream() {
31+
return results.entrySet().stream().filter(entry -> entry.getValue().error != null);
32+
}
33+
34+
protected Map<DependentResource, Detail<?>> results() {
35+
return results;
36+
}
37+
38+
@Override
39+
public Optional<DependentResource> getDependentResourceByName(String name) {
40+
if (name == null || name.isEmpty()) {
41+
return Optional.empty();
42+
}
43+
return results.keySet().stream().filter(dr -> dr.name().equals(name)).findFirst();
44+
}
45+
46+
@Override
47+
public <T> Optional<T> getDependentConditionResult(DependentResource dependentResource,
48+
Condition.Type conditionType, Class<T> expectedResultType) {
49+
if (dependentResource == null) {
50+
return Optional.empty();
51+
}
52+
53+
final var result = new Object[1];
54+
try {
55+
return Optional.ofNullable(results().get(dependentResource))
56+
.flatMap(detail -> detail.getResultForConditionWithType(conditionType))
57+
.map(r -> result[0] = r.getDetail())
58+
.map(expectedResultType::cast);
59+
} catch (Exception e) {
60+
throw new IllegalArgumentException("Condition " +
61+
"result " + result[0] +
62+
" for Dependent " + dependentResource.name() + " doesn't match expected type "
63+
+ expectedResultType.getSimpleName(), e);
64+
}
65+
}
66+
67+
protected List<DependentResource> listFilteredBy(
68+
Function<Detail, Boolean> filter) {
69+
return results.entrySet().stream()
70+
.filter(e -> filter.apply(e.getValue()))
71+
.map(Map.Entry::getKey)
72+
.toList();
73+
}
74+
75+
@Override
76+
public boolean erroredDependentsExist() {
77+
if (hasErroredDependents == null) {
78+
hasErroredDependents = !getErroredDependents().isEmpty();
79+
}
80+
return hasErroredDependents;
81+
}
82+
83+
@Override
84+
public void throwAggregateExceptionIfErrorsPresent() {
85+
if (erroredDependentsExist()) {
86+
throw new AggregatedOperatorException("Exception(s) during workflow execution.",
87+
getErroredDependentsStream()
88+
.collect(Collectors.toMap(e -> e.getKey().name(), e -> e.getValue().error)));
89+
}
90+
}
91+
92+
@SuppressWarnings("UnusedReturnValue")
93+
static class DetailBuilder<R> {
94+
private Exception error;
95+
private ReconcileResult<R> reconcileResult;
96+
private DetailedCondition.Result activationConditionResult;
97+
private DetailedCondition.Result deletePostconditionResult;
98+
private DetailedCondition.Result readyPostconditionResult;
99+
private DetailedCondition.Result reconcilePostconditionResult;
100+
private boolean deleted;
101+
private boolean visited;
102+
private boolean markedForDelete;
103+
104+
Detail<R> build() {
105+
return new Detail<>(error, reconcileResult, activationConditionResult,
106+
deletePostconditionResult, readyPostconditionResult, reconcilePostconditionResult,
107+
deleted, visited, markedForDelete);
108+
}
109+
110+
DetailBuilder<R> withResultForCondition(
111+
ConditionWithType conditionWithType,
112+
DetailedCondition.Result conditionResult) {
113+
switch (conditionWithType.type()) {
114+
case ACTIVATION -> activationConditionResult = conditionResult;
115+
case DELETE -> deletePostconditionResult = conditionResult;
116+
case READY -> readyPostconditionResult = conditionResult;
117+
case RECONCILE -> reconcilePostconditionResult = conditionResult;
118+
default ->
119+
throw new IllegalStateException("Unexpected condition type: " + conditionWithType);
120+
}
121+
return this;
122+
}
123+
124+
DetailBuilder<R> withError(Exception error) {
125+
this.error = error;
126+
return this;
127+
}
128+
129+
DetailBuilder<R> withReconcileResult(ReconcileResult<R> reconcileResult) {
130+
this.reconcileResult = reconcileResult;
131+
return this;
132+
}
133+
134+
DetailBuilder<R> markAsDeleted() {
135+
this.deleted = true;
136+
return this;
137+
}
138+
139+
public boolean hasError() {
140+
return error != null;
141+
}
142+
143+
public boolean hasPostDeleteConditionNotMet() {
144+
return deletePostconditionResult != null && !deletePostconditionResult.isSuccess();
145+
}
146+
147+
public boolean isReady() {
148+
return readyPostconditionResult == null || readyPostconditionResult.isSuccess();
149+
}
150+
151+
DetailBuilder<R> markAsVisited() {
152+
visited = true;
153+
return this;
154+
}
155+
156+
public boolean isVisited() {
157+
return visited;
158+
}
159+
160+
public boolean isMarkedForDelete() {
161+
return markedForDelete;
162+
}
163+
164+
DetailBuilder<R> markForDelete() {
165+
markedForDelete = true;
166+
return this;
167+
}
168+
}
169+
170+
171+
record Detail<R>(Exception error, ReconcileResult<R> reconcileResult,
172+
DetailedCondition.Result activationConditionResult,
173+
DetailedCondition.Result deletePostconditionResult,
174+
DetailedCondition.Result readyPostconditionResult,
175+
DetailedCondition.Result reconcilePostconditionResult,
176+
boolean deleted, boolean visited, boolean markedForDelete) {
177+
178+
boolean isConditionWithTypeMet(Condition.Type conditionType) {
179+
return getResultForConditionWithType(conditionType).map(DetailedCondition.Result::isSuccess)
180+
.orElse(true);
181+
}
182+
183+
Optional<DetailedCondition.Result<?>> getResultForConditionWithType(
184+
Condition.Type conditionType) {
185+
return switch (conditionType) {
186+
case ACTIVATION -> Optional.ofNullable(activationConditionResult);
187+
case DELETE -> Optional.ofNullable(deletePostconditionResult);
188+
case READY -> Optional.ofNullable(readyPostconditionResult);
189+
case RECONCILE -> Optional.ofNullable(reconcilePostconditionResult);
190+
};
191+
}
192+
}
193+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package io.javaoperatorsdk.operator.processing.dependent.workflow;
2+
3+
import java.util.List;
4+
import java.util.Map;
5+
6+
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
7+
8+
@SuppressWarnings("rawtypes")
9+
class DefaultWorkflowCleanupResult extends BaseWorkflowResult implements WorkflowCleanupResult {
10+
private Boolean allPostConditionsMet;
11+
12+
DefaultWorkflowCleanupResult(Map<DependentResource, BaseWorkflowResult.Detail<?>> results) {
13+
super(results);
14+
}
15+
16+
public List<DependentResource> getDeleteCalledOnDependents() {
17+
return listFilteredBy(BaseWorkflowResult.Detail::deleted);
18+
}
19+
20+
public List<DependentResource> getPostConditionNotMetDependents() {
21+
return listFilteredBy(detail -> !detail.isConditionWithTypeMet(Condition.Type.DELETE));
22+
}
23+
24+
public boolean allPostConditionsMet() {
25+
if (allPostConditionsMet == null) {
26+
allPostConditionsMet = getPostConditionNotMetDependents().isEmpty();
27+
}
28+
return allPostConditionsMet;
29+
}
30+
}

0 commit comments

Comments
 (0)