Skip to content

Commit fa43aba

Browse files
authored
fix: cleanup of resources with activation condition (#2223)
Signed-off-by: Attila Mészáros <[email protected]>
1 parent 7d17e4c commit fa43aba

File tree

11 files changed

+227
-19
lines changed

11 files changed

+227
-19
lines changed

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

+19
Original file line numberDiff line numberDiff line change
@@ -128,4 +128,23 @@ protected <R> void submit(DependentResourceNode<R, P> dependentResourceNode,
128128
logger().debug("Submitted to {}: {} primaryID: {}", operation, dependentResourceNode,
129129
primaryID);
130130
}
131+
132+
protected <R> void registerOrDeregisterEventSourceBasedOnActivation(
133+
boolean activationConditionMet,
134+
DependentResourceNode<R, P> dependentResourceNode) {
135+
if (dependentResourceNode.getActivationCondition().isPresent()) {
136+
if (activationConditionMet) {
137+
var eventSource =
138+
dependentResourceNode.getDependentResource().eventSource(context.eventSourceRetriever()
139+
.eventSourceContextForDynamicRegistration());
140+
var es = eventSource.orElseThrow();
141+
context.eventSourceRetriever()
142+
.dynamicallyRegisterEventSource(dependentResourceNode.getName(), es);
143+
144+
} else {
145+
context.eventSourceRetriever()
146+
.dynamicallyDeRegisterEventSource(dependentResourceNode.getName());
147+
}
148+
}
149+
}
131150
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ protected void doRun(DependentResourceNode<R, P> dependentResourceNode,
7070

7171
var active =
7272
isConditionMet(dependentResourceNode.getActivationCondition(), dependentResource);
73+
registerOrDeregisterEventSourceBasedOnActivation(active, dependentResourceNode);
7374

7475
if (dependentResource.isDeletable() && active) {
7576
((Deleter<P>) dependentResource).delete(primary, context);

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

-18
Original file line numberDiff line numberDiff line change
@@ -78,24 +78,6 @@ private synchronized <R> void handleReconcile(DependentResourceNode<R, P> depend
7878
}
7979
}
8080

81-
private <R> void registerOrDeregisterEventSourceBasedOnActivation(boolean activationConditionMet,
82-
DependentResourceNode<R, P> dependentResourceNode) {
83-
if (dependentResourceNode.getActivationCondition().isPresent()) {
84-
if (activationConditionMet) {
85-
var eventSource =
86-
dependentResourceNode.getDependentResource().eventSource(context.eventSourceRetriever()
87-
.eventSourceContextForDynamicRegistration());
88-
var es = eventSource.orElseThrow();
89-
context.eventSourceRetriever()
90-
.dynamicallyRegisterEventSource(dependentResourceNode.getName(), es);
91-
92-
} else {
93-
context.eventSourceRetriever()
94-
.dynamicallyDeRegisterEventSource(dependentResourceNode.getName());
95-
}
96-
}
97-
}
98-
9981
private synchronized void handleDelete(DependentResourceNode dependentResourceNode) {
10082
log.debug("Submitting for delete: {}", dependentResourceNode);
10183

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
1212
import io.javaoperatorsdk.operator.api.reconciler.dependent.GarbageCollected;
1313
import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult;
14+
import io.javaoperatorsdk.operator.processing.dependent.Creator;
1415
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource;
1516
import io.javaoperatorsdk.operator.sample.simple.TestCustomResource;
1617

@@ -55,7 +56,8 @@ public String toString() {
5556
}
5657
}
5758

58-
public class TestDeleterDependent extends TestDependent implements Deleter<TestCustomResource> {
59+
public class TestDeleterDependent extends TestDependent
60+
implements Creator<ConfigMap, TestCustomResource>, Deleter<TestCustomResource> {
5961

6062
public TestDeleterDependent(String name) {
6163
super(name);

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

+18
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,14 @@
77
import org.junit.jupiter.api.BeforeEach;
88
import org.junit.jupiter.api.Test;
99

10+
import io.fabric8.kubernetes.api.model.ConfigMap;
1011
import io.javaoperatorsdk.operator.AggregatedOperatorException;
12+
import io.javaoperatorsdk.operator.MockKubernetesClient;
13+
import io.javaoperatorsdk.operator.api.config.ConfigurationService;
14+
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
1115
import io.javaoperatorsdk.operator.api.reconciler.Context;
16+
import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext;
17+
import io.javaoperatorsdk.operator.processing.event.EventSourceRetriever;
1218
import io.javaoperatorsdk.operator.sample.simple.TestCustomResource;
1319

1420
import static io.javaoperatorsdk.operator.processing.dependent.workflow.ExecutionAssert.assertThat;
@@ -29,7 +35,19 @@ class WorkflowCleanupExecutorTest extends AbstractWorkflowExecutorTest {
2935

3036
@BeforeEach
3137
void setup() {
38+
var eventSourceContextMock = mock(EventSourceContext.class);
39+
var eventSourceRetrieverMock = mock(EventSourceRetriever.class);
40+
var mockControllerConfig = mock(ControllerConfiguration.class);
41+
when(eventSourceRetrieverMock.eventSourceContextForDynamicRegistration())
42+
.thenReturn(eventSourceContextMock);
43+
var client = MockKubernetesClient.client(ConfigMap.class);
44+
when(eventSourceContextMock.getClient()).thenReturn(client);
45+
when(eventSourceContextMock.getControllerConfiguration()).thenReturn(mockControllerConfig);
46+
when(mockControllerConfig.getConfigurationService())
47+
.thenReturn(mock(ConfigurationService.class));
48+
3249
when(mockContext.getWorkflowExecutorService()).thenReturn(executorService);
50+
when(mockContext.eventSourceRetriever()).thenReturn(eventSourceRetrieverMock);
3351
}
3452

3553
@Test
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package io.javaoperatorsdk.operator;
2+
3+
import org.junit.jupiter.api.*;
4+
5+
import io.fabric8.kubernetes.api.model.Namespace;
6+
import io.fabric8.kubernetes.api.model.NamespaceBuilder;
7+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
8+
import io.fabric8.kubernetes.client.KubernetesClient;
9+
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
10+
import io.fabric8.kubernetes.client.utils.KubernetesResourceUtil;
11+
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
12+
import io.javaoperatorsdk.operator.sample.workflowactivationcleanup.WorkflowActivationCleanupCustomResource;
13+
import io.javaoperatorsdk.operator.sample.workflowactivationcleanup.WorkflowActivationCleanupReconciler;
14+
import io.javaoperatorsdk.operator.sample.workflowactivationcleanup.WorkflowActivationCleanupSpec;
15+
16+
import static org.assertj.core.api.Assertions.assertThat;
17+
import static org.awaitility.Awaitility.await;
18+
19+
public class WorkflowActivationCleanupIT {
20+
21+
private final KubernetesClient client = new KubernetesClientBuilder().build();
22+
private Operator operator;
23+
24+
private String testNamespace;
25+
26+
@BeforeEach
27+
void beforeEach(TestInfo testInfo) {
28+
LocallyRunOperatorExtension.applyCrd(WorkflowActivationCleanupCustomResource.class,
29+
client);
30+
31+
testInfo.getTestMethod()
32+
.ifPresent(method -> testNamespace = KubernetesResourceUtil.sanitizeName(method.getName()));
33+
client.namespaces().resource(testNamespace(testNamespace)).create();
34+
operator = new Operator(o -> o.withCloseClientOnStop(false));
35+
operator.register(new WorkflowActivationCleanupReconciler(),
36+
o -> o.settingNamespaces(testNamespace));
37+
}
38+
39+
@AfterEach
40+
void stopOperator() {
41+
client.namespaces().withName(testNamespace).delete();
42+
await().untilAsserted(() -> {
43+
var ns = client.namespaces().withName(testNamespace).get();
44+
assertThat(ns).isNull();
45+
});
46+
operator.stop();
47+
}
48+
49+
@Test
50+
void testCleanupOnMarkedResourceOnOperatorStartup() {
51+
var resource = client.resource(testResourceWithFinalizer()).create();
52+
client.resource(resource).delete();
53+
operator.start();
54+
55+
await().untilAsserted(() -> {
56+
var res = client.resource(resource).get();
57+
assertThat(res).isNull();
58+
});
59+
}
60+
61+
private WorkflowActivationCleanupCustomResource testResourceWithFinalizer() {
62+
var resource = new WorkflowActivationCleanupCustomResource();
63+
resource.setMetadata(new ObjectMetaBuilder()
64+
.withName("test1")
65+
.withFinalizers("workflowactivationcleanupcustomresources.sample.javaoperatorsdk/finalizer")
66+
.withNamespace(testNamespace)
67+
.build());
68+
resource.setSpec(new WorkflowActivationCleanupSpec());
69+
resource.getSpec().setValue("val1");
70+
return resource;
71+
}
72+
73+
private Namespace testNamespace(String name) {
74+
return new NamespaceBuilder().withMetadata(new ObjectMetaBuilder()
75+
.withName(name)
76+
.build()).build();
77+
}
78+
79+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package io.javaoperatorsdk.operator.sample.workflowactivationcleanup;
2+
3+
import java.util.Map;
4+
5+
import io.fabric8.kubernetes.api.model.ConfigMap;
6+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
7+
import io.javaoperatorsdk.operator.api.reconciler.Context;
8+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDNoGCKubernetesDependentResource;
9+
10+
public class ConfigMapDependentResource
11+
extends
12+
CRUDNoGCKubernetesDependentResource<ConfigMap, WorkflowActivationCleanupCustomResource> {
13+
14+
public static final String DATA_KEY = "data";
15+
16+
public ConfigMapDependentResource() {
17+
super(ConfigMap.class);
18+
}
19+
20+
@Override
21+
protected ConfigMap desired(WorkflowActivationCleanupCustomResource primary,
22+
Context<WorkflowActivationCleanupCustomResource> context) {
23+
ConfigMap configMap = new ConfigMap();
24+
configMap.setMetadata(new ObjectMetaBuilder()
25+
.withName(primary.getMetadata().getName())
26+
.withNamespace(primary.getMetadata().getNamespace())
27+
.build());
28+
configMap.setData(Map.of(DATA_KEY, primary.getSpec().getValue()));
29+
return configMap;
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package io.javaoperatorsdk.operator.sample.workflowactivationcleanup;
2+
3+
import io.fabric8.kubernetes.api.model.ConfigMap;
4+
import io.javaoperatorsdk.operator.api.reconciler.Context;
5+
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
6+
import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition;
7+
8+
public class TestActivcationCondition
9+
implements Condition<ConfigMap, WorkflowActivationCleanupCustomResource> {
10+
11+
@Override
12+
public boolean isMet(
13+
DependentResource<ConfigMap, WorkflowActivationCleanupCustomResource> dependentResource,
14+
WorkflowActivationCleanupCustomResource primary,
15+
Context<WorkflowActivationCleanupCustomResource> context) {
16+
return true;
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package io.javaoperatorsdk.operator.sample.workflowactivationcleanup;
2+
3+
import io.fabric8.kubernetes.api.model.Namespaced;
4+
import io.fabric8.kubernetes.client.CustomResource;
5+
import io.fabric8.kubernetes.model.annotation.Group;
6+
import io.fabric8.kubernetes.model.annotation.ShortNames;
7+
import io.fabric8.kubernetes.model.annotation.Version;
8+
9+
@Group("sample.javaoperatorsdk")
10+
@Version("v1")
11+
@ShortNames("wacc")
12+
public class WorkflowActivationCleanupCustomResource
13+
extends CustomResource<WorkflowActivationCleanupSpec, Void>
14+
implements Namespaced {
15+
16+
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package io.javaoperatorsdk.operator.sample.workflowactivationcleanup;
2+
3+
import io.javaoperatorsdk.operator.api.reconciler.*;
4+
import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
5+
6+
@ControllerConfiguration(dependents = {
7+
@Dependent(type = ConfigMapDependentResource.class,
8+
activationCondition = TestActivcationCondition.class),
9+
})
10+
public class WorkflowActivationCleanupReconciler
11+
implements Reconciler<WorkflowActivationCleanupCustomResource>,
12+
Cleaner<WorkflowActivationCleanupCustomResource> {
13+
14+
@Override
15+
public UpdateControl<WorkflowActivationCleanupCustomResource> reconcile(
16+
WorkflowActivationCleanupCustomResource resource,
17+
Context<WorkflowActivationCleanupCustomResource> context) {
18+
19+
return UpdateControl.noUpdate();
20+
}
21+
22+
@Override
23+
public DeleteControl cleanup(WorkflowActivationCleanupCustomResource resource,
24+
Context<WorkflowActivationCleanupCustomResource> context) {
25+
return DeleteControl.defaultDelete();
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package io.javaoperatorsdk.operator.sample.workflowactivationcleanup;
2+
3+
public class WorkflowActivationCleanupSpec {
4+
5+
private String value;
6+
7+
public String getValue() {
8+
return value;
9+
}
10+
11+
public void setValue(String value) {
12+
this.value = value;
13+
}
14+
}

0 commit comments

Comments
 (0)