diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java index 8dc62b4ca7..a96c4b03ec 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java @@ -85,7 +85,7 @@ public ReconcileResult reconcile(P primary, Context

context) { protected ReconcileResult reconcile(P primary, R actualResource, Context

context) { if (creatable() || updatable()) { if (actualResource == null) { - if (creatable) { + if (creatable()) { var desired = getOrComputeDesired(context); throwIfNull(desired, primary, "Desired"); logForOperation("Creating", primary, desired); @@ -244,12 +244,12 @@ protected void handleDelete(P primary, R secondary, Context

context) { } protected boolean isCreatable() { - return creatable; + return creatable(); } @SuppressWarnings("unused") protected boolean isUpdatable() { - return updatable; + return updatable(); } @Override diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResourceReconciler.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResourceReconciler.java index 23135f81b1..827961b77f 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResourceReconciler.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResourceReconciler.java @@ -148,12 +148,12 @@ public R create(R desired, P primary, Context

context) { } @Override - protected boolean isCreatable() { + protected boolean creatable() { return bulkDependentResource instanceof Creator; } @Override - protected boolean isUpdatable() { + protected boolean updatable() { return bulkDependentResource instanceof Updater; } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResourceCapabilitiesTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResourceCapabilitiesTest.java new file mode 100644 index 0000000000..bfd8873d9d --- /dev/null +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResourceCapabilitiesTest.java @@ -0,0 +1,145 @@ +/* + * 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.processing.dependent; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; +import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult.Operation; +import io.javaoperatorsdk.operator.processing.dependent.Matcher.Result; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * A {@link BulkDependentResource} does not have to implement every one of {@link Creator}, {@link + * Updater} and {@link Deleter}. The capabilities of the bulk resource itself must therefore be + * honoured, not those of the internal per-item wrapper (which implements all three). + */ +@SuppressWarnings("unchecked") +class BulkDependentResourceCapabilitiesTest { + + private static final ConfigMap PRIMARY = + new ConfigMapBuilder().withNewMetadata().withName("primary").endMetadata().build(); + + @Test + void doesNotAttemptUpdateWhenBulkResourceIsNotUpdater() { + var result = new CreateAndDeleteOnlyBulk().reconcile(PRIMARY, mock(Context.class)); + + assertThat(result.getSingleOperation()).isEqualTo(Operation.NONE); + } + + @Test + void doesNotAttemptCreateWhenBulkResourceIsNotCreator() { + var result = new DeleteOnlyBulk().reconcile(PRIMARY, mock(Context.class)); + + assertThat(result.getResourceOperations()).isEmpty(); + } + + /** Can be created and deleted, but deliberately is not an {@link Updater}. */ + private static class CreateAndDeleteOnlyBulk extends BaseBulk + implements Creator, Deleter { + + @Override + public Map getSecondaryResources( + ConfigMap primary, Context context) { + // an actual resource exists, but differs from the desired one + var actual = new LinkedHashMap(); + actual.put("a", configMap(Map.of("key", "actual"))); + return actual; + } + + @Override + public ConfigMap create(ConfigMap desired, ConfigMap primary, Context context) { + throw new AssertionError("create must not be called, the resource already exists"); + } + } + + /** Can only be deleted: neither {@link Creator} nor {@link Updater}. */ + private static class DeleteOnlyBulk extends BaseBulk implements Deleter { + + @Override + public Map getSecondaryResources( + ConfigMap primary, Context context) { + return Map.of(); + } + } + + private abstract static class BaseBulk extends AbstractDependentResource + implements BulkDependentResource { + + @Override + public Map desiredResources(ConfigMap primary, Context context) { + var desired = new LinkedHashMap(); + desired.put("a", configMap(Map.of("key", "desired"))); + return desired; + } + + @Override + public void deleteTargetResource( + ConfigMap primary, ConfigMap resource, String key, Context context) {} + + @Override + public Result match( + ConfigMap actualResource, + ConfigMap desired, + ConfigMap primary, + Context context) { + return Result.computed(false, desired); + } + + @Override + public Result match( + ConfigMap resource, ConfigMap primary, Context context) { + return Result.computed(false, resource); + } + + @Override + protected Optional selectTargetSecondaryResource( + Set secondaryResources, ConfigMap primary, Context context) { + return Optional.empty(); + } + + @Override + protected void onCreated(ConfigMap primary, ConfigMap created, Context context) {} + + @Override + protected void onUpdated( + ConfigMap primary, ConfigMap updated, ConfigMap actual, Context context) {} + + @Override + public Class resourceType() { + return ConfigMap.class; + } + } + + private static ConfigMap configMap(Map data) { + return new ConfigMapBuilder() + .withNewMetadata() + .withName("a") + .endMetadata() + .withData(data) + .build(); + } +}