Skip to content

Commit 3666014

Browse files
committed
feat: garbage collected interface (#1164)
1 parent b4237f4 commit 3666014

File tree

23 files changed

+305
-72
lines changed

23 files changed

+305
-72
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.javaoperatorsdk.operator.api.reconciler.dependent;
2+
3+
import io.fabric8.kubernetes.api.model.HasMetadata;
4+
5+
/**
6+
* Should be implemented by {@link DependentResource} implementations that are explicitly deleted
7+
* during reconciliation but which should also benefit from Kubernetes' automated garbage collection
8+
* during the cleanup phase.
9+
* <p>
10+
* See <a href="https://github.com/java-operator-sdk/java-operator-sdk/issues/1127">this issue</a>
11+
* for more details.
12+
*/
13+
public interface GarbageCollected<P extends HasMetadata> extends Deleter<P> {
14+
15+
}

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

+7-5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter;
2424
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
2525
import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceProvider;
26+
import io.javaoperatorsdk.operator.api.reconciler.dependent.GarbageCollected;
2627
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DependentResourceConfigurator;
2728
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.KubernetesClientAware;
2829
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.ManagedDependentResourceException;
@@ -62,15 +63,16 @@ public Controller(Reconciler<P> reconciler,
6263

6364
final var hasDeleterHolder = new boolean[] {false};
6465
final var specs = configuration.getDependentResources();
65-
final var size = specs.size();
66-
if (size == 0) {
66+
final var specsSize = specs.size();
67+
if (specsSize == 0) {
6768
dependents = new LinkedHashMap<>();
6869
} else {
69-
final Map<String, DependentResource> dependentsHolder = new LinkedHashMap<>(size);
70+
final Map<String, DependentResource> dependentsHolder = new LinkedHashMap<>(specsSize);
7071
specs.forEach(drs -> {
7172
final var dependent = createAndConfigureFrom(drs, kubernetesClient);
7273
// check if dependent implements Deleter to record that fact
73-
if (!hasDeleterHolder[0] && dependent instanceof Deleter) {
74+
if (!hasDeleterHolder[0] && dependent instanceof Deleter
75+
&& !(dependent instanceof GarbageCollected)) {
7476
hasDeleterHolder[0] = true;
7577
}
7678
dependentsHolder.put(drs.getName(), dependent);
@@ -131,7 +133,7 @@ public DeleteControl execute() {
131133
initContextIfNeeded(resource, context);
132134
if (hasDeleterDependents) {
133135
dependents.values().stream()
134-
.filter(d -> d instanceof Deleter)
136+
.filter(d -> d instanceof Deleter && !(d instanceof GarbageCollected))
135137
.map(Deleter.class::cast)
136138
.forEach(deleter -> deleter.delete(resource, context));
137139
}

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
import io.fabric8.kubernetes.api.model.HasMetadata;
77
import io.javaoperatorsdk.operator.api.reconciler.Context;
8-
import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter;
98
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
109
import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult;
1110
import io.javaoperatorsdk.operator.processing.event.ResourceID;
@@ -16,7 +15,7 @@ public abstract class AbstractDependentResource<R, P extends HasMetadata>
1615

1716
protected final boolean creatable = this instanceof Creator;
1817
protected final boolean updatable = this instanceof Updater;
19-
protected final boolean deletable = this instanceof Deleter;
18+
2019
protected Creator<R, P> creator;
2120
protected Updater<R, P> updater;
2221

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

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
package io.javaoperatorsdk.operator.processing.dependent.kubernetes;
22

33
import io.fabric8.kubernetes.api.model.HasMetadata;
4-
import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter;
4+
import io.javaoperatorsdk.operator.api.reconciler.dependent.GarbageCollected;
55
import io.javaoperatorsdk.operator.processing.dependent.Creator;
66
import io.javaoperatorsdk.operator.processing.dependent.Updater;
77

88
/**
9-
* Adaptor Class for standalone mode for resources that manages Create, Read, Update and Delete
9+
* Adaptor class for standalone mode for resources that manage Create, Read and Update operations
10+
* and that should be automatically garbage-collected by Kubernetes when the associated primary
11+
* resource is destroyed.
1012
*
11-
* @param <R> Managed resource
12-
* @param <P> Primary Resource
13+
* @param <R> the type of the managed dependent resource
14+
* @param <P> the type of the associated primary resource
1315
*/
1416
public abstract class CRUDKubernetesDependentResource<R extends HasMetadata, P extends HasMetadata>
1517
extends
16-
KubernetesDependentResource<R, P> implements Creator<R, P>, Updater<R, P>, Deleter<P> {
18+
KubernetesDependentResource<R, P>
19+
implements Creator<R, P>, Updater<R, P>, GarbageCollected<P> {
1720

1821
public CRUDKubernetesDependentResource(Class<R> resourceType) {
1922
super(resourceType);

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

-21
This file was deleted.

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

+5-5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration;
1515
import io.javaoperatorsdk.operator.api.reconciler.Context;
1616
import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext;
17+
import io.javaoperatorsdk.operator.api.reconciler.dependent.GarbageCollected;
1718
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DependentResourceConfigurator;
1819
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.KubernetesClientAware;
1920
import io.javaoperatorsdk.operator.processing.dependent.AbstractEventSourceHolderDependentResource;
@@ -35,6 +36,7 @@ public abstract class KubernetesDependentResource<R extends HasMetadata, P exten
3536
private final Matcher<R, P> matcher;
3637
private final ResourceUpdatePreProcessor<R> processor;
3738
private final Class<R> resourceType;
39+
private final boolean garbageCollected = this instanceof GarbageCollected;
3840

3941
@SuppressWarnings("unchecked")
4042
public KubernetesDependentResource(Class<R> resourceType) {
@@ -114,10 +116,8 @@ public Result<R> match(R actualResource, P primary, Context<P> context) {
114116
}
115117

116118
public void delete(P primary, Context<P> context) {
117-
if (!addOwnerReference()) {
118-
var resource = getSecondaryResource(primary);
119-
resource.ifPresent(r -> client.resource(r).delete());
120-
}
119+
var resource = getSecondaryResource(primary);
120+
resource.ifPresent(r -> client.resource(r).delete());
121121
}
122122

123123
@SuppressWarnings("unchecked")
@@ -143,7 +143,7 @@ protected InformerEventSource<R, P> createEventSource(EventSourceContext<P> cont
143143
}
144144

145145
protected boolean addOwnerReference() {
146-
return creatable && !deletable;
146+
return garbageCollected;
147147
}
148148

149149
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package io.javaoperatorsdk.operator;
2+
3+
import java.time.Duration;
4+
5+
import org.junit.jupiter.api.Test;
6+
import org.junit.jupiter.api.extension.RegisterExtension;
7+
8+
import io.fabric8.kubernetes.api.model.ConfigMap;
9+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
10+
import io.javaoperatorsdk.operator.junit.OperatorExtension;
11+
import io.javaoperatorsdk.operator.sample.kubernetesdependentgarbagecollection.DependentGarbageCollectionTestCustomResource;
12+
import io.javaoperatorsdk.operator.sample.kubernetesdependentgarbagecollection.DependentGarbageCollectionTestCustomResourceSpec;
13+
import io.javaoperatorsdk.operator.sample.kubernetesdependentgarbagecollection.DependentGarbageCollectionTestReconciler;
14+
15+
import static org.assertj.core.api.Assertions.assertThat;
16+
import static org.awaitility.Awaitility.await;
17+
18+
class KubernetesDependentGarbageCollectionIT {
19+
20+
public static final String TEST_RESOURCE_NAME = "test1";
21+
@RegisterExtension
22+
OperatorExtension operator =
23+
OperatorExtension.builder()
24+
.withReconciler(new DependentGarbageCollectionTestReconciler())
25+
.build();
26+
27+
28+
@Test
29+
void resourceSecondaryResourceIsGarbageCollected() {
30+
var resource = customResource();
31+
var createdResources =
32+
operator.create(DependentGarbageCollectionTestCustomResource.class, resource);
33+
34+
await().untilAsserted(() -> {
35+
ConfigMap configMap = operator.get(ConfigMap.class, TEST_RESOURCE_NAME);
36+
assertThat(configMap).isNotNull();
37+
});
38+
39+
ConfigMap configMap = operator.get(ConfigMap.class, TEST_RESOURCE_NAME);
40+
assertThat(configMap.getMetadata().getOwnerReferences()).hasSize(1);
41+
assertThat(configMap.getMetadata().getOwnerReferences().get(0).getName())
42+
.isEqualTo(TEST_RESOURCE_NAME);
43+
44+
operator.delete(DependentGarbageCollectionTestCustomResource.class, createdResources);
45+
46+
await().atMost(Duration.ofSeconds(30)).untilAsserted(() -> {
47+
ConfigMap cm = operator.get(ConfigMap.class, TEST_RESOURCE_NAME);
48+
assertThat(cm).isNull();
49+
});
50+
}
51+
52+
@Test
53+
void deletesSecondaryResource() {
54+
var resource = customResource();
55+
var createdResources =
56+
operator.create(DependentGarbageCollectionTestCustomResource.class, resource);
57+
58+
await().untilAsserted(() -> {
59+
ConfigMap configMap = operator.get(ConfigMap.class, TEST_RESOURCE_NAME);
60+
assertThat(configMap).isNotNull();
61+
});
62+
63+
createdResources.getSpec().setCreateConfigMap(false);
64+
operator.replace(DependentGarbageCollectionTestCustomResource.class, createdResources);
65+
66+
await().untilAsserted(() -> {
67+
ConfigMap cm = operator.get(ConfigMap.class, TEST_RESOURCE_NAME);
68+
assertThat(cm).isNull();
69+
});
70+
}
71+
72+
DependentGarbageCollectionTestCustomResource customResource() {
73+
DependentGarbageCollectionTestCustomResource resource =
74+
new DependentGarbageCollectionTestCustomResource();
75+
resource.setMetadata(new ObjectMetaBuilder()
76+
.withName(TEST_RESOURCE_NAME)
77+
.build());
78+
resource.setSpec(new DependentGarbageCollectionTestCustomResourceSpec());
79+
resource.getSpec().setCreateConfigMap(true);
80+
return resource;
81+
}
82+
83+
}

operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/cleanermanageddependent/ConfigMapDependentResource.java

+8-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,16 @@
66
import io.fabric8.kubernetes.api.model.ConfigMap;
77
import io.fabric8.kubernetes.api.model.ObjectMeta;
88
import io.javaoperatorsdk.operator.api.reconciler.Context;
9-
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
9+
import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter;
10+
import io.javaoperatorsdk.operator.processing.dependent.Creator;
11+
import io.javaoperatorsdk.operator.processing.dependent.Updater;
12+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource;
1013

1114
public class ConfigMapDependentResource extends
12-
CRUDKubernetesDependentResource<ConfigMap, CleanerForManagedDependentCustomResource> {
15+
KubernetesDependentResource<ConfigMap, CleanerForManagedDependentCustomResource>
16+
implements Creator<ConfigMap, CleanerForManagedDependentCustomResource>,
17+
Updater<ConfigMap, CleanerForManagedDependentCustomResource>,
18+
Deleter<CleanerForManagedDependentCustomResource> {
1319

1420
private static final AtomicInteger numberOfCleanupExecutions = new AtomicInteger(0);
1521

operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentoperationeventfiltering/ConfigMapDependentResource.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
import io.fabric8.kubernetes.api.model.ConfigMap;
66
import io.fabric8.kubernetes.api.model.ObjectMeta;
77
import io.javaoperatorsdk.operator.api.reconciler.Context;
8-
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUKubernetesDependentResource;
8+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
99

1010
public class ConfigMapDependentResource extends
11-
CRUKubernetesDependentResource<ConfigMap, DependentOperationEventFilterCustomResource> {
11+
CRUDKubernetesDependentResource<ConfigMap, DependentOperationEventFilterCustomResource> {
1212

1313
public static final String KEY = "key1";
1414

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.javaoperatorsdk.operator.sample.kubernetesdependentgarbagecollection;
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("dgc")
12+
public class DependentGarbageCollectionTestCustomResource
13+
extends
14+
CustomResource<DependentGarbageCollectionTestCustomResourceSpec, DependentGarbageCollectionTestCustomResourceStatus>
15+
implements Namespaced {
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.javaoperatorsdk.operator.sample.kubernetesdependentgarbagecollection;
2+
3+
public class DependentGarbageCollectionTestCustomResourceSpec {
4+
5+
private boolean createConfigMap;
6+
7+
public boolean isCreateConfigMap() {
8+
return createConfigMap;
9+
}
10+
11+
public DependentGarbageCollectionTestCustomResourceSpec setCreateConfigMap(
12+
boolean createConfigMap) {
13+
this.createConfigMap = createConfigMap;
14+
return this;
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package io.javaoperatorsdk.operator.sample.kubernetesdependentgarbagecollection;
2+
3+
public class DependentGarbageCollectionTestCustomResourceStatus {
4+
5+
}

0 commit comments

Comments
 (0)