Skip to content

Commit 7f87c44

Browse files
committed
fix: improve duplicated controller detection, add tests
It is now possible to add different controllers for the same CR with different versions. Fixes #637.
1 parent 9a1551f commit 7f87c44

File tree

6 files changed

+108
-6
lines changed

6 files changed

+108
-6
lines changed

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

+12-6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.net.ConnectException;
44
import java.util.ArrayList;
5+
import java.util.Collections;
56
import java.util.HashMap;
67
import java.util.List;
78
import java.util.Map;
@@ -163,10 +164,13 @@ public void close() {
163164
}
164165
}
165166

166-
private static class ControllerManager implements LifecycleAware {
167+
static class ControllerManager implements LifecycleAware {
167168
private final Map<String, ConfiguredController> controllers = new HashMap<>();
168169
private boolean started = false;
169170

171+
synchronized Map<String, ConfiguredController> getControllers() {
172+
return Collections.unmodifiableMap(controllers);
173+
}
170174

171175
public synchronized void shouldStart() {
172176
if (started) {
@@ -198,13 +202,15 @@ public synchronized void stop() {
198202
public synchronized void add(ConfiguredController configuredController) {
199203
final var configuration = configuredController.getConfiguration();
200204
final var crdName = configuration.getCRDName();
201-
final var existing = controllers.get(crdName);
205+
final var version = configuration.getCustomResourceVersion();
206+
final var key = crdName + "/" + version;
207+
final var existing = controllers.get(key);
202208
if (existing != null) {
203-
throw new OperatorException("Cannot register controller " + configuration.getName()
204-
+ ": another controller (" + existing.getConfiguration().getName()
205-
+ ") is already registered for CRD " + crdName);
209+
throw new OperatorException("Cannot register controller '" + configuration.getName()
210+
+ "': another controller (" + existing.getConfiguration().getName()
211+
+ ") is already registered for CRD '" + crdName + "' (version: " + version + ")");
206212
}
207-
this.controllers.put(crdName, configuredController);
213+
this.controllers.put(key, configuredController);
208214
if (started) {
209215
configuredController.start();
210216
}

Diff for: operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java

+5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.util.Collections;
55
import java.util.Set;
66

7+
import io.fabric8.kubernetes.api.model.HasMetadata;
78
import io.fabric8.kubernetes.client.CustomResource;
89
import io.javaoperatorsdk.operator.ControllerUtils;
910
import io.javaoperatorsdk.operator.api.Controller;
@@ -20,6 +21,10 @@ default String getCRDName() {
2021
return CustomResource.getCRDName(getCustomResourceClass());
2122
}
2223

24+
default String getCustomResourceVersion() {
25+
return HasMetadata.getVersion(getCustomResourceClass());
26+
}
27+
2328
default String getFinalizer() {
2429
return ControllerUtils.getDefaultFinalizerName(getCRDName());
2530
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package io.javaoperatorsdk.operator;
2+
3+
import org.junit.Test;
4+
5+
import io.fabric8.kubernetes.client.CustomResource;
6+
import io.javaoperatorsdk.operator.Operator.ControllerManager;
7+
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
8+
import io.javaoperatorsdk.operator.api.config.DefaultControllerConfiguration;
9+
import io.javaoperatorsdk.operator.processing.ConfiguredController;
10+
import io.javaoperatorsdk.operator.sample.simple.DuplicateCRController;
11+
import io.javaoperatorsdk.operator.sample.simple.TestCustomResource;
12+
import io.javaoperatorsdk.operator.sample.simple.TestCustomResourceController;
13+
import io.javaoperatorsdk.operator.sample.simple.TestCustomResourceControllerV2;
14+
import io.javaoperatorsdk.operator.sample.simple.TestCustomResourceV2;
15+
16+
import static org.junit.jupiter.api.Assertions.assertEquals;
17+
18+
public class ControllerManagerTest {
19+
20+
@Test(expected = OperatorException.class)
21+
public void shouldNotAddMultipleControllersForSameCustomResource() {
22+
final var controllerManager = new ControllerManager();
23+
controllerManager
24+
.add(new ConfiguredController<>(new TestCustomResourceController(null), config(
25+
TestCustomResource.class), null));
26+
controllerManager.add(new ConfiguredController<>(new DuplicateCRController(),
27+
config(TestCustomResource.class), null));
28+
}
29+
30+
@Test
31+
public void addingMultipleControllersForCustomResourcesWithDifferentVersionsShouldWork() {
32+
final var controllerManager = new ControllerManager();
33+
controllerManager
34+
.add(new ConfiguredController<>(new TestCustomResourceController(null),
35+
config(TestCustomResource.class), null));
36+
controllerManager
37+
.add(new ConfiguredController<>(new TestCustomResourceControllerV2(), config(
38+
TestCustomResourceV2.class), null));
39+
final var controllers = controllerManager.getControllers();
40+
assertEquals(2, controllers.size());
41+
}
42+
43+
private <R extends CustomResource<?, ?>> ControllerConfiguration<R> config(Class<R> crClass) {
44+
return new DefaultControllerConfiguration<>(null, crClass.getSimpleName() + "Controller",
45+
CustomResource.getCRDName(crClass), null, false, null, null, null, null, crClass, null);
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.javaoperatorsdk.operator.sample.simple;
2+
3+
import io.javaoperatorsdk.operator.api.Context;
4+
import io.javaoperatorsdk.operator.api.Controller;
5+
import io.javaoperatorsdk.operator.api.ResourceController;
6+
import io.javaoperatorsdk.operator.api.UpdateControl;
7+
8+
@Controller
9+
public class DuplicateCRController implements ResourceController<TestCustomResource> {
10+
11+
@Override
12+
public UpdateControl<TestCustomResource> createOrUpdateResource(TestCustomResource resource,
13+
Context context) {
14+
return UpdateControl.noUpdate();
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.javaoperatorsdk.operator.sample.simple;
2+
3+
import io.javaoperatorsdk.operator.api.Context;
4+
import io.javaoperatorsdk.operator.api.Controller;
5+
import io.javaoperatorsdk.operator.api.ResourceController;
6+
import io.javaoperatorsdk.operator.api.UpdateControl;
7+
8+
@Controller
9+
public class TestCustomResourceControllerV2 implements ResourceController<TestCustomResourceV2> {
10+
11+
@Override
12+
public UpdateControl<TestCustomResourceV2> createOrUpdateResource(TestCustomResourceV2 resource,
13+
Context context) {
14+
return UpdateControl.noUpdate();
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package io.javaoperatorsdk.operator.sample.simple;
2+
3+
import io.fabric8.kubernetes.client.CustomResource;
4+
import io.fabric8.kubernetes.model.annotation.Group;
5+
import io.fabric8.kubernetes.model.annotation.Version;
6+
7+
@Group("sample.javaoperatorsdk.io")
8+
@Version("v2")
9+
public class TestCustomResourceV2
10+
extends CustomResource<TestCustomResourceSpec, TestCustomResourceStatus> {
11+
12+
}

0 commit comments

Comments
 (0)