Skip to content

Commit 0287c0c

Browse files
committed
fix: improve duplicated controller detection, add tests
While we originally planned to make it possible to register controllers with the same CR but with different version (see #637), that behavior should actually be forbidden since only one CR version can be served, see #94 for more details.
1 parent f0ca4ca commit 0287c0c

File tree

5 files changed

+124
-5
lines changed

5 files changed

+124
-5
lines changed

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

+4-5
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,10 @@ public <R extends CustomResource> void register(
159159
}
160160
}
161161

162-
private static class ControllerManager implements Closeable {
162+
static class ControllerManager implements Closeable {
163163
private final Map<String, ConfiguredController> controllers = new HashMap<>();
164164
private boolean started = false;
165165

166-
167166
public synchronized void shouldStart() {
168167
if (started) {
169168
return;
@@ -201,9 +200,9 @@ public synchronized void add(ConfiguredController configuredController) {
201200
final var crdName = configuration.getCRDName();
202201
final var existing = controllers.get(crdName);
203202
if (existing != null) {
204-
throw new OperatorException("Cannot register controller " + configuration.getName()
205-
+ ": another controller (" + existing.getConfiguration().getName()
206-
+ ") is already registered for CRD " + crdName);
203+
throw new OperatorException("Cannot register controller '" + configuration.getName()
204+
+ "': another controller named '" + existing.getConfiguration().getName()
205+
+ "' is already registered for CRD '" + crdName + "'");
207206
}
208207
this.controllers.put(crdName, configuredController);
209208
if (started) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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.ResourceController;
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.assertThrows;
17+
import static org.junit.jupiter.api.Assertions.assertTrue;
18+
19+
public class ControllerManagerTest {
20+
21+
@Test
22+
public void shouldNotAddMultipleControllersForSameCustomResource() {
23+
final var registered = new TestControllerConfiguration<>(new TestCustomResourceController(null),
24+
TestCustomResource.class);
25+
final var duplicated =
26+
new TestControllerConfiguration<>(new DuplicateCRController(), TestCustomResource.class);
27+
28+
checkException(registered, duplicated);
29+
}
30+
31+
@Test
32+
public void addingMultipleControllersForCustomResourcesWithDifferentVersionsShouldNotWork() {
33+
final var registered = new TestControllerConfiguration<>(new TestCustomResourceController(null),
34+
TestCustomResource.class);
35+
final var duplicated = new TestControllerConfiguration<>(new TestCustomResourceControllerV2(),
36+
TestCustomResourceV2.class);
37+
38+
checkException(registered, duplicated);
39+
40+
}
41+
42+
private <T extends CustomResource<?, ?>, U extends CustomResource<?, ?>> void checkException(
43+
TestControllerConfiguration<T> registered,
44+
TestControllerConfiguration<U> duplicated) {
45+
final var exception = assertThrows(OperatorException.class, () -> {
46+
final var controllerManager = new ControllerManager();
47+
controllerManager.add(new ConfiguredController<>(registered.controller, registered, null));
48+
controllerManager.add(new ConfiguredController<>(duplicated.controller, duplicated, null));
49+
});
50+
final var msg = exception.getMessage();
51+
assertTrue(
52+
msg.contains("Cannot register controller '" + duplicated.getControllerName() + "'")
53+
&& msg.contains(registered.getControllerName())
54+
&& msg.contains(registered.getCRDName()));
55+
}
56+
57+
private static class TestControllerConfiguration<R extends CustomResource<?, ?>>
58+
extends DefaultControllerConfiguration<R> {
59+
private final ResourceController<R> controller;
60+
61+
public TestControllerConfiguration(ResourceController<R> controller, Class<R> crClass) {
62+
super(null, getControllerName(controller),
63+
CustomResource.getCRDName(crClass), null, false, null, null, null, null, crClass, null);
64+
this.controller = controller;
65+
}
66+
67+
static <R extends CustomResource<?, ?>> String getControllerName(
68+
ResourceController<R> controller) {
69+
return controller.getClass().getSimpleName() + "Controller";
70+
}
71+
72+
private String getControllerName() {
73+
return getControllerName(controller);
74+
}
75+
}
76+
}
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<TestCustomResource> 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<TestCustomResourceV2> 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)