Skip to content

Commit 9d3dfd8

Browse files
authored
feat: multiple version in CRD Support (#879)
1 parent 071ee05 commit 9d3dfd8

15 files changed

+309
-14
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,8 @@ public synchronized void stop() {
180180

181181
public synchronized void add(Controller controller) {
182182
final var configuration = controller.getConfiguration();
183-
final var resourceTypeName = configuration.getResourceTypeName();
183+
final var resourceTypeName = ReconcilerUtils
184+
.getResourceTypeNameWithVersion(configuration.getResourceClass());
184185
final var existing = controllers.get(resourceTypeName);
185186
if (existing != null) {
186187
throw new OperatorException("Cannot register controller '" + configuration.getName()

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,13 @@ public void setApiVersion(String s) {
4040
return Constants.NO_FINALIZER.equals(finalizer) || validator.isFinalizerValid(finalizer);
4141
}
4242

43-
public static String getResourceTypeName(Class<? extends HasMetadata> resourceClass) {
44-
// todo: use fabric8 method when 5.12 is released
45-
// return HasMetadata.getFullResourceName(resourceClass);
43+
public static String getResourceTypeNameWithVersion(Class<? extends HasMetadata> resourceClass) {
44+
final var version = HasMetadata.getVersion(resourceClass);
45+
return getResourceTypeName(resourceClass) + "/" + version;
46+
}
47+
48+
public static String getResourceTypeName(
49+
Class<? extends HasMetadata> resourceClass) {
4650
final var group = HasMetadata.getGroup(resourceClass);
4751
final var plural = HasMetadata.getPlural(resourceClass);
4852
return (group == null || group.isEmpty()) ? plural : plural + "." + group;

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceManager.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public void start() {
7171
try {
7272
eventSource.start();
7373
} catch (Exception e) {
74-
log.warn("Error starting {} -> {}", eventSource, e);
74+
log.warn("Error starting {}", eventSource, e);
7575
}
7676
}
7777
eventProcessor.start();

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
import io.javaoperatorsdk.operator.processing.Controller;
1111
import io.javaoperatorsdk.operator.sample.simple.DuplicateCRController;
1212
import io.javaoperatorsdk.operator.sample.simple.TestCustomReconciler;
13-
import io.javaoperatorsdk.operator.sample.simple.TestCustomReconcilerV2;
13+
import io.javaoperatorsdk.operator.sample.simple.TestCustomReconcilerOtherV1;
1414
import io.javaoperatorsdk.operator.sample.simple.TestCustomResource;
15-
import io.javaoperatorsdk.operator.sample.simple.TestCustomResourceV2;
15+
import io.javaoperatorsdk.operator.sample.simple.TestCustomResourceOtherV1;
1616

1717
import static org.junit.jupiter.api.Assertions.assertThrows;
1818
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -30,11 +30,11 @@ public void shouldNotAddMultipleControllersForSameCustomResource() {
3030
}
3131

3232
@Test
33-
public void addingMultipleControllersForCustomResourcesWithDifferentVersionsShouldNotWork() {
33+
public void addingMultipleControllersForCustomResourcesWithSameVersionsShouldNotWork() {
3434
final var registered = new TestControllerConfiguration<>(new TestCustomReconciler(null),
3535
TestCustomResource.class);
36-
final var duplicated = new TestControllerConfiguration<>(new TestCustomReconcilerV2(),
37-
TestCustomResourceV2.class);
36+
final var duplicated = new TestControllerConfiguration<>(new TestCustomReconcilerOtherV1(),
37+
TestCustomResourceOtherV1.class);
3838

3939
checkException(registered, duplicated);
4040
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
77

88
@ControllerConfiguration
9-
public class TestCustomReconcilerV2 implements Reconciler<TestCustomResourceV2> {
9+
public class TestCustomReconcilerOtherV1 implements Reconciler<TestCustomResourceOtherV1> {
1010

1111
@Override
12-
public UpdateControl<TestCustomResourceV2> reconcile(TestCustomResourceV2 resource,
12+
public UpdateControl<TestCustomResourceOtherV1> reconcile(TestCustomResourceOtherV1 resource,
1313
Context context) {
1414
return UpdateControl.noUpdate();
1515
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
import io.fabric8.kubernetes.model.annotation.Version;
77

88
@Group("sample.javaoperatorsdk.io")
9-
@Version("v2")
9+
@Version("v1")
1010
@Kind("TestCustomResource") // this is needed to override the automatically generated kind
11-
public class TestCustomResourceV2
11+
public class TestCustomResourceOtherV1
1212
extends CustomResource<TestCustomResourceSpec, TestCustomResourceStatus> {
1313

1414
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package io.javaoperatorsdk.operator;
2+
3+
import java.time.Duration;
4+
import java.util.HashMap;
5+
6+
import org.junit.jupiter.api.Test;
7+
import org.junit.jupiter.api.extension.RegisterExtension;
8+
9+
import io.fabric8.kubernetes.api.model.ObjectMeta;
10+
import io.javaoperatorsdk.operator.config.runtime.DefaultConfigurationService;
11+
import io.javaoperatorsdk.operator.junit.OperatorExtension;
12+
import io.javaoperatorsdk.operator.sample.multiversioncrd.*;
13+
14+
import static org.awaitility.Awaitility.await;
15+
16+
class MultiVersionCRDIT {
17+
18+
public static final String CR_V1_NAME = "crv1";
19+
public static final String CR_V2_NAME = "crv2";
20+
@RegisterExtension
21+
OperatorExtension operator =
22+
OperatorExtension.builder()
23+
.withConfigurationService(DefaultConfigurationService.instance())
24+
.withReconciler(MultiVersionCRDTestReconciler1.class)
25+
.withReconciler(MultiVersionCRDTestReconciler2.class)
26+
.build();
27+
28+
@Test
29+
void multipleCRDVersions() {
30+
operator.create(MultiVersionCRDTestCustomResource1.class, createTestResourceV1WithoutLabel());
31+
operator.create(MultiVersionCRDTestCustomResource2.class, createTestResourceV2WithLabel());
32+
33+
await()
34+
.atMost(Duration.ofSeconds(2))
35+
.pollInterval(Duration.ofMillis(50))
36+
.until(
37+
() -> {
38+
var crV1Now = operator.get(MultiVersionCRDTestCustomResource1.class, CR_V1_NAME);
39+
var crV2Now = operator.get(MultiVersionCRDTestCustomResource2.class, CR_V2_NAME);
40+
return crV1Now.getStatus().getReconciledBy().size() == 1
41+
&& crV1Now.getStatus().getReconciledBy()
42+
.contains(MultiVersionCRDTestReconciler1.class.getSimpleName())
43+
&& crV2Now.getStatus().getReconciledBy().size() == 1
44+
&& crV2Now.getStatus().getReconciledBy()
45+
.contains(MultiVersionCRDTestReconciler2.class.getSimpleName());
46+
});
47+
}
48+
49+
MultiVersionCRDTestCustomResource1 createTestResourceV1WithoutLabel() {
50+
MultiVersionCRDTestCustomResource1 cr = new MultiVersionCRDTestCustomResource1();
51+
cr.setMetadata(new ObjectMeta());
52+
cr.getMetadata().setName(CR_V1_NAME);
53+
cr.setSpec(new MultiVersionCRDTestCustomResourceSpec1());
54+
cr.getSpec().setValue1(1);
55+
cr.getSpec().setValue2(1);
56+
return cr;
57+
}
58+
59+
MultiVersionCRDTestCustomResource2 createTestResourceV2WithLabel() {
60+
MultiVersionCRDTestCustomResource2 cr = new MultiVersionCRDTestCustomResource2();
61+
cr.setMetadata(new ObjectMeta());
62+
cr.getMetadata().setName(CR_V2_NAME);
63+
cr.getMetadata().setLabels(new HashMap<>());
64+
cr.getMetadata().getLabels().put("version", "v2");
65+
cr.setSpec(new MultiVersionCRDTestCustomResourceSpec2());
66+
cr.getSpec().setValue1(1);
67+
return cr;
68+
}
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package io.javaoperatorsdk.operator.sample.multiversioncrd;
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.Kind;
7+
import io.fabric8.kubernetes.model.annotation.ShortNames;
8+
import io.fabric8.kubernetes.model.annotation.Version;
9+
10+
@Group("sample.javaoperatorsdk")
11+
@Version("v1")
12+
@Kind("MultiVersionCRDTestCustomResource")
13+
@ShortNames("mv1")
14+
public class MultiVersionCRDTestCustomResource1
15+
extends
16+
CustomResource<MultiVersionCRDTestCustomResourceSpec1, MultiVersionCRDTestCustomResourceStatus1>
17+
implements Namespaced {
18+
19+
@Override
20+
protected MultiVersionCRDTestCustomResourceStatus1 initStatus() {
21+
return new MultiVersionCRDTestCustomResourceStatus1();
22+
}
23+
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package io.javaoperatorsdk.operator.sample.multiversioncrd;
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.Kind;
7+
import io.fabric8.kubernetes.model.annotation.ShortNames;
8+
import io.fabric8.kubernetes.model.annotation.Version;
9+
10+
@Group("sample.javaoperatorsdk")
11+
@Version(value = "v2", storage = false)
12+
@Kind("MultiVersionCRDTestCustomResource")
13+
@ShortNames("mv2")
14+
public class MultiVersionCRDTestCustomResource2
15+
extends
16+
CustomResource<MultiVersionCRDTestCustomResourceSpec2, MultiVersionCRDTestCustomResourceStatus2>
17+
implements Namespaced {
18+
19+
@Override
20+
protected MultiVersionCRDTestCustomResourceStatus2 initStatus() {
21+
return new MultiVersionCRDTestCustomResourceStatus2();
22+
}
23+
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.javaoperatorsdk.operator.sample.multiversioncrd;
2+
3+
public class MultiVersionCRDTestCustomResourceSpec1 {
4+
5+
private int value1;
6+
7+
private int value2;
8+
9+
public int getValue1() {
10+
return value1;
11+
}
12+
13+
public MultiVersionCRDTestCustomResourceSpec1 setValue1(int value1) {
14+
this.value1 = value1;
15+
return this;
16+
}
17+
18+
public int getValue2() {
19+
return value2;
20+
}
21+
22+
public MultiVersionCRDTestCustomResourceSpec1 setValue2(int value2) {
23+
this.value2 = value2;
24+
return this;
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.javaoperatorsdk.operator.sample.multiversioncrd;
2+
3+
public class MultiVersionCRDTestCustomResourceSpec2 {
4+
5+
private int value1;
6+
7+
public int getValue1() {
8+
return value1;
9+
}
10+
11+
public MultiVersionCRDTestCustomResourceSpec2 setValue1(int value1) {
12+
this.value1 = value1;
13+
return this;
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package io.javaoperatorsdk.operator.sample.multiversioncrd;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
public class MultiVersionCRDTestCustomResourceStatus1 {
7+
8+
private int value1;
9+
10+
private int value2;
11+
12+
private List<String> reconciledBy = new ArrayList<>();
13+
14+
public int getValue1() {
15+
return value1;
16+
}
17+
18+
public MultiVersionCRDTestCustomResourceStatus1 setValue1(int value1) {
19+
this.value1 = value1;
20+
return this;
21+
}
22+
23+
public int getValue2() {
24+
return value2;
25+
}
26+
27+
public MultiVersionCRDTestCustomResourceStatus1 setValue2(int value2) {
28+
this.value2 = value2;
29+
return this;
30+
}
31+
32+
public List<String> getReconciledBy() {
33+
return reconciledBy;
34+
}
35+
36+
public MultiVersionCRDTestCustomResourceStatus1 setReconciledBy(List<String> reconciledBy) {
37+
this.reconciledBy = reconciledBy;
38+
return this;
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.javaoperatorsdk.operator.sample.multiversioncrd;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
public class MultiVersionCRDTestCustomResourceStatus2 {
7+
8+
private int value1;
9+
10+
private List<String> reconciledBy = new ArrayList<>();
11+
12+
public int getValue1() {
13+
return value1;
14+
}
15+
16+
public MultiVersionCRDTestCustomResourceStatus2 setValue1(int value1) {
17+
this.value1 = value1;
18+
return this;
19+
}
20+
21+
public List<String> getReconciledBy() {
22+
return reconciledBy;
23+
}
24+
25+
public MultiVersionCRDTestCustomResourceStatus2 setReconciledBy(List<String> reconciledBy) {
26+
this.reconciledBy = reconciledBy;
27+
return this;
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package io.javaoperatorsdk.operator.sample.multiversioncrd;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
6+
import io.javaoperatorsdk.operator.api.reconciler.Context;
7+
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
8+
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
9+
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
10+
11+
import static io.javaoperatorsdk.operator.api.reconciler.Constants.NO_FINALIZER;
12+
13+
@ControllerConfiguration(finalizerName = NO_FINALIZER, labelSelector = "!version")
14+
public class MultiVersionCRDTestReconciler1
15+
implements Reconciler<MultiVersionCRDTestCustomResource1> {
16+
17+
private static final Logger log = LoggerFactory.getLogger(MultiVersionCRDTestReconciler1.class);
18+
19+
@Override
20+
public UpdateControl<MultiVersionCRDTestCustomResource1> reconcile(
21+
MultiVersionCRDTestCustomResource1 resource, Context context) {
22+
log.info("Reconcile MultiVersionCRDTestCustomResource1: {}",
23+
resource.getMetadata().getName());
24+
resource.getStatus().setValue1(resource.getStatus().getValue1() + 1);
25+
resource.getStatus().setValue2(resource.getStatus().getValue2() + 1);
26+
if (!resource.getStatus().getReconciledBy().contains(getClass().getSimpleName())) {
27+
resource.getStatus().getReconciledBy().add(getClass().getSimpleName());
28+
}
29+
return UpdateControl.updateStatus(resource);
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package io.javaoperatorsdk.operator.sample.multiversioncrd;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
6+
import io.javaoperatorsdk.operator.api.reconciler.Context;
7+
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
8+
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
9+
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
10+
11+
import static io.javaoperatorsdk.operator.api.reconciler.Constants.NO_FINALIZER;
12+
13+
@ControllerConfiguration(
14+
finalizerName = NO_FINALIZER,
15+
labelSelector = "version in (v2)")
16+
public class MultiVersionCRDTestReconciler2
17+
implements Reconciler<MultiVersionCRDTestCustomResource2> {
18+
19+
private static final Logger log = LoggerFactory.getLogger(MultiVersionCRDTestReconciler2.class);
20+
21+
@Override
22+
public UpdateControl<MultiVersionCRDTestCustomResource2> reconcile(
23+
MultiVersionCRDTestCustomResource2 resource, Context context) {
24+
log.info("Reconcile MultiVersionCRDTestCustomResource2: {}",
25+
resource.getMetadata().getName());
26+
resource.getStatus().setValue1(resource.getStatus().getValue1() + 1);
27+
if (!resource.getStatus().getReconciledBy().contains(getClass().getSimpleName())) {
28+
resource.getStatus().getReconciledBy().add(getClass().getSimpleName());
29+
}
30+
return UpdateControl.updateStatus(resource);
31+
}
32+
}

0 commit comments

Comments
 (0)