diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java index 2acf8d13ca..d6250e162c 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java @@ -1,14 +1,18 @@ package io.javaoperatorsdk.operator.api.reconciler; +import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.client.KubernetesClient; import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DefaultManagedWorkflowAndDependentResourceContext; import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.ManagedWorkflowAndDependentResourceContext; import io.javaoperatorsdk.operator.processing.Controller; @@ -24,6 +28,7 @@ public class DefaultContext

implements Context

{ private final ControllerConfiguration

controllerConfiguration; private final DefaultManagedWorkflowAndDependentResourceContext

defaultManagedDependentResourceContext; + private final Map, Object> desiredStates = new ConcurrentHashMap<>(); public DefaultContext(RetryInfo retryInfo, Controller

controller, P primaryResource) { this.retryInfo = retryInfo; @@ -123,4 +128,12 @@ public DefaultContext

setRetryInfo(RetryInfo retryInfo) { this.retryInfo = retryInfo; return this; } + + @SuppressWarnings("unchecked") + public R desiredStateFor( + DependentResource dependentResource, Function desiredStateComputer) { + return (R) + desiredStates.computeIfAbsent( + dependentResource, ignored -> desiredStateComputer.apply(getPrimaryResource())); + } } 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 9471d52cc4..304c4757a1 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 @@ -8,6 +8,7 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.DefaultContext; import io.javaoperatorsdk.operator.api.reconciler.Ignore; import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; @@ -70,7 +71,7 @@ protected ReconcileResult reconcile(P primary, R actualResource, Context

c if (creatable() || updatable()) { if (actualResource == null) { if (creatable) { - var desired = desired(primary, context); + var desired = doDesired(context); throwIfNull(desired, primary, "Desired"); logForOperation("Creating", primary, desired); var createdResource = handleCreate(desired, primary, context); @@ -80,7 +81,7 @@ protected ReconcileResult reconcile(P primary, R actualResource, Context

c if (updatable()) { final Matcher.Result match = match(actualResource, primary, context); if (!match.matched()) { - final var desired = match.computedDesired().orElseGet(() -> desired(primary, context)); + final var desired = match.computedDesired().orElseGet(() -> doDesired(context)); throwIfNull(desired, primary, "Desired"); logForOperation("Updating", primary, desired); var updatedResource = handleUpdate(actualResource, desired, primary, context); @@ -112,7 +113,6 @@ protected ReconcileResult reconcile(P primary, R actualResource, Context

c @Override public Optional getSecondaryResource(P primary, Context

context) { - var secondaryResources = context.getSecondaryResources(resourceType()); if (secondaryResources.isEmpty()) { return Optional.empty(); @@ -136,7 +136,7 @@ public Optional getSecondaryResource(P primary, Context

context) { */ protected Optional selectTargetSecondaryResource( Set secondaryResources, P primary, Context

context) { - R desired = desired(primary, context); + R desired = doDesired(context); var targetResources = secondaryResources.stream().filter(r -> r.equals(desired)).toList(); if (targetResources.size() > 1) { throw new IllegalStateException( @@ -205,6 +205,12 @@ protected R desired(P primary, Context

context) { + " updated"); } + protected R doDesired(Context

context) { + assert context instanceof DefaultContext

; + DefaultContext

defaultContext = (DefaultContext

) context; + return defaultContext.desiredStateFor(this, p -> desired(p, defaultContext)); + } + public void delete(P primary, Context

context) { dependentResourceReconciler.delete(primary, context); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractExternalDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractExternalDependentResource.java index 4c828b7eb9..cee9706bf4 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractExternalDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractExternalDependentResource.java @@ -83,7 +83,7 @@ protected void handleExplicitStateCreation(P primary, R created, Context

cont @Override public Matcher.Result match(R resource, P primary, Context

context) { - var desired = desired(primary, context); + var desired = doDesired(context); return Matcher.Result.computed(resource.equals(desired), desired); } 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 ebb47fd355..f75905ceca 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 @@ -11,7 +11,6 @@ import io.javaoperatorsdk.operator.api.reconciler.Ignore; import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; -import io.javaoperatorsdk.operator.processing.dependent.Matcher.Result; class BulkDependentResourceReconciler implements DependentResourceReconciler { diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java index f96c28d60a..9e41dd4267 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java @@ -123,7 +123,7 @@ public static Matcher.Result m Context

context, boolean labelsAndAnnotationsEquality, String... ignorePaths) { - final var desired = dependentResource.desired(primary, context); + final var desired = dependentResource.doDesired(context); return match(desired, actualResource, labelsAndAnnotationsEquality, context, ignorePaths); } @@ -135,7 +135,7 @@ public static Matcher.Result m boolean specEquality, boolean labelsAndAnnotationsEquality, String... ignorePaths) { - final var desired = dependentResource.desired(primary, context); + final var desired = dependentResource.doDesired(context); return match( desired, actualResource, labelsAndAnnotationsEquality, specEquality, context, ignorePaths); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java index 69d145866d..b22a7fe68e 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java @@ -109,7 +109,7 @@ public R update(R actual, R desired, P primary, Context

context) { @Override public Result match(R actualResource, P primary, Context

context) { - final var desired = desired(primary, context); + final var desired = doDesired(context); return match(actualResource, desired, primary, context); } @@ -288,7 +288,7 @@ protected Optional selectTargetSecondaryResource( * @return id of the target managed resource */ protected ResourceID targetSecondaryResourceID(P primary, Context

context) { - return ResourceID.fromResource(desired(primary, context)); + return ResourceID.fromResource(doDesired(context)); } protected boolean addOwnerReference() { @@ -296,8 +296,8 @@ protected boolean addOwnerReference() { } @Override - protected R desired(P primary, Context

context) { - return super.desired(primary, context); + protected R doDesired(Context

context) { + return super.doDesired(context); } @Override diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResourceTest.java index 939d046e5d..e73a0ddc53 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResourceTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResourceTest.java @@ -7,6 +7,7 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.DefaultContext; import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; import static org.junit.jupiter.api.Assertions.*; @@ -15,6 +16,10 @@ class AbstractDependentResourceTest { + private static final TestCustomResource PRIMARY = new TestCustomResource(); + private static final DefaultContext CONTEXT = + new DefaultContext<>(mock(), mock(), PRIMARY); + @Test void throwsExceptionIfDesiredIsNullOnCreate() { TestDependentResource testDependentResource = new TestDependentResource(); @@ -22,8 +27,7 @@ void throwsExceptionIfDesiredIsNullOnCreate() { testDependentResource.setDesired(null); assertThrows( - DependentResourceException.class, - () -> testDependentResource.reconcile(new TestCustomResource(), null)); + DependentResourceException.class, () -> testDependentResource.reconcile(PRIMARY, CONTEXT)); } @Test @@ -33,8 +37,7 @@ void throwsExceptionIfDesiredIsNullOnUpdate() { testDependentResource.setDesired(null); assertThrows( - DependentResourceException.class, - () -> testDependentResource.reconcile(new TestCustomResource(), null)); + DependentResourceException.class, () -> testDependentResource.reconcile(PRIMARY, CONTEXT)); } @Test @@ -44,8 +47,7 @@ void throwsExceptionIfCreateReturnsNull() { testDependentResource.setDesired(configMap()); assertThrows( - DependentResourceException.class, - () -> testDependentResource.reconcile(new TestCustomResource(), null)); + DependentResourceException.class, () -> testDependentResource.reconcile(PRIMARY, CONTEXT)); } @Test @@ -55,8 +57,7 @@ void throwsExceptionIfUpdateReturnsNull() { testDependentResource.setDesired(configMap()); assertThrows( - DependentResourceException.class, - () -> testDependentResource.reconcile(new TestCustomResource(), null)); + DependentResourceException.class, () -> testDependentResource.reconcile(PRIMARY, CONTEXT)); } private ConfigMap configMap() { diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcherTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcherTest.java index 3062e360e2..daacf8aed7 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcherTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcherTest.java @@ -3,37 +3,48 @@ import java.util.Map; import java.util.Optional; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import io.fabric8.kubernetes.api.model.*; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder; import io.fabric8.kubernetes.api.model.apps.DeploymentStatusBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; import io.javaoperatorsdk.operator.MockKubernetesClient; import io.javaoperatorsdk.operator.ReconcilerUtils; import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.DefaultContext; import static io.javaoperatorsdk.operator.processing.dependent.kubernetes.GenericKubernetesResourceMatcher.match; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; @SuppressWarnings({"unchecked"}) class GenericKubernetesResourceMatcherTest { - private static final Context context = mock(Context.class); + private static final Context context = new TestContext(); + + private static class TestContext extends DefaultContext { + private final KubernetesClient client = MockKubernetesClient.client(HasMetadata.class); + + public TestContext() { + this(null); + } + + public TestContext(HasMetadata primary) { + super(mock(), mock(), primary); + } + + @Override + public KubernetesClient getClient() { + return client; + } + } Deployment actual = createDeployment(); Deployment desired = createDeployment(); TestDependentResource dependentResource = new TestDependentResource(desired); - @BeforeAll - static void setUp() { - final var client = MockKubernetesClient.client(HasMetadata.class); - when(context.getClient()).thenReturn(client); - } - @Test void matchesTrivialCases() { assertThat(GenericKubernetesResourceMatcher.match(desired, actual, context).matched()).isTrue(); @@ -62,9 +73,10 @@ void matchesWithStrongSpecEquality() { @Test void doesNotMatchRemovedValues() { actual = createDeployment(); + final var localContext = new TestContext(createPrimary("removed")); assertThat( GenericKubernetesResourceMatcher.match( - dependentResource.desired(createPrimary("removed"), null), actual, context) + dependentResource.doDesired(localContext), actual, localContext) .matched()) .withFailMessage("Removing values in metadata should lead to a mismatch") .isFalse();