diff --git a/docs/content/en/docs/documentation/operations/leader-election.md b/docs/content/en/docs/documentation/operations/leader-election.md index a8b33a2084..ba82632cb1 100644 --- a/docs/content/en/docs/documentation/operations/leader-election.md +++ b/docs/content/en/docs/documentation/operations/leader-election.md @@ -44,7 +44,16 @@ See details under [configurations](configuration.md) page. the lease. 2. Once leadership is acquired, event processing begins normally. 3. If leadership is lost (e.g. the leader pod becomes unresponsive), another instance acquires the lease - and takes over reconciliation. The instance that lost the lead is terminated (`System.exit()`) + and takes over reconciliation. The instance that lost the lead is terminated (`System.exit(1)`), so + that it is restarted by Kubernetes and no two instances reconcile the same resources in parallel. + This does not happen on a graceful shutdown (`Operator.stop()`), only when the lead is lost while + the operator is running. + +{{% alert title="Note" color="primary" %}} +Exiting on lost leadership is always on in production. `LeaderElectionConfigurationBuilder` exposes +`buildForTest(boolean exitOnStopLeading)` to turn it off, but as the name says this is only meant for +tests, where terminating the JVM would kill the test run. +{{% /alert %}} ### Identity and Namespace Inference diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/LeaderElectionConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/LeaderElectionConfiguration.java index ca777bd2cc..c8e73db7db 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/LeaderElectionConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/LeaderElectionConfiguration.java @@ -25,6 +25,7 @@ public class LeaderElectionConfiguration { public static final Duration LEASE_DURATION_DEFAULT_VALUE = Duration.ofSeconds(15); public static final Duration RENEW_DEADLINE_DEFAULT_VALUE = Duration.ofSeconds(10); public static final Duration RETRY_PERIOD_DEFAULT_VALUE = Duration.ofSeconds(2); + public static final boolean EXIT_ON_STOP_LEADING_DEFAULT_VALUE = true; private final String leaseName; private final String leaseNamespace; @@ -50,7 +51,7 @@ public LeaderElectionConfiguration(String leaseName, String leaseNamespace, Stri RETRY_PERIOD_DEFAULT_VALUE, identity, null, - true); + EXIT_ON_STOP_LEADING_DEFAULT_VALUE); } /** @@ -79,7 +80,15 @@ public LeaderElectionConfiguration( Duration leaseDuration, Duration renewDeadline, Duration retryPeriod) { - this(leaseName, leaseNamespace, leaseDuration, renewDeadline, retryPeriod, null, null, true); + this( + leaseName, + leaseNamespace, + leaseDuration, + renewDeadline, + retryPeriod, + null, + null, + EXIT_ON_STOP_LEADING_DEFAULT_VALUE); } /** @@ -133,6 +142,12 @@ public Optional getLeaderCallbacks() { return Optional.ofNullable(leaderCallbacks); } + /** + * Whether the process should exit (via {@code System.exit(1)}) when this instance stops leading + * outside of a graceful shutdown. Defaults to {@value #EXIT_ON_STOP_LEADING_DEFAULT_VALUE}; + * {@code false} is only meant for testing purposes, see {@link + * LeaderElectionConfigurationBuilder#buildForTest(boolean)}. + */ public boolean isExitOnStopLeading() { return exitOnStopLeading; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/LeaderElectionConfigurationBuilder.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/LeaderElectionConfigurationBuilder.java index 51ee40d84c..38d0d98f6b 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/LeaderElectionConfigurationBuilder.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/LeaderElectionConfigurationBuilder.java @@ -81,11 +81,26 @@ public LeaderElectionConfigurationBuilder withExitOnStopLeading(boolean exitOnSt + " instead"); } + /** + * Builds the configuration with {@code exitOnStopLeading} set to {@value + * LeaderElectionConfiguration#EXIT_ON_STOP_LEADING_DEFAULT_VALUE}, meaning that the process exits + * when this instance stops leading outside of a graceful shutdown, so that another replica can + * take over without two instances reconciling in parallel. + */ public LeaderElectionConfiguration build() { - return buildForTest(false); + return build(EXIT_ON_STOP_LEADING_DEFAULT_VALUE); } + /** + * Same as {@link #build()}, but allows turning off the exit on stop leading behavior. This should + * only be used for testing purposes, since without exiting, two instances might reconcile the + * same resources in parallel. + */ public LeaderElectionConfiguration buildForTest(boolean exitOnStopLeading) { + return build(exitOnStopLeading); + } + + private LeaderElectionConfiguration build(boolean exitOnStopLeading) { return new LeaderElectionConfiguration( leaseName, leaseNamespace,