Skip to content

Commit 26e96c8

Browse files
authored
fix: make things more resilient in the absence of explicit configuration (#789)
1 parent 14a543e commit 26e96c8

File tree

10 files changed

+104
-73
lines changed

10 files changed

+104
-73
lines changed

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,23 @@ public class ExecutorServiceManager {
1919
private final ExecutorService executor;
2020
private final int terminationTimeoutSeconds;
2121

22-
private ExecutorServiceManager(ExecutorService executor, int terminationTimeoutSeconds) {
22+
private ExecutorServiceManager(InstrumentedExecutorService executor,
23+
int terminationTimeoutSeconds) {
2324
this.executor = executor;
2425
this.terminationTimeoutSeconds = terminationTimeoutSeconds;
2526
}
2627

2728
public static void init(ConfigurationService configuration) {
2829
if (instance == null) {
30+
if (configuration == null) {
31+
configuration = new BaseConfigurationService(Version.UNKNOWN);
32+
}
2933
instance = new ExecutorServiceManager(
3034
new InstrumentedExecutorService(configuration.getExecutorService()),
3135
configuration.getTerminationTimeoutSeconds());
36+
log.debug("Initialized ExecutorServiceManager executor: {}, timeout: {}",
37+
configuration.getExecutorService().getClass(),
38+
configuration.getTerminationTimeoutSeconds());
3239
} else {
3340
log.debug("Already started, reusing already setup instance!");
3441
}
@@ -45,8 +52,8 @@ public static void stop() {
4552

4653
public static ExecutorServiceManager instance() {
4754
if (instance == null) {
48-
throw new IllegalStateException(
49-
"ExecutorServiceManager hasn't been started. Call start method before using!");
55+
// provide a default configuration if none has been provided by init
56+
init(null);
5057
}
5158
return instance;
5259
}
@@ -72,6 +79,9 @@ private static class InstrumentedExecutorService implements ExecutorService {
7279
private final ExecutorService executor;
7380

7481
private InstrumentedExecutorService(ExecutorService executor) {
82+
if (executor == null) {
83+
throw new NullPointerException();
84+
}
7585
this.executor = executor;
7686
debug = Utils.debugThreadPool();
7787
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@
1212
import io.javaoperatorsdk.operator.CustomResourceUtils;
1313
import io.javaoperatorsdk.operator.MissingCRDException;
1414
import io.javaoperatorsdk.operator.OperatorException;
15+
import io.javaoperatorsdk.operator.api.config.BaseConfigurationService;
16+
import io.javaoperatorsdk.operator.api.config.ConfigurationService;
1517
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
18+
import io.javaoperatorsdk.operator.api.config.Version;
19+
import io.javaoperatorsdk.operator.api.monitoring.Metrics;
1620
import io.javaoperatorsdk.operator.api.monitoring.Metrics.ControllerExecution;
1721
import io.javaoperatorsdk.operator.api.reconciler.Context;
1822
import io.javaoperatorsdk.operator.api.reconciler.DeleteControl;
@@ -29,6 +33,7 @@ public class Controller<R extends HasMetadata> implements Reconciler<R>,
2933
private final ControllerConfiguration<R> configuration;
3034
private final KubernetesClient kubernetesClient;
3135
private EventSourceManager<R> eventSourceManager;
36+
private volatile ConfigurationService configurationService;
3237

3338
public Controller(Reconciler<R> reconciler,
3439
ControllerConfiguration<R> configuration,
@@ -40,7 +45,7 @@ public Controller(Reconciler<R> reconciler,
4045

4146
@Override
4247
public DeleteControl cleanup(R resource, Context context) {
43-
return configuration.getConfigurationService().getMetrics().timeControllerExecution(
48+
return metrics().timeControllerExecution(
4449
new ControllerExecution<>() {
4550
@Override
4651
public String name() {
@@ -66,7 +71,7 @@ public DeleteControl execute() {
6671

6772
@Override
6873
public UpdateControl<R> reconcile(R resource, Context context) {
69-
return configuration.getConfigurationService().getMetrics().timeControllerExecution(
74+
return metrics().timeControllerExecution(
7075
new ControllerExecution<>() {
7176
@Override
7277
public String name() {
@@ -97,6 +102,11 @@ public UpdateControl<R> execute() {
97102
});
98103
}
99104

105+
private Metrics metrics() {
106+
final var metrics = configurationService().getMetrics();
107+
return metrics != null ? metrics : Metrics.NOOP;
108+
}
109+
100110
@Override
101111
public List<EventSource> prepareEventSources(EventSourceInitializationContext<R> context) {
102112
throw new UnsupportedOperationException("This method should never be called directly");
@@ -157,7 +167,7 @@ public void start() throws OperatorException {
157167
try {
158168
// check that the custom resource is known by the cluster if configured that way
159169
final CustomResourceDefinition crd; // todo: check proper CRD spec version based on config
160-
if (configuration.getConfigurationService().checkCRDAndValidateLocalModel()) {
170+
if (configurationService().checkCRDAndValidateLocalModel()) {
161171
crd =
162172
kubernetesClient.apiextensions().v1().customResourceDefinitions().withName(crdName)
163173
.get();
@@ -174,7 +184,7 @@ public void start() throws OperatorException {
174184
((EventSourceInitializer<R>) reconciler)
175185
.prepareEventSources(new EventSourceInitializationContext<>(
176186
eventSourceManager.getControllerResourceEventSource().getResourceCache(),
177-
configuration.getConfigurationService()))
187+
configurationService()))
178188
.forEach(eventSourceManager::registerEventSource);
179189
}
180190
if (failOnMissingCurrentNS()) {
@@ -189,6 +199,23 @@ public void start() throws OperatorException {
189199
}
190200
}
191201

202+
private ConfigurationService configurationService() {
203+
if (configurationService == null) {
204+
configurationService = configuration.getConfigurationService();
205+
// make sure we always have a default configuration service
206+
if (configurationService == null) {
207+
// we shouldn't need to register the configuration with the default service
208+
configurationService = new BaseConfigurationService(Version.UNKNOWN) {
209+
@Override
210+
public boolean checkCRDAndValidateLocalModel() {
211+
return false;
212+
}
213+
};
214+
}
215+
}
216+
return configurationService;
217+
}
218+
192219
private void throwMissingCRDException(String crdName, String specVersion, String controllerName) {
193220
throw new MissingCRDException(
194221
crdName,

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,10 @@ class EventProcessor<R extends HasMetadata> implements EventHandler, LifecycleAw
5656
new ReconciliationDispatcher<>(eventSourceManager.getController()),
5757
GenericRetry.fromConfiguration(
5858
eventSourceManager.getController().getConfiguration().getRetryConfiguration()),
59-
eventSourceManager.getController().getConfiguration().getConfigurationService()
60-
.getMetrics(),
59+
eventSourceManager.getController().getConfiguration().getConfigurationService() == null
60+
? Metrics.NOOP
61+
: eventSourceManager.getController().getConfiguration().getConfigurationService()
62+
.getMetrics(),
6163
eventSourceManager);
6264
}
6365

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import io.fabric8.kubernetes.client.dsl.MixedOperation;
1111
import io.fabric8.kubernetes.client.dsl.Resource;
1212
import io.javaoperatorsdk.operator.api.ObservedGenerationAware;
13+
import io.javaoperatorsdk.operator.api.config.ConfigurationService;
1314
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
1415
import io.javaoperatorsdk.operator.api.reconciler.BaseControl;
1516
import io.javaoperatorsdk.operator.api.reconciler.Context;
@@ -131,7 +132,9 @@ private PostExecutionControl<R> handleReconcile(
131132
private R cloneResourceForErrorStatusHandlerIfNeeded(R resource, Context context) {
132133
if (isErrorStatusHandlerPresent() ||
133134
shouldUpdateObservedGenerationAutomatically(resource)) {
134-
return configuration().getConfigurationService().getResourceCloner().clone(resource);
135+
final var configurationService = configuration().getConfigurationService();
136+
return configurationService != null ? configurationService.getResourceCloner().clone(resource)
137+
: ConfigurationService.DEFAULT_CLONER.clone(resource);
135138
} else {
136139
return resource;
137140
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSource.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import io.fabric8.kubernetes.client.informers.ResourceEventHandler;
1717
import io.fabric8.kubernetes.client.informers.SharedIndexInformer;
1818
import io.javaoperatorsdk.operator.MissingCRDException;
19+
import io.javaoperatorsdk.operator.api.config.ConfigurationService;
1920
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
2021
import io.javaoperatorsdk.operator.processing.Controller;
2122
import io.javaoperatorsdk.operator.processing.MDCUtils;
@@ -45,7 +46,9 @@ public class ControllerResourceEventSource<T extends HasMetadata>
4546
public ControllerResourceEventSource(Controller<T> controller) {
4647
super(controller.getConfiguration().getResourceClass());
4748
this.controller = controller;
48-
var cloner = controller.getConfiguration().getConfigurationService().getResourceCloner();
49+
final var configurationService = controller.getConfiguration().getConfigurationService();
50+
var cloner = configurationService != null ? configurationService.getResourceCloner()
51+
: ConfigurationService.DEFAULT_CLONER;
4952
this.cache = new ControllerResourceCache<>(sharedIndexInformers, cloner);
5053

5154
var filters = new ResourceEventFilter[] {

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

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,20 @@
88
import io.fabric8.kubernetes.client.KubernetesClient;
99
import io.javaoperatorsdk.operator.api.config.ConfigurationService;
1010
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
11+
import io.javaoperatorsdk.operator.api.config.RetryConfiguration;
1112
import io.javaoperatorsdk.operator.api.reconciler.Context;
1213
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
1314
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
1415

1516
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
1617
import static org.mockito.Mockito.mock;
17-
import static org.mockito.Mockito.verify;
1818
import static org.mockito.Mockito.when;
1919

2020
class OperatorTest {
2121

2222
private final KubernetesClient kubernetesClient = mock(KubernetesClient.class);
2323
private final ConfigurationService configurationService = mock(ConfigurationService.class);
2424
private final ControllerConfiguration configuration = mock(ControllerConfiguration.class);
25-
2625
private final Operator operator = new Operator(kubernetesClient, configurationService);
2726
private final FooReconciler fooReconciler = FooReconciler.create();
2827

@@ -33,16 +32,13 @@ public void shouldRegisterReconcilerToController() {
3332
when(configurationService.getConfigurationFor(fooReconciler)).thenReturn(configuration);
3433
when(configuration.watchAllNamespaces()).thenReturn(true);
3534
when(configuration.getName()).thenReturn("FOO");
36-
when(configuration.getResourceClass()).thenReturn(FooReconciler.class);
35+
when(configuration.getResourceClass()).thenReturn(FooCustomResource.class);
36+
when(configuration.getRetryConfiguration()).thenReturn(RetryConfiguration.DEFAULT);
3737

3838
// when
3939
operator.register(fooReconciler);
4040

4141
// then
42-
verify(configuration).watchAllNamespaces();
43-
verify(configuration).getName();
44-
verify(configuration).getResourceClass();
45-
4642
assertThat(operator.getControllers().size()).isEqualTo(1);
4743
assertThat(operator.getControllers().get(0).getReconciler()).isEqualTo(fooReconciler);
4844
}

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/EventSourceManagerTest.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@
99

1010
import io.fabric8.kubernetes.api.model.HasMetadata;
1111
import io.javaoperatorsdk.operator.OperatorException;
12-
import io.javaoperatorsdk.operator.api.config.ConfigurationService;
1312
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
14-
import io.javaoperatorsdk.operator.api.config.ExecutorServiceManager;
1513
import io.javaoperatorsdk.operator.processing.Controller;
1614
import io.javaoperatorsdk.operator.processing.event.source.CachingEventSource;
1715
import io.javaoperatorsdk.operator.processing.event.source.EventSource;
@@ -174,10 +172,7 @@ private EventSourceManager initManager() {
174172
final Controller controller = mock(Controller.class);
175173
final ControllerConfiguration configuration = mock(ControllerConfiguration.class);
176174
when(configuration.getResourceClass()).thenReturn(HasMetadata.class);
177-
when(configuration.getConfigurationService()).thenReturn(mock(ConfigurationService.class));
178175
when(controller.getConfiguration()).thenReturn(configuration);
179-
ExecutorServiceManager.init(configuration.getConfigurationService());
180-
var manager = new EventSourceManager(controller);
181-
return manager;
176+
return new EventSourceManager(controller);
182177
}
183178
}

0 commit comments

Comments
 (0)