Skip to content

Commit 12f911a

Browse files
committed
IT
1 parent 94249d7 commit 12f911a

File tree

6 files changed

+183
-1
lines changed

6 files changed

+183
-1
lines changed

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,12 @@ public void installShutdownHook() {
8787
}
8888

8989
/**
90-
* Adds a shutdown hook that automatically calls {@link #stop()} when the app shuts down.
90+
* Adds a shutdown hook that automatically calls {@link #stop()} when the app shuts down. Note
91+
* that graceful shutdown is not always needed, just if you implementation of reconciler requires
92+
* it.
93+
*
94+
* Note that you might want to tune "terminationGracePeriodSeconds" for the Pod running the
95+
* controller.
9196
*
9297
* @param gracefulShutdownTimeout - timeout to wait for executor threads to complete actual
9398
* reconciliations
@@ -137,6 +142,9 @@ public synchronized void start() {
137142
}
138143

139144
public void stop(Duration gracefulShutdownTimeout) throws OperatorException {
145+
if (!started) {
146+
return;
147+
}
140148
final var configurationService = ConfigurationServiceProvider.instance();
141149
log.info(
142150
"Operator SDK {} is shutting down...", configurationService.getVersion().getSdkVersion());
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
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.ObjectMetaBuilder;
9+
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
10+
import io.javaoperatorsdk.operator.sample.gracefulstop.GracefulStopTestCustomResource;
11+
import io.javaoperatorsdk.operator.sample.gracefulstop.GracefulStopTestCustomResourceSpec;
12+
import io.javaoperatorsdk.operator.sample.gracefulstop.GracefulStopTestReconciler;
13+
14+
import static io.javaoperatorsdk.operator.sample.gracefulstop.GracefulStopTestReconciler.RECONCILER_SLEEP;
15+
import static org.assertj.core.api.Assertions.assertThat;
16+
import static org.awaitility.Awaitility.await;
17+
18+
public class GracefulStopIT {
19+
20+
public static final String TEST_1 = "test1";
21+
public static final String TEST_2 = "test2";
22+
23+
@RegisterExtension
24+
LocallyRunOperatorExtension operator =
25+
LocallyRunOperatorExtension.builder()
26+
.withConfigurationService(o -> o.withCloseClientOnStop(false))
27+
.withReconciler(new GracefulStopTestReconciler())
28+
.build();
29+
30+
@Test
31+
void stopsGracefullyWIthTimeout() {
32+
var testRes = operator.create(testResource1());
33+
await().untilAsserted(() -> {
34+
var r = operator.get(GracefulStopTestCustomResource.class, TEST_1);
35+
assertThat(r.getStatus()).isNotNull();
36+
assertThat(r.getStatus().getObservedGeneration()).isEqualTo(1);
37+
assertThat(operator.getReconcilerOfType(GracefulStopTestReconciler.class)
38+
.getNumberOfExecutions()).isEqualTo(1);
39+
});
40+
41+
testRes.getSpec().setValue(2);
42+
operator.replace(testRes);
43+
44+
await().pollDelay(Duration.ofMillis(50)).untilAsserted(
45+
() -> assertThat(operator.getReconcilerOfType(GracefulStopTestReconciler.class)
46+
.getNumberOfExecutions()).isEqualTo(2));
47+
48+
operator.getOperator().stop(Duration.ofMillis(RECONCILER_SLEEP));
49+
50+
await().untilAsserted(() -> {
51+
var r = operator.get(GracefulStopTestCustomResource.class, TEST_1);
52+
assertThat(r.getStatus()).isNotNull();
53+
assertThat(r.getStatus().getObservedGeneration()).isEqualTo(2);
54+
});
55+
}
56+
57+
@Test
58+
void stopsGracefullyWithExpiredTimeout() {
59+
var testRes = operator.create(testResource2());
60+
await().untilAsserted(() -> {
61+
var r = operator.get(GracefulStopTestCustomResource.class, TEST_2);
62+
assertThat(r.getStatus()).isNotNull();
63+
assertThat(r.getStatus().getObservedGeneration()).isEqualTo(1);
64+
});
65+
66+
testRes.getSpec().setValue(2);
67+
operator.replace(testRes);
68+
69+
await().pollDelay(Duration.ofMillis(50)).untilAsserted(
70+
() -> assertThat(operator.getReconcilerOfType(GracefulStopTestReconciler.class)
71+
.getNumberOfExecutions()).isEqualTo(2));
72+
73+
operator.getOperator().stop(Duration.ofMillis(RECONCILER_SLEEP / 5));
74+
75+
await().pollDelay(Duration.ofMillis(RECONCILER_SLEEP)).untilAsserted(() -> {
76+
var r = operator.get(GracefulStopTestCustomResource.class, TEST_2);
77+
assertThat(r.getStatus()).isNotNull();
78+
assertThat(r.getStatus().getObservedGeneration()).isEqualTo(1);
79+
});
80+
}
81+
82+
public GracefulStopTestCustomResource testResource1() {
83+
return testResource(TEST_1);
84+
}
85+
86+
public GracefulStopTestCustomResource testResource2() {
87+
return testResource(TEST_2);
88+
}
89+
90+
public GracefulStopTestCustomResource testResource(String name) {
91+
GracefulStopTestCustomResource resource =
92+
new GracefulStopTestCustomResource();
93+
resource.setMetadata(
94+
new ObjectMetaBuilder()
95+
.withName(name)
96+
.withNamespace(operator.getNamespace())
97+
.build());
98+
resource.setSpec(new GracefulStopTestCustomResourceSpec());
99+
resource.getSpec().setValue(1);
100+
return resource;
101+
}
102+
103+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.javaoperatorsdk.operator.sample.gracefulstop;
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("gst")
12+
public class GracefulStopTestCustomResource
13+
extends CustomResource<GracefulStopTestCustomResourceSpec, GracefulStopTestCustomResourceStatus>
14+
implements Namespaced {
15+
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package io.javaoperatorsdk.operator.sample.gracefulstop;
2+
3+
public class GracefulStopTestCustomResourceSpec {
4+
5+
private int value;
6+
7+
public int getValue() {
8+
return value;
9+
}
10+
11+
public void setValue(int value) {
12+
this.value = value;
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package io.javaoperatorsdk.operator.sample.gracefulstop;
2+
3+
import io.javaoperatorsdk.operator.api.ObservedGenerationAwareStatus;
4+
5+
public class GracefulStopTestCustomResourceStatus extends ObservedGenerationAwareStatus {
6+
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package io.javaoperatorsdk.operator.sample.gracefulstop;
2+
3+
import java.util.concurrent.atomic.AtomicInteger;
4+
5+
import io.javaoperatorsdk.operator.api.reconciler.Context;
6+
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
7+
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
8+
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
9+
10+
@ControllerConfiguration
11+
public class GracefulStopTestReconciler
12+
implements Reconciler<GracefulStopTestCustomResource> {
13+
14+
public static final int RECONCILER_SLEEP = 1000;
15+
16+
private final AtomicInteger numberOfExecutions = new AtomicInteger(0);
17+
18+
@Override
19+
public UpdateControl<GracefulStopTestCustomResource> reconcile(
20+
GracefulStopTestCustomResource resource,
21+
Context<GracefulStopTestCustomResource> context) throws InterruptedException {
22+
23+
numberOfExecutions.addAndGet(1);
24+
resource.setStatus(new GracefulStopTestCustomResourceStatus());
25+
Thread.sleep(RECONCILER_SLEEP);
26+
27+
return UpdateControl.patchStatus(resource);
28+
}
29+
30+
public int getNumberOfExecutions() {
31+
return numberOfExecutions.get();
32+
}
33+
34+
}

0 commit comments

Comments
 (0)