Skip to content

Commit 32b8b92

Browse files
csvirimetacosm
andauthored
feat: different reconciler same type (operator-framework#2229)
Signed-off-by: Attila Mészáros <csviri@gmail.com> Signed-off-by: Chris Laprun <claprun@redhat.com> Co-authored-by: Chris Laprun <claprun@redhat.com>
1 parent 3c080a0 commit 32b8b92

File tree

8 files changed

+177
-69
lines changed

8 files changed

+177
-69
lines changed

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

+9-12
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
*/
1818
class ControllerManager {
1919

20+
public static final String CANNOT_REGISTER_MULTIPLE_CONTROLLERS_WITH_SAME_NAME_MESSAGE =
21+
"Cannot register multiple controllers with same name: ";
2022
private static final Logger log = LoggerFactory.getLogger(ControllerManager.class);
2123

2224
@SuppressWarnings("rawtypes")
@@ -62,25 +64,20 @@ public synchronized void startEventProcessing() {
6264
}, c -> "Event processor starter for: " + c.getConfiguration().getName());
6365
}
6466

65-
@SuppressWarnings({"unchecked", "rawtypes"})
67+
@SuppressWarnings("rawtypes")
6668
synchronized void add(Controller controller) {
6769
final var configuration = controller.getConfiguration();
68-
final var resourceTypeName = ReconcilerUtils
69-
.getResourceTypeNameWithVersion(configuration.getResourceClass());
70-
final var existing = controllers.get(resourceTypeName);
71-
if (existing != null) {
72-
throw new OperatorException("Cannot register controller '" + configuration.getName()
73-
+ "': another controller named '" + existing.getConfiguration().getName()
74-
+ "' is already registered for resource '" + resourceTypeName + "'");
70+
final var name = configuration.getName();
71+
if (controllers.containsKey(name)) {
72+
throw new OperatorException(
73+
CANNOT_REGISTER_MULTIPLE_CONTROLLERS_WITH_SAME_NAME_MESSAGE + name);
7574
}
76-
controllers.put(resourceTypeName, controller);
75+
controllers.put(name, controller);
7776
}
7877

7978
@SuppressWarnings("rawtypes")
8079
synchronized Optional<Controller> get(String name) {
81-
return controllers().stream()
82-
.filter(c -> name.equals(c.getConfiguration().getName()))
83-
.findFirst();
80+
return Optional.ofNullable(controllers.get(name));
8481
}
8582

8683
@SuppressWarnings("rawtypes")

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

+17-41
Original file line numberDiff line numberDiff line change
@@ -8,66 +8,42 @@
88
import io.javaoperatorsdk.operator.api.config.ResolvedControllerConfiguration;
99
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
1010
import io.javaoperatorsdk.operator.processing.Controller;
11-
import io.javaoperatorsdk.operator.sample.simple.DuplicateCRController;
1211
import io.javaoperatorsdk.operator.sample.simple.TestCustomReconciler;
13-
import io.javaoperatorsdk.operator.sample.simple.TestCustomReconcilerOtherV1;
1412
import io.javaoperatorsdk.operator.sample.simple.TestCustomResource;
15-
import io.javaoperatorsdk.operator.sample.simple.TestCustomResourceOtherV1;
1613

14+
import static io.javaoperatorsdk.operator.ControllerManager.CANNOT_REGISTER_MULTIPLE_CONTROLLERS_WITH_SAME_NAME_MESSAGE;
1715
import static org.junit.jupiter.api.Assertions.assertThrows;
1816
import static org.junit.jupiter.api.Assertions.assertTrue;
1917

2018
class ControllerManagerTest {
2119

2220
@Test
23-
void shouldNotAddMultipleControllersForSameCustomResource() {
24-
final var registered = new TestControllerConfiguration<>(new TestCustomReconciler(null),
25-
TestCustomResource.class);
26-
final var duplicated =
27-
new TestControllerConfiguration<>(new DuplicateCRController(), TestCustomResource.class);
28-
29-
checkException(registered, duplicated);
30-
}
31-
32-
@Test
33-
void addingMultipleControllersForCustomResourcesWithSameVersionsShouldNotWork() {
34-
final var registered = new TestControllerConfiguration<>(new TestCustomReconciler(null),
35-
TestCustomResource.class);
36-
final var duplicated = new TestControllerConfiguration<>(new TestCustomReconcilerOtherV1(),
37-
TestCustomResourceOtherV1.class);
38-
39-
checkException(registered, duplicated);
40-
}
41-
42-
private <T extends HasMetadata, U extends HasMetadata> void checkException(
43-
TestControllerConfiguration<T> registered,
44-
TestControllerConfiguration<U> duplicated) {
45-
21+
void addingReconcilerWithSameNameShouldNotWork() {
22+
final var controllerConfiguration =
23+
new TestControllerConfiguration<>(new TestCustomReconciler(null),
24+
TestCustomResource.class);
25+
var controller = new Controller<>(controllerConfiguration.reconciler, controllerConfiguration,
26+
MockKubernetesClient.client(controllerConfiguration.getResourceClass()));
4627
ConfigurationService configurationService = new BaseConfigurationService();
28+
final var controllerManager =
29+
new ControllerManager(configurationService.getExecutorServiceManager());
30+
controllerManager.add(controller);
4731

48-
final var exception = assertThrows(OperatorException.class, () -> {
49-
final var controllerManager =
50-
new ControllerManager(configurationService.getExecutorServiceManager());
51-
controllerManager.add(new Controller<>(registered.controller, registered,
52-
MockKubernetesClient.client(registered.getResourceClass())));
53-
controllerManager.add(new Controller<>(duplicated.controller, duplicated,
54-
MockKubernetesClient.client(duplicated.getResourceClass())));
32+
var ex = assertThrows(OperatorException.class, () -> {
33+
controllerManager.add(controller);
5534
});
56-
final var msg = exception.getMessage();
5735
assertTrue(
58-
msg.contains("Cannot register controller '" + duplicated.getName() + "'")
59-
&& msg.contains(registered.getName())
60-
&& msg.contains(registered.getResourceTypeName()));
36+
ex.getMessage().contains(CANNOT_REGISTER_MULTIPLE_CONTROLLERS_WITH_SAME_NAME_MESSAGE));
6137
}
6238

6339
private static class TestControllerConfiguration<R extends HasMetadata>
6440
extends ResolvedControllerConfiguration<R> {
65-
private final Reconciler<R> controller;
41+
private final Reconciler<R> reconciler;
6642

67-
public TestControllerConfiguration(Reconciler<R> controller, Class<R> crClass) {
68-
super(crClass, getControllerName(controller), controller.getClass(),
43+
public TestControllerConfiguration(Reconciler<R> reconciler, Class<R> crClass) {
44+
super(crClass, getControllerName(reconciler), reconciler.getClass(),
6945
new BaseConfigurationService());
70-
this.controller = controller;
46+
this.reconciler = reconciler;
7147
}
7248

7349
static <R extends HasMetadata> String getControllerName(

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/DuplicateCRController.java

-16
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package io.javaoperatorsdk.operator;
2+
3+
import org.junit.jupiter.api.Test;
4+
import org.junit.jupiter.api.extension.RegisterExtension;
5+
6+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
7+
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
8+
import io.javaoperatorsdk.operator.sample.multiplereconcilersametype.MultipleReconcilerSameTypeCustomResource;
9+
import io.javaoperatorsdk.operator.sample.multiplereconcilersametype.MultipleReconcilerSameTypeReconciler1;
10+
import io.javaoperatorsdk.operator.sample.multiplereconcilersametype.MultipleReconcilerSameTypeReconciler2;
11+
12+
import static org.assertj.core.api.Assertions.assertThat;
13+
import static org.awaitility.Awaitility.await;
14+
15+
public class MultipleReconcilerSameTypeIT {
16+
17+
public static final String TEST_RESOURCE_1 = "test1";
18+
public static final String TEST_RESOURCE_2 = "test2";
19+
@RegisterExtension
20+
LocallyRunOperatorExtension extension =
21+
LocallyRunOperatorExtension.builder()
22+
.withReconciler(MultipleReconcilerSameTypeReconciler1.class)
23+
.withReconciler(MultipleReconcilerSameTypeReconciler2.class)
24+
.build();
25+
26+
27+
@Test
28+
void multipleReconcilersBasedOnLeaderElection() {
29+
extension.create(testResource(TEST_RESOURCE_1, true));
30+
extension.create(testResource(TEST_RESOURCE_2, false));
31+
32+
33+
await().untilAsserted(() -> {
34+
assertThat(extension.getReconcilerOfType(MultipleReconcilerSameTypeReconciler1.class)
35+
.getNumberOfExecutions()).isEqualTo(1);
36+
assertThat(extension.getReconcilerOfType(MultipleReconcilerSameTypeReconciler2.class)
37+
.getNumberOfExecutions()).isEqualTo(1);
38+
39+
var res1 = extension.get(MultipleReconcilerSameTypeCustomResource.class, TEST_RESOURCE_1);
40+
var res2 = extension.get(MultipleReconcilerSameTypeCustomResource.class, TEST_RESOURCE_2);
41+
assertThat(res1).isNotNull();
42+
assertThat(res2).isNotNull();
43+
assertThat(res1.getStatus().getReconciledBy())
44+
.isEqualTo(MultipleReconcilerSameTypeReconciler1.class.getSimpleName());
45+
assertThat(res2.getStatus().getReconciledBy())
46+
.isEqualTo(MultipleReconcilerSameTypeReconciler2.class.getSimpleName());
47+
});
48+
}
49+
50+
MultipleReconcilerSameTypeCustomResource testResource(String name, boolean type1) {
51+
var res = new MultipleReconcilerSameTypeCustomResource();
52+
res.setMetadata(new ObjectMetaBuilder()
53+
.withName(name)
54+
.build());
55+
if (type1) {
56+
res.getMetadata().getLabels().put("reconciler", "1");
57+
}
58+
return res;
59+
}
60+
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.javaoperatorsdk.operator.sample.multiplereconcilersametype;
2+
3+
import io.fabric8.kubernetes.api.model.Namespaced;
4+
import io.fabric8.kubernetes.client.CustomResource;
5+
import io.fabric8.kubernetes.model.annotation.Group;
6+
import io.fabric8.kubernetes.model.annotation.ShortNames;
7+
import io.fabric8.kubernetes.model.annotation.Version;
8+
9+
@Group("sample.javaoperatorsdk")
10+
@Version("v1")
11+
@ShortNames("mrst")
12+
public class MultipleReconcilerSameTypeCustomResource
13+
extends CustomResource<Void, MultipleReconcilerSameTypeStatus>
14+
implements Namespaced {
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.javaoperatorsdk.operator.sample.multiplereconcilersametype;
2+
3+
import java.util.concurrent.atomic.AtomicInteger;
4+
5+
import io.javaoperatorsdk.operator.api.reconciler.*;
6+
import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider;
7+
8+
@ControllerConfiguration(labelSelector = "reconciler = 1")
9+
public class MultipleReconcilerSameTypeReconciler1
10+
implements Reconciler<MultipleReconcilerSameTypeCustomResource>, TestExecutionInfoProvider {
11+
12+
private final AtomicInteger numberOfExecutions = new AtomicInteger(0);
13+
14+
@Override
15+
public UpdateControl<MultipleReconcilerSameTypeCustomResource> reconcile(
16+
MultipleReconcilerSameTypeCustomResource resource,
17+
Context<MultipleReconcilerSameTypeCustomResource> context) {
18+
numberOfExecutions.addAndGet(1);
19+
20+
resource.setStatus(new MultipleReconcilerSameTypeStatus());
21+
resource.getStatus().setReconciledBy(getClass().getSimpleName());
22+
return UpdateControl.patchStatus(resource);
23+
}
24+
25+
public int getNumberOfExecutions() {
26+
return numberOfExecutions.get();
27+
}
28+
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package io.javaoperatorsdk.operator.sample.multiplereconcilersametype;
2+
3+
import java.util.concurrent.atomic.AtomicInteger;
4+
5+
import io.javaoperatorsdk.operator.api.reconciler.Context;
6+
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
7+
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
8+
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
9+
import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider;
10+
11+
@ControllerConfiguration(labelSelector = "reconciler != 1")
12+
public class MultipleReconcilerSameTypeReconciler2
13+
implements Reconciler<MultipleReconcilerSameTypeCustomResource>, TestExecutionInfoProvider {
14+
15+
private final AtomicInteger numberOfExecutions = new AtomicInteger(0);
16+
17+
@Override
18+
public UpdateControl<MultipleReconcilerSameTypeCustomResource> reconcile(
19+
MultipleReconcilerSameTypeCustomResource resource,
20+
Context<MultipleReconcilerSameTypeCustomResource> context) {
21+
numberOfExecutions.addAndGet(1);
22+
23+
resource.setStatus(new MultipleReconcilerSameTypeStatus());
24+
resource.getStatus().setReconciledBy(getClass().getSimpleName());
25+
return UpdateControl.patchStatus(resource);
26+
}
27+
28+
public int getNumberOfExecutions() {
29+
return numberOfExecutions.get();
30+
}
31+
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package io.javaoperatorsdk.operator.sample.multiplereconcilersametype;
2+
3+
public class MultipleReconcilerSameTypeStatus {
4+
5+
private String reconciledBy;
6+
7+
public String getReconciledBy() {
8+
return reconciledBy;
9+
}
10+
11+
public void setReconciledBy(String reconciledBy) {
12+
this.reconciledBy = reconciledBy;
13+
}
14+
}

0 commit comments

Comments
 (0)