Skip to content

Commit 1ab7e43

Browse files
authored
refactor: avoid unneeded initializations in injection contexts (#2950)
* refactor: avoid unneeded initializations in injection contexts Signed-off-by: Chris Laprun <[email protected]> * docs: added javadoc and tests Signed-off-by: Chris Laprun <[email protected]> --------- Signed-off-by: Chris Laprun <[email protected]>
1 parent aad964b commit 1ab7e43

File tree

2 files changed

+103
-29
lines changed

2 files changed

+103
-29
lines changed

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

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,17 @@
2424
public class Operator implements LifecycleAware {
2525
private static final Logger log = LoggerFactory.getLogger(Operator.class);
2626

27-
private final ControllerManager controllerManager;
28-
private final LeaderElectionManager leaderElectionManager;
29-
private final ConfigurationService configurationService;
27+
private ControllerManager controllerManager;
28+
private LeaderElectionManager leaderElectionManager;
29+
private ConfigurationService configurationService;
3030
private volatile boolean started = false;
3131

3232
public Operator() {
33-
this((KubernetesClient) null);
33+
init(initConfigurationService(null, null), true);
3434
}
3535

3636
Operator(KubernetesClient kubernetesClient) {
37-
this(initConfigurationService(kubernetesClient, null));
37+
init(initConfigurationService(kubernetesClient, null), false);
3838
}
3939

4040
/**
@@ -46,12 +46,7 @@ public Operator() {
4646
* operator
4747
*/
4848
public Operator(ConfigurationService configurationService) {
49-
this.configurationService = configurationService;
50-
51-
final var executorServiceManager = configurationService.getExecutorServiceManager();
52-
controllerManager = new ControllerManager(executorServiceManager);
53-
54-
leaderElectionManager = new LeaderElectionManager(controllerManager, configurationService);
49+
init(configurationService, false);
5550
}
5651

5752
/**
@@ -62,10 +57,55 @@ public Operator(ConfigurationService configurationService) {
6257
* {@link ConfigurationService} values
6358
*/
6459
public Operator(Consumer<ConfigurationServiceOverrider> overrider) {
65-
this(initConfigurationService(null, overrider));
60+
init(initConfigurationService(null, overrider), false);
61+
}
62+
63+
/**
64+
* In a deferred initialization scenario, the default constructor will typically be called to
65+
* create a proxy instance, usually to be replaced at some later time when the dependents (in this
66+
* case the ConfigurationService instance) are available. In this situation, we want to make it
67+
* possible to not perform the initialization steps directly so this implementation makes it
68+
* possible to not crash when a null ConfigurationService is passed only if deferred
69+
* initialization is allowed
70+
*
71+
* @param configurationService the potentially {@code null} {@link ConfigurationService} to use
72+
* for this operator
73+
* @param allowDeferredInit whether or not deferred initialization of the configuration service is
74+
* allowed
75+
* @throws IllegalStateException if the specified configuration service is {@code null} but
76+
* deferred initialization is not allowed
77+
*/
78+
private void init(ConfigurationService configurationService, boolean allowDeferredInit) {
79+
if (configurationService == null) {
80+
if (!allowDeferredInit) {
81+
throw new IllegalStateException(
82+
"Deferred initialization of ConfigurationService is not allowed");
83+
}
84+
} else {
85+
this.configurationService = configurationService;
86+
87+
final var executorServiceManager = configurationService.getExecutorServiceManager();
88+
controllerManager = new ControllerManager(executorServiceManager);
89+
90+
leaderElectionManager = new LeaderElectionManager(controllerManager, configurationService);
91+
}
6692
}
6793

68-
private static ConfigurationService initConfigurationService(
94+
/**
95+
* Overridable by subclasses to enable deferred configuration, useful to avoid unneeded processing
96+
* in injection scenarios, typically returning {@code null} here instead of performing any
97+
* configuration
98+
*
99+
* @param client a potentially {@code null} {@link KubernetesClient} to initialize the operator's
100+
* {@link ConfigurationService} with
101+
* @param overrider a potentially {@code null} {@link ConfigurationServiceOverrider} consumer to
102+
* override the default {@link ConfigurationService} with
103+
* @return a ready to use {@link ConfigurationService} using values provided by the specified
104+
* overrides and kubernetes client, if provided or {@code null} in case deferred
105+
* initialization is possible, in which case it is up to the extension to ensure that the
106+
* {@link ConfigurationService} is properly set before the operator instance is used
107+
*/
108+
protected ConfigurationService initConfigurationService(
69109
KubernetesClient client, Consumer<ConfigurationServiceOverrider> overrider) {
70110
// initialize the client if the user didn't provide one
71111
if (client == null) {
@@ -232,8 +272,8 @@ public <P extends HasMetadata> RegisteredController<P> register(
232272
*
233273
* @param reconciler part of the reconciler to register
234274
* @param configOverrider consumer to use to change config values
235-
* @return registered controller
236275
* @param <P> the {@code HasMetadata} type associated with the reconciler
276+
* @return registered controller
237277
*/
238278
public <P extends HasMetadata> RegisteredController<P> register(
239279
Reconciler<P> reconciler, Consumer<ControllerConfigurationOverrider<P>> configOverrider) {
@@ -266,4 +306,14 @@ boolean isStarted() {
266306
public ConfigurationService getConfigurationService() {
267307
return configurationService;
268308
}
309+
310+
/**
311+
* Make it possible for extensions to set the {@link ConfigurationService} after the operator has
312+
* been initialized
313+
*
314+
* @param configurationService the {@link ConfigurationService} to use for this operator
315+
*/
316+
protected void setConfigurationService(ConfigurationService configurationService) {
317+
init(configurationService, false);
318+
}
269319
}
Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,24 @@
11
package io.javaoperatorsdk.operator;
22

3-
import org.junit.jupiter.api.BeforeEach;
3+
import java.util.function.Consumer;
4+
45
import org.junit.jupiter.api.Test;
56

67
import io.fabric8.kubernetes.api.model.ConfigMap;
78
import io.fabric8.kubernetes.client.KubernetesClient;
9+
import io.javaoperatorsdk.operator.api.config.ConfigurationService;
10+
import io.javaoperatorsdk.operator.api.config.ConfigurationServiceOverrider;
811
import io.javaoperatorsdk.operator.api.reconciler.Context;
9-
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
1012
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
1113
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
1214

13-
import static org.junit.jupiter.api.Assertions.assertEquals;
14-
import static org.junit.jupiter.api.Assertions.assertTrue;
15+
import static org.junit.jupiter.api.Assertions.*;
1516

1617
@SuppressWarnings("rawtypes")
1718
class OperatorTest {
18-
19-
private final KubernetesClient kubernetesClient = MockKubernetesClient.client(ConfigMap.class);
20-
private Operator operator;
21-
22-
@BeforeEach
23-
void initOperator() {
24-
operator = new Operator(kubernetesClient);
25-
}
26-
2719
@Test
2820
void shouldBePossibleToRetrieveNumberOfRegisteredControllers() {
21+
final var operator = new Operator();
2922
assertEquals(0, operator.getRegisteredControllersNumber());
3023

3124
operator.register(new FooReconciler());
@@ -34,6 +27,7 @@ void shouldBePossibleToRetrieveNumberOfRegisteredControllers() {
3427

3528
@Test
3629
void shouldBePossibleToRetrieveRegisteredControllerByName() {
30+
final var operator = new Operator();
3731
final var reconciler = new FooReconciler();
3832
final var name = ReconcilerUtils.getNameFor(reconciler);
3933

@@ -51,12 +45,42 @@ void shouldBePossibleToRetrieveRegisteredControllerByName() {
5145
assertEquals(maybeController.get(), registeredControllers.stream().findFirst().orElseThrow());
5246
}
5347

54-
@ControllerConfiguration
55-
private static class FooReconciler implements Reconciler<ConfigMap> {
48+
@Test
49+
void shouldThrowExceptionIf() {
50+
final var operator = new OperatorExtension();
51+
assertNotNull(operator);
52+
operator.setConfigurationService(ConfigurationService.newOverriddenConfigurationService(null));
53+
assertNotNull(operator.getConfigurationService());
54+
55+
// should fail because the implementation is not providing a valid configuration service when
56+
// constructing the operator
57+
assertThrows(
58+
IllegalStateException.class,
59+
() -> new OperatorExtension(MockKubernetesClient.client(ConfigMap.class)));
60+
}
5661

62+
private static class FooReconciler implements Reconciler<ConfigMap> {
5763
@Override
5864
public UpdateControl<ConfigMap> reconcile(ConfigMap resource, Context context) {
5965
return UpdateControl.noUpdate();
6066
}
6167
}
68+
69+
private static class OperatorExtension extends Operator {
70+
public OperatorExtension() {}
71+
72+
public OperatorExtension(KubernetesClient client) {
73+
super(client);
74+
}
75+
76+
/**
77+
* Overridden to mimic deferred initialization (or rather the fact that we don't want to do that
78+
* processing at this time so return null).
79+
*/
80+
@Override
81+
protected ConfigurationService initConfigurationService(
82+
KubernetesClient client, Consumer<ConfigurationServiceOverrider> overrider) {
83+
return null;
84+
}
85+
}
6286
}

0 commit comments

Comments
 (0)