Skip to content

Commit 47ef076

Browse files
authored
feat: compute cleaner status based on specs, resolve from config (#1648)
1 parent 5f69aa2 commit 47ef076

File tree

12 files changed

+113
-68
lines changed

12 files changed

+113
-68
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public Controller(Reconciler<P> reconciler,
8484
contextInitializer = reconciler instanceof ContextInitializer;
8585
isCleaner = reconciler instanceof Cleaner;
8686
managedWorkflow = configurationService.getWorkflowFactory().workflowFor(configuration);
87-
managedWorkflow.resolve(kubernetesClient, configuration.getDependentResources());
87+
managedWorkflow.resolve(kubernetesClient, configuration);
8888

8989
eventSourceManager = new EventSourceManager<>(this);
9090
eventProcessor = new EventProcessor<>(eventSourceManager);

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package io.javaoperatorsdk.operator.processing.dependent;
22

3-
import java.util.*;
3+
import java.util.Optional;
44

55
import org.slf4j.Logger;
66
import org.slf4j.LoggerFactory;
@@ -9,6 +9,7 @@
99
import io.javaoperatorsdk.operator.api.reconciler.Context;
1010
import io.javaoperatorsdk.operator.api.reconciler.Ignore;
1111
import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator;
12+
import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter;
1213
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
1314
import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult;
1415
import io.javaoperatorsdk.operator.processing.dependent.Matcher.Result;
@@ -21,6 +22,7 @@ public abstract class AbstractDependentResource<R, P extends HasMetadata>
2122

2223
private final boolean creatable = this instanceof Creator;
2324
private final boolean updatable = this instanceof Updater;
25+
private final boolean deletable = this instanceof Deleter;
2426

2527
protected Creator<R, P> creator;
2628
protected Updater<R, P> updater;
@@ -172,4 +174,9 @@ protected boolean isCreatable() {
172174
protected boolean isUpdatable() {
173175
return updatable;
174176
}
177+
178+
@Override
179+
public boolean isDeletable() {
180+
return deletable;
181+
}
175182
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultManagedWorkflow.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import io.fabric8.kubernetes.api.model.HasMetadata;
88
import io.fabric8.kubernetes.client.KubernetesClient;
9+
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
910
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
1011
import io.javaoperatorsdk.operator.api.reconciler.Context;
1112
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
@@ -49,9 +50,10 @@ public Map<String, DependentResource> getDependentResourcesByName() {
4950
}
5051

5152
@Override
52-
public ManagedWorkflow<P> resolve(KubernetesClient client, List<DependentResourceSpec> specs) {
53+
public ManagedWorkflow<P> resolve(KubernetesClient client,
54+
ControllerConfiguration<P> configuration) {
5355
if (!resolved) {
54-
workflow.resolve(client, specs);
56+
workflow.resolve(client, configuration);
5557
resolved = true;
5658
}
5759
return this;

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import io.fabric8.kubernetes.api.model.HasMetadata;
77
import io.fabric8.kubernetes.client.KubernetesClient;
8-
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
8+
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
99

1010
@SuppressWarnings("rawtypes")
1111
public interface DependentResourceNode<R, P extends HasMetadata> {
@@ -26,5 +26,5 @@ public interface DependentResourceNode<R, P extends HasMetadata> {
2626

2727
String getName();
2828

29-
default void resolve(KubernetesClient client, List<DependentResourceSpec> dependentResources) {}
29+
default void resolve(KubernetesClient client, ControllerConfiguration<P> configuration) {}
3030
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflow.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
package io.javaoperatorsdk.operator.processing.dependent.workflow;
22

33
import java.util.Collections;
4-
import java.util.List;
54
import java.util.Map;
65

76
import io.fabric8.kubernetes.api.model.HasMetadata;
87
import io.fabric8.kubernetes.client.KubernetesClient;
9-
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
8+
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
109
import io.javaoperatorsdk.operator.api.reconciler.Context;
1110
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
1211

@@ -40,7 +39,7 @@ public Map<String, DependentResource> getDependentResourcesByName() {
4039
}
4140

4241
@Override
43-
public ManagedWorkflow resolve(KubernetesClient client, List dependentResources) {
42+
public ManagedWorkflow resolve(KubernetesClient client, ControllerConfiguration configuration) {
4443
return this;
4544
}
4645
};
@@ -55,6 +54,5 @@ public ManagedWorkflow resolve(KubernetesClient client, List dependentResources)
5554

5655
Map<String, DependentResource> getDependentResourcesByName();
5756

58-
ManagedWorkflow<P> resolve(KubernetesClient client,
59-
List<DependentResourceSpec> dependentResources);
57+
ManagedWorkflow<P> resolve(KubernetesClient client, ControllerConfiguration<P> configuration);
6058
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowSupport.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,19 @@ public <P extends HasMetadata> Workflow<P> createWorkflow(
5050
List<DependentResourceSpec> dependentResourceSpecs) {
5151
var orderedResourceSpecs = orderAndDetectCycles(dependentResourceSpecs);
5252
final var alreadyCreated = new ArrayList<DependentResourceNode>(orderedResourceSpecs.size());
53+
final boolean[] cleanerHolder = {false};
5354
final var nodes = orderedResourceSpecs.stream()
54-
.map(spec -> createFrom(spec, alreadyCreated))
55+
.map(spec -> createFrom(spec, alreadyCreated, cleanerHolder))
5556
.collect(Collectors.toSet());
56-
return new Workflow<>(nodes);
57+
return new Workflow<>(nodes, cleanerHolder[0]);
5758
}
5859

5960
private DependentResourceNode createFrom(DependentResourceSpec spec,
60-
List<DependentResourceNode> alreadyCreated) {
61+
List<DependentResourceNode> alreadyCreated, boolean[] cleanerHolder) {
6162
final var node = new SpecDependentResourceNode<>(spec);
6263
alreadyCreated.add(node);
64+
// if any previously checked dependent was a cleaner, no need to check further
65+
cleanerHolder[0] = cleanerHolder[0] || Workflow.isDeletable(spec.getDependentResourceClass());
6366
spec.getDependsOn().forEach(depend -> {
6467
final DependentResourceNode dependsOn = alreadyCreated.stream()
6568
.filter(drn -> depend.equals(drn.getName())).findFirst()

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/SpecDependentResourceNode.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package io.javaoperatorsdk.operator.processing.dependent.workflow;
22

3-
import java.util.List;
4-
53
import io.fabric8.kubernetes.api.model.HasMetadata;
64
import io.fabric8.kubernetes.client.KubernetesClient;
5+
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
76
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
87
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
98
import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceReferencer;
@@ -21,8 +20,8 @@ public SpecDependentResourceNode(DependentResourceSpec<R, P, ?> spec) {
2120

2221
@Override
2322
@SuppressWarnings({"rawtypes", "unchecked"})
24-
public void resolve(KubernetesClient client, List<DependentResourceSpec> dependentResources) {
25-
final var spec = dependentResources.stream()
23+
public void resolve(KubernetesClient client, ControllerConfiguration<P> configuration) {
24+
final var spec = configuration.getDependentResources().stream()
2625
.filter(drs -> drs.getName().equals(getName()))
2726
.findFirst().orElseThrow();
2827

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,13 @@
1111

1212
import io.fabric8.kubernetes.api.model.HasMetadata;
1313
import io.fabric8.kubernetes.client.KubernetesClient;
14+
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
1415
import io.javaoperatorsdk.operator.api.config.ExecutorServiceManager;
15-
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
1616
import io.javaoperatorsdk.operator.api.reconciler.Context;
17+
import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter;
1718
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
19+
import io.javaoperatorsdk.operator.api.reconciler.dependent.GarbageCollected;
20+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource;
1821

1922
/**
2023
* Dependents definition: so if B depends on A, the B is dependent of A.
@@ -34,23 +37,40 @@ public class Workflow<P extends HasMetadata> {
3437
// it's "global" executor service shared between multiple reconciliations running parallel
3538
private final ExecutorService executorService;
3639
private boolean resolved;
37-
private boolean hasCleaner;
40+
private final boolean hasCleaner;
3841

39-
Workflow(Set<DependentResourceNode> dependentResourceNodes) {
42+
Workflow(Set<DependentResourceNode> dependentResourceNodes, boolean hasCleaner) {
4043
this(dependentResourceNodes, ExecutorServiceManager.instance().workflowExecutorService(),
41-
THROW_EXCEPTION_AUTOMATICALLY_DEFAULT, false, false);
44+
THROW_EXCEPTION_AUTOMATICALLY_DEFAULT, false, hasCleaner);
4245
}
4346

4447
Workflow(Set<DependentResourceNode> dependentResourceNodes,
4548
ExecutorService executorService, boolean throwExceptionAutomatically, boolean resolved,
4649
boolean hasCleaner) {
4750
this.executorService = executorService;
48-
this.dependentResourceNodes = dependentResourceNodes.stream()
49-
.collect(Collectors.toMap(DependentResourceNode::getName, Function.identity()));
51+
this.dependentResourceNodes = toMap(dependentResourceNodes);
5052
this.throwExceptionAutomatically = throwExceptionAutomatically;
5153
this.resolved = resolved;
5254
this.hasCleaner = hasCleaner;
53-
preprocessForReconcile();
55+
}
56+
57+
private Map<String, DependentResourceNode> toMap(
58+
Set<DependentResourceNode> dependentResourceNodes) {
59+
final var nodes = new ArrayList<>(dependentResourceNodes);
60+
bottomLevelResource.addAll(nodes);
61+
return dependentResourceNodes.stream()
62+
.peek(drn -> {
63+
// add cycle detection?
64+
if (drn.getDependsOn().isEmpty()) {
65+
topLevelResources.add(drn);
66+
} else {
67+
for (DependentResourceNode dependsOn : (List<DependentResourceNode>) drn
68+
.getDependsOn()) {
69+
bottomLevelResource.remove(dependsOn);
70+
}
71+
}
72+
})
73+
.collect(Collectors.toMap(DependentResourceNode::getName, Function.identity()));
5474
}
5575

5676
public DependentResource getDependentResourceFor(DependentResourceNode node) {
@@ -92,22 +112,6 @@ public WorkflowCleanupResult cleanup(P primary, Context<P> context) {
92112
return result;
93113
}
94114

95-
// add cycle detection?
96-
@SuppressWarnings("unchecked")
97-
private void preprocessForReconcile() {
98-
final var nodes = new ArrayList<>(dependentResourceNodes.values());
99-
bottomLevelResource.addAll(nodes);
100-
for (DependentResourceNode<?, P> node : nodes) {
101-
if (node.getDependsOn().isEmpty()) {
102-
topLevelResources.add(node);
103-
} else {
104-
for (DependentResourceNode dependsOn : node.getDependsOn()) {
105-
bottomLevelResource.remove(dependsOn);
106-
}
107-
}
108-
}
109-
}
110-
111115
Set<DependentResourceNode> getTopLevelDependentResources() {
112116
return topLevelResources;
113117
}
@@ -125,24 +129,26 @@ Map<String, DependentResourceNode> nodes() {
125129
}
126130

127131
@SuppressWarnings("unchecked")
128-
void resolve(KubernetesClient client, List<DependentResourceSpec> dependentResources) {
132+
void resolve(KubernetesClient client, ControllerConfiguration<P> configuration) {
129133
if (!resolved) {
130-
final boolean[] cleanerHolder = {false};
131-
dependentResourceNodes.values()
132-
.forEach(drn -> {
133-
drn.resolve(client, dependentResources);
134-
final var dr = dependentResource(drn);
135-
if (dr.isDeletable()) {
136-
cleanerHolder[0] = true;
137-
}
138-
});
134+
dependentResourceNodes.values().forEach(drn -> drn.resolve(client, configuration));
139135
resolved = true;
140-
hasCleaner = cleanerHolder[0];
141136
}
142137
}
143138

144139
boolean hasCleaner() {
145-
throwIfUnresolved();
146140
return hasCleaner;
147141
}
142+
143+
static boolean isDeletable(Class<? extends DependentResource> drClass) {
144+
final var isDeleter = Deleter.class.isAssignableFrom(drClass);
145+
if (!isDeleter) {
146+
return false;
147+
}
148+
149+
if (KubernetesDependentResource.class.isAssignableFrom(drClass)) {
150+
return !GarbageCollected.class.isAssignableFrom(drClass);
151+
}
152+
return true;
153+
}
148154
}

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowSupportTest.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,11 @@
88
import org.junit.jupiter.api.Assertions;
99
import org.junit.jupiter.api.Test;
1010

11-
import io.fabric8.kubernetes.client.KubernetesClient;
1211
import io.javaoperatorsdk.operator.OperatorException;
1312
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
1413

1514
import static io.javaoperatorsdk.operator.processing.dependent.workflow.ManagedWorkflowTestUtils.createDRS;
1615
import static org.assertj.core.api.Assertions.assertThat;
17-
import static org.mockito.Mockito.mock;
1816

1917
class ManagedWorkflowSupportTest {
2018

@@ -140,9 +138,7 @@ void createsWorkflow() {
140138
createDRS(NAME_3, NAME_1),
141139
createDRS(NAME_4, NAME_3, NAME_2));
142140

143-
final var client = mock(KubernetesClient.class);
144141
var workflow = managedWorkflowSupport.createWorkflow(specs);
145-
workflow.resolve(client, specs);
146142

147143
assertThat(workflow.nodes().values()).map(DependentResourceNode::getName)
148144
.containsExactlyInAnyOrder(NAME_1, NAME_2, NAME_3, NAME_4);

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowTest.java

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
import org.junit.jupiter.api.Test;
66

7-
import io.fabric8.kubernetes.client.KubernetesClient;
87
import io.javaoperatorsdk.operator.api.config.ConfigurationServiceProvider;
98
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
109
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
@@ -17,7 +16,7 @@
1716
import static org.mockito.Mockito.mock;
1817
import static org.mockito.Mockito.when;
1918

20-
@SuppressWarnings({"rawtypes", "unchecked"})
19+
@SuppressWarnings({"rawtypes"})
2120
class ManagedWorkflowTest {
2221

2322
public static final String NAME = "name";
@@ -39,6 +38,19 @@ void isNotCleanerIfGarbageCollected() {
3938
.isCleaner()).isFalse();
4039
}
4140

41+
@Test
42+
void isCleanerShouldWork() {
43+
assertThat(managedWorkflow(
44+
createDRSWithTraits(NAME, GarbageCollected.class),
45+
createDRSWithTraits("foo", Deleter.class))
46+
.isCleaner()).isTrue();
47+
48+
assertThat(managedWorkflow(
49+
createDRSWithTraits("foo", Deleter.class),
50+
createDRSWithTraits(NAME, GarbageCollected.class))
51+
.isCleaner()).isTrue();
52+
}
53+
4254
@Test
4355
void isCleanerIfHasDeleter() {
4456
var spec = createDRSWithTraits(NAME, Deleter.class);
@@ -49,12 +61,9 @@ ManagedWorkflow managedWorkflow(DependentResourceSpec... specs) {
4961
final var configuration = mock(ControllerConfiguration.class);
5062
final var specList = List.of(specs);
5163

52-
KubernetesClient kubernetesClientMock = mock(KubernetesClient.class);
53-
5464
when(configuration.getDependentResources()).thenReturn(specList);
5565
return ConfigurationServiceProvider.instance().getWorkflowFactory()
56-
.workflowFor(configuration)
57-
.resolve(kubernetesClientMock, specList);
66+
.workflowFor(configuration);
5867
}
5968

6069
}

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowTestUtils.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
1010
import io.javaoperatorsdk.operator.api.reconciler.dependent.GarbageCollected;
1111
import io.javaoperatorsdk.operator.processing.dependent.EmptyTestDependentResource;
12+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource;
1213

1314
import static org.mockito.Mockito.mock;
1415
import static org.mockito.Mockito.when;
@@ -31,13 +32,12 @@ public static DependentResourceSpec createDRSWithTraits(String name,
3132
Class<? extends DependentResource> toMock = DependentResource.class;
3233
final var garbageCollected = dependentResourceTraits != null &&
3334
Arrays.asList(dependentResourceTraits).contains(GarbageCollected.class);
35+
if (garbageCollected) {
36+
toMock = KubernetesDependentResource.class;
37+
}
3438

3539
final var dr = mock(toMock, withSettings().extraInterfaces(dependentResourceTraits));
36-
// it would be better to call the real method here but it doesn't work because
37-
// KubernetesDependentResource checks for GarbageCollected trait when instantiated which doesn't
38-
// happen when using mocks
39-
when(dr.isDeletable()).thenReturn(!garbageCollected);
40-
when(spy.getDependentResource()).thenReturn(dr);
40+
when(spy.getDependentResourceClass()).thenReturn(dr.getClass());
4141
return spy;
4242
}
4343

0 commit comments

Comments
 (0)