diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtilsInternal.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtilsInternal.java index 26ae5af554..a969f52c9b 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtilsInternal.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtilsInternal.java @@ -45,6 +45,13 @@ public class ReconcilerUtilsInternal { private static final String GET_STATUS = "getStatus"; private static final Pattern API_URI_PATTERN = Pattern.compile(".*http(s?)://[^/]*/api(s?)/(\\S*).*"); // NOSONAR: input is controlled + private static final String DOT = "."; + private static final char DOT_CHAR = '.'; + private static final String SLASH = "/"; + private static final char SLASH_CHAR = '/'; + private static final String EMPTY = ""; + private static final char ZERO = '0'; + private static final char NINE = '9'; // prevent instantiation of util class private ReconcilerUtilsInternal() {} @@ -55,7 +62,7 @@ public static boolean isFinalizerValid(String finalizer) { public static String getResourceTypeNameWithVersion(Class resourceClass) { final var version = HasMetadata.getVersion(resourceClass); - return getResourceTypeName(resourceClass) + "/" + version; + return getResourceTypeName(resourceClass) + SLASH + version; } public static String getResourceTypeName(Class resourceClass) { @@ -69,7 +76,7 @@ public static String getDefaultFinalizerName(Class resour public static String getDefaultFinalizerName(String resourceName) { // resource names for historic resources such as Pods are missing periods and therefore do not // constitute valid domain names as mandated by Kubernetes so generate one that does - if (resourceName.indexOf('.') < 0) { + if (resourceName.indexOf(DOT_CHAR) < 0) { resourceName = resourceName + MISSING_GROUP_SUFFIX; } return resourceName + FINALIZER_NAME_SUFFIX; @@ -102,7 +109,7 @@ public static String getDefaultNameFor(Class reconcilerCla public static String getDefaultReconcilerName(String reconcilerClassName) { // if the name is fully qualified, extract the simple class name - final var lastDot = reconcilerClassName.lastIndexOf('.'); + final var lastDot = reconcilerClassName.lastIndexOf(DOT_CHAR); if (lastDot > 0) { reconcilerClassName = reconcilerClassName.substring(lastDot + 1); } @@ -228,15 +235,15 @@ private static boolean matchesResourceType( final var regex = API_URI_PATTERN.matcher(message); if (regex.matches()) { var group = regex.group(3); - if (group.endsWith(".")) { + if (group.endsWith(DOT)) { group = group.substring(0, group.length() - 1); } final var segments = - Arrays.stream(group.split("/")).filter(Predicate.not(String::isEmpty)).toList(); + Arrays.stream(group.split(SLASH)).filter(Predicate.not(String::isEmpty)).toList(); if (segments.size() != 3) { return false; } - final var targetResourceName = segments.get(2) + "." + segments.get(0); + final var targetResourceName = segments.get(2) + DOT_CHAR + segments.get(0); return resourceTypeName.equals(targetResourceName); } } @@ -349,16 +356,25 @@ private static int validateResourceVersion(String v1) { } for (int i = 0; i < v1Length; i++) { char char1 = v1.charAt(i); - if (char1 == '0') { + if (char1 == ZERO) { if (i == 0) { throw new NonComparableResourceVersionException( "Resource version cannot begin with 0: " + v1); } - } else if (char1 < '0' || char1 > '9') { + } else if (char1 < ZERO || char1 > NINE) { throw new NonComparableResourceVersionException( "Non numeric characters in resource version: " + v1); } } return v1Length; } + + public static String getGroup(String apiVersion) { + var index = apiVersion.indexOf(SLASH_CHAR); + if (index < 0) { + return EMPTY; + } else { + return apiVersion.substring(0, index); + } + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/Mappers.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/Mappers.java index 3bbef527d9..fcf0db2bd6 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/Mappers.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/Mappers.java @@ -24,6 +24,8 @@ import io.javaoperatorsdk.operator.processing.event.ResourceID; import io.javaoperatorsdk.operator.processing.event.source.SecondaryToPrimaryMapper; +import static io.javaoperatorsdk.operator.ReconcilerUtilsInternal.getGroup; + public class Mappers { public static final String DEFAULT_ANNOTATION_FOR_NAME = "io.javaoperatorsdk/primary-name"; @@ -98,10 +100,12 @@ public static SecondaryToPrimaryMapper fromOwnerRefer public static SecondaryToPrimaryMapper fromOwnerReferences( String apiVersion, String kind, boolean clusterScope) { - String correctApiVersion = apiVersion.startsWith("/") ? apiVersion.substring(1) : apiVersion; return resource -> resource.getMetadata().getOwnerReferences().stream() - .filter(r -> r.getKind().equals(kind) && r.getApiVersion().equals(correctApiVersion)) + .filter( + r -> + r.getKind().equals(kind) + && getGroup(r.getApiVersion()).equals(getGroup(apiVersion))) .map(or -> ResourceID.fromOwnerReference(resource, or, clusterScope)) .collect(Collectors.toSet()); } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ReconcilerUtilsInternalTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ReconcilerUtilsInternalTest.java index 129351e8af..bd64333c6b 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ReconcilerUtilsInternalTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ReconcilerUtilsInternalTest.java @@ -39,6 +39,7 @@ import static io.javaoperatorsdk.operator.ReconcilerUtilsInternal.getDefaultFinalizerName; import static io.javaoperatorsdk.operator.ReconcilerUtilsInternal.getDefaultNameFor; import static io.javaoperatorsdk.operator.ReconcilerUtilsInternal.getDefaultReconcilerName; +import static io.javaoperatorsdk.operator.ReconcilerUtilsInternal.getGroup; import static io.javaoperatorsdk.operator.ReconcilerUtilsInternal.handleKubernetesClientException; import static io.javaoperatorsdk.operator.ReconcilerUtilsInternal.isFinalizerValid; import static org.assertj.core.api.Assertions.assertThat; @@ -308,6 +309,12 @@ void compareResourceVersionsWithHasMetadata() { assertThat(ReconcilerUtilsInternal.compareResourceVersions(resource2, resource1)).isPositive(); } + @Test + void extractsGroupFromApiVersion() { + assertThat(getGroup("v1")).isEqualTo(""); + assertThat(getGroup("apps/v1")).isEqualTo("apps"); + } + private HasMetadata createResourceWithVersion(String resourceVersion) { return new PodBuilder() .withMetadata( diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/MappersTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/MappersTest.java index a6d081a1e2..8d08c4e8a0 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/MappersTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/MappersTest.java @@ -21,6 +21,7 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.javaoperatorsdk.operator.TestUtils; import io.javaoperatorsdk.operator.processing.event.ResourceID; @@ -83,6 +84,30 @@ void secondaryToPrimaryMapperFromOwnerReferenceFiltersByType() { assertThat(res).isEmpty(); } + @Test + void fromOwnerReferenceIgnoresVersionFromApiVersion() { + var primary = TestUtils.testCustomResource(); + primary.getMetadata().setUid(UUID.randomUUID().toString()); + var secondary = + new ConfigMapBuilder() + .withMetadata( + new ObjectMetaBuilder() + .withName("test1") + .withNamespace(primary.getMetadata().getNamespace()) + .build()) + .build(); + secondary.addOwnerReference(primary); + + var res = + Mappers.fromOwnerReferences( + HasMetadata.getGroup(TestCustomResource.class) + "/v2", + HasMetadata.getKind(TestCustomResource.class), + false) + .toPrimaryResourceIDs(secondary); + + assertThat(res).contains(ResourceID.fromResource(primary)); + } + private static ConfigMap getConfigMap(TestCustomResource primary) { return new ConfigMapBuilder() .withMetadata( diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ownerreferencemultiversion/OwnerRefMultiVersionCR1.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ownerreferencemultiversion/OwnerRefMultiVersionCR1.java new file mode 100644 index 0000000000..f575def6aa --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ownerreferencemultiversion/OwnerRefMultiVersionCR1.java @@ -0,0 +1,29 @@ +/* + * Copyright Java Operator SDK Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.javaoperatorsdk.operator.baseapi.ownerreferencemultiversion; + +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.Version; + +@Group("sample.javaoperatorsdk") +@Version("v1") +@Kind("OwnerRefMultiVersionCR") +public class OwnerRefMultiVersionCR1 + extends CustomResource + implements Namespaced {} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ownerreferencemultiversion/OwnerRefMultiVersionCR2.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ownerreferencemultiversion/OwnerRefMultiVersionCR2.java new file mode 100644 index 0000000000..98e51bcfe3 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ownerreferencemultiversion/OwnerRefMultiVersionCR2.java @@ -0,0 +1,29 @@ +/* + * Copyright Java Operator SDK Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.javaoperatorsdk.operator.baseapi.ownerreferencemultiversion; + +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.Version; + +@Group("sample.javaoperatorsdk") +@Version(value = "v2", storage = false) +@Kind("OwnerRefMultiVersionCR") +public class OwnerRefMultiVersionCR2 + extends CustomResource + implements Namespaced {} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ownerreferencemultiversion/OwnerRefMultiVersionIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ownerreferencemultiversion/OwnerRefMultiVersionIT.java new file mode 100644 index 0000000000..7be254ad13 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ownerreferencemultiversion/OwnerRefMultiVersionIT.java @@ -0,0 +1,161 @@ +/* + * Copyright Java Operator SDK Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.javaoperatorsdk.operator.baseapi.ownerreferencemultiversion; + +import java.time.Duration; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionVersion; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +/** + * Integration test verifying that {@code Mappers.fromOwnerReferences} correctly maps secondary + * resources back to primary resources even after a CRD version change. The mapper compares only the + * group part of the apiVersion (ignoring the version), so owner references created under v1 should + * still work when the CRD storage version switches to v2. + */ +class OwnerRefMultiVersionIT { + + private static final Logger log = LoggerFactory.getLogger(OwnerRefMultiVersionIT.class); + + private static final String CR_NAME = "test-ownerref-mv"; + private static final String CRD_NAME = "ownerrefmultiversioncrs.sample.javaoperatorsdk"; + + @RegisterExtension + LocallyRunOperatorExtension operator = + LocallyRunOperatorExtension.builder() + .withReconciler(new OwnerRefMultiVersionReconciler()) + .withBeforeStartHook( + ext -> { + // The auto-generated CRD has both v1 (storage) and v2. Remove v2 so the + // cluster initially only knows about v1. + var client = ext.getKubernetesClient(); + var crd = + client + .apiextensions() + .v1() + .customResourceDefinitions() + .withName(CRD_NAME) + .get(); + if (crd != null) { + crd.getSpec().getVersions().removeIf(v -> "v2".equals(v.getName())); + crd.getMetadata().setResourceVersion(null); + crd.getMetadata().setManagedFields(null); + client.resource(crd).serverSideApply(); + log.info("Applied CRD with v1 only"); + } + }) + .build(); + + @Test + void mapperWorksAcrossVersionChange() { + var reconciler = operator.getReconcilerOfType(OwnerRefMultiVersionReconciler.class); + + // 1. Create a v1 custom resource + var cr = createCR(); + operator.create(cr); + + // 2. Wait for initial reconciliation: ConfigMap is created with owner ref to v1 + await() + .atMost(Duration.ofSeconds(30)) + .pollInterval(Duration.ofMillis(300)) + .untilAsserted( + () -> { + var current = operator.get(OwnerRefMultiVersionCR1.class, CR_NAME); + assertThat(current.getStatus()).isNotNull(); + assertThat(current.getStatus().getReconcileCount()).isPositive(); + + var cm = operator.get(ConfigMap.class, CR_NAME); + assertThat(cm).isNotNull(); + assertThat(cm.getMetadata().getOwnerReferences()).hasSize(1); + assertThat(cm.getMetadata().getOwnerReferences().get(0).getApiVersion()) + .isEqualTo("sample.javaoperatorsdk/v1"); + }); + + int countBeforeUpdate = reconciler.getReconcileCount(); + log.info("Reconcile count before CRD update: {}", countBeforeUpdate); + + // 3. Update CRD to add v2 as the new storage version + updateCrdWithV2AsStorage(operator.getKubernetesClient()); + + // 4. Modify the ConfigMap to trigger the informer event source. + // The mapper should still map the ConfigMap (with v1 owner ref) to the primary CR. + var cm = operator.get(ConfigMap.class, CR_NAME); + cm.getData().put("updated", "true"); + operator.replace(cm); + + // 5. Verify reconciliation was triggered again + await() + .atMost(Duration.ofSeconds(30)) + .pollInterval(Duration.ofMillis(300)) + .untilAsserted( + () -> { + assertThat(reconciler.getReconcileCount()).isGreaterThan(countBeforeUpdate); + }); + + log.info("Reconcile count after CRD update: {}", reconciler.getReconcileCount()); + } + + private void updateCrdWithV2AsStorage(KubernetesClient client) { + var crd = client.apiextensions().v1().customResourceDefinitions().withName(CRD_NAME).get(); + + // Set v1 to non-storage + for (var version : crd.getSpec().getVersions()) { + if ("v1".equals(version.getName())) { + version.setStorage(false); + } + } + + // Add v2 as storage version, reusing v1's schema (specs are compatible) + var v1 = + crd.getSpec().getVersions().stream() + .filter(v -> "v1".equals(v.getName())) + .findFirst() + .orElseThrow(); + + var v2 = new CustomResourceDefinitionVersion(); + v2.setName("v2"); + v2.setServed(true); + v2.setStorage(true); + v2.setSchema(v1.getSchema()); + v2.setSubresources(v1.getSubresources()); + crd.getSpec().getVersions().add(v2); + + crd.getMetadata().setResourceVersion(null); + crd.getMetadata().setManagedFields(null); + client.resource(crd).serverSideApply(); + log.info("Updated CRD with v2 as storage version"); + } + + private OwnerRefMultiVersionCR1 createCR() { + var cr = new OwnerRefMultiVersionCR1(); + cr.setMetadata(new ObjectMeta()); + cr.getMetadata().setName(CR_NAME); + cr.setSpec(new OwnerRefMultiVersionSpec()); + cr.getSpec().setValue("initial"); + return cr; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ownerreferencemultiversion/OwnerRefMultiVersionReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ownerreferencemultiversion/OwnerRefMultiVersionReconciler.java new file mode 100644 index 0000000000..c9680b0c81 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ownerreferencemultiversion/OwnerRefMultiVersionReconciler.java @@ -0,0 +1,93 @@ +/* + * Copyright Java Operator SDK Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.javaoperatorsdk.operator.baseapi.ownerreferencemultiversion; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.api.config.informer.InformerEventSourceConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; +import io.javaoperatorsdk.operator.processing.event.source.EventSource; +import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; +import io.javaoperatorsdk.operator.processing.event.source.informer.Mappers; + +@ControllerConfiguration +public class OwnerRefMultiVersionReconciler implements Reconciler { + + public static final String LABEL_KEY = "ownerref-multiversion-test"; + public static final String LABEL_VALUE = "true"; + public static final String DATA_KEY = "data"; + + private final AtomicInteger reconcileCount = new AtomicInteger(0); + + @Override + public UpdateControl reconcile( + OwnerRefMultiVersionCR1 resource, Context context) { + + var client = context.getClient(); + var configMapName = resource.getMetadata().getName(); + var namespace = resource.getMetadata().getNamespace(); + + var existingCM = client.configMaps().inNamespace(namespace).withName(configMapName).get(); + if (existingCM == null) { + var cm = + new ConfigMapBuilder() + .withMetadata( + new ObjectMetaBuilder() + .withName(configMapName) + .withNamespace(namespace) + .withLabels(Map.of(LABEL_KEY, LABEL_VALUE)) + .build()) + .withData(Map.of(DATA_KEY, resource.getSpec().getValue())) + .build(); + cm.addOwnerReference(resource); + client.configMaps().resource(cm).create(); + } + + int count = reconcileCount.incrementAndGet(); + if (resource.getStatus() == null) { + resource.setStatus(new OwnerRefMultiVersionStatus()); + } + resource.getStatus().setReconcileCount(count); + return UpdateControl.patchStatus(resource); + } + + @Override + public List> prepareEventSources( + EventSourceContext context) { + var ies = + new InformerEventSource<>( + InformerEventSourceConfiguration.from(ConfigMap.class, OwnerRefMultiVersionCR1.class) + .withSecondaryToPrimaryMapper( + Mappers.fromOwnerReferences(context.getPrimaryResourceClass())) + .withLabelSelector(LABEL_KEY + "=" + LABEL_VALUE) + .build(), + context); + return List.of(ies); + } + + public int getReconcileCount() { + return reconcileCount.get(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ownerreferencemultiversion/OwnerRefMultiVersionSpec.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ownerreferencemultiversion/OwnerRefMultiVersionSpec.java new file mode 100644 index 0000000000..5fc52ff87c --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ownerreferencemultiversion/OwnerRefMultiVersionSpec.java @@ -0,0 +1,30 @@ +/* + * Copyright Java Operator SDK Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.javaoperatorsdk.operator.baseapi.ownerreferencemultiversion; + +public class OwnerRefMultiVersionSpec { + + private String value; + + public String getValue() { + return value; + } + + public OwnerRefMultiVersionSpec setValue(String value) { + this.value = value; + return this; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ownerreferencemultiversion/OwnerRefMultiVersionStatus.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ownerreferencemultiversion/OwnerRefMultiVersionStatus.java new file mode 100644 index 0000000000..135f9de79e --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ownerreferencemultiversion/OwnerRefMultiVersionStatus.java @@ -0,0 +1,30 @@ +/* + * Copyright Java Operator SDK Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.javaoperatorsdk.operator.baseapi.ownerreferencemultiversion; + +public class OwnerRefMultiVersionStatus { + + private int reconcileCount; + + public int getReconcileCount() { + return reconcileCount; + } + + public OwnerRefMultiVersionStatus setReconcileCount(int reconcileCount) { + this.reconcileCount = reconcileCount; + return this; + } +}