Skip to content

Commit d250f5a

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 f001644 commit d250f5a

File tree

6 files changed

+109
-6
lines changed

6 files changed

+109
-6
lines changed

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

+13-6
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
import java.io.IOException;
55
import java.net.ConnectException;
66
import java.util.ArrayList;
7+
import java.util.Collections;
78
import java.util.HashMap;
89
import java.util.List;
910
import java.util.Map;
1011

1112
import org.slf4j.Logger;
1213
import org.slf4j.LoggerFactory;
1314

15+
import io.fabric8.kubernetes.api.model.HasMetadata;
1416
import io.fabric8.kubernetes.client.CustomResource;
1517
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
1618
import io.fabric8.kubernetes.client.KubernetesClient;
@@ -159,10 +161,13 @@ public <R extends CustomResource> void register(
159161
}
160162
}
161163

162-
private static class ControllerManager implements Closeable {
164+
static class ControllerManager implements Closeable {
163165
private final Map<String, ConfiguredController> controllers = new HashMap<>();
164166
private boolean started = false;
165167

168+
synchronized Map<String, ConfiguredController> getControllers() {
169+
return Collections.unmodifiableMap(controllers);
170+
}
166171

167172
public synchronized void shouldStart() {
168173
if (started) {
@@ -199,13 +204,15 @@ public synchronized void close() {
199204
public synchronized void add(ConfiguredController configuredController) {
200205
final var configuration = configuredController.getConfiguration();
201206
final var crdName = configuration.getCRDName();
202-
final var existing = controllers.get(crdName);
207+
final var version = configuration.getCustomResourceVersion();
208+
final var key = crdName + "/" + version;
209+
final var existing = controllers.get(key);
203210
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);
211+
throw new OperatorException("Cannot register controller '" + configuration.getName()
212+
+ "': another controller (" + existing.getConfiguration().getName()
213+
+ ") is already registered for CRD '" + crdName + "' (version: " + version + ")");
207214
}
208-
this.controllers.put(crdName, configuredController);
215+
this.controllers.put(key, configuredController);
209216
if (started) {
210217
configuredController.start();
211218
}

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

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.javaoperatorsdk.operator.api.config;
22

3+
import io.fabric8.kubernetes.api.model.HasMetadata;
34
import java.lang.reflect.ParameterizedType;
45
import java.util.Collections;
56
import java.util.Set;
@@ -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 io.fabric8.kubernetes.api.model.HasMetadata;
4+
import io.fabric8.kubernetes.client.CustomResource;
5+
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
6+
import io.javaoperatorsdk.operator.api.config.DefaultControllerConfiguration;
7+
import io.javaoperatorsdk.operator.sample.simple.TestCustomResource;
8+
import io.javaoperatorsdk.operator.sample.simple.TestCustomResourceV2;
9+
import org.junit.Test;
10+
11+
import io.javaoperatorsdk.operator.Operator.ControllerManager;
12+
import io.javaoperatorsdk.operator.processing.ConfiguredController;
13+
import io.javaoperatorsdk.operator.sample.simple.DuplicateCRController;
14+
import io.javaoperatorsdk.operator.sample.simple.TestCustomResourceController;
15+
import io.javaoperatorsdk.operator.sample.simple.TestCustomResourceControllerV2;
16+
17+
import static org.junit.jupiter.api.Assertions.assertEquals;
18+
import static org.junit.jupiter.api.Assertions.assertTrue;
19+
20+
public class ControllerManagerTest {
21+
22+
@Test(expected = OperatorException.class)
23+
public void shouldNotAddMultipleControllersForSameCustomResource() {
24+
final var controllerManager = new ControllerManager();
25+
controllerManager
26+
.add(new ConfiguredController<>(new TestCustomResourceController(null), config(
27+
TestCustomResource.class), null));
28+
controllerManager.add(new ConfiguredController<>(new DuplicateCRController(), config(TestCustomResource.class), null));
29+
}
30+
31+
@Test
32+
public void addingMultipleControllersForCustomResourcesWithDifferentVersionsShouldWork() {
33+
final var controllerManager = new ControllerManager();
34+
controllerManager
35+
.add(new ConfiguredController<>(new TestCustomResourceController(null), 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<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.Controller;
4+
import io.javaoperatorsdk.operator.api.Context;
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)