From 362d1d235e35ba7143c9ded33a1fb84078614a19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 21 Jun 2024 17:33:01 +0200 Subject: [PATCH 1/2] feat: integration test to show mulitple dependents with activation condition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../MultipleDependentWithActivationIT.java | 60 +++++++++++++++++++ .../ActivationCondition.java | 20 +++++++ .../ConfigMapDependentResource1.java | 35 +++++++++++ .../ConfigMapDependentResource2.java | 35 +++++++++++ ...ipleDependentActivationCustomResource.java | 17 ++++++ ...MultipleDependentActivationReconciler.java | 34 +++++++++++ .../MultipleDependentActivationSpec.java | 14 +++++ .../SecretDependentResource.java | 31 ++++++++++ 8 files changed, 246 insertions(+) create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleDependentWithActivationIT.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/ActivationCondition.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/ConfigMapDependentResource1.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/ConfigMapDependentResource2.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/MultipleDependentActivationCustomResource.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/MultipleDependentActivationReconciler.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/MultipleDependentActivationSpec.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/SecretDependentResource.java diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleDependentWithActivationIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleDependentWithActivationIT.java new file mode 100644 index 0000000000..a7db843e67 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleDependentWithActivationIT.java @@ -0,0 +1,60 @@ +package io.javaoperatorsdk.operator; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.api.model.Secret; +import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; +import io.javaoperatorsdk.operator.sample.multipledependentwithactivation.*; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +public class MultipleDependentWithActivationIT { + + public static final String INITIAL_VALUE = "initial_value"; + public static final String TEST_RESOURCE_NAME = "test1"; + + @RegisterExtension + LocallyRunOperatorExtension extension = + LocallyRunOperatorExtension.builder() + .withReconciler(new MultipleDependentActivationReconciler()) + .build(); + + @Test + void bothDependentsWithActivationAreHandled() { + var resource = extension.create(testResource()); + + await().untilAsserted(() -> { + var cm1 = + extension.get(ConfigMap.class, TEST_RESOURCE_NAME + ConfigMapDependentResource1.SUFFIX); + var cm2 = + extension.get(ConfigMap.class, TEST_RESOURCE_NAME + ConfigMapDependentResource2.SUFFIX); + var secret = extension.get(Secret.class, TEST_RESOURCE_NAME); + + assertThat(secret).isNotNull(); + assertThat(cm1).isNotNull(); + assertThat(cm2).isNotNull(); + assertThat(cm1.getData()).containsEntry(ConfigMapDependentResource1.DATA_KEY, + INITIAL_VALUE + ConfigMapDependentResource1.SUFFIX); + assertThat(cm2.getData()).containsEntry(ConfigMapDependentResource2.DATA_KEY, + INITIAL_VALUE + ConfigMapDependentResource2.SUFFIX); + }); + + } + + MultipleDependentActivationCustomResource testResource() { + var res = new MultipleDependentActivationCustomResource(); + res.setMetadata(new ObjectMetaBuilder() + .withName(TEST_RESOURCE_NAME) + .build()); + res.setSpec(new MultipleDependentActivationSpec()); + res.getSpec().setValue(INITIAL_VALUE); + + return res; + } + + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/ActivationCondition.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/ActivationCondition.java new file mode 100644 index 0000000000..a1d1b7bdbe --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/ActivationCondition.java @@ -0,0 +1,20 @@ +package io.javaoperatorsdk.operator.sample.multipledependentwithactivation; + +import io.fabric8.openshift.api.model.Route; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition; + +public class ActivationCondition + implements Condition { + + public static volatile boolean MET = true; + + @Override + public boolean isMet( + DependentResource dependentResource, + MultipleDependentActivationCustomResource primary, + Context context) { + return MET; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/ConfigMapDependentResource1.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/ConfigMapDependentResource1.java new file mode 100644 index 0000000000..e9b53898b8 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/ConfigMapDependentResource1.java @@ -0,0 +1,35 @@ +package io.javaoperatorsdk.operator.sample.multipledependentwithactivation; + +import java.util.Map; + +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.processing.dependent.kubernetes.CRUDNoGCKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.InformerConfig; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; + +@KubernetesDependent(informerConfig = @InformerConfig(name = "configMapInformer")) +public class ConfigMapDependentResource1 + extends + CRUDNoGCKubernetesDependentResource { + + public static final String DATA_KEY = "data"; + public static final String SUFFIX = "1"; + + public ConfigMapDependentResource1() { + super(ConfigMap.class); + } + + @Override + protected ConfigMap desired(MultipleDependentActivationCustomResource primary, + Context context) { + ConfigMap configMap = new ConfigMap(); + configMap.setMetadata(new ObjectMetaBuilder() + .withName(primary.getMetadata().getName() + SUFFIX) + .withNamespace(primary.getMetadata().getNamespace()) + .build()); + configMap.setData(Map.of(DATA_KEY, primary.getSpec().getValue() + SUFFIX)); + return configMap; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/ConfigMapDependentResource2.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/ConfigMapDependentResource2.java new file mode 100644 index 0000000000..c88929a61f --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/ConfigMapDependentResource2.java @@ -0,0 +1,35 @@ +package io.javaoperatorsdk.operator.sample.multipledependentwithactivation; + +import java.util.Map; + +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.processing.dependent.kubernetes.CRUDNoGCKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.InformerConfig; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; + +@KubernetesDependent(informerConfig = @InformerConfig(name = "configMapInformer")) +public class ConfigMapDependentResource2 + extends + CRUDNoGCKubernetesDependentResource { + + public static final String DATA_KEY = "data"; + public static final String SUFFIX = "2"; + + public ConfigMapDependentResource2() { + super(ConfigMap.class); + } + + @Override + protected ConfigMap desired(MultipleDependentActivationCustomResource primary, + Context context) { + ConfigMap configMap = new ConfigMap(); + configMap.setMetadata(new ObjectMetaBuilder() + .withName(primary.getMetadata().getName() + SUFFIX) + .withNamespace(primary.getMetadata().getNamespace()) + .build()); + configMap.setData(Map.of(DATA_KEY, primary.getSpec().getValue() + SUFFIX)); + return configMap; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/MultipleDependentActivationCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/MultipleDependentActivationCustomResource.java new file mode 100644 index 0000000000..e373a7bc01 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/MultipleDependentActivationCustomResource.java @@ -0,0 +1,17 @@ +package io.javaoperatorsdk.operator.sample.multipledependentwithactivation; + +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.ShortNames; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("sample.javaoperatorsdk") +@Version("v1") +@ShortNames("mdar") +public class MultipleDependentActivationCustomResource + extends CustomResource + implements Namespaced { + + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/MultipleDependentActivationReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/MultipleDependentActivationReconciler.java new file mode 100644 index 0000000000..5a4961c6c6 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/MultipleDependentActivationReconciler.java @@ -0,0 +1,34 @@ +package io.javaoperatorsdk.operator.sample.multipledependentwithactivation; + +import java.util.concurrent.atomic.AtomicInteger; + +import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; + +@Workflow(dependents = { + @Dependent(type = ConfigMapDependentResource1.class, + activationCondition = ActivationCondition.class), + @Dependent(type = ConfigMapDependentResource2.class, + activationCondition = ActivationCondition.class), + @Dependent(type = SecretDependentResource.class) +}) +@ControllerConfiguration +public class MultipleDependentActivationReconciler + implements Reconciler { + + private final AtomicInteger numberOfReconciliationExecution = new AtomicInteger(0); + + @Override + public UpdateControl reconcile( + MultipleDependentActivationCustomResource resource, + Context context) { + + numberOfReconciliationExecution.incrementAndGet(); + + return UpdateControl.noUpdate(); + } + + public int getNumberOfReconciliationExecution() { + return numberOfReconciliationExecution.get(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/MultipleDependentActivationSpec.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/MultipleDependentActivationSpec.java new file mode 100644 index 0000000000..93bf4b18f3 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/MultipleDependentActivationSpec.java @@ -0,0 +1,14 @@ +package io.javaoperatorsdk.operator.sample.multipledependentwithactivation; + +public class MultipleDependentActivationSpec { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/SecretDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/SecretDependentResource.java new file mode 100644 index 0000000000..821f5482dc --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/SecretDependentResource.java @@ -0,0 +1,31 @@ +package io.javaoperatorsdk.operator.sample.multipledependentwithactivation; + +import java.util.Base64; +import java.util.Map; + +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.api.model.Secret; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; + +public class SecretDependentResource + extends CRUDKubernetesDependentResource { + + public SecretDependentResource() { + super(Secret.class); + } + + @Override + protected Secret desired(MultipleDependentActivationCustomResource primary, + Context context) { + // basically does not matter since this should not be called + Secret secret = new Secret(); + secret.setMetadata(new ObjectMetaBuilder() + .withName(primary.getMetadata().getName()) + .withNamespace(primary.getMetadata().getNamespace()) + .build()); + secret.setData(Map.of("data", + Base64.getEncoder().encodeToString(primary.getSpec().getValue().getBytes()))); + return secret; + } +} From 5085bc208284787d8e773c58383b4a3af17e646f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 28 Jun 2024 15:23:45 +0200 Subject: [PATCH 2/2] docs and test fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- docs/content/en/docs/workflows/_index.md | 8 +++++--- .../MultipleDependentWithActivationIT.java | 20 +++++++++++++++++-- .../ActivationCondition.java | 2 +- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/docs/content/en/docs/workflows/_index.md b/docs/content/en/docs/workflows/_index.md index a48eabb5ec..2f7a156991 100644 --- a/docs/content/en/docs/workflows/_index.md +++ b/docs/content/en/docs/workflows/_index.md @@ -48,9 +48,11 @@ reconciliation process. with the dependent's resource type is not present on the cluster. See related [integration test](https://github.com/operator-framework/java-operator-sdk/blob/ba5e33527bf9e3ea0bd33025ccb35e677f9d44b4/operator-framework/src/test/java/io/javaoperatorsdk/operator/CRDPresentActivationConditionIT.java). - Activation condition is semi-experimental at the moment, and it has its limitations. - For example event sources cannot be shared between multiple managed dependent resources which use activation condition. - The intention is to further improve and explore the possibilities with this approach. + To have multiple resources of same type with an activation condition is a bit tricky, since you + don't want to have multiple `InformerEvetnSource` for the same type, you have to explicitly + name the informer for the Dependent Resource (`@KubernetesDependent(informerConfig = @InformerConfig(name = "configMapInformer"))`) + for all resource of same type with activation condition. This will make sure that only one is registered. + See details at [low level api](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceRetriever.java#L20-L52). ## Defining Workflows diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleDependentWithActivationIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleDependentWithActivationIT.java index a7db843e67..e5d1542b19 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleDependentWithActivationIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleDependentWithActivationIT.java @@ -15,6 +15,7 @@ public class MultipleDependentWithActivationIT { public static final String INITIAL_VALUE = "initial_value"; + public static final String CHANGED_VALUE = "changed_value"; public static final String TEST_RESOURCE_NAME = "test1"; @RegisterExtension @@ -27,6 +28,21 @@ public class MultipleDependentWithActivationIT { void bothDependentsWithActivationAreHandled() { var resource = extension.create(testResource()); + await().untilAsserted(() -> { + var cm1 = + extension.get(ConfigMap.class, TEST_RESOURCE_NAME + ConfigMapDependentResource1.SUFFIX); + var cm2 = + extension.get(ConfigMap.class, TEST_RESOURCE_NAME + ConfigMapDependentResource2.SUFFIX); + var secret = extension.get(Secret.class, TEST_RESOURCE_NAME); + assertThat(secret).isNotNull(); + assertThat(cm1).isNull(); + assertThat(cm2).isNull(); + }); + + ActivationCondition.MET = true; + resource.getSpec().setValue(CHANGED_VALUE); + extension.replace(resource); + await().untilAsserted(() -> { var cm1 = extension.get(ConfigMap.class, TEST_RESOURCE_NAME + ConfigMapDependentResource1.SUFFIX); @@ -38,9 +54,9 @@ void bothDependentsWithActivationAreHandled() { assertThat(cm1).isNotNull(); assertThat(cm2).isNotNull(); assertThat(cm1.getData()).containsEntry(ConfigMapDependentResource1.DATA_KEY, - INITIAL_VALUE + ConfigMapDependentResource1.SUFFIX); + CHANGED_VALUE + ConfigMapDependentResource1.SUFFIX); assertThat(cm2.getData()).containsEntry(ConfigMapDependentResource2.DATA_KEY, - INITIAL_VALUE + ConfigMapDependentResource2.SUFFIX); + CHANGED_VALUE + ConfigMapDependentResource2.SUFFIX); }); } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/ActivationCondition.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/ActivationCondition.java index a1d1b7bdbe..5e357351f5 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/ActivationCondition.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentwithactivation/ActivationCondition.java @@ -8,7 +8,7 @@ public class ActivationCondition implements Condition { - public static volatile boolean MET = true; + public static volatile boolean MET = false; @Override public boolean isMet(