Skip to content

Commit b7dffeb

Browse files
authored
fix: cleaner issue for pod caused by missing generation in metadata (#1600)
1 parent 3fed8da commit b7dffeb

File tree

7 files changed

+150
-1
lines changed

7 files changed

+150
-1
lines changed

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/InternalEventFilters.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ static <T extends HasMetadata> OnUpdateFilter<T> onUpdateGenerationAware(
1818
if (!generationAware) {
1919
return true;
2020
}
21+
// for example pods don't have generation
22+
if (oldResource.getMetadata().getGeneration() == null) {
23+
return true;
24+
}
25+
2126
return oldResource.getMetadata().getGeneration() < newResource
2227
.getMetadata().getGeneration();
2328
};

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/InternalEventFiltersTest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

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

7+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
8+
import io.fabric8.kubernetes.api.model.Service;
79
import io.javaoperatorsdk.operator.TestUtils;
810

911
import static io.javaoperatorsdk.operator.TestUtils.markForDeletion;
@@ -31,6 +33,12 @@ void generationAware() {
3133
assertThat(InternalEventFilters.onUpdateGenerationAware(false).accept(res, res)).isTrue();
3234
}
3335

36+
@Test
37+
void acceptsEventIfNoGenerationOnResource() {
38+
assertThat(InternalEventFilters.onUpdateGenerationAware(true)
39+
.accept(testService(), testService())).isTrue();
40+
}
41+
3442
@Test
3543
void finalizerCheckedIfConfigured() {
3644
assertThat(InternalEventFilters.onUpdateFinalizerNeededAndApplied(true, FINALIZER)
@@ -57,4 +65,10 @@ void dontAcceptIfFinalizerNotUsed() {
5765
assertThat(InternalEventFilters.onUpdateFinalizerNeededAndApplied(false, FINALIZER)
5866
.accept(TestUtils.testCustomResource1(), TestUtils.testCustomResource1())).isFalse();
5967
}
68+
69+
Service testService() {
70+
var service = new Service();
71+
service.setMetadata(new ObjectMetaBuilder().withName("test").withNamespace("default").build());
72+
return service;
73+
}
6074
}

operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,15 @@ public abstract class AbstractOperatorExtension implements HasKubernetesClient,
3434

3535
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractOperatorExtension.class);
3636
public static final int CRD_READY_WAIT = 2000;
37+
public static final int DEFAULT_NAMESPACE_DELETE_TIMEOUT = 90;
3738

3839
private final KubernetesClient kubernetesClient;
3940
protected final List<HasMetadata> infrastructure;
4041
protected Duration infrastructureTimeout;
4142
protected final boolean oneNamespacePerClass;
4243
protected final boolean preserveNamespaceOnError;
4344
protected final boolean waitForNamespaceDeletion;
45+
protected final int namespaceDeleteTimeout = DEFAULT_NAMESPACE_DELETE_TIMEOUT;
4446

4547
protected String namespace;
4648

@@ -198,7 +200,7 @@ protected void after(ExtensionContext context) {
198200
LOGGER.info("Waiting for namespace {} to be deleted", namespace);
199201
Awaitility.await("namespace deleted")
200202
.pollInterval(50, TimeUnit.MILLISECONDS)
201-
.atMost(90, TimeUnit.SECONDS)
203+
.atMost(namespaceDeleteTimeout, TimeUnit.SECONDS)
202204
.until(() -> kubernetesClient.namespaces().withName(namespace).get() == null);
203205
}
204206
}
@@ -216,6 +218,7 @@ public static abstract class AbstractBuilder<T extends AbstractBuilder<T>> {
216218
protected boolean preserveNamespaceOnError;
217219
protected boolean waitForNamespaceDeletion;
218220
protected boolean oneNamespacePerClass;
221+
protected int namespaceDeleteTimeout;
219222

220223
protected AbstractBuilder() {
221224
this.infrastructure = new ArrayList<>();
@@ -232,6 +235,10 @@ protected AbstractBuilder() {
232235
this.oneNamespacePerClass = Utils.getSystemPropertyOrEnvVar(
233236
"josdk.it.oneNamespacePerClass",
234237
false);
238+
239+
this.namespaceDeleteTimeout = Utils.getSystemPropertyOrEnvVar(
240+
"josdk.it.namespaceDeleteTimeout",
241+
DEFAULT_NAMESPACE_DELETE_TIMEOUT);
235242
}
236243

237244
public T preserveNamespaceOnError(boolean value) {
@@ -269,5 +276,9 @@ public T withInfrastructure(HasMetadata... hms) {
269276
return (T) this;
270277
}
271278

279+
public T withNamespaceDeleteTimeout(int timeout) {
280+
this.namespaceDeleteTimeout = timeout;
281+
return (T) this;
282+
}
272283
}
273284
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package io.javaoperatorsdk.operator;
2+
3+
import java.util.Map;
4+
5+
import org.junit.jupiter.api.Test;
6+
import org.junit.jupiter.api.extension.RegisterExtension;
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
9+
10+
import io.fabric8.kubernetes.api.model.Service;
11+
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
12+
import io.javaoperatorsdk.operator.sample.builtinresourcecleaner.ObservedGenerationTestReconciler;
13+
14+
import static org.assertj.core.api.Assertions.assertThat;
15+
import static org.awaitility.Awaitility.await;
16+
17+
class BuiltInResourceCleanerIT {
18+
19+
private static final Logger log = LoggerFactory.getLogger(BuiltInResourceCleanerIT.class);
20+
21+
@RegisterExtension
22+
LocallyRunOperatorExtension operator =
23+
LocallyRunOperatorExtension.builder()
24+
.withReconciler(new ObservedGenerationTestReconciler())
25+
.build();
26+
27+
/**
28+
* Issue is with generation, some built in resources like Pod, Service does not seem to use
29+
* generation.
30+
*/
31+
@Test
32+
void cleanerIsCalledOnBuiltInResource() {
33+
var service = operator.create(testService());
34+
35+
await().untilAsserted(() -> {
36+
assertThat(operator.getReconcilerOfType(ObservedGenerationTestReconciler.class)
37+
.getReconcileCount()).isPositive();
38+
var actualService = operator.get(Service.class, service.getMetadata().getName());
39+
assertThat(actualService.getMetadata().getFinalizers()).isNotEmpty();
40+
});
41+
42+
operator.delete(service);
43+
44+
await().untilAsserted(() -> {
45+
assertThat(operator.getReconcilerOfType(ObservedGenerationTestReconciler.class)
46+
.getCleanCount()).isPositive();
47+
});
48+
}
49+
50+
Service testService() {
51+
Service service = ReconcilerUtils.loadYaml(Service.class, StandaloneDependentResourceIT.class,
52+
"service-template.yaml");
53+
service.getMetadata().setLabels(Map.of("builtintest", "true"));
54+
return service;
55+
}
56+
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package io.javaoperatorsdk.operator.sample.builtinresourcecleaner;
2+
3+
import java.util.concurrent.atomic.AtomicInteger;
4+
5+
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
7+
8+
import io.fabric8.kubernetes.api.model.Service;
9+
import io.javaoperatorsdk.operator.api.reconciler.*;
10+
11+
@ControllerConfiguration(labelSelector = "builtintest=true")
12+
public class ObservedGenerationTestReconciler
13+
implements Reconciler<Service>, Cleaner<Service> {
14+
15+
private static final Logger log = LoggerFactory.getLogger(ObservedGenerationTestReconciler.class);
16+
17+
private AtomicInteger reconciled = new AtomicInteger(0);
18+
private AtomicInteger cleaned = new AtomicInteger(0);
19+
20+
@Override
21+
public UpdateControl<Service> reconcile(
22+
Service resource,
23+
Context<Service> context) {
24+
reconciled.addAndGet(1);
25+
return UpdateControl.noUpdate();
26+
}
27+
28+
@Override
29+
public DeleteControl cleanup(Service resource, Context<Service> context) {
30+
cleaned.addAndGet(1);
31+
return DeleteControl.defaultDelete();
32+
}
33+
34+
public int getReconcileCount() {
35+
return reconciled.get();
36+
}
37+
38+
public int getCleanCount() {
39+
return cleaned.get();
40+
}
41+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
apiVersion: v1
2+
kind: Service
3+
metadata:
4+
name: my-service
5+
spec:
6+
selector:
7+
app.kubernetes.io/name: MyApp
8+
ports:
9+
- protocol: TCP
10+
port: 80
11+
targetPort: 9376

pod.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
apiVersion: v1
2+
kind: Pod
3+
metadata:
4+
name: nginx
5+
spec:
6+
containers:
7+
- name: nginx
8+
image: nginx:1.14.2
9+
ports:
10+
- containerPort: 80

0 commit comments

Comments
 (0)