Skip to content

Commit 2c9e151

Browse files
committed
refactor: make Operator only deal with ConfiguredController management
Introduced ConfiguredController to gather in one entity ResourceController, its associated configuration and Kubernetes client so that they can be passed around more easily in an always-associated manner (as, right now, configuration can be disassociated from the controller). Another aspect is that it allows us to simplify the management of the lifecycle as ConfiguredControllers can be started/stopped and the Operator doesn't need to bother about the details.
1 parent 1df1e0f commit 2c9e151

File tree

2 files changed

+184
-116
lines changed

2 files changed

+184
-116
lines changed
Lines changed: 20 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,35 @@
11
package io.javaoperatorsdk.operator;
22

3-
import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition;
4-
import io.fabric8.kubernetes.client.CustomResource;
5-
import io.fabric8.kubernetes.client.KubernetesClient;
6-
import io.fabric8.kubernetes.client.Version;
7-
import io.javaoperatorsdk.operator.api.ResourceController;
8-
import io.javaoperatorsdk.operator.api.config.ConfigurationService;
9-
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
10-
import io.javaoperatorsdk.operator.processing.event.DefaultEventSourceManager;
113
import java.io.Closeable;
124
import java.io.IOException;
135
import java.util.ArrayList;
146
import java.util.List;
15-
import java.util.Objects;
7+
168
import org.slf4j.Logger;
179
import org.slf4j.LoggerFactory;
1810

