Skip to content

Commit f2a9ffb

Browse files
metacosmcsviri
authored andcommitted
fix: improve ConfigurationServiceOverrider's resilience (#1420)
1 parent 083d5b0 commit f2a9ffb

File tree

7 files changed

+182
-50
lines changed

7 files changed

+182
-50
lines changed

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

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,23 @@ public Operator() {
3131
}
3232

3333
public Operator(KubernetesClient kubernetesClient) {
34-
this(kubernetesClient, ConfigurationServiceProvider.instance(), null);
34+
this(kubernetesClient, ConfigurationServiceProvider.instance());
3535
}
3636

3737
/**
3838
* @deprecated Use {@link #Operator(Consumer)} instead
3939
*/
40-
@Deprecated
40+
@Deprecated(forRemoval = true)
4141
public Operator(ConfigurationService configurationService) {
42-
this(null, configurationService, null);
42+
this(null, configurationService);
4343
}
4444

4545
public Operator(Consumer<ConfigurationServiceOverrider> overrider) {
4646
this(null, overrider);
4747
}
4848

4949
public Operator(KubernetesClient client, Consumer<ConfigurationServiceOverrider> overrider) {
50-
this(client, ConfigurationServiceProvider.instance(), overrider);
50+
this(client, ConfigurationServiceProvider.overrideCurrent(overrider));
5151
}
5252

5353
/**
@@ -58,19 +58,11 @@ public Operator(KubernetesClient client, Consumer<ConfigurationServiceOverrider>
5858
* @param configurationService provides configuration
5959
*/
6060
public Operator(KubernetesClient kubernetesClient, ConfigurationService configurationService) {
61-
this(kubernetesClient, configurationService, null);
62-
}
63-
64-
private Operator(KubernetesClient kubernetesClient, ConfigurationService configurationService,
65-
Consumer<ConfigurationServiceOverrider> overrider) {
6661
this.kubernetesClient =
6762
kubernetesClient != null ? kubernetesClient : new KubernetesClientBuilder().build();
68-
ConfigurationServiceProvider.set(configurationService);
69-
if (overrider != null) {
70-
ConfigurationServiceProvider.overrideCurrent(overrider);
71-
}
72-
ConfigurationServiceProvider.instance().getLeaderElectionConfiguration()
63+
configurationService.getLeaderElectionConfiguration()
7364
.ifPresent(c -> leaderElectionManager.init(c, this.kubernetesClient));
65+
ConfigurationServiceProvider.set(configurationService);
7466
}
7567

