From 7f53c3521c6803483bc0328a5d6b983ed3c586a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Tue, 13 Aug 2024 17:14:29 +0200 Subject: [PATCH 1/6] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros format Signed-off-by: Attila Mészáros docs Signed-off-by: Attila Mészáros feat: support to handle different cluster for InformerEventSource Signed-off-by: Attila Mészáros --- docs/content/en/docs/features/_index.md | 18 ++++++++++ .../InformerEventSourceConfiguration.java | 35 +++++++++++++++++-- .../source/informer/InformerEventSource.java | 4 ++- .../InformerRemoteClusterCustomResource.java | 14 ++++++++ .../InformerRemoteClusterIT.java | 23 ++++++++++++ .../InformerRemoteClusterReconciler.java | 32 +++++++++++++++++ .../InformerRemoteClusterStatus.java | 7 ++++ 7 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterCustomResource.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterIT.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterReconciler.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterStatus.java diff --git a/docs/content/en/docs/features/_index.md b/docs/content/en/docs/features/_index.md index 8877e55a41..79efbd5089 100644 --- a/docs/content/en/docs/features/_index.md +++ b/docs/content/en/docs/features/_index.md @@ -606,6 +606,24 @@ parts of reconciliation logic and during the execution of the controller: For more information about MDC see this [link](https://www.baeldung.com/mdc-in-log4j-2-logback). +## InformerEventSource Multi-Cluster Support + +It is possible to handle resources for remote cluster with `InformerEventSource`. To do so, +simply just set a client that connects to a remote cluster: + +```java + +InformerEventSourceConfiguration configuration = + InformerEventSourceConfiguration.from(SecondaryResource.class, PrimaryResource.class) + .withKubernetesClient(remoteClusterClient) + .withSecondaryToPrimaryMapper(Mappers.fromDefaultAnnotations()); + +``` + +Of course, you will need to specify a `SecondaryToPrimaryMapper`, since the default that +is based on owner references naturally won't work, rather use the one that supports annotations. + + ## Dynamically Changing Target Namespaces A controller can be configured to watch a specific set of namespaces in addition of the diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerEventSourceConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerEventSourceConfiguration.java index 20e6c7f131..379b9e9804 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerEventSourceConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerEventSourceConfiguration.java @@ -7,6 +7,11 @@ import io.fabric8.kubernetes.api.model.GenericKubernetesResource; import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.config.Informable; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; +import io.javaoperatorsdk.operator.api.config.DefaultResourceConfiguration; +import io.javaoperatorsdk.operator.api.config.ResourceConfiguration; +import io.javaoperatorsdk.operator.api.config.Utils; import io.javaoperatorsdk.operator.processing.GroupVersionKind; import io.javaoperatorsdk.operator.processing.event.source.PrimaryToSecondaryMapper; import io.javaoperatorsdk.operator.processing.event.source.SecondaryToPrimaryMapper; @@ -57,22 +62,31 @@ default String name() { return getInformerConfig().getName(); } + /** + * Use of a specific kubernetes client, typically a client connects to a different cluster. Note + * that this is solely for multi cluster support. + */ + KubernetesClient getKubernetesClient(); + class DefaultInformerEventSourceConfiguration implements InformerEventSourceConfiguration { private final PrimaryToSecondaryMapper primaryToSecondaryMapper; private final SecondaryToPrimaryMapper secondaryToPrimaryMapper; private final GroupVersionKind groupVersionKind; private final InformerConfiguration informerConfig; + private final KubernetesClient kubernetesClient; protected DefaultInformerEventSourceConfiguration( GroupVersionKind groupVersionKind, PrimaryToSecondaryMapper primaryToSecondaryMapper, SecondaryToPrimaryMapper secondaryToPrimaryMapper, - InformerConfiguration informerConfig) { + InformerConfiguration informerConfig, + KubernetesClient kubernetesClient) { this.informerConfig = Objects.requireNonNull(informerConfig); this.groupVersionKind = groupVersionKind; this.primaryToSecondaryMapper = primaryToSecondaryMapper; this.secondaryToPrimaryMapper = secondaryToPrimaryMapper; + this.kubernetesClient = kubernetesClient; } @Override @@ -95,8 +109,12 @@ public

PrimaryToSecondaryMapper

getPrimaryToSecondary public Optional getGroupVersionKind() { return Optional.ofNullable(groupVersionKind); } - } + @Override + public KubernetesClient getKubernetesClient() { + return kubernetesClient; + } + } @SuppressWarnings({"unused", "UnusedReturnValue"}) class Builder { @@ -108,6 +126,7 @@ class Builder { private String name; private PrimaryToSecondaryMapper primaryToSecondaryMapper; private SecondaryToPrimaryMapper secondaryToPrimaryMapper; + private KubernetesClient kubernetesClient; private Builder(Class resourceClass, Class primaryResourceClass) { @@ -152,6 +171,16 @@ public Builder withSecondaryToPrimaryMapper( return this; } + /** + * Use this is case want to create an InformerEventSource that handles resources from different + * cluster. + */ + public Builder withKubernetesClient( + KubernetesClient kubernetesClient) { + this.kubernetesClient = kubernetesClient; + return this; + } + public String getName() { return name; } @@ -192,7 +221,7 @@ public InformerEventSourceConfiguration build() { Objects.requireNonNullElse(secondaryToPrimaryMapper, Mappers.fromOwnerReferences(HasMetadata.getApiVersion(primaryResourceClass), HasMetadata.getKind(primaryResourceClass), false)), - config.buildForInformerEventSource()); + config.buildForInformerEventSource(), kubernetesClient); } } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSource.java index 2568683600..cb1be7a6cd 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSource.java @@ -78,7 +78,9 @@ public class InformerEventSource public InformerEventSource( InformerEventSourceConfiguration configuration, EventSourceContext

context) { - this(configuration, context.getClient(), + this(configuration, + configuration.getKubernetesClient() != null ? configuration.getKubernetesClient() + : context.getClient(), context.getControllerConfiguration().getConfigurationService() .parseResourceVersionsForEventFilteringAndCaching()); } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterCustomResource.java new file mode 100644 index 0000000000..c7e6ee43b4 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterCustomResource.java @@ -0,0 +1,14 @@ +package io.javaoperatorsdk.operator.baseapi.informerremotecluster; + +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("irc") +public class InformerRemoteClusterCustomResource + extends CustomResource implements Namespaced { +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterIT.java new file mode 100644 index 0000000000..0d4ff03c7b --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterIT.java @@ -0,0 +1,23 @@ +package io.javaoperatorsdk.operator.baseapi.informerremotecluster; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.javaoperatorsdk.jenvtest.junit.EnableKubeAPIServer; +import io.javaoperatorsdk.operator.baseapi.labelselector.LabelSelectorTestReconciler; +import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; + +@EnableKubeAPIServer(apiServerFlags = {"--min-request-timeout", "1"}) +public class InformerRemoteClusterIT { + + @RegisterExtension + LocallyRunOperatorExtension extension = + LocallyRunOperatorExtension.builder().withReconciler(new LabelSelectorTestReconciler()) + .build(); + + @Test + void testRemoteClusterInformer() { + + } + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterReconciler.java new file mode 100644 index 0000000000..40d4b6936d --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterReconciler.java @@ -0,0 +1,32 @@ +package io.javaoperatorsdk.operator.baseapi.informerremotecluster; + +import java.util.List; + +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; + +@ControllerConfiguration +public class InformerRemoteClusterReconciler + implements Reconciler { + + + @Override + public UpdateControl reconcile( + InformerRemoteClusterCustomResource resource, + Context context) throws Exception { + + + + return UpdateControl.noUpdate(); + } + + @Override + public List> prepareEventSources( + EventSourceContext context) { + return Reconciler.super.prepareEventSources(context); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterStatus.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterStatus.java new file mode 100644 index 0000000000..b51b565f85 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterStatus.java @@ -0,0 +1,7 @@ +package io.javaoperatorsdk.operator.baseapi.informerremotecluster; + +public class InformerRemoteClusterStatus { + + + +} From 373a085a33644f9b1a309ceef4d6f199d84f8583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Thu, 15 Aug 2024 15:24:50 +0200 Subject: [PATCH 2/6] integration test 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/features/_index.md | 1 + .../InformerRemoteClusterIT.java | 74 +++++++++++++++++-- .../InformerRemoteClusterReconciler.java | 41 +++++++++- .../InformerRemoteClusterStatus.java | 7 ++ .../LabelSelectorTestReconciler.java | 1 + 5 files changed, 115 insertions(+), 9 deletions(-) diff --git a/docs/content/en/docs/features/_index.md b/docs/content/en/docs/features/_index.md index 79efbd5089..1914d1eb64 100644 --- a/docs/content/en/docs/features/_index.md +++ b/docs/content/en/docs/features/_index.md @@ -623,6 +623,7 @@ InformerEventSourceConfiguration configuration = Of course, you will need to specify a `SecondaryToPrimaryMapper`, since the default that is based on owner references naturally won't work, rather use the one that supports annotations. +See related [integration test](https://github.com/operator-framework/java-operator-sdk/tree/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster). ## Dynamically Changing Target Namespaces diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterIT.java index 0d4ff03c7b..9cb65b8528 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterIT.java @@ -1,23 +1,87 @@ package io.javaoperatorsdk.operator.baseapi.informerremotecluster; +import java.util.Map; + import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import io.javaoperatorsdk.jenvtest.junit.EnableKubeAPIServer; -import io.javaoperatorsdk.operator.baseapi.labelselector.LabelSelectorTestReconciler; +import io.fabric8.kubeapitest.junit.EnableKubeAPIServer; +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; +import io.javaoperatorsdk.operator.processing.event.source.informer.Mappers; + +import static io.javaoperatorsdk.operator.baseapi.informerremotecluster.InformerRemoteClusterReconciler.DATA_KEY; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +@EnableKubeAPIServer +class InformerRemoteClusterIT { -@EnableKubeAPIServer(apiServerFlags = {"--min-request-timeout", "1"}) -public class InformerRemoteClusterIT { + public static final String NAME = "test1"; + public static final String CONFIG_MAP_NAME = "testcm"; + public static final String INITIAL_VALUE = "initial_value"; + public static final String CHANGED_VALUE = "changed_value"; + public static final String CM_NAMESPACE = "default"; + + static KubernetesClient kubernetesClient; @RegisterExtension LocallyRunOperatorExtension extension = - LocallyRunOperatorExtension.builder().withReconciler(new LabelSelectorTestReconciler()) + LocallyRunOperatorExtension.builder() + .withReconciler(new InformerRemoteClusterReconciler(kubernetesClient)) .build(); @Test void testRemoteClusterInformer() { + var r = extension.create(testCustomResource()); + + var cm = kubernetesClient.configMaps() + .resource(remoteConfigMap(r.getMetadata().getName(), + r.getMetadata().getNamespace())) + .create(); + + // config map not exists on the primary resource cluster + assertThat(extension.getKubernetesClient().configMaps() + .inNamespace(CM_NAMESPACE) + .withName(CONFIG_MAP_NAME).get()).isNull(); + + await().untilAsserted(() -> { + var cr = extension.get(InformerRemoteClusterCustomResource.class, NAME); + assertThat(cr.getStatus()).isNotNull(); + assertThat(cr.getStatus().getRemoteConfigMapMessage()).isEqualTo(INITIAL_VALUE); + }); + + cm.getData().put(DATA_KEY, CHANGED_VALUE); + kubernetesClient.configMaps().resource(cm).update(); + + await().untilAsserted(() -> { + var cr = extension.get(InformerRemoteClusterCustomResource.class, NAME); + assertThat(cr.getStatus().getRemoteConfigMapMessage()).isEqualTo(CHANGED_VALUE); + }); + } + + InformerRemoteClusterCustomResource testCustomResource() { + var res = new InformerRemoteClusterCustomResource(); + res.setMetadata(new ObjectMetaBuilder() + .withName(NAME) + .build()); + return res; + } + ConfigMap remoteConfigMap(String ownerName, String ownerNamespace) { + return new ConfigMapBuilder() + .withMetadata(new ObjectMetaBuilder() + .withName(CONFIG_MAP_NAME) + .withNamespace(CM_NAMESPACE) + .withAnnotations(Map.of( + Mappers.DEFAULT_ANNOTATION_FOR_NAME, ownerName, + Mappers.DEFAULT_ANNOTATION_FOR_NAMESPACE, ownerNamespace)) + .build()) + .withData(Map.of(DATA_KEY, INITIAL_VALUE)) + .build(); } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterReconciler.java index 40d4b6936d..e202927bb2 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterReconciler.java @@ -2,31 +2,64 @@ import java.util.List; +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; +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 InformerRemoteClusterReconciler implements Reconciler { + public static final String DATA_KEY = "key"; + + private final KubernetesClient remoteClient; + + public InformerRemoteClusterReconciler(KubernetesClient remoteClient) { + this.remoteClient = remoteClient; + } @Override public UpdateControl reconcile( InformerRemoteClusterCustomResource resource, Context context) throws Exception { - - - return UpdateControl.noUpdate(); + return context.getSecondaryResource(ConfigMap.class).map(cm -> { + var r = new InformerRemoteClusterCustomResource(); + r.setMetadata(new ObjectMetaBuilder() + .withName(resource.getMetadata().getName()) + .withNamespace(resource.getMetadata().getNamespace()) + .build()); + r.setStatus(new InformerRemoteClusterStatus()); + r.getStatus().setRemoteConfigMapMessage(cm.getData().get(DATA_KEY)); + return UpdateControl.patchStatus(r); + }).orElseGet(UpdateControl::noUpdate); } @Override public List> prepareEventSources( EventSourceContext context) { - return Reconciler.super.prepareEventSources(context); + + var es = new InformerEventSource<>(InformerEventSourceConfiguration + .from(ConfigMap.class, InformerRemoteClusterCustomResource.class) + // owner references naturally not work cross cluster, using + // annotations here to reference primary resource + .withSecondaryToPrimaryMapper(Mappers.fromDefaultAnnotations()) + // setting remote client for informer + .withKubernetesClient(remoteClient) + .withInformerConfiguration( + InformerConfiguration.Builder::withWatchAllNamespaces) + .build(), context); + + return List.of(es); } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterStatus.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterStatus.java index b51b565f85..e49322fb10 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterStatus.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterStatus.java @@ -2,6 +2,13 @@ public class InformerRemoteClusterStatus { + private String remoteConfigMapMessage; + public String getRemoteConfigMapMessage() { + return remoteConfigMapMessage; + } + public void setRemoteConfigMapMessage(String remoteConfigMapMessage) { + this.remoteConfigMapMessage = remoteConfigMapMessage; + } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/labelselector/LabelSelectorTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/labelselector/LabelSelectorTestReconciler.java index 771effc3a1..a225f6a7be 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/labelselector/LabelSelectorTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/labelselector/LabelSelectorTestReconciler.java @@ -22,6 +22,7 @@ public class LabelSelectorTestReconciler @Override public UpdateControl reconcile( LabelSelectorTestCustomResource resource, Context context) { + numberOfExecutions.addAndGet(1); return UpdateControl.noUpdate(); } From 571802fc04180fee8340ebeb476827f366df02c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Tue, 20 Aug 2024 12:30:50 +0200 Subject: [PATCH 3/6] rebase and related fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../InformerEventSourceConfiguration.java | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerEventSourceConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerEventSourceConfiguration.java index 379b9e9804..a94f959d2b 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerEventSourceConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerEventSourceConfiguration.java @@ -6,12 +6,8 @@ import io.fabric8.kubernetes.api.model.GenericKubernetesResource; import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.api.config.Informable; import io.fabric8.kubernetes.client.KubernetesClient; -import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; -import io.javaoperatorsdk.operator.api.config.DefaultResourceConfiguration; -import io.javaoperatorsdk.operator.api.config.ResourceConfiguration; -import io.javaoperatorsdk.operator.api.config.Utils; +import io.javaoperatorsdk.operator.api.config.Informable; import io.javaoperatorsdk.operator.processing.GroupVersionKind; import io.javaoperatorsdk.operator.processing.event.source.PrimaryToSecondaryMapper; import io.javaoperatorsdk.operator.processing.event.source.SecondaryToPrimaryMapper; @@ -62,11 +58,11 @@ default String name() { return getInformerConfig().getName(); } - /** - * Use of a specific kubernetes client, typically a client connects to a different cluster. Note - * that this is solely for multi cluster support. - */ - KubernetesClient getKubernetesClient(); + /** + * Use of a specific kubernetes client, typically a client connects to a different cluster. Note + * that this is solely for multi cluster support. + */ + KubernetesClient getKubernetesClient(); class DefaultInformerEventSourceConfiguration implements InformerEventSourceConfiguration { @@ -110,10 +106,10 @@ public Optional getGroupVersionKind() { return Optional.ofNullable(groupVersionKind); } - @Override - public KubernetesClient getKubernetesClient() { - return kubernetesClient; - } + @Override + public KubernetesClient getKubernetesClient() { + return kubernetesClient; + } } @SuppressWarnings({"unused", "UnusedReturnValue"}) From 8baddaefd60bf07331fffb675d00e134441afe5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Tue, 20 Aug 2024 12:34:00 +0200 Subject: [PATCH 4/6] comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../baseapi/informerremotecluster/InformerRemoteClusterIT.java | 1 + 1 file changed, 1 insertion(+) diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterIT.java index 9cb65b8528..5194562d35 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterIT.java @@ -26,6 +26,7 @@ class InformerRemoteClusterIT { public static final String CHANGED_VALUE = "changed_value"; public static final String CM_NAMESPACE = "default"; + // injected by Kube API Test. Client for another cluster. static KubernetesClient kubernetesClient; @RegisterExtension From 621e2fcd257ddbb9c922651c38a6606d7fe8a424 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Tue, 20 Aug 2024 13:00:28 +0200 Subject: [PATCH 5/6] refactor: make kube client optional and provide default implementation Signed-off-by: Chris Laprun --- .../InformerEventSourceConfiguration.java | 12 +++++++----- .../source/informer/InformerEventSource.java | 19 +++++++++---------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerEventSourceConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerEventSourceConfiguration.java index a94f959d2b..9ad6dded4c 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerEventSourceConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerEventSourceConfiguration.java @@ -59,10 +59,12 @@ default String name() { } /** - * Use of a specific kubernetes client, typically a client connects to a different cluster. Note - * that this is solely for multi cluster support. + * Optional, specific kubernetes client, typically to connect to a different cluster than the rest + * of the operator. Note that this is solely for multi cluster support. */ - KubernetesClient getKubernetesClient(); + default Optional getKubernetesClient() { + return Optional.empty(); + } class DefaultInformerEventSourceConfiguration implements InformerEventSourceConfiguration { @@ -107,8 +109,8 @@ public Optional getGroupVersionKind() { } @Override - public KubernetesClient getKubernetesClient() { - return kubernetesClient; + public Optional getKubernetesClient() { + return Optional.ofNullable(kubernetesClient); } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSource.java index cb1be7a6cd..48534a27b3 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSource.java @@ -1,6 +1,8 @@ package io.javaoperatorsdk.operator.processing.event.source.informer; -import java.util.*; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; import java.util.stream.Collectors; import org.slf4j.Logger; @@ -67,10 +69,8 @@ public class InformerEventSource extends ManagedInformerEventSource> implements ResourceEventHandler { - public static String PREVIOUS_ANNOTATION_KEY = "javaoperatorsdk.io/previous"; - private static final Logger log = LoggerFactory.getLogger(InformerEventSource.class); - + public static final String PREVIOUS_ANNOTATION_KEY = "javaoperatorsdk.io/previous"; // we need direct control for the indexer to propagate the just update resource also to the index private final PrimaryToSecondaryIndex primaryToSecondaryIndex; private final PrimaryToSecondaryMapper

primaryToSecondaryMapper; @@ -79,8 +79,7 @@ public class InformerEventSource public InformerEventSource( InformerEventSourceConfiguration configuration, EventSourceContext

context) { this(configuration, - configuration.getKubernetesClient() != null ? configuration.getKubernetesClient() - : context.getClient(), + configuration.getKubernetesClient().orElse(context.getClient()), context.getControllerConfiguration().getConfigurationService() .parseResourceVersionsForEventFilteringAndCaching()); } @@ -289,10 +288,6 @@ private boolean eventAcceptedByFilter(Operation operation, R newObject, R oldObj } } - private enum Operation { - ADD, UPDATE - } - private boolean acceptedByDeleteFilters(R resource, boolean b) { return (onDeleteFilter == null || onDeleteFilter.accept(resource, b)) && (genericFilter == null || genericFilter.accept(resource)); @@ -309,4 +304,8 @@ public R addPreviousAnnotation(String resourceVersion, R target) { id + Optional.ofNullable(resourceVersion).map(rv -> "," + rv).orElse("")); return target; } + + private enum Operation { + ADD, UPDATE + } } From cd53861b93c6c4ac87c39f4c0d2531cf1ee39029 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Tue, 20 Aug 2024 13:00:44 +0200 Subject: [PATCH 6/6] chore(docs): minor improvements Signed-off-by: Chris Laprun --- docs/content/en/docs/features/_index.md | 6 +++--- .../informerremotecluster/InformerRemoteClusterIT.java | 2 +- .../InformerRemoteClusterReconciler.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/content/en/docs/features/_index.md b/docs/content/en/docs/features/_index.md index 1914d1eb64..c38fb66aa8 100644 --- a/docs/content/en/docs/features/_index.md +++ b/docs/content/en/docs/features/_index.md @@ -609,7 +609,7 @@ For more information about MDC see this [link](https://www.baeldung.com/mdc-in-l ## InformerEventSource Multi-Cluster Support It is possible to handle resources for remote cluster with `InformerEventSource`. To do so, -simply just set a client that connects to a remote cluster: +simply set a client that connects to a remote cluster: ```java @@ -620,8 +620,8 @@ InformerEventSourceConfiguration configuration = ``` -Of course, you will need to specify a `SecondaryToPrimaryMapper`, since the default that -is based on owner references naturally won't work, rather use the one that supports annotations. +You will also need to specify a `SecondaryToPrimaryMapper`, since the default one +is based on owner references and won't work across cluster instances. You could, for example, use the provided implementation that relies on annotations added to the secondary resources to identify the associated primary resource. See related [integration test](https://github.com/operator-framework/java-operator-sdk/tree/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster). diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterIT.java index 5194562d35..3ad274d954 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterIT.java @@ -44,7 +44,7 @@ void testRemoteClusterInformer() { r.getMetadata().getNamespace())) .create(); - // config map not exists on the primary resource cluster + // config map does not exist on the primary resource cluster assertThat(extension.getKubernetesClient().configMaps() .inNamespace(CM_NAMESPACE) .withName(CONFIG_MAP_NAME).get()).isNull(); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterReconciler.java index e202927bb2..44d81f5327 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterReconciler.java @@ -51,7 +51,7 @@ public List> prepareEventSou var es = new InformerEventSource<>(InformerEventSourceConfiguration .from(ConfigMap.class, InformerRemoteClusterCustomResource.class) - // owner references naturally not work cross cluster, using + // owner references do not work cross cluster, using // annotations here to reference primary resource .withSecondaryToPrimaryMapper(Mappers.fromDefaultAnnotations()) // setting remote client for informer