11+
import io.fabric8.kubernetes.client.CustomResource;
12+
import io.fabric8.kubernetes.client.KubernetesClient;
13+
import io.fabric8.kubernetes.client.Version;
14+
import io.javaoperatorsdk.operator.api.ResourceController;
15+
import io.javaoperatorsdk.operator.api.config.ConfigurationService;
16+
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
17+
import io.javaoperatorsdk.operator.processing.ConfiguredController;
18+
1919
@SuppressWarnings("rawtypes")
2020
public class Operator implements AutoCloseable {
2121
private static final Logger log = LoggerFactory.getLogger(Operator.class);
2222
private final KubernetesClient k8sClient;
2323
private final ConfigurationService configurationService;
24-
private final List<Closeable> closeables;
2524
private final Object lock;
26-
private final List<ControllerRef> controllers;
25+
private final List<ConfiguredController> controllers;
2726
private volatile boolean started;
2827
private final Metrics metrics;
2928

3029
public Operator(
3130
KubernetesClient k8sClient, ConfigurationService configurationService, Metrics metrics) {
3231
this.k8sClient = k8sClient;
3332
this.configurationService = configurationService;
34-
this.closeables = new ArrayList<>();
3533
this.lock = new Object();
3634
this.controllers = new ArrayList<>();
3735
this.started = false;
@@ -89,9 +87,7 @@ public void start() {
8987
throw new OperatorException("Error retrieving the server version", e);
9088
}
9189

92-
for (ControllerRef ref : controllers) {
93-
startController(ref.controller, ref.configuration);
94-
}
90+
controllers.forEach(ConfiguredController::start);
9591

9692
started = true;
9793
}
@@ -108,7 +104,7 @@ public void close() {
108104
log.info(
109105
"Operator SDK {} is shutting down...", configurationService.getVersion().getSdkVersion());
110106

111-
for (Closeable closeable : this.closeables) {
107+
for (Closeable closeable : this.controllers) {
112108
try {
113109
log.debug("closing {}", closeable);
114110
closeable.close();
@@ -150,32 +146,6 @@ public <R extends CustomResource> void register(ResourceController<R> controller
150146
public <R extends CustomResource> void register(
151147
ResourceController<R> controller, ControllerConfiguration<R> configuration)
152148
throws OperatorException {
153-
synchronized (lock) {
154-
if (!started) {
155-
this.controllers.add(new ControllerRef(controller, configuration));
156-
} else {
157-
this.controllers.add(new ControllerRef(controller, configuration));
158-
startController(controller, configuration);
159-
}
160-
}
161-
}
162-
163-
/**
164-
* Registers the specified controller with this operator, overriding its default configuration by
165-
* the specified one (usually created via
166-
* {@link io.javaoperatorsdk.operator.api.config.ControllerConfigurationOverrider#override(ControllerConfiguration)},
167-
* passing it the controller's original configuration.
168-
*
169-
* @param controller the controller to register
170-
* @param configuration the configuration with which we want to register the controller, if {@code
171-
* null}, the controller's original configuration is used
172-
* @param <R> the {@code CustomResource} type associated with the controller
173-
* @throws OperatorException if a problem occurred during the registration process
174-
*/
175-
private <R extends CustomResource> void startController(
176-
ResourceController<R> controller, ControllerConfiguration<R> configuration)
177-
throws OperatorException {
178-
179149
final var existing = configurationService.getConfigurationFor(controller);
180150
if (existing == null) {
181151
log.warn(
@@ -188,39 +158,13 @@ private <R extends CustomResource> void startController(
188158
if (configuration == null) {
189159
configuration = existing;
190160
}
191-
192-
final Class<R> resClass = configuration.getCustomResourceClass();
193-
final String controllerName = configuration.getName();
194-
final var crdName = configuration.getCRDName();
195-
final var specVersion = "v1";
196-
197-
// check that the custom resource is known by the cluster if configured that way
198-
final CustomResourceDefinition crd; // todo: check proper CRD spec version based on config
199-
if (configurationService.checkCRDAndValidateLocalModel()) {
200-
crd = k8sClient.apiextensions().v1().customResourceDefinitions().withName(crdName).get();
201-
if (crd == null) {
202-
throwMissingCRDException(crdName, specVersion, controllerName);
161+
synchronized (lock) {
162+
final var configuredController =
163+
new ConfiguredController(controller, configuration, k8sClient);
164+
this.controllers.add(configuredController);
165+
if (started) {
166+
configuredController.start();
203167
}
204-
205-
// Apply validations that are not handled by fabric8
206-
CustomResourceUtils.assertCustomResource(resClass, crd);
207-
}
208-
209-
try {
210-
DefaultEventSourceManager eventSourceManager =
211-
new DefaultEventSourceManager(
212-
controller, configuration, k8sClient.customResources(resClass));
213-
controller.init(eventSourceManager);
214-
closeables.add(eventSourceManager);
215-
} catch (MissingCRDException e) {
216-
throwMissingCRDException(crdName, specVersion, controllerName);
217-
}
218-
219-
if (failOnMissingCurrentNS(configuration)) {
220-
throw new OperatorException(
221-
"Controller '"
222-
+ controllerName
223-
+ "' is configured to watch the current namespace but it couldn't be inferred from the current configuration.");
224168
}
225169

226170
final var watchedNS =
@@ -229,49 +173,9 @@ private <R extends CustomResource> void startController(
229173
: configuration.getEffectiveNamespaces();
230174
log.info(
231175
"Registered Controller: '{}' for CRD: '{}' for namespace(s): {}",
232-
controllerName,
233-
resClass,
176+
configuration.getName(),
177+
configuration.getCustomResourceClass(),
234178
watchedNS);
235179
}
236180
}
237-
238-
private void throwMissingCRDException(String crdName, String specVersion, String controllerName) {
239-
throw new MissingCRDException(
240-
crdName,
241-
specVersion,
242-
"'"
243-
+ crdName
244-
+ "' "
245-
+ specVersion
246-
+ " CRD was not found on the cluster, controller '"
247-
+ controllerName
248-
+ "' cannot be registered");
249-
}
250-
251-
/**
252-
* Determines whether we should fail because the current namespace is request as target namespace
253-
* but is missing
254-
*
255-
* @return {@code true} if the current namespace is requested but is missing, {@code false}
256-
* otherwise
257-
*/
258-
private static <R extends CustomResource> boolean failOnMissingCurrentNS(
259-
ControllerConfiguration<R> configuration) {
260-
if (configuration.watchCurrentNamespace()) {
261-
final var effectiveNamespaces = configuration.getEffectiveNamespaces();
262-
return effectiveNamespaces.size() == 1
263-
&& effectiveNamespaces.stream().allMatch(Objects::isNull);
264-
}
265-
return false;
266-
}
267-
268-
private static class ControllerRef {
269-
public final ResourceController controller;
270-
public final ControllerConfiguration configuration;
271-
272-
public ControllerRef(ResourceController controller, ControllerConfiguration configuration) {
273-
this.controller = controller;
274-
this.configuration = configuration;
275-
}
276-
}
277181
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
package io.javaoperatorsdk.operator.processing;
2+
3+
import java.io.Closeable;
4+
import java.io.IOException;
5+
import java.util.Objects;
6+
7+
import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition;
8+
import io.fabric8.kubernetes.client.CustomResource;
9+
import io.fabric8.kubernetes.client.KubernetesClient;
10+
import io.javaoperatorsdk.operator.CustomResourceUtils;
11+
import io.javaoperatorsdk.operator.MissingCRDException;
12+
import io.javaoperatorsdk.operator.OperatorException;
13+
import io.javaoperatorsdk.operator.api.Context;
14+
import io.javaoperatorsdk.operator.api.DeleteControl;
15+
import io.javaoperatorsdk.operator.api.ResourceController;
16+
import io.javaoperatorsdk.operator.api.UpdateControl;
17+
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
18+
import io.javaoperatorsdk.operator.processing.event.DefaultEventSourceManager;
19+
import io.javaoperatorsdk.operator.processing.event.EventSourceManager;
20+
21+
public class ConfiguredController<R extends CustomResource<?, ?>> implements ResourceController<R>,
22+
Closeable {
23+
private final ResourceController<R> controller;
24+
private final ControllerConfiguration<R> configuration;
25+
private final KubernetesClient k8sClient;
26+
private EventSourceManager manager;
27+
28+
public ConfiguredController(ResourceController<R> controller,
29+
ControllerConfiguration<R> configuration,
30+
KubernetesClient k8sClient) {
31+
this.controller = controller;
32+
this.configuration = configuration;
33+
this.k8sClient = k8sClient;
34+
}
35+
36+
@Override
37+
public DeleteControl deleteResource(R resource, Context<R> context) {
38+
return controller.deleteResource(resource, context);
39+
}
40+
41+
@Override
42+
public UpdateControl<R> createOrUpdateResource(R resource, Context<R> context) {
43+
return controller.createOrUpdateResource(resource, context);
44+
}
45+
46+
@Override
47+
public void init(EventSourceManager eventSourceManager) {
48+
this.manager = eventSourceManager;
49+
controller.init(eventSourceManager);
50+
}
51+
52+
@Override
53+
public boolean equals(Object o) {
54+
if (this == o) {
55+
return true;
56+
}
57+
if (o == null || getClass() != o.getClass()) {
58+
return false;
59+
}
60+
61+
ConfiguredController<?> that = (ConfiguredController<?>) o;
62+
return configuration.getName().equals(that.configuration.getName());
63+
}
64+
65+
@Override
66+
public int hashCode() {
67+
return configuration.getName().hashCode();
68+
}
69+
70+
@Override
71+
public String toString() {
72+
return "'" + configuration.getName() + "' Controller";
73+
}
74+
75+
public ResourceController<R> getController() {
76+
return controller;
77+
}
78+
79+
public ControllerConfiguration<R> getConfiguration() {
80+
return configuration;
81+
}
82+
83+
public KubernetesClient getClient() {
84+
return k8sClient;
85+
}
86+
87+
/**
88+
* Registers the specified controller with this operator, overriding its default configuration by
89+
* the specified one (usually created via
90+
* {@link io.javaoperatorsdk.operator.api.config.ControllerConfigurationOverrider#override(ControllerConfiguration)},
91+
* passing it the controller's original configuration.
92+
*
93+
* @throws OperatorException if a problem occurred during the registration process
94+
*/
95+
public void start() throws OperatorException {
96+
final Class<R> resClass = configuration.getCustomResourceClass();
97+
final String controllerName = configuration.getName();
98+
final var crdName = configuration.getCRDName();
99+
final var specVersion = "v1";
100+
101+
// check that the custom resource is known by the cluster if configured that way
102+
final CustomResourceDefinition crd; // todo: check proper CRD spec version based on config
103+
if (configuration.getConfigurationService().checkCRDAndValidateLocalModel()) {
104+
crd = k8sClient.apiextensions().v1().customResourceDefinitions().withName(crdName).get();
105+
if (crd == null) {
106+
throwMissingCRDException(crdName, specVersion, controllerName);
107+
}
108+
109+
// Apply validations that are not handled by fabric8
110+
CustomResourceUtils.assertCustomResource(resClass, crd);
111+
}
112+
113+
try {
114+
DefaultEventSourceManager eventSourceManager =
115+
new DefaultEventSourceManager(
116+
controller, configuration, k8sClient.customResources(resClass));
117+
controller.init(eventSourceManager);
118+
} catch (MissingCRDException e) {
119+
throwMissingCRDException(crdName, specVersion, controllerName);
120+
}
121+
122+
if (failOnMissingCurrentNS()) {
123+
throw new OperatorException(
124+
"Controller '"
125+
+ controllerName
126+
+ "' is configured to watch the current namespace but it couldn't be inferred from the current configuration.");
127+
}
128+
}
129+
130+
private void throwMissingCRDException(String crdName, String specVersion, String controllerName) {
131+
throw new MissingCRDException(
132+
crdName,
133+
specVersion,
134+
"'"
135+
+ crdName
136+
+ "' "
137+
+ specVersion
138+
+ " CRD was not found on the cluster, controller '"
139+
+ controllerName
140+
+ "' cannot be registered");
141+
}
142+
143+
/**
144+
* Determines whether we should fail because the current namespace is request as target namespace
145+
* but is missing
146+
*
147+
* @return {@code true} if the current namespace is requested but is missing, {@code false}
148+
* otherwise
149+
*/
150+
private boolean failOnMissingCurrentNS() {
151+
if (configuration.watchCurrentNamespace()) {
152+
final var effectiveNamespaces = configuration.getEffectiveNamespaces();
153+
return effectiveNamespaces.size() == 1
154+
&& effectiveNamespaces.stream().allMatch(Objects::isNull);
155+
}
156+
return false;
157+
}
158+
159+
160+
@Override
161+
public void close() throws IOException {
162+
manager.close();
163+
}
164+
}

0 commit comments

Comments
 (0)