-
Notifications
You must be signed in to change notification settings - Fork 218
feat: graceful shutdown #1735
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: graceful shutdown #1735
Changes from all commits
c81bada
6277605
81520a6
148cb62
7a728dc
b06946a
48fc7e2
12e800c
4812c8d
1a4c345
d9e0cb5
b6d3259
0be2471
430b1af
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -121,8 +121,12 @@ public HasMetadata clone(HasMetadata object) { | |
* Retrieves the number of seconds the SDK waits for reconciliation threads to terminate before | ||
* shutting down. | ||
* | ||
* @deprecated use {@link io.javaoperatorsdk.operator.Operator#stop(Duration)} instead. Where the | ||
* parameter can be passed to specify graceful timeout. | ||
* | ||
* @return the number of seconds to wait before terminating reconciliation threads | ||
*/ | ||
@Deprecated(forRemoval = true) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This also needs an associated There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added |
||
default int getTerminationTimeoutSeconds() { | ||
return DEFAULT_TERMINATION_TIMEOUT_SECONDS; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -388,6 +388,13 @@ private ReconcilerExecutor(ResourceID resourceID, ExecutionScope<P> executionSco | |
|
||
@Override | ||
public void run() { | ||
if (!running) { | ||
// this is needed for the case when controller stopped, but there is a graceful shutdown | ||
// timeout. that should finish the currently executing reconciliations but not the ones | ||
// which where submitted but not started yet | ||
log.debug("Event processor not running skipping resource processing: {}", resourceID); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't this missing a return statement here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, wonder how the test passed, will check There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed the unit test, was not trivial, wonder if we should refactor this in the future. |
||
return; | ||
} | ||
// change thread name for easier debugging | ||
final var thread = Thread.currentThread(); | ||
final var name = thread.getName(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package io.javaoperatorsdk.operator; | ||
|
||
import java.time.Duration; | ||
|
||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.RegisterExtension; | ||
|
||
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; | ||
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; | ||
import io.javaoperatorsdk.operator.sample.gracefulstop.GracefulStopTestCustomResource; | ||
import io.javaoperatorsdk.operator.sample.gracefulstop.GracefulStopTestCustomResourceSpec; | ||
import io.javaoperatorsdk.operator.sample.gracefulstop.GracefulStopTestReconciler; | ||
|
||
import static io.javaoperatorsdk.operator.sample.gracefulstop.GracefulStopTestReconciler.RECONCILER_SLEEP; | ||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.awaitility.Awaitility.await; | ||
|
||
public class GracefulStopIT { | ||
|
||
public static final String TEST_1 = "test1"; | ||
public static final String TEST_2 = "test2"; | ||
|
||
@RegisterExtension | ||
LocallyRunOperatorExtension operator = | ||
LocallyRunOperatorExtension.builder() | ||
.withConfigurationService(o -> o.withCloseClientOnStop(false)) | ||
.withReconciler(new GracefulStopTestReconciler()) | ||
.build(); | ||
|
||
@Test | ||
void stopsGracefullyWIthTimeout() { | ||
testGracefulStop(TEST_1, RECONCILER_SLEEP, 2); | ||
} | ||
|
||
@Test | ||
void stopsGracefullyWithExpiredTimeout() { | ||
testGracefulStop(TEST_2, RECONCILER_SLEEP / 5, 1); | ||
} | ||
|
||
private void testGracefulStop(String resourceName, int stopTimeout, int expectedFinalGeneration) { | ||
var testRes = operator.create(testResource(resourceName)); | ||
await().untilAsserted(() -> { | ||
var r = operator.get(GracefulStopTestCustomResource.class, resourceName); | ||
assertThat(r.getStatus()).isNotNull(); | ||
assertThat(r.getStatus().getObservedGeneration()).isEqualTo(1); | ||
assertThat(operator.getReconcilerOfType(GracefulStopTestReconciler.class) | ||
.getNumberOfExecutions()).isEqualTo(1); | ||
}); | ||
|
||
testRes.getSpec().setValue(2); | ||
operator.replace(testRes); | ||
|
||
await().pollDelay(Duration.ofMillis(50)).untilAsserted( | ||
() -> assertThat(operator.getReconcilerOfType(GracefulStopTestReconciler.class) | ||
.getNumberOfExecutions()).isEqualTo(2)); | ||
|
||
operator.getOperator().stop(Duration.ofMillis(stopTimeout)); | ||
|
||
await().untilAsserted(() -> { | ||
var r = operator.get(GracefulStopTestCustomResource.class, resourceName); | ||
assertThat(r.getStatus()).isNotNull(); | ||
assertThat(r.getStatus().getObservedGeneration()).isEqualTo(expectedFinalGeneration); | ||
}); | ||
} | ||
|
||
public GracefulStopTestCustomResource testResource(String name) { | ||
GracefulStopTestCustomResource resource = | ||
new GracefulStopTestCustomResource(); | ||
resource.setMetadata( | ||
new ObjectMetaBuilder() | ||
.withName(name) | ||
.withNamespace(operator.getNamespace()) | ||
.build()); | ||
resource.setSpec(new GracefulStopTestCustomResourceSpec()); | ||
resource.getSpec().setValue(1); | ||
return resource; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package io.javaoperatorsdk.operator.sample.gracefulstop; | ||
|
||
import io.fabric8.kubernetes.api.model.Namespaced; | ||
import io.fabric8.kubernetes.client.CustomResource; | ||
import io.fabric8.kubernetes.model.annotation.Group; | ||
import io.fabric8.kubernetes.model.annotation.ShortNames; | ||
import io.fabric8.kubernetes.model.annotation.Version; | ||
|
||
@Group("sample.javaoperatorsdk") | ||
@Version("v1") | ||
@ShortNames("gst") | ||
public class GracefulStopTestCustomResource | ||
extends CustomResource<GracefulStopTestCustomResourceSpec, GracefulStopTestCustomResourceStatus> | ||
implements Namespaced { | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package io.javaoperatorsdk.operator.sample.gracefulstop; | ||
|
||
public class GracefulStopTestCustomResourceSpec { | ||
|
||
private int value; | ||
|
||
public int getValue() { | ||
return value; | ||
} | ||
|
||
public void setValue(int value) { | ||
this.value = value; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package io.javaoperatorsdk.operator.sample.gracefulstop; | ||
|
||
import io.javaoperatorsdk.operator.api.ObservedGenerationAwareStatus; | ||
|
||
public class GracefulStopTestCustomResourceStatus extends ObservedGenerationAwareStatus { | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: why not keep this? It could be useful to be able to configure the default timeout as previously…
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer to have a single way to configure this, and it is much simpler with the parameters. Keeping this, would be two way to do the same thing.