Skip to content

feat: different reconciler same type #2229

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
*/
class ControllerManager {

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

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

@SuppressWarnings({"unchecked", "rawtypes"})
@SuppressWarnings("rawtypes")
synchronized void add(Controller controller) {
final var configuration = controller.getConfiguration();
final var resourceTypeName = ReconcilerUtils
.getResourceTypeNameWithVersion(configuration.getResourceClass());
final var existing = controllers.get(resourceTypeName);
if (existing != null) {
throw new OperatorException("Cannot register controller '" + configuration.getName()
+ "': another controller named '" + existing.getConfiguration().getName()
+ "' is already registered for resource '" + resourceTypeName + "'");
final var name = configuration.getName();
if (controllers.containsKey(name)) {
throw new OperatorException(
CANNOT_REGISTER_MULTIPLE_CONTROLLERS_WITH_SAME_NAME_MESSAGE + name);
}
controllers.put(resourceTypeName, controller);
controllers.put(name, controller);
}

@SuppressWarnings("rawtypes")
synchronized Optional<Controller> get(String name) {
return controllers().stream()
.filter(c -> name.equals(c.getConfiguration().getName()))
.findFirst();
return Optional.ofNullable(controllers.get(name));
}

@SuppressWarnings("rawtypes")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,66 +8,42 @@
import io.javaoperatorsdk.operator.api.config.ResolvedControllerConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.javaoperatorsdk.operator.processing.Controller;
import io.javaoperatorsdk.operator.sample.simple.DuplicateCRController;
import io.javaoperatorsdk.operator.sample.simple.TestCustomReconciler;
import io.javaoperatorsdk.operator.sample.simple.TestCustomReconcilerOtherV1;
import io.javaoperatorsdk.operator.sample.simple.TestCustomResource;
import io.javaoperatorsdk.operator.sample.simple.TestCustomResourceOtherV1;

import static io.javaoperatorsdk.operator.ControllerManager.CANNOT_REGISTER_MULTIPLE_CONTROLLERS_WITH_SAME_NAME_MESSAGE;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

class ControllerManagerTest {

@Test
void shouldNotAddMultipleControllersForSameCustomResource() {
final var registered = new TestControllerConfiguration<>(new TestCustomReconciler(null),
TestCustomResource.class);
final var duplicated =
new TestControllerConfiguration<>(new DuplicateCRController(), TestCustomResource.class);

checkException(registered, duplicated);
}

@Test
void addingMultipleControllersForCustomResourcesWithSameVersionsShouldNotWork() {
final var registered = new TestControllerConfiguration<>(new TestCustomReconciler(null),
TestCustomResource.class);
final var duplicated = new TestControllerConfiguration<>(new TestCustomReconcilerOtherV1(),
TestCustomResourceOtherV1.class);

checkException(registered, duplicated);
}

private <T extends HasMetadata, U extends HasMetadata> void checkException(
TestControllerConfiguration<T> registered,
TestControllerConfiguration<U> duplicated) {

void addingReconcilerWithSameNameShouldNotWork() {
final var controllerConfiguration =
new TestControllerConfiguration<>(new TestCustomReconciler(null),
TestCustomResource.class);
var controller = new Controller<>(controllerConfiguration.reconciler, controllerConfiguration,
MockKubernetesClient.client(controllerConfiguration.getResourceClass()));
ConfigurationService configurationService = new BaseConfigurationService();
final var controllerManager =
new ControllerManager(configurationService.getExecutorServiceManager());
controllerManager.add(controller);

final var exception = assertThrows(OperatorException.class, () -> {
final var controllerManager =
new ControllerManager(configurationService.getExecutorServiceManager());
controllerManager.add(new Controller<>(registered.controller, registered,
MockKubernetesClient.client(registered.getResourceClass())));
controllerManager.add(new Controller<>(duplicated.controller, duplicated,
MockKubernetesClient.client(duplicated.getResourceClass())));
var ex = assertThrows(OperatorException.class, () -> {
controllerManager.add(controller);
});
final var msg = exception.getMessage();
assertTrue(
msg.contains("Cannot register controller '" + duplicated.getName() + "'")
&& msg.contains(registered.getName())
&& msg.contains(registered.getResourceTypeName()));
ex.getMessage().contains(CANNOT_REGISTER_MULTIPLE_CONTROLLERS_WITH_SAME_NAME_MESSAGE));
}

private static class TestControllerConfiguration<R extends HasMetadata>
extends ResolvedControllerConfiguration<R> {
private final Reconciler<R> controller;
private final Reconciler<R> reconciler;

public TestControllerConfiguration(Reconciler<R> controller, Class<R> crClass) {
super(crClass, getControllerName(controller), controller.getClass(),
public TestControllerConfiguration(Reconciler<R> reconciler, Class<R> crClass) {
super(crClass, getControllerName(reconciler), reconciler.getClass(),
new BaseConfigurationService());
this.controller = controller;
this.reconciler = reconciler;
}

static <R extends HasMetadata> String getControllerName(
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package io.javaoperatorsdk.operator;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
import io.javaoperatorsdk.operator.sample.multiplereconcilersametype.MultipleReconcilerSameTypeCustomResource;
import io.javaoperatorsdk.operator.sample.multiplereconcilersametype.MultipleReconcilerSameTypeReconciler1;
import io.javaoperatorsdk.operator.sample.multiplereconcilersametype.MultipleReconcilerSameTypeReconciler2;

import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;

public class MultipleReconcilerSameTypeIT {

public static final String TEST_RESOURCE_1 = "test1";
public static final String TEST_RESOURCE_2 = "test2";
@RegisterExtension
LocallyRunOperatorExtension extension =
LocallyRunOperatorExtension.builder()
.withReconciler(MultipleReconcilerSameTypeReconciler1.class)
.withReconciler(MultipleReconcilerSameTypeReconciler2.class)
.build();


@Test
void multipleReconcilersBasedOnLeaderElection() {
extension.create(testResource(TEST_RESOURCE_1, true));
extension.create(testResource(TEST_RESOURCE_2, false));


await().untilAsserted(() -> {
assertThat(extension.getReconcilerOfType(MultipleReconcilerSameTypeReconciler1.class)
.getNumberOfExecutions()).isEqualTo(1);
assertThat(extension.getReconcilerOfType(MultipleReconcilerSameTypeReconciler2.class)
.getNumberOfExecutions()).isEqualTo(1);

var res1 = extension.get(MultipleReconcilerSameTypeCustomResource.class, TEST_RESOURCE_1);
var res2 = extension.get(MultipleReconcilerSameTypeCustomResource.class, TEST_RESOURCE_2);
assertThat(res1).isNotNull();
assertThat(res2).isNotNull();
assertThat(res1.getStatus().getReconciledBy())
.isEqualTo(MultipleReconcilerSameTypeReconciler1.class.getSimpleName());
assertThat(res2.getStatus().getReconciledBy())
.isEqualTo(MultipleReconcilerSameTypeReconciler2.class.getSimpleName());
});
}

MultipleReconcilerSameTypeCustomResource testResource(String name, boolean type1) {
var res = new MultipleReconcilerSameTypeCustomResource();
res.setMetadata(new ObjectMetaBuilder()
.withName(name)
.build());
if (type1) {
res.getMetadata().getLabels().put("reconciler", "1");
}
return res;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.javaoperatorsdk.operator.sample.multiplereconcilersametype;

import io.fabric8.kubernetes.api.model.Namespaced;
import io.fabric8.kubernetes.client.CustomResource;
import io.fabric8.kubernetes.model.annotation.Group;
import io.fabric8.kubernetes.model.annotation.ShortNames;
import io.fabric8.kubernetes.model.annotation.Version;

@Group("sample.javaoperatorsdk")
@Version("v1")
@ShortNames("mrst")
public class MultipleReconcilerSameTypeCustomResource
extends CustomResource<Void, MultipleReconcilerSameTypeStatus>
implements Namespaced {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.javaoperatorsdk.operator.sample.multiplereconcilersametype;

import java.util.concurrent.atomic.AtomicInteger;

import io.javaoperatorsdk.operator.api.reconciler.*;
import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider;

@ControllerConfiguration(labelSelector = "reconciler = 1")
public class MultipleReconcilerSameTypeReconciler1
implements Reconciler<MultipleReconcilerSameTypeCustomResource>, TestExecutionInfoProvider {

private final AtomicInteger numberOfExecutions = new AtomicInteger(0);

@Override
public UpdateControl<MultipleReconcilerSameTypeCustomResource> reconcile(
MultipleReconcilerSameTypeCustomResource resource,
Context<MultipleReconcilerSameTypeCustomResource> context) {
numberOfExecutions.addAndGet(1);

resource.setStatus(new MultipleReconcilerSameTypeStatus());
resource.getStatus().setReconciledBy(getClass().getSimpleName());
return UpdateControl.patchStatus(resource);
}

public int getNumberOfExecutions() {
return numberOfExecutions.get();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.javaoperatorsdk.operator.sample.multiplereconcilersametype;

import java.util.concurrent.atomic.AtomicInteger;

import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider;

@ControllerConfiguration(labelSelector = "reconciler != 1")
public class MultipleReconcilerSameTypeReconciler2
implements Reconciler<MultipleReconcilerSameTypeCustomResource>, TestExecutionInfoProvider {

private final AtomicInteger numberOfExecutions = new AtomicInteger(0);

@Override
public UpdateControl<MultipleReconcilerSameTypeCustomResource> reconcile(
MultipleReconcilerSameTypeCustomResource resource,
Context<MultipleReconcilerSameTypeCustomResource> context) {
numberOfExecutions.addAndGet(1);

resource.setStatus(new MultipleReconcilerSameTypeStatus());
resource.getStatus().setReconciledBy(getClass().getSimpleName());
return UpdateControl.patchStatus(resource);
}

public int getNumberOfExecutions() {
return numberOfExecutions.get();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.javaoperatorsdk.operator.sample.multiplereconcilersametype;

public class MultipleReconcilerSameTypeStatus {

private String reconciledBy;

public String getReconciledBy() {
return reconciledBy;
}

public void setReconciledBy(String reconciledBy) {
this.reconciledBy = reconciledBy;
}
}