diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java index 39298c2ee0..5f71ec2747 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java @@ -180,7 +180,8 @@ public synchronized void stop() { public synchronized void add(Controller controller) { final var configuration = controller.getConfiguration(); - final var resourceTypeName = configuration.getResourceTypeName(); + final var resourceTypeName = ReconcilerUtils + .getResourceTypeNameWithVersion(configuration.getResourceClass()); final var existing = controllers.get(resourceTypeName); if (existing != null) { throw new OperatorException("Cannot register controller '" + configuration.getName() diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtils.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtils.java index 26dc4cdcca..e3a6da1e5a 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtils.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtils.java @@ -40,9 +40,13 @@ public void setApiVersion(String s) { return Constants.NO_FINALIZER.equals(finalizer) || validator.isFinalizerValid(finalizer); } - public static String getResourceTypeName(Class resourceClass) { - // todo: use fabric8 method when 5.12 is released - // return HasMetadata.getFullResourceName(resourceClass); + public static String getResourceTypeNameWithVersion(Class resourceClass) { + final var version = HasMetadata.getVersion(resourceClass); + return getResourceTypeName(resourceClass) + "/" + version; + } + + public static String getResourceTypeName( + Class resourceClass) { final var group = HasMetadata.getGroup(resourceClass); final var plural = HasMetadata.getPlural(resourceClass); return (group == null || group.isEmpty()) ? plural : plural + "." + group; diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceManager.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceManager.java index 55f42cd438..22920ac7a2 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceManager.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceManager.java @@ -71,7 +71,7 @@ public void start() { try { eventSource.start(); } catch (Exception e) { - log.warn("Error starting {} -> {}", eventSource, e); + log.warn("Error starting {}", eventSource, e); } } eventProcessor.start(); diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ControllerManagerTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ControllerManagerTest.java index a1cfa3e82d..724c964d88 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ControllerManagerTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ControllerManagerTest.java @@ -10,9 +10,9 @@ 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.TestCustomReconcilerV2; +import io.javaoperatorsdk.operator.sample.simple.TestCustomReconcilerOtherV1; import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; -import io.javaoperatorsdk.operator.sample.simple.TestCustomResourceV2; +import io.javaoperatorsdk.operator.sample.simple.TestCustomResourceOtherV1; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -30,11 +30,11 @@ public void shouldNotAddMultipleControllersForSameCustomResource() { } @Test - public void addingMultipleControllersForCustomResourcesWithDifferentVersionsShouldNotWork() { + public void addingMultipleControllersForCustomResourcesWithSameVersionsShouldNotWork() { final var registered = new TestControllerConfiguration<>(new TestCustomReconciler(null), TestCustomResource.class); - final var duplicated = new TestControllerConfiguration<>(new TestCustomReconcilerV2(), - TestCustomResourceV2.class); + final var duplicated = new TestControllerConfiguration<>(new TestCustomReconcilerOtherV1(), + TestCustomResourceOtherV1.class); checkException(registered, duplicated); } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestCustomReconcilerV2.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestCustomReconcilerOtherV1.java similarity index 69% rename from operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestCustomReconcilerV2.java rename to operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestCustomReconcilerOtherV1.java index bab22f309a..9a45fbbf93 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestCustomReconcilerV2.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestCustomReconcilerOtherV1.java @@ -6,10 +6,10 @@ import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; @ControllerConfiguration -public class TestCustomReconcilerV2 implements Reconciler { +public class TestCustomReconcilerOtherV1 implements Reconciler { @Override - public UpdateControl reconcile(TestCustomResourceV2 resource, + public UpdateControl reconcile(TestCustomResourceOtherV1 resource, Context context) { return UpdateControl.noUpdate(); } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestCustomResourceV2.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestCustomResourceOtherV1.java similarity index 89% rename from operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestCustomResourceV2.java rename to operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestCustomResourceOtherV1.java index d4c71e0662..f768ba491f 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestCustomResourceV2.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestCustomResourceOtherV1.java @@ -6,9 +6,9 @@ import io.fabric8.kubernetes.model.annotation.Version; @Group("sample.javaoperatorsdk.io") -@Version("v2") +@Version("v1") @Kind("TestCustomResource") // this is needed to override the automatically generated kind -public class TestCustomResourceV2 +public class TestCustomResourceOtherV1 extends CustomResource { } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultiVersionCRDIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultiVersionCRDIT.java new file mode 100644 index 0000000000..b417c53e6e --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultiVersionCRDIT.java @@ -0,0 +1,69 @@ +package io.javaoperatorsdk.operator; + +import java.time.Duration; +import java.util.HashMap; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.javaoperatorsdk.operator.config.runtime.DefaultConfigurationService; +import io.javaoperatorsdk.operator.junit.OperatorExtension; +import io.javaoperatorsdk.operator.sample.multiversioncrd.*; + +import static org.awaitility.Awaitility.await; + +class MultiVersionCRDIT { + + public static final String CR_V1_NAME = "crv1"; + public static final String CR_V2_NAME = "crv2"; + @RegisterExtension + OperatorExtension operator = + OperatorExtension.builder() + .withConfigurationService(DefaultConfigurationService.instance()) + .withReconciler(MultiVersionCRDTestReconciler1.class) + .withReconciler(MultiVersionCRDTestReconciler2.class) + .build(); + + @Test + void multipleCRDVersions() { + operator.create(MultiVersionCRDTestCustomResource1.class, createTestResourceV1WithoutLabel()); + operator.create(MultiVersionCRDTestCustomResource2.class, createTestResourceV2WithLabel()); + + await() + .atMost(Duration.ofSeconds(2)) + .pollInterval(Duration.ofMillis(50)) + .until( + () -> { + var crV1Now = operator.get(MultiVersionCRDTestCustomResource1.class, CR_V1_NAME); + var crV2Now = operator.get(MultiVersionCRDTestCustomResource2.class, CR_V2_NAME); + return crV1Now.getStatus().getReconciledBy().size() == 1 + && crV1Now.getStatus().getReconciledBy() + .contains(MultiVersionCRDTestReconciler1.class.getSimpleName()) + && crV2Now.getStatus().getReconciledBy().size() == 1 + && crV2Now.getStatus().getReconciledBy() + .contains(MultiVersionCRDTestReconciler2.class.getSimpleName()); + }); + } + + MultiVersionCRDTestCustomResource1 createTestResourceV1WithoutLabel() { + MultiVersionCRDTestCustomResource1 cr = new MultiVersionCRDTestCustomResource1(); + cr.setMetadata(new ObjectMeta()); + cr.getMetadata().setName(CR_V1_NAME); + cr.setSpec(new MultiVersionCRDTestCustomResourceSpec1()); + cr.getSpec().setValue1(1); + cr.getSpec().setValue2(1); + return cr; + } + + MultiVersionCRDTestCustomResource2 createTestResourceV2WithLabel() { + MultiVersionCRDTestCustomResource2 cr = new MultiVersionCRDTestCustomResource2(); + cr.setMetadata(new ObjectMeta()); + cr.getMetadata().setName(CR_V2_NAME); + cr.getMetadata().setLabels(new HashMap<>()); + cr.getMetadata().getLabels().put("version", "v2"); + cr.setSpec(new MultiVersionCRDTestCustomResourceSpec2()); + cr.getSpec().setValue1(1); + return cr; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiversioncrd/MultiVersionCRDTestCustomResource1.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiversioncrd/MultiVersionCRDTestCustomResource1.java new file mode 100644 index 0000000000..10236563c7 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiversioncrd/MultiVersionCRDTestCustomResource1.java @@ -0,0 +1,24 @@ +package io.javaoperatorsdk.operator.sample.multiversioncrd; + +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.Kind; +import io.fabric8.kubernetes.model.annotation.ShortNames; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("sample.javaoperatorsdk") +@Version("v1") +@Kind("MultiVersionCRDTestCustomResource") +@ShortNames("mv1") +public class MultiVersionCRDTestCustomResource1 + extends + CustomResource + implements Namespaced { + + @Override + protected MultiVersionCRDTestCustomResourceStatus1 initStatus() { + return new MultiVersionCRDTestCustomResourceStatus1(); + } + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiversioncrd/MultiVersionCRDTestCustomResource2.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiversioncrd/MultiVersionCRDTestCustomResource2.java new file mode 100644 index 0000000000..de0a804049 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiversioncrd/MultiVersionCRDTestCustomResource2.java @@ -0,0 +1,24 @@ +package io.javaoperatorsdk.operator.sample.multiversioncrd; + +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.Kind; +import io.fabric8.kubernetes.model.annotation.ShortNames; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("sample.javaoperatorsdk") +@Version(value = "v2", storage = false) +@Kind("MultiVersionCRDTestCustomResource") +@ShortNames("mv2") +public class MultiVersionCRDTestCustomResource2 + extends + CustomResource + implements Namespaced { + + @Override + protected MultiVersionCRDTestCustomResourceStatus2 initStatus() { + return new MultiVersionCRDTestCustomResourceStatus2(); + } + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiversioncrd/MultiVersionCRDTestCustomResourceSpec1.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiversioncrd/MultiVersionCRDTestCustomResourceSpec1.java new file mode 100644 index 0000000000..20759eef76 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiversioncrd/MultiVersionCRDTestCustomResourceSpec1.java @@ -0,0 +1,26 @@ +package io.javaoperatorsdk.operator.sample.multiversioncrd; + +public class MultiVersionCRDTestCustomResourceSpec1 { + + private int value1; + + private int value2; + + public int getValue1() { + return value1; + } + + public MultiVersionCRDTestCustomResourceSpec1 setValue1(int value1) { + this.value1 = value1; + return this; + } + + public int getValue2() { + return value2; + } + + public MultiVersionCRDTestCustomResourceSpec1 setValue2(int value2) { + this.value2 = value2; + return this; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiversioncrd/MultiVersionCRDTestCustomResourceSpec2.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiversioncrd/MultiVersionCRDTestCustomResourceSpec2.java new file mode 100644 index 0000000000..e97d110673 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiversioncrd/MultiVersionCRDTestCustomResourceSpec2.java @@ -0,0 +1,15 @@ +package io.javaoperatorsdk.operator.sample.multiversioncrd; + +public class MultiVersionCRDTestCustomResourceSpec2 { + + private int value1; + + public int getValue1() { + return value1; + } + + public MultiVersionCRDTestCustomResourceSpec2 setValue1(int value1) { + this.value1 = value1; + return this; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiversioncrd/MultiVersionCRDTestCustomResourceStatus1.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiversioncrd/MultiVersionCRDTestCustomResourceStatus1.java new file mode 100644 index 0000000000..535292c33f --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiversioncrd/MultiVersionCRDTestCustomResourceStatus1.java @@ -0,0 +1,40 @@ +package io.javaoperatorsdk.operator.sample.multiversioncrd; + +import java.util.ArrayList; +import java.util.List; + +public class MultiVersionCRDTestCustomResourceStatus1 { + + private int value1; + + private int value2; + + private List reconciledBy = new ArrayList<>(); + + public int getValue1() { + return value1; + } + + public MultiVersionCRDTestCustomResourceStatus1 setValue1(int value1) { + this.value1 = value1; + return this; + } + + public int getValue2() { + return value2; + } + + public MultiVersionCRDTestCustomResourceStatus1 setValue2(int value2) { + this.value2 = value2; + return this; + } + + public List getReconciledBy() { + return reconciledBy; + } + + public MultiVersionCRDTestCustomResourceStatus1 setReconciledBy(List reconciledBy) { + this.reconciledBy = reconciledBy; + return this; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiversioncrd/MultiVersionCRDTestCustomResourceStatus2.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiversioncrd/MultiVersionCRDTestCustomResourceStatus2.java new file mode 100644 index 0000000000..5df57ef76d --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiversioncrd/MultiVersionCRDTestCustomResourceStatus2.java @@ -0,0 +1,29 @@ +package io.javaoperatorsdk.operator.sample.multiversioncrd; + +import java.util.ArrayList; +import java.util.List; + +public class MultiVersionCRDTestCustomResourceStatus2 { + + private int value1; + + private List reconciledBy = new ArrayList<>(); + + public int getValue1() { + return value1; + } + + public MultiVersionCRDTestCustomResourceStatus2 setValue1(int value1) { + this.value1 = value1; + return this; + } + + public List getReconciledBy() { + return reconciledBy; + } + + public MultiVersionCRDTestCustomResourceStatus2 setReconciledBy(List reconciledBy) { + this.reconciledBy = reconciledBy; + return this; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiversioncrd/MultiVersionCRDTestReconciler1.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiversioncrd/MultiVersionCRDTestReconciler1.java new file mode 100644 index 0000000000..a8f58971b3 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiversioncrd/MultiVersionCRDTestReconciler1.java @@ -0,0 +1,31 @@ +package io.javaoperatorsdk.operator.sample.multiversioncrd; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +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 static io.javaoperatorsdk.operator.api.reconciler.Constants.NO_FINALIZER; + +@ControllerConfiguration(finalizerName = NO_FINALIZER, labelSelector = "!version") +public class MultiVersionCRDTestReconciler1 + implements Reconciler { + + private static final Logger log = LoggerFactory.getLogger(MultiVersionCRDTestReconciler1.class); + + @Override + public UpdateControl reconcile( + MultiVersionCRDTestCustomResource1 resource, Context context) { + log.info("Reconcile MultiVersionCRDTestCustomResource1: {}", + resource.getMetadata().getName()); + resource.getStatus().setValue1(resource.getStatus().getValue1() + 1); + resource.getStatus().setValue2(resource.getStatus().getValue2() + 1); + if (!resource.getStatus().getReconciledBy().contains(getClass().getSimpleName())) { + resource.getStatus().getReconciledBy().add(getClass().getSimpleName()); + } + return UpdateControl.updateStatus(resource); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiversioncrd/MultiVersionCRDTestReconciler2.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiversioncrd/MultiVersionCRDTestReconciler2.java new file mode 100644 index 0000000000..d25297d1c6 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiversioncrd/MultiVersionCRDTestReconciler2.java @@ -0,0 +1,32 @@ +package io.javaoperatorsdk.operator.sample.multiversioncrd; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +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 static io.javaoperatorsdk.operator.api.reconciler.Constants.NO_FINALIZER; + +@ControllerConfiguration( + finalizerName = NO_FINALIZER, + labelSelector = "version in (v2)") +public class MultiVersionCRDTestReconciler2 + implements Reconciler { + + private static final Logger log = LoggerFactory.getLogger(MultiVersionCRDTestReconciler2.class); + + @Override + public UpdateControl reconcile( + MultiVersionCRDTestCustomResource2 resource, Context context) { + log.info("Reconcile MultiVersionCRDTestCustomResource2: {}", + resource.getMetadata().getName()); + resource.getStatus().setValue1(resource.getStatus().getValue1() + 1); + if (!resource.getStatus().getReconciledBy().contains(getClass().getSimpleName())) { + resource.getStatus().getReconciledBy().add(getClass().getSimpleName()); + } + return UpdateControl.updateStatus(resource); + } +}