Skip to content

Commit 87275f9

Browse files
authored
improve: eventing on cleanup in case status is changed after marked for deletion (#2153)
Signed-off-by: Attila Mészáros <[email protected]>
1 parent fb9e073 commit 87275f9

File tree

6 files changed

+155
-2
lines changed

6 files changed

+155
-2
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ public class InternalEventFilters {
88
private InternalEventFilters() {}
99

1010
static <T extends HasMetadata> OnUpdateFilter<T> onUpdateMarkedForDeletion() {
11-
return (newResource, oldResource) -> newResource.isMarkedForDeletion();
11+
// the old resource is checked since in corner cases users might still want to update the status
12+
// for a resource that is marked for deletion
13+
14+
return (newResource, oldResource) -> !oldResource.isMarkedForDeletion()
15+
&& newResource.isMarkedForDeletion();
1216
}
1317

1418
static <T extends HasMetadata> OnUpdateFilter<T> onUpdateGenerationAware(

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ class InternalEventFiltersTest {
1717

1818
@Test
1919
void onUpdateMarkedForDeletion() {
20+
var oldRes = TestUtils.testCustomResource();
2021
var res = markForDeletion(TestUtils.testCustomResource());
21-
assertThat(InternalEventFilters.onUpdateMarkedForDeletion().accept(res, res)).isTrue();
22+
assertThat(InternalEventFilters.onUpdateMarkedForDeletion().accept(res, oldRes)).isTrue();
2223
}
2324

2425
@Test
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package io.javaoperatorsdk.operator;
2+
3+
import org.junit.jupiter.api.Test;
4+
import org.junit.jupiter.api.extension.RegisterExtension;
5+
6+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
7+
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
8+
import io.javaoperatorsdk.operator.sample.updatestatusincleanupandreschedule.UpdateStatusInCleanupAndRescheduleCustomResource;
9+
import io.javaoperatorsdk.operator.sample.updatestatusincleanupandreschedule.UpdateStatusInCleanupAndRescheduleReconciler;
10+
11+
import static org.assertj.core.api.Assertions.assertThat;
12+
import static org.awaitility.Awaitility.await;
13+
14+
public class UpdateStatusInCleanupAndRescheduleIT {
15+
16+
public static final String TEST_RESOURCE = "test1";
17+
@RegisterExtension
18+
LocallyRunOperatorExtension extension =
19+
LocallyRunOperatorExtension.builder()
20+
.withReconciler(UpdateStatusInCleanupAndRescheduleReconciler.class)
21+
.build();
22+
23+
24+
@Test
25+
void testRescheduleAfterPatch() {
26+
var res = extension.create(testResource());
27+
28+
await().untilAsserted(() -> {
29+
var resource =
30+
extension.get(UpdateStatusInCleanupAndRescheduleCustomResource.class, TEST_RESOURCE);
31+
assertThat(resource.getMetadata().getFinalizers()).isNotEmpty();
32+
});
33+
34+
extension.delete(res);
35+
36+
await().untilAsserted(() -> {
37+
var resource =
38+
extension.get(UpdateStatusInCleanupAndRescheduleCustomResource.class, TEST_RESOURCE);
39+
assertThat(resource).isNull();
40+
});
41+
42+
assertThat(extension.getReconcilerOfType(UpdateStatusInCleanupAndRescheduleReconciler.class)
43+
.getRescheduleDelayWorked())
44+
.isTrue();
45+
}
46+
47+
UpdateStatusInCleanupAndRescheduleCustomResource testResource() {
48+
var resource = new UpdateStatusInCleanupAndRescheduleCustomResource();
49+
resource.setMetadata(new ObjectMetaBuilder()
50+
.withName(TEST_RESOURCE)
51+
.build());
52+
return resource;
53+
}
54+
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package io.javaoperatorsdk.operator.sample.updatestatusincleanupandreschedule;
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("usc")
12+
public class UpdateStatusInCleanupAndRescheduleCustomResource
13+
extends
14+
CustomResource<Void, UpdateStatusInCleanupAndRescheduleCustomStatus>
15+
implements Namespaced {
16+
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.javaoperatorsdk.operator.sample.updatestatusincleanupandreschedule;
2+
3+
public class UpdateStatusInCleanupAndRescheduleCustomStatus {
4+
5+
private Integer cleanupAttempt;
6+
7+
public Integer getCleanupAttempt() {
8+
return cleanupAttempt;
9+
}
10+
11+
public UpdateStatusInCleanupAndRescheduleCustomStatus setCleanupAttempt(Integer cleanupAttempt) {
12+
this.cleanupAttempt = cleanupAttempt;
13+
return this;
14+
}
15+
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package io.javaoperatorsdk.operator.sample.updatestatusincleanupandreschedule;
2+
3+
import java.time.LocalTime;
4+
import java.time.temporal.ChronoUnit;
5+
6+
import io.javaoperatorsdk.operator.api.reconciler.*;
7+
8+
@ControllerConfiguration
9+
public class UpdateStatusInCleanupAndRescheduleReconciler
10+
implements Reconciler<UpdateStatusInCleanupAndRescheduleCustomResource>,
11+
Cleaner<UpdateStatusInCleanupAndRescheduleCustomResource> {
12+
13+
public static final Integer DELAY = 150;
14+
15+
private LocalTime lastCleanupExecution;
16+
17+
public Boolean rescheduleDelayWorked;
18+
19+
@Override
20+
public UpdateControl<UpdateStatusInCleanupAndRescheduleCustomResource> reconcile(
21+
UpdateStatusInCleanupAndRescheduleCustomResource resource,
22+
Context<UpdateStatusInCleanupAndRescheduleCustomResource> context) {
23+
24+
return UpdateControl.noUpdate();
25+
}
26+
27+
@Override
28+
public DeleteControl cleanup(UpdateStatusInCleanupAndRescheduleCustomResource resource,
29+
Context<UpdateStatusInCleanupAndRescheduleCustomResource> context) {
30+
31+
var status = resource.getStatus();
32+
if (status == null) {
33+
resource.setStatus(new UpdateStatusInCleanupAndRescheduleCustomStatus());
34+
resource.getStatus().setCleanupAttempt(1);
35+
lastCleanupExecution = LocalTime.now();
36+
} else {
37+
var currentAttempt = resource.getStatus().getCleanupAttempt();
38+
resource.getStatus().setCleanupAttempt(currentAttempt + 1);
39+
if (!Boolean.FALSE.equals(rescheduleDelayWorked)) {
40+
var diff = ChronoUnit.MILLIS.between(lastCleanupExecution, LocalTime.now());
41+
if (diff < DELAY) {
42+
rescheduleDelayWorked = false;
43+
} else {
44+
rescheduleDelayWorked = true;
45+
}
46+
}
47+
}
48+
var res = context.getClient().resource(resource).updateStatus();
49+
50+
if (resource.getStatus().getCleanupAttempt() > 5) {
51+
return DeleteControl.defaultDelete();
52+
} else {
53+
return DeleteControl.noFinalizerRemoval().rescheduleAfter(DELAY);
54+
}
55+
}
56+
57+
public Boolean getRescheduleDelayWorked() {
58+
return rescheduleDelayWorked;
59+
}
60+
}

0 commit comments

Comments
 (0)