7668
/** Adds a shutdown hook that automatically calls {@link #stop()} when the app shuts down. */

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverrider.java

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,19 @@ public class ConfigurationServiceOverrider {
1515
private final ConfigurationService original;
1616
private Metrics metrics;
1717
private Config clientConfig;
18-
private boolean checkCR;
19-
private int threadNumber;
18+
private Boolean checkCR;
19+
private Integer threadNumber;
2020
private Cloner cloner;
21-
private int timeoutSeconds;
22-
private boolean closeClientOnStop;
21+
private Integer timeoutSeconds;
22+
private Boolean closeClientOnStop;
2323
private ObjectMapper objectMapper;
24-
private ExecutorService executorService = null;
24+
private ExecutorService executorService;
2525
private LeaderElectionConfiguration leaderElectionConfiguration;
2626

2727
ConfigurationServiceOverrider(ConfigurationService original) {
2828
this.original = original;
29-
this.clientConfig = original.getClientConfiguration();
30-
this.checkCR = original.checkCRDAndValidateLocalModel();
31-
this.threadNumber = original.concurrentReconciliationThreads();
32-
this.cloner = original.getResourceCloner();
33-
this.timeoutSeconds = original.getTerminationTimeoutSeconds();
34-
this.metrics = original.getMetrics();
35-
this.closeClientOnStop = original.closeClientOnStop();
36-
this.objectMapper = original.getObjectMapper();
3729
}
3830

39-
4031
public ConfigurationServiceOverrider withClientConfiguration(Config configuration) {
4132
this.clientConfig = configuration;
4233
return this;
@@ -97,51 +88,48 @@ public Set<String> getKnownReconcilerNames() {
9788

9889
@Override
9990
public Config getClientConfiguration() {
100-
return clientConfig;
91+
return clientConfig != null ? clientConfig : original.getClientConfiguration();
10192
}
10293

10394
@Override
10495
public boolean checkCRDAndValidateLocalModel() {
105-
return checkCR;
96+
return checkCR != null ? checkCR : original.checkCRDAndValidateLocalModel();
10697
}
10798

10899
@Override
109100
public int concurrentReconciliationThreads() {
110-
return threadNumber;
101+
return threadNumber != null ? threadNumber : original.concurrentReconciliationThreads();
111102
}
112103

113104
@Override
114105
public int getTerminationTimeoutSeconds() {
115-
return timeoutSeconds;
106+
return timeoutSeconds != null ? timeoutSeconds : original.getTerminationTimeoutSeconds();
116107
}
117108

118109
@Override
119110
public Metrics getMetrics() {
120-
return metrics;
111+
return metrics != null ? metrics : original.getMetrics();
121112
}
122113

123114
@Override
124115
public boolean closeClientOnStop() {
125-
return closeClientOnStop;
116+
return closeClientOnStop != null ? closeClientOnStop : original.closeClientOnStop();
126117
}
127118

128119
@Override
129120
public ExecutorService getExecutorService() {
130-
if (executorService != null) {
131-
return executorService;
132-
} else {
133-
return super.getExecutorService();
134-
}
121+
return executorService != null ? executorService : original.getExecutorService();
135122
}
136123

137124
@Override
138125
public ObjectMapper getObjectMapper() {
139-
return objectMapper;
126+
return objectMapper != null ? objectMapper : original.getObjectMapper();
140127
}
141128

142129
@Override
143130
public Optional<LeaderElectionConfiguration> getLeaderElectionConfiguration() {
144-
return Optional.ofNullable(leaderElectionConfiguration);
131+
return leaderElectionConfiguration != null ? Optional.of(leaderElectionConfiguration)
132+
: original.getLeaderElectionConfiguration();
145133
}
146134
};
147135
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceProvider.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,14 @@ private static void set(ConfigurationService instance, boolean overriding) {
4545
ConfigurationServiceProvider.instance = instance;
4646
}
4747

48-
public synchronized static void overrideCurrent(
48+
public synchronized static ConfigurationService overrideCurrent(
4949
Consumer<ConfigurationServiceOverrider> overrider) {
50-
final var toOverride =
51-
new ConfigurationServiceOverrider(ConfigurationServiceProvider.instance());
52-
overrider.accept(toOverride);
53-
ConfigurationServiceProvider.set(toOverride.build(), true);
50+
if (overrider != null) {
51+
final var toOverride = new ConfigurationServiceOverrider(instance());
52+
overrider.accept(toOverride);
53+
set(toOverride.build(), true);
54+
}
55+
return instance();
5456
}
5557

5658
public synchronized static void setDefault(ConfigurationService defaultConfigurationService) {

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/OperatorTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import io.fabric8.kubernetes.client.KubernetesClient;
1212
import io.javaoperatorsdk.operator.api.config.AbstractConfigurationService;
1313
import io.javaoperatorsdk.operator.api.config.ConfigurationServiceProvider;
14+
import io.javaoperatorsdk.operator.api.config.LeaderElectionConfiguration;
1415
import io.javaoperatorsdk.operator.api.reconciler.Context;
1516
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
1617
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
@@ -74,6 +75,15 @@ void shouldBePossibleToRetrieveRegisteredControllerByName() {
7475
assertEquals(maybeController.get(), registeredControllers.stream().findFirst().orElseThrow());
7576
}
7677

78+
@Test
79+
void shouldBeAbleToProvideLeaderElectionConfiguration() {
80+
assertTrue(ConfigurationServiceProvider.instance().getLeaderElectionConfiguration().isEmpty());
81+
new Operator(kubernetesClient, c -> c.withLeaderElectionConfiguration(
82+
new LeaderElectionConfiguration("leader-election-test", "namespace", "identity")));
83+
assertEquals("identity", ConfigurationServiceProvider.instance()
84+
.getLeaderElectionConfiguration().orElseThrow().getIdentity().orElseThrow());
85+
}
86+
7787
@ControllerConfiguration
7888
private static class FooReconciler implements Reconciler<ConfigMap> {
7989

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package io.javaoperatorsdk.operator.api.config;
2+
3+
import java.util.Optional;
4+
import java.util.concurrent.ExecutorService;
5+
import java.util.concurrent.Executors;
6+
7+
import org.junit.jupiter.api.Test;
8+
9+
import io.fabric8.kubernetes.api.model.HasMetadata;
10+
import io.fabric8.kubernetes.client.Config;
11+
import io.fabric8.kubernetes.client.ConfigBuilder;
12+
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
13+
import io.javaoperatorsdk.operator.api.monitoring.Metrics;
14+
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
15+
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResourceFactory;
16+
17+
import com.fasterxml.jackson.databind.ObjectMapper;
18+
19+
import static org.junit.jupiter.api.Assertions.*;
20+
21+
class ConfigurationServiceOverriderTest {
22+
23+
private static final Metrics METRICS = new Metrics() {};
24+
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
25+
private static final LeaderElectionConfiguration LEADER_ELECTION_CONFIGURATION =
26+
new LeaderElectionConfiguration("foo", "fooNS");
27+
private static final DependentResourceFactory FACTORY = new DependentResourceFactory() {
28+
@Override
29+
public <T extends DependentResource<?, ?>> T createFrom(DependentResourceSpec<T, ?> spec) {
30+
return DependentResourceFactory.super.createFrom(spec);
31+
}
32+
};
33+
34+
private static final Cloner CLONER = new Cloner() {
35+
@Override
36+
public <R extends HasMetadata> R clone(R object) {
37+
return null;
38+
}
39+
};
40+
41+
@Test
42+
void overrideShouldWork() {
43+
final var config = new BaseConfigurationService(null) {
44+
@Override
45+
public boolean checkCRDAndValidateLocalModel() {
46+
return false;
47+
}
48+
49+
@Override
50+
public Config getClientConfiguration() {
51+
return new ConfigBuilder().withNamespace("namespace").build();
52+
}
53+
54+
@Override
55+
public int concurrentReconciliationThreads() {
56+
return -1;
57+
}
58+
59+
@Override
60+
public int getTerminationTimeoutSeconds() {
61+
return -1;
62+
}
63+
64+
@Override
65+
public Metrics getMetrics() {
66+
return METRICS;
67+
}
68+
69+
@Override
70+
public ExecutorService getExecutorService() {
71+
return null;
72+
}
73+
74+
@Override
75+
public boolean closeClientOnStop() {
76+
return true;
77+
}
78+
79+
@Override
80+
public ObjectMapper getObjectMapper() {
81+
return OBJECT_MAPPER;
82+
}
83+
84+
@Override
85+
public Cloner getResourceCloner() {
86+
return CLONER;
87+
}
88+
89+
@Override
90+
public DependentResourceFactory dependentResourceFactory() {
91+
return FACTORY;
92+
}
93+
94+
@Override
95+
public Optional<LeaderElectionConfiguration> getLeaderElectionConfiguration() {
96+
return Optional.of(LEADER_ELECTION_CONFIGURATION);
97+
}
98+
};
99+
final var overridden = new ConfigurationServiceOverrider(config)
100+
.withClientConfiguration(new ConfigBuilder().withNamespace("newNS").build())
101+
.checkingCRDAndValidateLocalModel(true)
102+
.withExecutorService(Executors.newSingleThreadExecutor())
103+
.withCloseClientOnStop(false)
104+
.withObjectMapper(new ObjectMapper())
105+
.withResourceCloner(new Cloner() {
106+
@Override
107+
public <R extends HasMetadata> R clone(R object) {
108+
return null;
109+
}
110+
})
111+
.withConcurrentReconciliationThreads(25)
112+
.withTerminationTimeoutSeconds(100)
113+
.withMetrics(new Metrics() {})
114+
.withLeaderElectionConfiguration(new LeaderElectionConfiguration("newLease", "newLeaseNS"))
115+
.build();
116+
117+
assertNotEquals(config.closeClientOnStop(), overridden.closeClientOnStop());
118+
assertNotEquals(config.checkCRDAndValidateLocalModel(),
119+
overridden.checkCRDAndValidateLocalModel());
120+
assertNotEquals(config.concurrentReconciliationThreads(),
121+
overridden.concurrentReconciliationThreads());
122+
assertNotEquals(config.getTerminationTimeoutSeconds(),
123+
overridden.getTerminationTimeoutSeconds());
124+
assertNotEquals(config.dependentResourceFactory(), overridden.dependentResourceFactory());
125+
assertNotEquals(config.getClientConfiguration(), overridden.getClientConfiguration());
126+
assertNotEquals(config.getExecutorService(), overridden.getExecutorService());
127+
assertNotEquals(config.getMetrics(), overridden.getMetrics());
128+
assertNotEquals(config.getObjectMapper(), overridden.getObjectMapper());
129+
assertNotEquals(config.getLeaderElectionConfiguration(),
130+
overridden.getLeaderElectionConfiguration());
131+
}
132+
}

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceProviderTest.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,23 @@ void shouldProvideTheSetInstanceIfProvided() {
3434

3535
@Test
3636
void shouldBePossibleToOverrideConfigOnce() {
37-
final var config = new AbstractConfigurationService(null);
37+
final var config = new ConfigurationServiceOverrider(new AbstractConfigurationService(null))
38+
.withLeaderElectionConfiguration(new LeaderElectionConfiguration("bar", "barNS"))
39+
.build();
3840
assertFalse(config.checkCRDAndValidateLocalModel());
41+
assertEquals("bar", config.getLeaderElectionConfiguration().orElseThrow().getLeaseName());
3942

4043
ConfigurationServiceProvider.set(config);
4144
var instance = ConfigurationServiceProvider.instance();
4245
assertEquals(config, instance);
4346

44-
ConfigurationServiceProvider.overrideCurrent(o -> o.checkingCRDAndValidateLocalModel(true));
47+
final ConfigurationService overridden = ConfigurationServiceProvider.overrideCurrent(
48+
o -> o.checkingCRDAndValidateLocalModel(true));
4549
instance = ConfigurationServiceProvider.instance();
50+
assertEquals(overridden, instance);
4651
assertNotEquals(config, instance);
4752
assertTrue(instance.checkCRDAndValidateLocalModel());
53+
assertEquals("bar", instance.getLeaderElectionConfiguration().orElseThrow().getLeaseName());
4854

4955
assertThrows(IllegalStateException.class,
5056
() -> ConfigurationServiceProvider.overrideCurrent(o -> o.withCloseClientOnStop(false)));

sample-operators/leader-election/src/test/java/io/javaoperatorsdk/operator/sample/LeaderElectionE2E.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ class LeaderElectionE2E {
5050
// not for local mode by design
5151
@EnabledIfSystemProperty(named = "test.deployment", matches = "remote")
5252
void otherInstancesTakesOverWhenSteppingDown() {
53-
log.info("Deploying operator");
54-
deployOperatorsInOrder();
5553
log.info("Applying custom resource");
5654
applyCustomResource();
55+
log.info("Deploying operator instances");
56+
deployOperatorsInOrder();
5757

5858
log.info("Awaiting custom resource reconciliations");
5959
await().pollDelay(Duration.ofSeconds(MINIMAL_SECONDS_FOR_RENEWAL))
@@ -131,12 +131,14 @@ void tearDown() {
131131
}
132132

133133
private void deployOperatorsInOrder() {
134+
log.info("Installing 1st instance");
134135
applyResources("k8s/operator.yaml");
135136
await().atMost(Duration.ofSeconds(POD_STARTUP_TIMEOUT)).untilAsserted(() -> {
136137
var pod = client.pods().inNamespace(namespace).withName(OPERATOR_1_POD_NAME).get();
137138
assertThat(pod.getStatus().getContainerStatuses().get(0).getReady()).isTrue();
138139
});
139140

141+
log.info("Installing 2nd instance");
140142
applyResources("k8s/operator-instance-2.yaml");
141143
await().atMost(Duration.ofSeconds(POD_STARTUP_TIMEOUT)).untilAsserted(() -> {
142144
var pod = client.pods().inNamespace(namespace).withName(OPERATOR_2_POD_NAME).get();

0 commit comments

Comments
 (0)