Skip to content

Commit 013589a

Browse files
committed
tests
Signed-off-by: Attila Mészáros <[email protected]>
1 parent ca52637 commit 013589a

File tree

7 files changed

+248
-27
lines changed

7 files changed

+248
-27
lines changed

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/FinalizerUtils.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,16 @@ public class FinalizerUtils {
99

1010
private static final Logger log = LoggerFactory.getLogger(FinalizerUtils.class);
1111

12-
// todo SSA
12+
// todo SSA, revisit if informer is ok for this
1313

1414
public static <P extends HasMetadata> P patchFinalizer(
1515
P resource, String finalizer, Context<P> context) {
16+
17+
if (resource.hasFinalizer(finalizer)) {
18+
log.debug("Skipping adding finalizer, since already present.");
19+
return resource;
20+
}
21+
1622
return PrimaryUpdateAndCacheUtils.updateAndCacheResource(
1723
resource,
1824
context,
@@ -30,7 +36,10 @@ public static <P extends HasMetadata> P patchFinalizer(
3036

3137
public static <P extends HasMetadata> P removeFinalizer(
3238
P resource, String finalizer, Context<P> context) {
33-
39+
if (!resource.hasFinalizer(finalizer)) {
40+
log.debug("Skipping removing finalizer, since not present.");
41+
return resource;
42+
}
3443
return PrimaryUpdateAndCacheUtils.updateAndCacheResource(
3544
resource,
3645
context,

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import io.fabric8.kubernetes.api.model.*;
2121
import io.fabric8.kubernetes.client.KubernetesClient;
2222
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
23+
import io.fabric8.kubernetes.client.dsl.NonDeletingOperation;
2324
import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation;
2425
import io.fabric8.kubernetes.client.dsl.Resource;
2526
import io.fabric8.kubernetes.client.utils.Utils;
@@ -126,8 +127,15 @@ public <T extends HasMetadata> T serverSideApply(T resource) {
126127
return kubernetesClient.resource(resource).inNamespace(namespace).serverSideApply();
127128
}
128129

130+
public <T extends HasMetadata> T update(T resource) {
131+
return kubernetesClient.resource(resource).inNamespace(namespace).update();
132+
}
133+
129134
public <T extends HasMetadata> T replace(T resource) {
130-
return kubernetesClient.resource(resource).inNamespace(namespace).replace();
135+
return kubernetesClient
136+
.resource(resource)
137+
.inNamespace(namespace)
138+
.createOr(NonDeletingOperation::update);
131139
}
132140

133141
public <T extends HasMetadata> boolean delete(T resource) {

operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/alleventmode/AbstractAllEventReconciler.java

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,16 @@ public class AbstractAllEventReconciler {
66

77
public static final String FINALIZER = "all.event.mode/finalizer";
88
public static final String ADDITIONAL_FINALIZER = "all.event.mode/finalizer2";
9+
public static final String NO_MORE_EXCEPTION_ANNOTATION_KEY = "no.more.exception";
910

1011
protected volatile boolean useFinalizer = true;
1112
protected volatile boolean throwExceptionOnFirstDeleteEvent = false;
13+
protected volatile boolean throwExceptionIfNoAnnotation = false;
14+
15+
protected volatile boolean waitAfterFirstRetry = false;
16+
protected volatile boolean continuerOnRetryWait = false;
17+
protected volatile boolean waiting = false;
18+
1219
protected volatile boolean isFirstDeleteEvent = true;
1320

1421
private boolean resourceEventPresent = false;
@@ -41,7 +48,7 @@ public void setEventOnMarkedForDeletion(boolean eventOnMarkedForDeletion) {
4148
this.eventOnMarkedForDeletion = eventOnMarkedForDeletion;
4249
}
4350

44-
public int getEventCounter() {
51+
public int getEventCount() {
4552
return eventCounter.get();
4653
}
4754

@@ -72,4 +79,36 @@ public boolean isThrowExceptionOnFirstDeleteEvent() {
7279
public void setThrowExceptionOnFirstDeleteEvent(boolean throwExceptionOnFirstDeleteEvent) {
7380
this.throwExceptionOnFirstDeleteEvent = throwExceptionOnFirstDeleteEvent;
7481
}
82+
83+
public boolean isThrowExceptionIfNoAnnotation() {
84+
return throwExceptionIfNoAnnotation;
85+
}
86+
87+
public void setThrowExceptionIfNoAnnotation(boolean throwExceptionIfNoAnnotation) {
88+
this.throwExceptionIfNoAnnotation = throwExceptionIfNoAnnotation;
89+
}
90+
91+
public boolean isWaitAfterFirstRetry() {
92+
return waitAfterFirstRetry;
93+
}
94+
95+
public void setWaitAfterFirstRetry(boolean waitAfterFirstRetry) {
96+
this.waitAfterFirstRetry = waitAfterFirstRetry;
97+
}
98+
99+
public boolean isContinuerOnRetryWait() {
100+
return continuerOnRetryWait;
101+
}
102+
103+
public void setContinuerOnRetryWait(boolean continuerOnRetryWait) {
104+
this.continuerOnRetryWait = continuerOnRetryWait;
105+
}
106+
107+
public boolean isWaiting() {
108+
return waiting;
109+
}
110+
111+
public void setWaiting(boolean waiting) {
112+
this.waiting = waiting;
113+
}
75114
}

operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/alleventmode/cleaner/AllEventCleanerIT.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ public class AllEventCleanerIT {
1818
LocallyRunOperatorExtension extension =
1919
LocallyRunOperatorExtension.builder().withReconciler(new AllEventCleanerReconciler()).build();
2020

21-
// todo delete event without finalizer
2221
@Test
2322
void eventsPresent() {
2423
var reconciler = extension.getReconcilerOfType(AllEventCleanerReconciler.class);

operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/alleventmode/cleaner/AllEventCleanerReconciler.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import io.javaoperatorsdk.operator.api.reconciler.Context;
66
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
77
import io.javaoperatorsdk.operator.api.reconciler.DeleteControl;
8+
import io.javaoperatorsdk.operator.api.reconciler.FinalizerUtils;
89
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
910
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
1011
import io.javaoperatorsdk.operator.baseapi.alleventmode.AbstractAllEventReconciler;
@@ -15,16 +16,15 @@ public class AllEventCleanerReconciler extends AbstractAllEventReconciler
1516

1617
@Override
1718
public UpdateControl<AllEventCleanerCustomResource> reconcile(
18-
AllEventCleanerCustomResource resource, Context<AllEventCleanerCustomResource> context) {
19+
AllEventCleanerCustomResource primary, Context<AllEventCleanerCustomResource> context) {
1920

2021
increaseEventCount();
21-
if (!resource.isMarkedForDeletion()) {
22+
if (!primary.isMarkedForDeletion()) {
2223
setResourceEventPresent(true);
2324
}
2425

25-
if (!resource.hasFinalizer(FINALIZER)) {
26-
resource.addFinalizer(FINALIZER);
27-
context.getClient().resource(resource).update();
26+
if (useFinalizer && !primary.hasFinalizer(FINALIZER)) {
27+
FinalizerUtils.patchFinalizer(primary, FINALIZER, context);
2828
return UpdateControl.noUpdate();
2929
}
3030

@@ -40,15 +40,13 @@ public DeleteControl cleanup(
4040
if (resource.isMarkedForDeletion() && !context.isDeleteEventPresent()) {
4141
setEventOnMarkedForDeletion(true);
4242
if (resource.hasFinalizer(FINALIZER)) {
43-
resource.removeFinalizer(FINALIZER);
44-
context.getClient().resource(resource).update();
43+
FinalizerUtils.removeFinalizer(resource, FINALIZER, context);
4544
}
4645
}
4746

4847
if (context.isDeleteEventPresent()) {
4948
setDeleteEventPresent(true);
5049
}
51-
// todo handle this document
5250
return DeleteControl.defaultDelete();
5351
}
5452
}

operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/alleventmode/onlyreconcile/AllEventIT.java

Lines changed: 147 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,38 @@
11
package io.javaoperatorsdk.operator.baseapi.alleventmode.onlyreconcile;
22

3+
import java.time.Duration;
4+
35
import org.junit.jupiter.api.Test;
46
import org.junit.jupiter.api.extension.RegisterExtension;
57

68
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
79
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
10+
import io.javaoperatorsdk.operator.processing.retry.GenericRetry;
811

12+
import static io.javaoperatorsdk.operator.baseapi.alleventmode.AbstractAllEventReconciler.ADDITIONAL_FINALIZER;
913
import static io.javaoperatorsdk.operator.baseapi.alleventmode.AbstractAllEventReconciler.FINALIZER;
14+
import static io.javaoperatorsdk.operator.baseapi.alleventmode.AbstractAllEventReconciler.NO_MORE_EXCEPTION_ANNOTATION_KEY;
1015
import static org.assertj.core.api.Assertions.assertThat;
1116
import static org.awaitility.Awaitility.await;
1217

1318
public class AllEventIT {
1419

1520
public static final String TEST = "test1";
21+
public static final int MAX_RETRY_ATTEMPTS = 2;
1622

1723
@RegisterExtension
1824
LocallyRunOperatorExtension extension =
19-
LocallyRunOperatorExtension.builder().withReconciler(new AllEventReconciler()).build();
25+
LocallyRunOperatorExtension.builder()
26+
.withReconciler(
27+
new AllEventReconciler(),
28+
o ->
29+
o.withRetry(
30+
new GenericRetry()
31+
.setInitialInterval(800)
32+
.setMaxAttempts(MAX_RETRY_ATTEMPTS)
33+
.setIntervalMultiplier(1)))
34+
.build();
2035

21-
// todo additional finalizer, events after that
22-
// todo retry on delete event + event received meanwhile
2336
@Test
2437
void eventsPresent() {
2538
var reconciler = extension.getReconcilerOfType(AllEventReconciler.class);
@@ -84,7 +97,137 @@ void retriesExceptionOnDeleteEvent() {
8497
}
8598

8699
@Test
87-
void eventReceivedOnDeleteEventRetry() {}
100+
void additionalFinalizer() {
101+
var reconciler = extension.getReconcilerOfType(AllEventReconciler.class);
102+
reconciler.setUseFinalizer(true);
103+
var res = testResource();
104+
res.addFinalizer(ADDITIONAL_FINALIZER);
105+
106+
extension.create(res);
107+
108+
extension.delete(getResource());
109+
110+
await()
111+
.untilAsserted(
112+
() -> {
113+
var r = getResource();
114+
assertThat(r).isNotNull();
115+
assertThat(r.getMetadata().getFinalizers()).containsExactly(ADDITIONAL_FINALIZER);
116+
});
117+
var eventCount = reconciler.getEventCount();
118+
119+
res = getResource();
120+
res.removeFinalizer(ADDITIONAL_FINALIZER);
121+
extension.update(res);
122+
123+
await()
124+
.untilAsserted(
125+
() -> {
126+
var r = getResource();
127+
assertThat(r).isNull();
128+
assertThat(reconciler.getEventCount()).isEqualTo(eventCount + 1);
129+
});
130+
}
131+
132+
@Test
133+
void additionalEventDuringRetryOnDeleteEvent() {
134+
135+
var reconciler = extension.getReconcilerOfType(AllEventReconciler.class);
136+
reconciler.setThrowExceptionIfNoAnnotation(true);
137+
reconciler.setWaitAfterFirstRetry(true);
138+
var res = testResource();
139+
res.addFinalizer(ADDITIONAL_FINALIZER);
140+
extension.create(res);
141+
extension.delete(getResource());
142+
143+
await()
144+
.pollDelay(Duration.ofMillis(30))
145+
.untilAsserted(
146+
() -> {
147+
assertThat(reconciler.getEventCount()).isGreaterThan(2);
148+
});
149+
var eventCount = reconciler.getEventCount();
150+
151+
await()
152+
.untilAsserted(
153+
() -> {
154+
assertThat(reconciler.isWaiting());
155+
});
156+
157+
res = getResource();
158+
res.getMetadata().getAnnotations().put("my-annotation", "true");
159+
extension.update(res);
160+
reconciler.setContinuerOnRetryWait(true);
161+
162+
await()
163+
.pollDelay(Duration.ofMillis(30))
164+
.untilAsserted(
165+
() -> {
166+
assertThat(reconciler.getEventCount()).isEqualTo(eventCount + 1);
167+
});
168+
169+
// second retry
170+
await()
171+
.pollDelay(Duration.ofMillis(30))
172+
.untilAsserted(
173+
() -> {
174+
assertThat(reconciler.getEventCount()).isEqualTo(eventCount + 2);
175+
});
176+
177+
addNoMoreExceptionAnnotation();
178+
179+
await()
180+
.untilAsserted(
181+
() -> {
182+
var r = getResource();
183+
assertThat(r.getMetadata().getFinalizers()).doesNotContain(FINALIZER);
184+
});
185+
186+
removeAdditionalFinalizerWaitForResourceDeletion();
187+
}
188+
189+
@Test
190+
void additionalEventAfterExhaustedRetry() {
191+
192+
var reconciler = extension.getReconcilerOfType(AllEventReconciler.class);
193+
reconciler.setThrowExceptionIfNoAnnotation(true);
194+
var res = testResource();
195+
res.addFinalizer(ADDITIONAL_FINALIZER);
196+
extension.create(res);
197+
extension.delete(getResource());
198+
199+
await()
200+
.pollDelay(Duration.ofMillis(30))
201+
.untilAsserted(
202+
() -> {
203+
assertThat(reconciler.getEventCount()).isEqualTo(MAX_RETRY_ATTEMPTS + 1);
204+
});
205+
206+
addNoMoreExceptionAnnotation();
207+
208+
await()
209+
.pollDelay(Duration.ofMillis(30))
210+
.untilAsserted(
211+
() -> {
212+
assertThat(reconciler.getEventCount()).isGreaterThan(MAX_RETRY_ATTEMPTS + 1);
213+
});
214+
215+
removeAdditionalFinalizerWaitForResourceDeletion();
216+
}
217+
218+
private void removeAdditionalFinalizerWaitForResourceDeletion() {
219+
var res = getResource();
220+
res.removeFinalizer(ADDITIONAL_FINALIZER);
221+
extension.update(res);
222+
await().untilAsserted(() -> assertThat(getResource()).isNull());
223+
}
224+
225+
private void addNoMoreExceptionAnnotation() {
226+
AllEventCustomResource res;
227+
res = getResource();
228+
res.getMetadata().getAnnotations().put(NO_MORE_EXCEPTION_ANNOTATION_KEY, "true");
229+
extension.update(res);
230+
}
88231

89232
AllEventCustomResource getResource() {
90233
return extension.get(AllEventCustomResource.class, TEST);

0 commit comments

Comments
 (0)