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 1e5e1ce666..b31888b846 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,7 @@ import io.fabric8.kubernetes.api.model.GenericKubernetesResource; 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.config.DefaultResourceConfiguration; import io.javaoperatorsdk.operator.api.config.ResourceConfiguration; @@ -74,6 +75,12 @@ 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(); + @SuppressWarnings("unchecked") @Override default Class getResourceClass() { @@ -86,17 +93,20 @@ class DefaultInformerEventSourceConfiguration extends private final PrimaryToSecondaryMapper primaryToSecondaryMapper; private final SecondaryToPrimaryMapper secondaryToPrimaryMapper; private final GroupVersionKind groupVersionKind; + private final KubernetesClient kubernetesClient; protected DefaultInformerEventSourceConfiguration( Class resourceClass, GroupVersionKind groupVersionKind, PrimaryToSecondaryMapper primaryToSecondaryMapper, SecondaryToPrimaryMapper secondaryToPrimaryMapper, - InformerConfiguration informerConfig) { + InformerConfiguration informerConfig, + KubernetesClient kubernetesClient) { super(resourceClass, informerConfig); this.groupVersionKind = groupVersionKind; this.primaryToSecondaryMapper = primaryToSecondaryMapper; this.secondaryToPrimaryMapper = secondaryToPrimaryMapper; + this.kubernetesClient = kubernetesClient; } @Override @@ -120,6 +130,11 @@ public Optional getGroupVersionKind() { return Optional.ofNullable(groupVersionKind); } + @Override + public KubernetesClient getKubernetesClient() { + return kubernetesClient; + } + public boolean inheritsNamespacesFromController() { return InformerEventSourceConfiguration.inheritsNamespacesFromController(getNamespaces()); } @@ -145,6 +160,7 @@ class Builder { private String name; private PrimaryToSecondaryMapper primaryToSecondaryMapper; private SecondaryToPrimaryMapper secondaryToPrimaryMapper; + private KubernetesClient kubernetesClient; private Builder(Class resourceClass, Class primaryResourceClass) { @@ -189,6 +205,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; } @@ -229,7 +255,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 db3b9d6601..40f734fda5 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 { + + + +}