Skip to content

Commit 2179ca0

Browse files
committed
add leader election configs
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
1 parent dd7cefe commit 2179ca0

File tree

2 files changed

+154
-1
lines changed

2 files changed

+154
-1
lines changed

operator-framework/src/main/java/io/javaoperatorsdk/operator/config/loader/ConfigLoader.java

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import io.fabric8.kubernetes.api.model.HasMetadata;
2727
import io.javaoperatorsdk.operator.api.config.ConfigurationServiceOverrider;
2828
import io.javaoperatorsdk.operator.api.config.ControllerConfigurationOverrider;
29+
import io.javaoperatorsdk.operator.api.config.LeaderElectionConfigurationBuilder;
2930
import io.javaoperatorsdk.operator.config.loader.provider.AgregatePrirityListConfigProvider;
3031
import io.javaoperatorsdk.operator.config.loader.provider.EnvVarConfigProvider;
3132
import io.javaoperatorsdk.operator.config.loader.provider.SystemPropertyConfigProvider;
@@ -101,6 +102,17 @@ public static ConfigLoader getDefault() {
101102
Boolean.class,
102103
ConfigurationServiceOverrider::withCloneSecondaryResourcesWhenGettingFromCache));
103104

105+
// ---------------------------------------------------------------------------
106+
// Operator-level leader-election property keys
107+
// ---------------------------------------------------------------------------
108+
static final String LEADER_ELECTION_ENABLED_KEY = "leader-election.enabled";
109+
static final String LEADER_ELECTION_LEASE_NAME_KEY = "leader-election.lease-name";
110+
static final String LEADER_ELECTION_LEASE_NAMESPACE_KEY = "leader-election.lease-namespace";
111+
static final String LEADER_ELECTION_IDENTITY_KEY = "leader-election.identity";
112+
static final String LEADER_ELECTION_LEASE_DURATION_KEY = "leader-election.lease-duration";
113+
static final String LEADER_ELECTION_RENEW_DEADLINE_KEY = "leader-election.renew-deadline";
114+
static final String LEADER_ELECTION_RETRY_PERIOD_KEY = "leader-election.retry-period";
115+
104116
// ---------------------------------------------------------------------------
105117
// Controller-level retry property suffixes
106118
// ---------------------------------------------------------------------------
@@ -166,7 +178,15 @@ public ConfigLoader(
166178
* no binding has a matching value, preserving the previous behavior.
167179
*/
168180
public Consumer<ConfigurationServiceOverrider> applyConfigs() {
169-
return buildConsumer(OPERATOR_BINDINGS, operatorKeyPrefix);
181+
Consumer<ConfigurationServiceOverrider> consumer =
182+
buildConsumer(OPERATOR_BINDINGS, operatorKeyPrefix);
183+
184+
Consumer<ConfigurationServiceOverrider> leaderElectionStep =
185+
buildLeaderElectionConsumer(operatorKeyPrefix);
186+
if (leaderElectionStep != null) {
187+
consumer = consumer.andThen(leaderElectionStep);
188+
}
189+
return consumer;
170190
}
171191

172192
/**
@@ -226,6 +246,60 @@ private <R extends HasMetadata> Consumer<ControllerConfigurationOverrider<R>> bu
226246
};
227247
}
228248

249+
/**
250+
* If leader election is explicitly disabled via {@code leader-election.enabled=false}, returns
251+
* {@code null}. Otherwise, if at least one leader-election property is present (with {@code
252+
* leader-election.lease-name} being required), returns a {@link Consumer} that builds a {@link
253+
* io.javaoperatorsdk.operator.api.config.LeaderElectionConfiguration} via {@link
254+
* LeaderElectionConfigurationBuilder} and applies it to the overrider. Returns {@code null} when
255+
* no leader-election properties are present at all.
256+
*/
257+
private Consumer<ConfigurationServiceOverrider> buildLeaderElectionConsumer(String prefix) {
258+
Optional<Boolean> enabled =
259+
configProvider.getValue(prefix + LEADER_ELECTION_ENABLED_KEY, Boolean.class);
260+
if (enabled.isPresent() && !enabled.get()) {
261+
return null;
262+
}
263+
264+
Optional<String> leaseName =
265+
configProvider.getValue(prefix + LEADER_ELECTION_LEASE_NAME_KEY, String.class);
266+
Optional<String> leaseNamespace =
267+
configProvider.getValue(prefix + LEADER_ELECTION_LEASE_NAMESPACE_KEY, String.class);
268+
Optional<String> identity =
269+
configProvider.getValue(prefix + LEADER_ELECTION_IDENTITY_KEY, String.class);
270+
Optional<Duration> leaseDuration =
271+
configProvider.getValue(prefix + LEADER_ELECTION_LEASE_DURATION_KEY, Duration.class);
272+
Optional<Duration> renewDeadline =
273+
configProvider.getValue(prefix + LEADER_ELECTION_RENEW_DEADLINE_KEY, Duration.class);
274+
Optional<Duration> retryPeriod =
275+
configProvider.getValue(prefix + LEADER_ELECTION_RETRY_PERIOD_KEY, Duration.class);
276+
277+
if (leaseName.isEmpty()
278+
&& leaseNamespace.isEmpty()
279+
&& identity.isEmpty()
280+
&& leaseDuration.isEmpty()
281+
&& renewDeadline.isEmpty()
282+
&& retryPeriod.isEmpty()) {
283+
return null;
284+
}
285+
286+
return overrider -> {
287+
var builder =
288+
LeaderElectionConfigurationBuilder.aLeaderElectionConfiguration(
289+
leaseName.orElseThrow(
290+
() ->
291+
new IllegalStateException(
292+
"leader-election.lease-name must be set when configuring leader"
293+
+ " election")));
294+
leaseNamespace.ifPresent(builder::withLeaseNamespace);
295+
identity.ifPresent(builder::withIdentity);
296+
leaseDuration.ifPresent(builder::withLeaseDuration);
297+
renewDeadline.ifPresent(builder::withRenewDeadline);
298+
retryPeriod.ifPresent(builder::withRetryPeriod);
299+
overrider.withLeaderElectionConfiguration(builder.build());
300+
};
301+
}
302+
229303
/**
230304
* Iterates {@code bindings} and, for each one whose key (optionally prefixed by {@code
231305
* keyPrefix}) is present in the {@link ConfigProvider}, accumulates a call to the binding's

operator-framework/src/test/java/io/javaoperatorsdk/operator/config/loader/ConfigLoaderTest.java

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import io.javaoperatorsdk.operator.api.config.ControllerConfigurationOverrider;
3333

3434
import static org.assertj.core.api.Assertions.assertThat;
35+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
3536

3637
class ConfigLoaderTest {
3738

@@ -288,6 +289,84 @@ void controllerBindingsCoverAllSingleScalarSettersOnControllerConfigurationOverr
288289
.containsExactlyInAnyOrderElementsOf(expectedSetters);
289290
}
290291

292+
// -- leader election --------------------------------------------------------
293+
294+
@Test
295+
void leaderElectionIsNotConfiguredWhenNoPropertiesPresent() {
296+
var loader = new ConfigLoader(mapProvider(Map.of()));
297+
var base = new BaseConfigurationService(null);
298+
var result =
299+
ConfigurationService.newOverriddenConfigurationService(base, loader.applyConfigs());
300+
assertThat(result.getLeaderElectionConfiguration()).isEmpty();
301+
}
302+
303+
@Test
304+
void leaderElectionIsNotConfiguredWhenExplicitlyDisabled() {
305+
var values = new HashMap<String, Object>();
306+
values.put("josdk.leader-election.enabled", false);
307+
values.put("josdk.leader-election.lease-name", "my-lease");
308+
var loader = new ConfigLoader(mapProvider(values));
309+
var base = new BaseConfigurationService(null);
310+
var result =
311+
ConfigurationService.newOverriddenConfigurationService(base, loader.applyConfigs());
312+
assertThat(result.getLeaderElectionConfiguration()).isEmpty();
313+
}
314+
315+
@Test
316+
void leaderElectionConfiguredWithLeaseNameOnly() {
317+
var loader =
318+
new ConfigLoader(mapProvider(Map.of("josdk.leader-election.lease-name", "my-lease")));
319+
var base = new BaseConfigurationService(null);
320+
var result =
321+
ConfigurationService.newOverriddenConfigurationService(base, loader.applyConfigs());
322+
assertThat(result.getLeaderElectionConfiguration())
323+
.hasValueSatisfying(
324+
le -> {
325+
assertThat(le.getLeaseName()).isEqualTo("my-lease");
326+
assertThat(le.getLeaseNamespace()).isEmpty();
327+
assertThat(le.getIdentity()).isEmpty();
328+
});
329+
}
330+
331+
@Test
332+
void leaderElectionConfiguredWithAllProperties() {
333+
var values = new HashMap<String, Object>();
334+
values.put("josdk.leader-election.enabled", true);
335+
values.put("josdk.leader-election.lease-name", "my-lease");
336+
values.put("josdk.leader-election.lease-namespace", "my-ns");
337+
values.put("josdk.leader-election.identity", "pod-1");
338+
values.put("josdk.leader-election.lease-duration", Duration.ofSeconds(20));
339+
values.put("josdk.leader-election.renew-deadline", Duration.ofSeconds(15));
340+
values.put("josdk.leader-election.retry-period", Duration.ofSeconds(3));
341+
var loader = new ConfigLoader(mapProvider(values));
342+
343+
var base = new BaseConfigurationService(null);
344+
var result =
345+
ConfigurationService.newOverriddenConfigurationService(base, loader.applyConfigs());
346+
347+
assertThat(result.getLeaderElectionConfiguration())
348+
.hasValueSatisfying(
349+
le -> {
350+
assertThat(le.getLeaseName()).isEqualTo("my-lease");
351+
assertThat(le.getLeaseNamespace()).hasValue("my-ns");
352+
assertThat(le.getIdentity()).hasValue("pod-1");
353+
assertThat(le.getLeaseDuration()).isEqualTo(Duration.ofSeconds(20));
354+
assertThat(le.getRenewDeadline()).isEqualTo(Duration.ofSeconds(15));
355+
assertThat(le.getRetryPeriod()).isEqualTo(Duration.ofSeconds(3));
356+
});
357+
}
358+
359+
@Test
360+
void leaderElectionMissingLeaseNameThrowsWhenOtherPropertiesPresent() {
361+
var loader =
362+
new ConfigLoader(mapProvider(Map.of("josdk.leader-election.lease-namespace", "my-ns")));
363+
var base = new BaseConfigurationService(null);
364+
var consumer = loader.applyConfigs();
365+
assertThatExceptionOfType(IllegalStateException.class)
366+
.isThrownBy(() -> ConfigurationService.newOverriddenConfigurationService(base, consumer))
367+
.withMessageContaining("lease-name");
368+
}
369+
291370
/** Returns true when the two types are the same or one is the boxed/unboxed form of the other. */
292371
private static boolean isTypeCompatible(Class<?> methodParam, Class<?> bindingType) {
293372
if (methodParam == bindingType) return true;

0 commit comments

Comments
 (0)