metadata,
String... additionalTags) {
final var additionalTagsNb =
diff --git a/operator-framework-bom/pom.xml b/operator-framework-bom/pom.xml
index 6b22c02bbb..7c435928ae 100644
--- a/operator-framework-bom/pom.xml
+++ b/operator-framework-bom/pom.xml
@@ -5,7 +5,7 @@
io.javaoperatorsdk
operator-framework-bom
- 4.1.3-SNAPSHOT
+ 4.2.0-SNAPSHOT
Operator SDK - Bill of Materials
pom
Java SDK for implementing Kubernetes operators
diff --git a/operator-framework-core/pom.xml b/operator-framework-core/pom.xml
index 65de6f8323..e885984058 100644
--- a/operator-framework-core/pom.xml
+++ b/operator-framework-core/pom.xml
@@ -6,7 +6,7 @@
io.javaoperatorsdk
java-operator-sdk
- 4.1.3-SNAPSHOT
+ 4.2.0-SNAPSHOT
../pom.xml
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java
index 37a1a501d0..60808c899e 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java
@@ -72,6 +72,7 @@ public Operator(KubernetesClient kubernetesClient, ConfigurationService configur
}
/** Adds a shutdown hook that automatically calls {@link #stop()} when the app shuts down. */
+ @Deprecated(forRemoval = true)
public void installShutdownHook() {
if (!leaderElectionManager.isLeaderElectionEnabled()) {
Runtime.getRuntime().addShutdownHook(new Thread(this::stop));
@@ -89,12 +90,11 @@ public KubernetesClient getKubernetesClient() {
* where there is no obvious entrypoint to the application which can trigger the injection process
* and start the cluster monitoring processes.
*/
- public void start() {
+ public synchronized void start() {
try {
if (started) {
return;
}
- started = true;
controllerManager.shouldStart();
final var version = ConfigurationServiceProvider.instance().getVersion();
log.info(
@@ -110,6 +110,7 @@ public void start() {
// the leader election would start subsequently the processor if on
controllerManager.start(!leaderElectionManager.isLeaderElectionEnabled());
leaderElectionManager.start();
+ started = true;
} catch (Exception e) {
log.error("Error starting operator", e);
stop();
@@ -216,4 +217,11 @@ public int getRegisteredControllersNumber() {
return controllerManager.size();
}
+ public RuntimeInfo getRuntimeInfo() {
+ return new RuntimeInfo(this);
+ }
+
+ boolean isStarted() {
+ return started;
+ }
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/RegisteredController.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/RegisteredController.java
index 832c2df6ee..88cd0123b0 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/RegisteredController.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/RegisteredController.java
@@ -3,7 +3,12 @@
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
import io.javaoperatorsdk.operator.api.config.NamespaceChangeable;
+import io.javaoperatorsdk.operator.health.ControllerHealthInfo;
public interface RegisteredController extends NamespaceChangeable {
+
ControllerConfiguration
getConfiguration();
+
+ ControllerHealthInfo getControllerHealthInfo();
+
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/RuntimeInfo.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/RuntimeInfo.java
new file mode 100644
index 0000000000..961e519d62
--- /dev/null
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/RuntimeInfo.java
@@ -0,0 +1,81 @@
+package io.javaoperatorsdk.operator;
+
+import java.util.*;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.javaoperatorsdk.operator.health.EventSourceHealthIndicator;
+import io.javaoperatorsdk.operator.health.InformerWrappingEventSourceHealthIndicator;
+
+/**
+ * RuntimeInfo in general is available when operator is fully started. You can use "isStarted" to
+ * check that.
+ */
+@SuppressWarnings("rawtypes")
+public class RuntimeInfo {
+
+ private static final Logger log = LoggerFactory.getLogger(RuntimeInfo.class);
+
+ private final Set registeredControllers;
+ private final Operator operator;
+
+ public RuntimeInfo(Operator operator) {
+ this.registeredControllers = operator.getRegisteredControllers();
+ this.operator = operator;
+ }
+
+ public boolean isStarted() {
+ return operator.isStarted();
+ }
+
+ public Set getRegisteredControllers() {
+ checkIfStarted();
+ return registeredControllers;
+ }
+
+ private void checkIfStarted() {
+ if (!isStarted()) {
+ log.warn(
+ "Operator not started yet while accessing runtime info, this might lead to an unreliable behavior");
+ }
+ }
+
+ public boolean allEventSourcesAreHealthy() {
+ checkIfStarted();
+ return registeredControllers.stream()
+ .filter(rc -> !rc.getControllerHealthInfo().unhealthyEventSources().isEmpty())
+ .findFirst().isEmpty();
+ }
+
+ /**
+ * @return Aggregated Map with controller related event sources.
+ */
+
+ public Map> unhealthyEventSources() {
+ checkIfStarted();
+ Map> res = new HashMap<>();
+ for (var rc : registeredControllers) {
+ res.put(rc.getConfiguration().getName(),
+ rc.getControllerHealthInfo().unhealthyEventSources());
+ }
+ return res;
+ }
+
+ /**
+ * @return Aggregated Map with controller related event sources that wraps an informer. Thus,
+ * either a
+ * {@link io.javaoperatorsdk.operator.processing.event.source.controller.ControllerResourceEventSource}
+ * or an
+ * {@link io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource}.
+ */
+ public Map> unhealthyInformerWrappingEventSourceHealthIndicator() {
+ checkIfStarted();
+ Map> res = new HashMap<>();
+ for (var rc : registeredControllers) {
+ res.put(rc.getConfiguration().getName(), rc.getControllerHealthInfo()
+ .unhealthyInformerEventSourceHealthIndicators());
+ }
+ return res;
+ }
+}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java
deleted file mode 100644
index 43c61319ac..0000000000
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java
+++ /dev/null
@@ -1,274 +0,0 @@
-package io.javaoperatorsdk.operator.api.config;
-
-import java.lang.annotation.Annotation;
-import java.time.Duration;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-
-import io.fabric8.kubernetes.api.model.HasMetadata;
-import io.javaoperatorsdk.operator.OperatorException;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
-import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
-import io.javaoperatorsdk.operator.api.reconciler.Constants;
-import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
-import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
-import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
-import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
-import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.AnnotationDependentResourceConfigurator;
-import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition;
-import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
-import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
-import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilters;
-import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter;
-import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter;
-import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter;
-import io.javaoperatorsdk.operator.processing.retry.Retry;
-
-import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_NAMESPACES_SET;
-
-@SuppressWarnings("rawtypes")
-public class AnnotationControllerConfiguration
- implements io.javaoperatorsdk.operator.api.config.ControllerConfiguration
{
-
- protected final Reconciler
reconciler;
- private final ControllerConfiguration annotation;
- private List specs;
- private Class resourceClass;
-
- public AnnotationControllerConfiguration(Reconciler
reconciler) {
- this.reconciler = reconciler;
- this.annotation = reconciler.getClass().getAnnotation(ControllerConfiguration.class);
- if (annotation == null) {
- throw new OperatorException(
- "Missing mandatory @" + ControllerConfiguration.class.getSimpleName() +
- " annotation for reconciler: " + reconciler);
- }
- }
-
- @Override
- public String getName() {
- return ReconcilerUtils.getNameFor(reconciler);
- }
-
- @Override
- public String getFinalizerName() {
- if (annotation == null || annotation.finalizerName().isBlank()) {
- return ReconcilerUtils.getDefaultFinalizerName(getResourceClass());
- } else {
- final var finalizer = annotation.finalizerName();
- if (ReconcilerUtils.isFinalizerValid(finalizer)) {
- return finalizer;
- } else {
- throw new IllegalArgumentException(
- finalizer
- + " is not a valid finalizer. See https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#finalizers for details");
- }
- }
- }
-
- @Override
- public boolean isGenerationAware() {
- return valueOrDefault(
- annotation, ControllerConfiguration::generationAwareEventProcessing, true);
- }
-
- @Override
- public Set getNamespaces() {
- return Set.of(valueOrDefault(annotation, ControllerConfiguration::namespaces,
- DEFAULT_NAMESPACES_SET.toArray(String[]::new)));
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public Class getResourceClass() {
- if (resourceClass == null) {
- resourceClass =
- (Class
) Utils.getFirstTypeArgumentFromSuperClassOrInterface(reconciler.getClass(),
- Reconciler.class);
- }
- return resourceClass;
- }
-
- @Override
- public String getLabelSelector() {
- return valueOrDefault(annotation, ControllerConfiguration::labelSelector, "");
- }
-
- @Override
- public String getAssociatedReconcilerClassName() {
- return reconciler.getClass().getCanonicalName();
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public ResourceEventFilter
getEventFilter() {
- ResourceEventFilter
answer = null;
-
- Class>[] filterTypes =
- (Class>[]) valueOrDefault(annotation,
- ControllerConfiguration::eventFilters, new Object[] {});
- if (filterTypes.length > 0) {
- for (var filterType : filterTypes) {
- try {
- ResourceEventFilter filter = filterType.getConstructor().newInstance();
-
- if (answer == null) {
- answer = filter;
- } else {
- answer = answer.and(filter);
- }
- } catch (Exception e) {
- throw new IllegalArgumentException(e);
- }
- }
- }
- return answer != null ? answer : ResourceEventFilters.passthrough();
- }
-
- @Override
- public Optional maxReconciliationInterval() {
- final var newConfig = annotation.maxReconciliationInterval();
- if (newConfig != null && newConfig.interval() > 0) {
- return Optional.of(Duration.of(newConfig.interval(), newConfig.timeUnit().toChronoUnit()));
- }
- return Optional.empty();
- }
-
- @Override
- public RateLimiter getRateLimiter() {
- final Class extends RateLimiter> rateLimiterClass = annotation.rateLimiter();
- return Utils.instantiateAndConfigureIfNeeded(rateLimiterClass, RateLimiter.class,
- Utils.contextFor(this, null, null), this::configureFromAnnotatedReconciler);
- }
-
- @Override
- public Retry getRetry() {
- final Class extends Retry> retryClass = annotation.retry();
- return Utils.instantiateAndConfigureIfNeeded(retryClass, Retry.class,
- Utils.contextFor(this, null, null), this::configureFromAnnotatedReconciler);
- }
-
-
- @SuppressWarnings("unchecked")
- private void configureFromAnnotatedReconciler(Object instance) {
- if (instance instanceof AnnotationConfigurable) {
- AnnotationConfigurable configurable = (AnnotationConfigurable) instance;
- final Class extends Annotation> configurationClass =
- (Class extends Annotation>) Utils.getFirstTypeArgumentFromSuperClassOrInterface(
- instance.getClass(), AnnotationConfigurable.class);
- final var configAnnotation = reconciler.getClass().getAnnotation(configurationClass);
- if (configAnnotation != null) {
- configurable.initFrom(configAnnotation);
- }
- }
- }
-
- @SuppressWarnings("unchecked")
- private void configureFromCustomAnnotation(Object instance) {
- if (instance instanceof AnnotationDependentResourceConfigurator) {
- AnnotationDependentResourceConfigurator configurator =
- (AnnotationDependentResourceConfigurator) instance;
- final Class extends Annotation> configurationClass =
- (Class extends Annotation>) Utils.getFirstTypeArgumentFromInterface(
- instance.getClass(), AnnotationDependentResourceConfigurator.class);
- final var configAnnotation = instance.getClass().getAnnotation(configurationClass);
- // always called even if the annotation is null so that implementations can provide default
- // values
- final var config = configurator.configFrom(configAnnotation, this);
- configurator.configureWith(config);
- }
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public Optional> onAddFilter() {
- return Optional.ofNullable(
- Utils.instantiate(annotation.onAddFilter(), OnAddFilter.class,
- Utils.contextFor(this, null, null)));
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public Optional> onUpdateFilter() {
- return Optional.ofNullable(
- Utils.instantiate(annotation.onUpdateFilter(), OnUpdateFilter.class,
- Utils.contextFor(this, null, null)));
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public Optional> genericFilter() {
- return Optional.ofNullable(
- Utils.instantiate(annotation.genericFilter(), GenericFilter.class,
- Utils.contextFor(this, null, null)));
- }
-
- @SuppressWarnings({"rawtypes", "unchecked"})
- @Override
- public List getDependentResources() {
- if (specs == null) {
- final var dependents =
- valueOrDefault(annotation, ControllerConfiguration::dependents, new Dependent[] {});
- if (dependents.length == 0) {
- specs = Collections.emptyList();
- return specs;
- }
-
- final var specsMap = new LinkedHashMap(dependents.length);
- for (Dependent dependent : dependents) {
- final Class extends DependentResource> dependentType = dependent.type();
-
- final var name = getName(dependent, dependentType);
- var spec = specsMap.get(name);
- if (spec != null) {
- throw new IllegalArgumentException(
- "A DependentResource named '" + name + "' already exists: " + spec);
- }
-
- final var dependentResource = Utils.instantiateAndConfigureIfNeeded(dependentType,
- DependentResource.class,
- Utils.contextFor(this, dependentType, Dependent.class),
- this::configureFromCustomAnnotation);
-
- var eventSourceName = dependent.useEventSourceWithName();
- eventSourceName = Constants.NO_VALUE_SET.equals(eventSourceName) ? null : eventSourceName;
- final var context = Utils.contextFor(this, dependentType, null);
- spec = new DependentResourceSpec(dependentResource, name,
- Set.of(dependent.dependsOn()),
- Utils.instantiate(dependent.readyPostcondition(), Condition.class, context),
- Utils.instantiate(dependent.reconcilePrecondition(), Condition.class, context),
- Utils.instantiate(dependent.deletePostcondition(), Condition.class, context),
- eventSourceName);
- specsMap.put(name, spec);
- }
-
- specs = specsMap.values().stream().collect(Collectors.toUnmodifiableList());
- }
- return specs;
- }
-
- private String getName(Dependent dependent, Class extends DependentResource> dependentType) {
- var name = dependent.name();
- if (name.isBlank()) {
- name = DependentResource.defaultNameFor(dependentType);
- }
- return name;
- }
-
- public static T valueOrDefault(
- ControllerConfiguration controllerConfiguration,
- Function mapper,
- T defaultValue) {
- if (controllerConfiguration == null) {
- return defaultValue;
- } else {
- return mapper.apply(controllerConfiguration);
- }
- }
-
-}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java
index 1e4b535202..db91ee22af 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java
@@ -1,13 +1,40 @@
package io.javaoperatorsdk.operator.api.config;
+import java.lang.annotation.Annotation;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.function.UnaryOperator;
+import java.util.stream.Collectors;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.fabric8.kubernetes.api.model.HasMetadata;
+import io.javaoperatorsdk.operator.OperatorException;
+import io.javaoperatorsdk.operator.ReconcilerUtils;
+import io.javaoperatorsdk.operator.api.config.Utils.Configurator;
+import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
+import io.javaoperatorsdk.operator.api.reconciler.Constants;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
+import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition;
+import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
+import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
+import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilters;
+import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter;
+import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter;
+import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter;
+import io.javaoperatorsdk.operator.processing.retry.Retry;
import com.fasterxml.jackson.databind.ObjectMapper;
+import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_NAMESPACES_SET;
+
public class BaseConfigurationService extends AbstractConfigurationService {
private static final String LOGGER_NAME = "Default ConfigurationService implementation";
@@ -67,8 +94,140 @@ public ControllerConfiguration getConfigurationFor(
return config;
}
- protected ControllerConfiguration configFor(Reconciler reconciler) {
- return new AnnotationControllerConfiguration<>(reconciler);
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ protected ControllerConfiguration
configFor(Reconciler
reconciler) {
+ final var annotation = reconciler.getClass().getAnnotation(
+ io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration.class);
+ if (annotation == null) {
+ throw new OperatorException(
+ "Missing mandatory @"
+ + io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration.class
+ .getSimpleName()
+ +
+ " annotation for reconciler: " + reconciler);
+ }
+
+ final var resourceClass = (Class
) Utils.getFirstTypeArgumentFromSuperClassOrInterface(
+ reconciler.getClass(), Reconciler.class);
+ final var name = ReconcilerUtils.getNameFor(reconciler);
+ final var generationAware = valueOrDefault(
+ annotation,
+ io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::generationAwareEventProcessing,
+ true);
+ final var associatedReconcilerClass =
+ ResolvedControllerConfiguration.getAssociatedReconcilerClassName(reconciler.getClass());
+
+ final Class extends Retry> retryClass = annotation.retry();
+ final var retry = Utils.instantiateAndConfigureIfNeeded(retryClass, Retry.class,
+ Utils.contextFor(name, null, null), configuratorFor(Retry.class, reconciler));
+
+ final Class extends RateLimiter> rateLimiterClass = annotation.rateLimiter();
+ final var rateLimiter = Utils.instantiateAndConfigureIfNeeded(rateLimiterClass,
+ RateLimiter.class,
+ Utils.contextFor(name, null, null), configuratorFor(RateLimiter.class, reconciler));
+
+ final var reconciliationInterval = annotation.maxReconciliationInterval();
+ long interval = -1;
+ TimeUnit timeUnit = null;
+ if (reconciliationInterval != null && reconciliationInterval.interval() > 0) {
+ interval = reconciliationInterval.interval();
+ timeUnit = reconciliationInterval.timeUnit();
+ }
+
+ final var config = new ResolvedControllerConfiguration
(
+ resourceClass, name, generationAware,
+ associatedReconcilerClass, retry, rateLimiter,
+ ResolvedControllerConfiguration.getMaxReconciliationInterval(interval, timeUnit),
+ Utils.instantiate(annotation.onAddFilter(), OnAddFilter.class,
+ Utils.contextFor(name, null, null)),
+ Utils.instantiate(annotation.onUpdateFilter(), OnUpdateFilter.class,
+ Utils.contextFor(name, null, null)),
+ Utils.instantiate(annotation.genericFilter(), GenericFilter.class,
+ Utils.contextFor(name, null, null)),
+ Utils.instantiate(annotation.cachePruneFunction(), UnaryOperator.class,
+ Utils.contextFor(name, null, null)),
+ Set.of(valueOrDefault(annotation,
+ io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::namespaces,
+ DEFAULT_NAMESPACES_SET.toArray(String[]::new))),
+ valueOrDefault(annotation,
+ io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::finalizerName,
+ Constants.NO_VALUE_SET),
+ valueOrDefault(annotation,
+ io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::labelSelector,
+ Constants.NO_VALUE_SET),
+ null);
+
+ ResourceEventFilter
answer = deprecatedEventFilter(annotation);
+ config.setEventFilter(answer != null ? answer : ResourceEventFilters.passthrough());
+
+ List specs = dependentResources(annotation, config);
+ config.setDependentResources(specs);
+
+ return config;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static ResourceEventFilter
deprecatedEventFilter(
+ io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration annotation) {
+ ResourceEventFilter
answer = null;
+
+ Class>[] filterTypes =
+ (Class>[]) valueOrDefault(annotation,
+ io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::eventFilters,
+ new Object[] {});
+ for (var filterType : filterTypes) {
+ try {
+ ResourceEventFilter filter = filterType.getConstructor().newInstance();
+
+ if (answer == null) {
+ answer = filter;
+ } else {
+ answer = answer.and(filter);
+ }
+ } catch (Exception e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+ return answer;
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ private static List dependentResources(
+ io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration annotation,
+ ControllerConfiguration> parent) {
+ final var dependents =
+ valueOrDefault(annotation,
+ io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::dependents,
+ new Dependent[] {});
+ if (dependents.length == 0) {
+ return Collections.emptyList();
+ }
+
+ final var specsMap = new LinkedHashMap(dependents.length);
+ for (Dependent dependent : dependents) {
+ final Class extends DependentResource> dependentType = dependent.type();
+
+ final var dependentName = getName(dependent.name(), dependentType);
+ var spec = specsMap.get(dependentName);
+ if (spec != null) {
+ throw new IllegalArgumentException(
+ "A DependentResource named '" + dependentName + "' already exists: " + spec);
+ }
+
+ final var name = parent.getName();
+
+ var eventSourceName = dependent.useEventSourceWithName();
+ eventSourceName = Constants.NO_VALUE_SET.equals(eventSourceName) ? null : eventSourceName;
+ final var context = Utils.contextFor(name, dependentType, null);
+ spec = new DependentResourceSpec(dependentType, dependentName,
+ Set.of(dependent.dependsOn()),
+ Utils.instantiate(dependent.readyPostcondition(), Condition.class, context),
+ Utils.instantiate(dependent.reconcilePrecondition(), Condition.class, context),
+ Utils.instantiate(dependent.deletePostcondition(), Condition.class, context),
+ eventSourceName);
+ specsMap.put(dependentName, spec);
+ }
+ return specsMap.values().stream().collect(Collectors.toUnmodifiableList());
}
protected boolean createIfNeeded() {
@@ -79,4 +238,43 @@ protected boolean createIfNeeded() {
public boolean checkCRDAndValidateLocalModel() {
return Utils.shouldCheckCRDAndValidateLocalModel();
}
+
+ private static T valueOrDefault(
+ io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration controllerConfiguration,
+ Function mapper,
+ T defaultValue) {
+ if (controllerConfiguration == null) {
+ return defaultValue;
+ } else {
+ return mapper.apply(controllerConfiguration);
+ }
+ }
+
+ @SuppressWarnings("rawtypes")
+ private static String getName(String name, Class extends DependentResource> dependentType) {
+ if (name.isBlank()) {
+ name = DependentResource.defaultNameFor(dependentType);
+ }
+ return name;
+ }
+
+ @SuppressWarnings("unused")
+ private static Configurator configuratorFor(Class instanceType,
+ Reconciler> reconciler) {
+ return instance -> configureFromAnnotatedReconciler(instance, reconciler);
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ private static void configureFromAnnotatedReconciler(Object instance, Reconciler> reconciler) {
+ if (instance instanceof AnnotationConfigurable) {
+ AnnotationConfigurable configurable = (AnnotationConfigurable) instance;
+ final Class extends Annotation> configurationClass =
+ (Class extends Annotation>) Utils.getFirstTypeArgumentFromSuperClassOrInterface(
+ instance.getClass(), AnnotationConfigurable.class);
+ final var configAnnotation = reconciler.getClass().getAnnotation(configurationClass);
+ if (configAnnotation != null) {
+ configurable.initFrom(configAnnotation);
+ }
+ }
+ }
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java
index 036aa76bbf..45d7162184 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java
@@ -145,9 +145,9 @@ default ObjectMapper getObjectMapper() {
return Serialization.jsonMapper();
}
- @Deprecated(forRemoval = true)
+ @SuppressWarnings("rawtypes")
default DependentResourceFactory dependentResourceFactory() {
- return null;
+ return DependentResourceFactory.DEFAULT;
}
default Optional getLeaderElectionConfiguration() {
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java
index 74c40ed9a8..b2640777a7 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java
@@ -17,19 +17,37 @@
import io.javaoperatorsdk.operator.processing.retry.GradualRetry;
import io.javaoperatorsdk.operator.processing.retry.Retry;
-public interface ControllerConfiguration extends ResourceConfiguration {
+public interface ControllerConfiguration extends ResourceConfiguration
{
@SuppressWarnings("rawtypes")
RateLimiter DEFAULT_RATE_LIMITER = LinearRateLimiter.deactivatedRateLimiter();
default String getName() {
- return ReconcilerUtils.getDefaultReconcilerName(getAssociatedReconcilerClassName());
+ return ensureValidName(null, getAssociatedReconcilerClassName());
}
default String getFinalizerName() {
return ReconcilerUtils.getDefaultFinalizerName(getResourceClass());
}
+ static String ensureValidName(String name, String reconcilerClassName) {
+ return name != null ? name : ReconcilerUtils.getDefaultReconcilerName(reconcilerClassName);
+ }
+
+ static String ensureValidFinalizerName(String finalizer, String resourceTypeName) {
+ if (finalizer != null && !finalizer.isBlank()) {
+ if (ReconcilerUtils.isFinalizerValid(finalizer)) {
+ return finalizer;
+ } else {
+ throw new IllegalArgumentException(
+ finalizer
+ + " is not a valid finalizer. See https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#finalizers for details");
+ }
+ } else {
+ return ReconcilerUtils.getDefaultFinalizerName(resourceTypeName);
+ }
+ }
+
default boolean isGenerationAware() {
return true;
}
@@ -70,8 +88,12 @@ default RateLimiter getRateLimiter() {
*
*
* @return filter
+ * @deprecated use {@link ResourceConfiguration#onAddFilter()},
+ * {@link ResourceConfiguration#onUpdateFilter()} or
+ * {@link ResourceConfiguration#genericFilter()} instead
*/
- default ResourceEventFilter getEventFilter() {
+ @Deprecated(forRemoval = true)
+ default ResourceEventFilter getEventFilter() {
return ResourceEventFilters.passthrough();
}
@@ -91,8 +113,8 @@ default ConfigurationService getConfigurationService() {
@SuppressWarnings("unchecked")
@Override
- default Class getResourceClass() {
- return (Class) Utils.getFirstTypeArgumentFromSuperClassOrInterface(getClass(),
+ default Class getResourceClass() {
+ return (Class
) Utils.getFirstTypeArgumentFromSuperClassOrInterface(getClass(),
ControllerConfiguration.class);
}
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java
index c36aa51d2e..789de127f5 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java
@@ -1,16 +1,15 @@
package io.javaoperatorsdk.operator.api.config;
import java.time.Duration;
+import java.util.HashMap;
import java.util.HashSet;
-import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Optional;
+import java.util.Map;
import java.util.Set;
-import java.util.stream.Collectors;
+import java.util.function.UnaryOperator;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
-import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DependentResourceConfigurator;
import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter;
@@ -33,11 +32,12 @@ public class ControllerConfigurationOverrider {
private ResourceEventFilter customResourcePredicate;
private final ControllerConfiguration original;
private Duration reconciliationMaxInterval;
- private final LinkedHashMap namedDependentResourceSpecs;
private OnAddFilter onAddFilter;
private OnUpdateFilter onUpdateFilter;
private GenericFilter genericFilter;
private RateLimiter rateLimiter;
+ private UnaryOperator cachePruneFunction;
+ private Map configurations;
private ControllerConfigurationOverrider(ControllerConfiguration original) {
finalizer = original.getFinalizerName();
@@ -47,15 +47,12 @@ private ControllerConfigurationOverrider(ControllerConfiguration original) {
labelSelector = original.getLabelSelector();
customResourcePredicate = original.getEventFilter();
reconciliationMaxInterval = original.maxReconciliationInterval().orElse(null);
- // make the original specs modifiable
- final var dependentResources = original.getDependentResources();
- namedDependentResourceSpecs = new LinkedHashMap<>(dependentResources.size());
this.onAddFilter = original.onAddFilter().orElse(null);
this.onUpdateFilter = original.onUpdateFilter().orElse(null);
this.genericFilter = original.genericFilter().orElse(null);
- dependentResources.forEach(drs -> namedDependentResourceSpecs.put(drs.getName(), drs));
this.original = original;
this.rateLimiter = original.getRateLimiter();
+ this.cachePruneFunction = original.cachePruneFunction().orElse(null);
}
public ControllerConfigurationOverrider withFinalizer(String finalizer) {
@@ -158,57 +155,38 @@ public ControllerConfigurationOverrider withGenericFilter(GenericFilter ge
return this;
}
- @SuppressWarnings("unchecked")
+ public ControllerConfigurationOverrider withCachePruneFunction(
+ UnaryOperator cachePruneFunction) {
+ this.cachePruneFunction = cachePruneFunction;
+ return this;
+ }
+
public ControllerConfigurationOverrider replacingNamedDependentResourceConfig(String name,
Object dependentResourceConfig) {
- var current = namedDependentResourceSpecs.get(name);
- if (current == null) {
- throw new IllegalArgumentException("Cannot find a DependentResource named: " + name);
- }
+ final var specs = original.getDependentResources();
+ final var spec = specs.stream()
+ .filter(drs -> drs.getName().equals(name)).findFirst()
+ .orElseThrow(
+ () -> new IllegalArgumentException("Cannot find a DependentResource named: " + name));
- var dependentResource = current.getDependentResource();
- if (dependentResource instanceof DependentResourceConfigurator) {
- var configurator = (DependentResourceConfigurator) dependentResource;
- configurator.configureWith(dependentResourceConfig);
+ if (configurations == null) {
+ configurations = new HashMap<>(specs.size());
}
+ configurations.put(spec, dependentResourceConfig);
return this;
}
public ControllerConfiguration build() {
- final var hasModifiedNamespaces = !original.getNamespaces().equals(namespaces);
- final var newDependentSpecs = namedDependentResourceSpecs.values().stream()
- .peek(spec -> {
- // if the dependent resource has a NamespaceChangeable config
- // update the namespaces if needed, otherwise, do nothing
- if (hasModifiedNamespaces) {
- final Optional> maybeConfig = spec.getDependentResourceConfiguration();
- maybeConfig
- .filter(NamespaceChangeable.class::isInstance)
- .map(NamespaceChangeable.class::cast)
- .filter(NamespaceChangeable::allowsNamespaceChanges)
- .ifPresent(nc -> nc.changeNamespaces(namespaces));
- }
- }).collect(Collectors.toList());
-
- return new DefaultControllerConfiguration<>(
- original.getAssociatedReconcilerClassName(),
- original.getName(),
- original.getResourceTypeName(),
- finalizer,
- generationAware,
- namespaces,
- retry,
- labelSelector,
- customResourcePredicate,
- original.getResourceClass(),
- reconciliationMaxInterval,
- onAddFilter,
- onUpdateFilter,
- genericFilter,
- rateLimiter,
- newDependentSpecs);
+ final var overridden = new ResolvedControllerConfiguration<>(
+ original.getResourceClass(), original.getName(),
+ generationAware, original.getAssociatedReconcilerClassName(), retry, rateLimiter,
+ reconciliationMaxInterval, onAddFilter, onUpdateFilter, genericFilter, cachePruneFunction,
+ original.getDependentResources(),
+ namespaces, finalizer, labelSelector, configurations);
+ overridden.setEventFilter(customResourcePredicate);
+ return overridden;
}
public static ControllerConfigurationOverrider override(
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java
deleted file mode 100644
index 3f4d952133..0000000000
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java
+++ /dev/null
@@ -1,119 +0,0 @@
-package io.javaoperatorsdk.operator.api.config;
-
-import java.time.Duration;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-
-import io.fabric8.kubernetes.api.model.HasMetadata;
-import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
-import io.javaoperatorsdk.operator.processing.event.rate.LinearRateLimiter;
-import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
-import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
-import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter;
-import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter;
-import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter;
-import io.javaoperatorsdk.operator.processing.retry.Retry;
-
-@SuppressWarnings("rawtypes")
-public class DefaultControllerConfiguration
- extends DefaultResourceConfiguration
- implements ControllerConfiguration {
-
- private final String associatedControllerClassName;
- private final String name;
- private final String crdName;
- private final String finalizer;
- private final boolean generationAware;
- private final Retry retry;
- private final ResourceEventFilter resourceEventFilter;
- private final List dependents;
- private final Duration reconciliationMaxInterval;
- private final RateLimiter rateLimiter;
-
- // NOSONAR constructor is meant to provide all information
- public DefaultControllerConfiguration(
- String associatedControllerClassName,
- String name,
- String crdName,
- String finalizer,
- boolean generationAware,
- Set namespaces,
- Retry retry,
- String labelSelector,
- ResourceEventFilter resourceEventFilter,
- Class resourceClass,
- Duration reconciliationMaxInterval,
- OnAddFilter onAddFilter,
- OnUpdateFilter onUpdateFilter,
- GenericFilter genericFilter,
- RateLimiter rateLimiter,
- List dependents) {
- super(labelSelector, resourceClass, onAddFilter, onUpdateFilter, genericFilter, namespaces);
- this.associatedControllerClassName = associatedControllerClassName;
- this.name = name;
- this.crdName = crdName;
- this.finalizer = finalizer;
- this.generationAware = generationAware;
- this.reconciliationMaxInterval = reconciliationMaxInterval;
- this.retry =
- retry == null
- ? ControllerConfiguration.super.getRetry()
- : retry;
- this.resourceEventFilter = resourceEventFilter;
- this.rateLimiter =
- rateLimiter != null ? rateLimiter : LinearRateLimiter.deactivatedRateLimiter();
- this.dependents = dependents != null ? dependents : Collections.emptyList();
- }
-
- @Override
- public String getName() {
- return name;
- }
-
- @Override
- public String getResourceTypeName() {
- return crdName;
- }
-
- @Override
- public String getFinalizerName() {
- return finalizer;
- }
-
- @Override
- public boolean isGenerationAware() {
- return generationAware;
- }
-
- @Override
- public String getAssociatedReconcilerClassName() {
- return associatedControllerClassName;
- }
-
- @Override
- public Retry getRetry() {
- return retry;
- }
-
- @Override
- public ResourceEventFilter getEventFilter() {
- return resourceEventFilter;
- }
-
- @Override
- public List getDependentResources() {
- return dependents;
- }
-
- @Override
- public Optional maxReconciliationInterval() {
- return Optional.ofNullable(reconciliationMaxInterval);
- }
-
- @Override
- public RateLimiter getRateLimiter() {
- return rateLimiter;
- }
-}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceConfiguration.java
index 9bc6ce5dba..bb9f365d58 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceConfiguration.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceConfiguration.java
@@ -2,48 +2,44 @@
import java.util.Optional;
import java.util.Set;
+import java.util.function.UnaryOperator;
import io.fabric8.kubernetes.api.model.HasMetadata;
+import io.javaoperatorsdk.operator.ReconcilerUtils;
import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter;
-import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_NAMESPACES_SET;
-
public class DefaultResourceConfiguration
implements ResourceConfiguration {
- private final String labelSelector;
- private final Set namespaces;
private final Class resourceClass;
+ private final String resourceTypeName;
private final OnAddFilter onAddFilter;
private final OnUpdateFilter onUpdateFilter;
private final GenericFilter genericFilter;
+ private final String labelSelector;
+ private final Set namespaces;
+ private final UnaryOperator cachePruneFunction;
- public DefaultResourceConfiguration(String labelSelector, Class resourceClass,
- OnAddFilter onAddFilter,
- OnUpdateFilter onUpdateFilter, GenericFilter genericFilter, String... namespaces) {
- this(labelSelector, resourceClass, onAddFilter, onUpdateFilter, genericFilter,
- namespaces == null || namespaces.length == 0 ? DEFAULT_NAMESPACES_SET
- : Set.of(namespaces));
- }
-
- public DefaultResourceConfiguration(String labelSelector, Class resourceClass,
- OnAddFilter onAddFilter,
- OnUpdateFilter onUpdateFilter, GenericFilter genericFilter, Set namespaces) {
- this.labelSelector = labelSelector;
+ protected DefaultResourceConfiguration(Class resourceClass,
+ Set namespaces, String labelSelector, OnAddFilter onAddFilter,
+ OnUpdateFilter onUpdateFilter, GenericFilter genericFilter,
+ UnaryOperator cachePruneFunction) {
this.resourceClass = resourceClass;
+ this.resourceTypeName = ReconcilerUtils.getResourceTypeName(resourceClass);
this.onAddFilter = onAddFilter;
this.onUpdateFilter = onUpdateFilter;
this.genericFilter = genericFilter;
- this.namespaces =
- namespaces == null || namespaces.isEmpty() ? DEFAULT_NAMESPACES_SET
- : namespaces;
+
+ this.namespaces = ResourceConfiguration.ensureValidNamespaces(namespaces);
+ this.labelSelector = ResourceConfiguration.ensureValidLabelSelector(labelSelector);
+ this.cachePruneFunction = cachePruneFunction;
}
@Override
public String getResourceTypeName() {
- return ResourceConfiguration.super.getResourceTypeName();
+ return resourceTypeName;
}
@Override
@@ -56,6 +52,11 @@ public Set getNamespaces() {
return namespaces;
}
+ @Override
+ public Optional> cachePruneFunction() {
+ return Optional.ofNullable(this.cachePruneFunction);
+ }
+
@Override
public Class getResourceClass() {
return resourceClass;
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResolvedControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResolvedControllerConfiguration.java
new file mode 100644
index 0000000000..b1f653cb75
--- /dev/null
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResolvedControllerConfiguration.java
@@ -0,0 +1,169 @@
+package io.javaoperatorsdk.operator.api.config;
+
+import java.time.Duration;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.function.UnaryOperator;
+
+import io.fabric8.kubernetes.api.model.HasMetadata;
+import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceConfigurationProvider;
+import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
+import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
+import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
+import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
+import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter;
+import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter;
+import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter;
+import io.javaoperatorsdk.operator.processing.retry.Retry;
+
+@SuppressWarnings("rawtypes")
+public class ResolvedControllerConfiguration
+ extends DefaultResourceConfiguration
+ implements io.javaoperatorsdk.operator.api.config.ControllerConfiguration
,
+ DependentResourceConfigurationProvider {
+
+ private final String name;
+ private final boolean generationAware;
+ private final String associatedReconcilerClassName;
+ private final Retry retry;
+ private final RateLimiter rateLimiter;
+ private final Optional maxReconciliationInterval;
+ private final String finalizer;
+ private final Map configurations;
+
+ private ResourceEventFilter eventFilter;
+ private List dependentResources;
+
+ public ResolvedControllerConfiguration(Class resourceClass, ControllerConfiguration
other) {
+ this(resourceClass, other.getName(), other.isGenerationAware(),
+ other.getAssociatedReconcilerClassName(), other.getRetry(), other.getRateLimiter(),
+ other.maxReconciliationInterval().orElse(null),
+ other.onAddFilter().orElse(null), other.onUpdateFilter().orElse(null),
+ other.genericFilter().orElse(null), other.cachePruneFunction().orElse(null),
+ other.getDependentResources(), other.getNamespaces(),
+ other.getFinalizerName(), other.getLabelSelector(), Collections.emptyMap());
+ }
+
+ public static Duration getMaxReconciliationInterval(long interval, TimeUnit timeUnit) {
+ return interval > 0 ? Duration.of(interval, timeUnit.toChronoUnit()) : null;
+ }
+
+ public static String getAssociatedReconcilerClassName(
+ Class extends Reconciler> reconcilerClass) {
+ return reconcilerClass.getCanonicalName();
+ }
+
+ protected Retry ensureRetry(Retry given) {
+ return given == null ? ControllerConfiguration.super.getRetry() : given;
+ }
+
+ protected RateLimiter ensureRateLimiter(RateLimiter given) {
+ return given == null ? ControllerConfiguration.super.getRateLimiter() : given;
+ }
+
+ public ResolvedControllerConfiguration(Class
resourceClass, String name,
+ boolean generationAware, String associatedReconcilerClassName, Retry retry,
+ RateLimiter rateLimiter, Duration maxReconciliationInterval,
+ OnAddFilter
onAddFilter, OnUpdateFilter
onUpdateFilter,
+ GenericFilter
genericFilter, UnaryOperator
cachePruneFunction,
+ List dependentResources,
+ Set namespaces, String finalizer, String labelSelector,
+ Map configurations) {
+ this(resourceClass, name, generationAware, associatedReconcilerClassName, retry, rateLimiter,
+ maxReconciliationInterval, onAddFilter, onUpdateFilter, genericFilter, cachePruneFunction,
+ namespaces, finalizer, labelSelector, configurations);
+ setDependentResources(dependentResources);
+ }
+
+ protected ResolvedControllerConfiguration(Class resourceClass, String name,
+ boolean generationAware, String associatedReconcilerClassName, Retry retry,
+ RateLimiter rateLimiter, Duration maxReconciliationInterval,
+ OnAddFilter
onAddFilter, OnUpdateFilter
onUpdateFilter, GenericFilter
genericFilter,
+ UnaryOperator
cachePruneFunction,
+ Set namespaces, String finalizer, String labelSelector,
+ Map configurations) {
+ super(resourceClass, namespaces, labelSelector, onAddFilter, onUpdateFilter, genericFilter,
+ cachePruneFunction);
+ this.name = ControllerConfiguration.ensureValidName(name, associatedReconcilerClassName);
+ this.generationAware = generationAware;
+ this.associatedReconcilerClassName = associatedReconcilerClassName;
+ this.retry = ensureRetry(retry);
+ this.rateLimiter = ensureRateLimiter(rateLimiter);
+ this.maxReconciliationInterval = Optional.ofNullable(maxReconciliationInterval);
+ this.configurations = configurations != null ? configurations : Collections.emptyMap();
+
+ this.finalizer =
+ ControllerConfiguration.ensureValidFinalizerName(finalizer, getResourceTypeName());
+ }
+
+ protected ResolvedControllerConfiguration(Class resourceClass, String name,
+ Class extends Reconciler> reconcilerClas) {
+ this(resourceClass, name, false, getAssociatedReconcilerClassName(reconcilerClas), null, null,
+ null, null, null, null, null,
+ null, null, null, null);
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getFinalizerName() {
+ return finalizer;
+ }
+
+ @Override
+ public boolean isGenerationAware() {
+ return generationAware;
+ }
+
+ @Override
+ public String getAssociatedReconcilerClassName() {
+ return associatedReconcilerClassName;
+ }
+
+ @Override
+ public Retry getRetry() {
+ return retry;
+ }
+
+ @Override
+ public RateLimiter getRateLimiter() {
+ return rateLimiter;
+ }
+
+ @Override
+ public List getDependentResources() {
+ return dependentResources;
+ }
+
+ protected void setDependentResources(List dependentResources) {
+ this.dependentResources = dependentResources == null ? Collections.emptyList()
+ : Collections.unmodifiableList(dependentResources);
+ }
+
+ @Override
+ public Optional maxReconciliationInterval() {
+ return maxReconciliationInterval;
+ }
+
+ @Override
+ public ResourceEventFilter getEventFilter() {
+ return eventFilter;
+ }
+
+ @Deprecated(forRemoval = true)
+ protected void setEventFilter(ResourceEventFilter
eventFilter) {
+ this.eventFilter = eventFilter;
+ }
+
+ @Override
+ public Object getConfigurationFor(DependentResourceSpec spec) {
+ return configurations.get(spec);
+ }
+}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResourceConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResourceConfiguration.java
index 90e18f3e52..85f3bc3b36 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResourceConfiguration.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResourceConfiguration.java
@@ -1,13 +1,16 @@
package io.javaoperatorsdk.operator.api.config;
+import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
+import java.util.function.UnaryOperator;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.OperatorException;
import io.javaoperatorsdk.operator.ReconcilerUtils;
import io.javaoperatorsdk.operator.api.reconciler.Constants;
+import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter;
@@ -45,6 +48,11 @@ default String getLabelSelector() {
return null;
}
+ static String ensureValidLabelSelector(String labelSelector) {
+ // might want to implement validation here?
+ return labelSelector;
+ }
+
@SuppressWarnings("unchecked")
default Class getResourceClass() {
return (Class) Utils.getFirstTypeArgumentFromSuperClassOrInterface(getClass(),
@@ -88,6 +96,14 @@ static void failIfNotValid(Set namespaces) {
+ Constants.WATCH_CURRENT_NAMESPACE + "'");
}
+ static Set ensureValidNamespaces(Collection namespaces) {
+ if (namespaces != null && !namespaces.isEmpty()) {
+ return Set.copyOf(namespaces);
+ } else {
+ return Constants.DEFAULT_NAMESPACES_SET;
+ }
+ }
+
/**
* Computes the effective namespaces based on the set specified by the user, in particular
* retrieves the current namespace from the client when the user specified that they wanted to
@@ -108,4 +124,11 @@ default Set getEffectiveNamespaces() {
}
return targetNamespaces;
}
+
+ /**
+ * See {@link ControllerConfiguration#cachePruneFunction()} for details.
+ */
+ default Optional> cachePruneFunction() {
+ return Optional.empty();
+ }
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/Utils.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/Utils.java
index fd0f612fa1..5838eb9b97 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/Utils.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/Utils.java
@@ -227,6 +227,13 @@ public interface Configurator {
public static String contextFor(ControllerConfiguration> controllerConfiguration,
Class extends DependentResource> dependentType,
Class extends Annotation> configurationAnnotation) {
+ return contextFor(controllerConfiguration.getName(), dependentType, configurationAnnotation);
+ }
+
+ @SuppressWarnings("rawtypes")
+ public static String contextFor(String reconcilerName,
+ Class extends DependentResource> dependentType,
+ Class extends Annotation> configurationAnnotation) {
final var annotationName =
configurationAnnotation != null ? configurationAnnotation.getSimpleName()
: io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration.class
@@ -235,8 +242,7 @@ public static String contextFor(ControllerConfiguration> controllerConfigurati
if (dependentType != null) {
context += "DependentResource: " + dependentType.getName() + ", ";
}
- context += "reconciler: " + controllerConfiguration.getName();
-
+ context += "reconciler: " + reconcilerName;
return context;
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/ConfigurationConverter.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/ConfigurationConverter.java
new file mode 100644
index 0000000000..68e0f521de
--- /dev/null
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/ConfigurationConverter.java
@@ -0,0 +1,12 @@
+package io.javaoperatorsdk.operator.api.config.dependent;
+
+import java.lang.annotation.Annotation;
+
+import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DependentResourceConfigurator;
+
+public interface ConfigurationConverter> {
+
+ C configFrom(A configAnnotation, ControllerConfiguration> parentConfiguration,
+ Class originatingClass);
+}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/Configured.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/Configured.java
new file mode 100644
index 0000000000..db8c6f6db3
--- /dev/null
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/Configured.java
@@ -0,0 +1,16 @@
+package io.javaoperatorsdk.operator.api.config.dependent;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Configured {
+
+ Class extends Annotation> by();
+
+ Class> with();
+
+ @SuppressWarnings("rawtypes")
+ Class extends ConfigurationConverter> converter();
+}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceConfigurationProvider.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceConfigurationProvider.java
new file mode 100644
index 0000000000..a0c9dc67ae
--- /dev/null
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceConfigurationProvider.java
@@ -0,0 +1,6 @@
+package io.javaoperatorsdk.operator.api.config.dependent;
+
+public interface DependentResourceConfigurationProvider {
+ @SuppressWarnings("rawtypes")
+ Object getConfigurationFor(DependentResourceSpec spec);
+}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceConfigurationResolver.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceConfigurationResolver.java
new file mode 100644
index 0000000000..9a143fc57c
--- /dev/null
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceConfigurationResolver.java
@@ -0,0 +1,181 @@
+package io.javaoperatorsdk.operator.api.config.dependent;
+
+import java.lang.annotation.Annotation;
+import java.util.HashMap;
+import java.util.Map;
+
+import io.fabric8.kubernetes.api.model.HasMetadata;
+import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
+import io.javaoperatorsdk.operator.api.config.Utils;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DependentResourceConfigurator;
+
+@SuppressWarnings({"rawtypes", "unchecked"})
+public class DependentResourceConfigurationResolver {
+
+ private DependentResourceConfigurationResolver() {}
+
+ private static final Map, ConverterAnnotationPair> converters =
+ new HashMap<>();
+ private static final Map, ConfigurationConverter> knownConverters =
+ new HashMap<>();
+
+ public static > void configure(
+ DependentResource dependentResource, DependentResourceSpec spec, C parentConfiguration) {
+ if (dependentResource instanceof DependentResourceConfigurator) {
+ final var configurator = (DependentResourceConfigurator) dependentResource;
+ final var config = configurationFor(spec, parentConfiguration);
+ configurator.configureWith(config);
+ }
+ }
+
+ public static > Object configurationFor(
+ DependentResourceSpec spec, C parentConfiguration) {
+
+ // first check if the parent configuration has potentially already resolved the configuration
+ if (parentConfiguration instanceof DependentResourceConfigurationProvider) {
+ final var provider = (DependentResourceConfigurationProvider) parentConfiguration;
+ final var configuration = provider.getConfigurationFor(spec);
+ if (configuration != null) {
+ return configuration;
+ }
+ }
+
+ // find Configured-annotated class if it exists
+ return extractConfigurationFromConfigured(spec.getDependentResourceClass(),
+ parentConfiguration);
+ }
+
+ public static > Object extractConfigurationFromConfigured(
+ Class extends DependentResource> dependentResourceClass, C parentConfiguration) {
+ var converterAnnotationPair = converters.get(dependentResourceClass);
+
+ Annotation configAnnotation;
+ if (converterAnnotationPair == null) {
+ var configuredClassPair = getConfigured(dependentResourceClass);
+ if (configuredClassPair == null) {
+ return null;
+ }
+
+ // check if we already have a converter registered for the found Configured annotated class
+ converterAnnotationPair = converters.get(configuredClassPair.annotatedClass);
+ if (converterAnnotationPair == null) {
+ final var configured = configuredClassPair.configured;
+ converterAnnotationPair =
+ getOrCreateConverter(dependentResourceClass, parentConfiguration,
+ configured.converter(),
+ configured.by());
+ } else {
+ // only register the converter pair for this dependent resource class as well
+ converters.put(dependentResourceClass, converterAnnotationPair);
+ }
+ }
+
+ // find the associated configuration annotation
+ configAnnotation =
+ dependentResourceClass.getAnnotation(converterAnnotationPair.annotationClass);
+ final var converter = converterAnnotationPair.converter;
+
+ // always called even if the annotation is null so that implementations can provide default
+ // values
+ return converter.configFrom(configAnnotation, parentConfiguration, dependentResourceClass);
+ }
+
+ private static ConfiguredClassPair getConfigured(
+ Class extends DependentResource> dependentResourceClass) {
+ Class extends DependentResource> currentClass = dependentResourceClass;
+ Configured configured;
+ ConfiguredClassPair result = null;
+ while (DependentResource.class.isAssignableFrom(currentClass)) {
+ configured = currentClass.getAnnotation(Configured.class);
+ if (configured != null) {
+ result = new ConfiguredClassPair(configured, currentClass);
+ break;
+ }
+ currentClass = (Class extends DependentResource>) currentClass.getSuperclass();
+ }
+ return result;
+ }
+
+ private static > ConverterAnnotationPair getOrCreateConverter(
+ Class extends DependentResource> dependentResourceClass, C parentConfiguration,
+ Class extends ConfigurationConverter> converterClass,
+ Class extends Annotation> annotationClass) {
+ var converterPair = converters.get(dependentResourceClass);
+ if (converterPair == null) {
+ // only instantiate a new converter if we haven't done so already for this converter type
+ var converter = knownConverters.get(converterClass);
+ if (converter == null) {
+ converter = Utils.instantiate(converterClass,
+ ConfigurationConverter.class,
+ Utils.contextFor(parentConfiguration, dependentResourceClass, Configured.class));
+ knownConverters.put(converterClass, converter);
+ }
+ // record dependent class - converter association for faster future retrieval
+ converterPair = new ConverterAnnotationPair(converter, annotationClass);
+ converters.put(dependentResourceClass, converterPair);
+ }
+ return converterPair;
+ }
+
+ static ConfigurationConverter getConverter(
+ Class extends DependentResource> dependentResourceClass) {
+ final var converterAnnotationPair = converters.get(dependentResourceClass);
+ return converterAnnotationPair != null ? converterAnnotationPair.converter : null;
+ }
+
+ @SuppressWarnings("unused")
+ public static void registerConverter(Class extends DependentResource> dependentResourceClass,
+ ConfigurationConverter converter) {
+ var configured = getConfigured(dependentResourceClass);
+ if (configured == null) {
+ throw new IllegalArgumentException("There is no @" + Configured.class.getSimpleName()
+ + " annotation on " + dependentResourceClass.getName()
+ + " or its superclasses and thus doesn't need to be associated with a converter");
+ }
+
+ // find the associated configuration annotation
+ final var toRegister = new ConverterAnnotationPair(converter, configured.configured.by());
+ final Class extends ConfigurationConverter> converterClass = converter.getClass();
+ converters.put(dependentResourceClass, toRegister);
+
+ // also register the Configured-annotated class if not the one we're registering
+ if (!dependentResourceClass.equals(configured.annotatedClass)) {
+ converters.put(configured.annotatedClass, toRegister);
+ }
+
+ knownConverters.put(converterClass, converter);
+ }
+
+ private static class ConfiguredClassPair {
+ private final Configured configured;
+ private final Class extends DependentResource> annotatedClass;
+
+ private ConfiguredClassPair(Configured configured,
+ Class extends DependentResource> annotatedClass) {
+ this.configured = configured;
+ this.annotatedClass = annotatedClass;
+ }
+
+ @Override
+ public String toString() {
+ return annotatedClass.getName() + " -> " + configured;
+ }
+ }
+
+ private static class ConverterAnnotationPair {
+ private final ConfigurationConverter converter;
+ private final Class extends Annotation> annotationClass;
+
+ private ConverterAnnotationPair(ConfigurationConverter converter,
+ Class extends Annotation> annotationClass) {
+ this.converter = converter;
+ this.annotationClass = annotationClass;
+ }
+
+ @Override
+ public String toString() {
+ return converter.toString() + " -> " + annotationClass.getName();
+ }
+ }
+}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceSpec.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceSpec.java
index 5d0b8c6b01..58fd9ace4b 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceSpec.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceSpec.java
@@ -6,12 +6,11 @@
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
-import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DependentResourceConfigurator;
import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition;
-public class DependentResourceSpec {
+public class DependentResourceSpec {
- private final DependentResource dependentResource;
+ private final Class extends DependentResource> dependentResourceClass;
private final String name;
@@ -25,11 +24,11 @@ public class DependentResourceSpec {
private final String useEventSourceWithName;
- public DependentResourceSpec(DependentResource dependentResource,
+ public DependentResourceSpec(Class extends DependentResource> dependentResourceClass,
String name, Set dependsOn, Condition, ?> readyCondition,
Condition, ?> reconcileCondition, Condition, ?> deletePostCondition,
String useEventSourceWithName) {
- this.dependentResource = dependentResource;
+ this.dependentResourceClass = dependentResourceClass;
this.name = name;
this.dependsOn = dependsOn;
this.readyCondition = readyCondition;
@@ -38,28 +37,8 @@ public DependentResourceSpec(DependentResource dependentResource,
this.useEventSourceWithName = useEventSourceWithName;
}
- public DependentResourceSpec(DependentResourceSpec other) {
- this.dependentResource = other.dependentResource;
- this.name = other.name;
- this.dependsOn = other.dependsOn;
- this.readyCondition = other.readyCondition;
- this.reconcileCondition = other.reconcileCondition;
- this.deletePostCondition = other.deletePostCondition;
- this.useEventSourceWithName = other.useEventSourceWithName;
- }
-
- @SuppressWarnings("unchecked")
- public Class> getDependentResourceClass() {
- return (Class>) dependentResource.getClass();
- }
-
- @SuppressWarnings({"unchecked", "rawtypes"})
- public Optional getDependentResourceConfiguration() {
- if (dependentResource instanceof DependentResourceConfigurator) {
- var configurator = (DependentResourceConfigurator) dependentResource;
- return configurator.configuration();
- }
- return Optional.empty();
+ public Class extends DependentResource> getDependentResourceClass() {
+ return dependentResourceClass;
}
public String getName() {
@@ -80,7 +59,7 @@ public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) {
return false;
}
- DependentResourceSpec, ?, ?> that = (DependentResourceSpec, ?, ?>) o;
+ DependentResourceSpec, ?> that = (DependentResourceSpec, ?>) o;
return name.equals(that.name);
}
@@ -108,10 +87,6 @@ public Condition getDeletePostCondition() {
return deletePostCondition;
}
- public DependentResource getDependentResource() {
- return dependentResource;
- }
-
public Optional getUseEventSourceWithName() {
return Optional.ofNullable(useEventSourceWithName);
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerConfiguration.java
index 52f71501a9..e26597dde5 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerConfiguration.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerConfiguration.java
@@ -3,6 +3,7 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.function.UnaryOperator;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.api.config.DefaultResourceConfiguration;
@@ -38,8 +39,10 @@ protected DefaultInformerConfiguration(String labelSelector,
OnAddFilter onAddFilter,
OnUpdateFilter onUpdateFilter,
OnDeleteFilter onDeleteFilter,
- GenericFilter genericFilter) {
- super(labelSelector, resourceClass, onAddFilter, onUpdateFilter, genericFilter, namespaces);
+ GenericFilter genericFilter,
+ UnaryOperator cachePruneFunction) {
+ super(resourceClass, namespaces, labelSelector, onAddFilter, onUpdateFilter, genericFilter,
+ cachePruneFunction);
this.followControllerNamespaceChanges = followControllerNamespaceChanges;
this.primaryToSecondaryMapper = primaryToSecondaryMapper;
@@ -64,6 +67,7 @@ public Optional> onDeleteFilter() {
}
@Override
+ @SuppressWarnings("unchecked")
public PrimaryToSecondaryMapper
getPrimaryToSecondaryMapper() {
return (PrimaryToSecondaryMapper
) primaryToSecondaryMapper;
}
@@ -102,6 +106,7 @@ class InformerConfigurationBuilder {
private OnDeleteFilter onDeleteFilter;
private GenericFilter genericFilter;
private boolean inheritControllerNamespacesOnChange = false;
+ private UnaryOperator cachePruneFunction;
private InformerConfigurationBuilder(Class resourceClass) {
this.resourceClass = resourceClass;
@@ -202,12 +207,18 @@ public InformerConfigurationBuilder withGenericFilter(GenericFilter generi
return this;
}
+ public InformerConfigurationBuilder withCachePruneFunction(
+ UnaryOperator cachePruneFunction) {
+ this.cachePruneFunction = cachePruneFunction;
+ return this;
+ }
+
public InformerConfiguration build() {
return new DefaultInformerConfiguration<>(labelSelector, resourceClass,
primaryToSecondaryMapper,
secondaryToPrimaryMapper,
namespaces, inheritControllerNamespacesOnChange, onAddFilter, onUpdateFilter,
- onDeleteFilter, genericFilter);
+ onDeleteFilter, genericFilter, cachePruneFunction);
}
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/monitoring/Metrics.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/monitoring/Metrics.java
index be608b098e..c134a5522f 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/monitoring/Metrics.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/monitoring/Metrics.java
@@ -6,6 +6,7 @@
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.RetryInfo;
+import io.javaoperatorsdk.operator.processing.Controller;
import io.javaoperatorsdk.operator.processing.event.Event;
import io.javaoperatorsdk.operator.processing.event.ResourceID;
@@ -20,6 +21,11 @@ public interface Metrics {
*/
Metrics NOOP = new Metrics() {};
+ /**
+ * Do initialization if necessary;
+ */
+ default void controllerRegistered(Controller> controller) {}
+
/**
* Called when an event has been accepted by the SDK from an event source, which would result in
* potentially triggering the associated Reconciler.
@@ -29,55 +35,44 @@ public interface Metrics {
*/
default void receivedEvent(Event event, Map metadata) {}
- /**
- *
- * @deprecated Use (and implement) {@link #receivedEvent(Event, Map)} instead
- */
- @Deprecated
- default void receivedEvent(Event event) {
- receivedEvent(event, Collections.emptyMap());
- }
-
- /**
- *
- * @deprecated Use (and implement) {@link #reconcileCustomResource(ResourceID, RetryInfo, Map)}
- * instead
- */
- @Deprecated
- default void reconcileCustomResource(ResourceID resourceID, RetryInfo retryInfo) {
- reconcileCustomResource(resourceID, retryInfo, Collections.emptyMap());
- }
+ @Deprecated(forRemoval = true)
+ default void reconcileCustomResource(ResourceID resourceID, RetryInfo retryInfo,
+ Map metadata) {}
/**
* Called right before a resource is dispatched to the ExecutorService for reconciliation.
*
- * @param resourceID the {@link ResourceID} associated with the resource
+ * @param resource the associated with the resource
* @param retryInfo the current retry state information for the reconciliation request
* @param metadata metadata associated with the resource being processed
*/
- default void reconcileCustomResource(ResourceID resourceID, RetryInfo retryInfo,
- Map metadata) {}
-
- /**
- *
- * @deprecated Use (and implement) {@link #failedReconciliation(ResourceID, Exception, Map)}
- * instead
- */
- @Deprecated
- default void failedReconciliation(ResourceID resourceID, Exception exception) {
- failedReconciliation(resourceID, exception, Collections.emptyMap());
+ default void reconcileCustomResource(HasMetadata resource, RetryInfo retryInfo,
+ Map metadata) {
+ reconcileCustomResource(ResourceID.fromResource(resource), retryInfo, metadata);
}
+ @Deprecated(forRemoval = true)
+ default void failedReconciliation(ResourceID resourceID, Exception exception,
+ Map metadata) {}
+
/**
* Called when a precedent reconciliation for the resource associated with the specified
* {@link ResourceID} resulted in the provided exception, resulting in a retry of the
* reconciliation.
*
- * @param resourceID the {@link ResourceID} associated with the resource being processed
+ * @param resource the {@link ResourceID} associated with the resource being processed
* @param exception the exception that caused the failed reconciliation resulting in a retry
* @param metadata metadata associated with the resource being processed
*/
- default void failedReconciliation(ResourceID resourceID, Exception exception,
+ default void failedReconciliation(HasMetadata resource, Exception exception,
+ Map metadata) {
+ failedReconciliation(ResourceID.fromResource(resource), exception, metadata);
+ }
+
+
+ default void reconciliationExecutionStarted(HasMetadata resource, Map metadata) {}
+
+ default void reconciliationExecutionFinished(HasMetadata resource,
Map metadata) {}
/**
@@ -107,16 +102,21 @@ default void finishedReconciliation(ResourceID resourceID) {
finishedReconciliation(resourceID, Collections.emptyMap());
}
+ @Deprecated(forRemoval = true)
+ default void finishedReconciliation(ResourceID resourceID, Map metadata) {}
+
/**
* Called when the
* {@link io.javaoperatorsdk.operator.api.reconciler.Reconciler#reconcile(HasMetadata, Context)}
* method of the Reconciler associated with the resource associated with the specified
* {@link ResourceID} has sucessfully finished.
*
- * @param resourceID the {@link ResourceID} associated with the resource being processed
+ * @param resource the {@link ResourceID} associated with the resource being processed
* @param metadata metadata associated with the resource being processed
*/
- default void finishedReconciliation(ResourceID resourceID, Map metadata) {}
+ default void finishedReconciliation(HasMetadata resource, Map metadata) {
+ finishedReconciliation(ResourceID.fromResource(resource), metadata);
+ }
/**
* Encapsulates the information about a controller execution i.e. a call to either
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Constants.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Constants.java
index 55a9cbdbed..08aeb16f90 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Constants.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Constants.java
@@ -21,6 +21,7 @@ public final class Constants {
public static final String SAME_AS_CONTROLLER = "JOSDK_SAME_AS_CONTROLLER";
public static final String RESOURCE_GVK_KEY = "josdk.resource.gvk";
+ public static final String CONTROLLER_NAME = "controller.name";
private Constants() {}
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java
index ec76adf89d..df3efa7d40 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java
@@ -5,6 +5,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.util.function.UnaryOperator;
import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
import io.javaoperatorsdk.operator.processing.event.rate.LinearRateLimiter;
@@ -118,4 +119,25 @@ MaxReconciliationInterval maxReconciliationInterval() default @MaxReconciliation
* accessible no-arg constructor.
*/
Class extends RateLimiter> rateLimiter() default LinearRateLimiter.class;
+
+ /**
+ *
+ * This is an experimental feature, might be a subject of change and even removal in the
+ * future.
+ *
+ *
+ * In order to optimize cache, thus set null on some attributes, this function can be set. Note
+ * that this has subtle implications how updates on the resources should be handled. Notably only
+ * patching of the resource can be used from that point, since update would remove not cached
+ * parts of the resource.
+ *
+ *
+ * Note that this feature does not work with Dependent Resources.
+ *
+ *
+ *
+ *
+ * @return function to remove parts of the resource.
+ */
+ Class extends UnaryOperator> cachePruneFunction() default UnaryOperator.class;
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResourceFactory.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResourceFactory.java
index 139d70b002..e9e47f6d97 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResourceFactory.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResourceFactory.java
@@ -1,18 +1,21 @@
package io.javaoperatorsdk.operator.api.reconciler.dependent;
+import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
import io.javaoperatorsdk.operator.api.config.Utils;
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
-@Deprecated
+import static io.javaoperatorsdk.operator.api.config.dependent.DependentResourceConfigurationResolver.configure;
+
@SuppressWarnings({"rawtypes", "unchecked"})
-public interface DependentResourceFactory {
+public interface DependentResourceFactory> {
- default DependentResource createFrom(DependentResourceSpec spec) {
- return createFrom(spec.getDependentResourceClass());
- }
+ DependentResourceFactory DEFAULT = new DependentResourceFactory() {};
- default T createFrom(Class dependentResourceClass) {
- return (T) Utils.instantiate(dependentResourceClass, DependentResource.class, null);
+ default DependentResource createFrom(DependentResourceSpec spec, C configuration) {
+ final var dependentResourceClass = spec.getDependentResourceClass();
+ return Utils.instantiateAndConfigureIfNeeded(dependentResourceClass,
+ DependentResource.class,
+ Utils.contextFor(configuration, dependentResourceClass, Dependent.class),
+ (instance) -> configure(instance, spec, configuration));
}
-
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/AnnotationDependentResourceConfigurator.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/AnnotationDependentResourceConfigurator.java
deleted file mode 100644
index d65249b753..0000000000
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/AnnotationDependentResourceConfigurator.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package io.javaoperatorsdk.operator.api.reconciler.dependent.managed;
-
-import java.lang.annotation.Annotation;
-
-import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
-
-public interface AnnotationDependentResourceConfigurator
- extends DependentResourceConfigurator {
-
- C configFrom(A annotation, ControllerConfiguration> parentConfiguration);
-}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/health/ControllerHealthInfo.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/health/ControllerHealthInfo.java
new file mode 100644
index 0000000000..2adb3a8508
--- /dev/null
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/health/ControllerHealthInfo.java
@@ -0,0 +1,50 @@
+package io.javaoperatorsdk.operator.health;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import io.javaoperatorsdk.operator.processing.event.EventSourceManager;
+
+@SuppressWarnings("rawtypes")
+public class ControllerHealthInfo {
+
+ private EventSourceManager> eventSourceManager;
+
+ public ControllerHealthInfo(EventSourceManager eventSourceManager) {
+ this.eventSourceManager = eventSourceManager;
+ }
+
+ public Map eventSourceHealthIndicators() {
+ return eventSourceManager.allEventSources().entrySet().stream()
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ }
+
+ public Map unhealthyEventSources() {
+ return eventSourceManager.allEventSources().entrySet().stream()
+ .filter(e -> e.getValue().getStatus() == Status.UNHEALTHY)
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ }
+
+ public Map informerEventSourceHealthIndicators() {
+ return eventSourceManager.allEventSources().entrySet().stream()
+ .filter(e -> e.getValue() instanceof InformerWrappingEventSourceHealthIndicator)
+ .collect(Collectors.toMap(Map.Entry::getKey,
+ e -> (InformerWrappingEventSourceHealthIndicator) e.getValue()));
+
+ }
+
+ /**
+ * @return Map with event sources that wraps an informer. Thus, either a
+ * {@link io.javaoperatorsdk.operator.processing.event.source.controller.ControllerResourceEventSource}
+ * or an
+ * {@link io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource}.
+ */
+ public Map unhealthyInformerEventSourceHealthIndicators() {
+ return eventSourceManager.allEventSources().entrySet().stream()
+ .filter(e -> e.getValue().getStatus() == Status.UNHEALTHY)
+ .filter(e -> e.getValue() instanceof InformerWrappingEventSourceHealthIndicator)
+ .collect(Collectors.toMap(Map.Entry::getKey,
+ e -> (InformerWrappingEventSourceHealthIndicator) e.getValue()));
+ }
+
+}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/health/EventSourceHealthIndicator.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/health/EventSourceHealthIndicator.java
new file mode 100644
index 0000000000..e44fcb5b72
--- /dev/null
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/health/EventSourceHealthIndicator.java
@@ -0,0 +1,6 @@
+package io.javaoperatorsdk.operator.health;
+
+public interface EventSourceHealthIndicator {
+
+ Status getStatus();
+}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/health/InformerHealthIndicator.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/health/InformerHealthIndicator.java
new file mode 100644
index 0000000000..afd8b61bed
--- /dev/null
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/health/InformerHealthIndicator.java
@@ -0,0 +1,17 @@
+package io.javaoperatorsdk.operator.health;
+
+public interface InformerHealthIndicator extends EventSourceHealthIndicator {
+
+ boolean hasSynced();
+
+ boolean isWatching();
+
+ boolean isRunning();
+
+ @Override
+ default Status getStatus() {
+ return isRunning() && hasSynced() && isWatching() ? Status.HEALTHY : Status.UNHEALTHY;
+ }
+
+ String getTargetNamespace();
+}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/health/InformerWrappingEventSourceHealthIndicator.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/health/InformerWrappingEventSourceHealthIndicator.java
new file mode 100644
index 0000000000..5a603ad321
--- /dev/null
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/health/InformerWrappingEventSourceHealthIndicator.java
@@ -0,0 +1,22 @@
+package io.javaoperatorsdk.operator.health;
+
+import java.util.Map;
+
+import io.fabric8.kubernetes.api.model.HasMetadata;
+import io.javaoperatorsdk.operator.api.config.ResourceConfiguration;
+
+public interface InformerWrappingEventSourceHealthIndicator
+ extends EventSourceHealthIndicator {
+
+ Map informerHealthIndicators();
+
+ @Override
+ default Status getStatus() {
+ var nonUp = informerHealthIndicators().values().stream()
+ .filter(i -> i.getStatus() != Status.HEALTHY).findAny();
+
+ return nonUp.isPresent() ? Status.UNHEALTHY : Status.HEALTHY;
+ }
+
+ ResourceConfiguration getInformerConfiguration();
+}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/health/Status.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/health/Status.java
new file mode 100644
index 0000000000..d3a300b7d8
--- /dev/null
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/health/Status.java
@@ -0,0 +1,11 @@
+package io.javaoperatorsdk.operator.health;
+
+public enum Status {
+
+ HEALTHY, UNHEALTHY,
+ /**
+ * For event sources where it cannot be determined if it is healthy ot not.
+ */
+ UNKNOWN
+
+}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java
index 2d9e6b4007..8753e0862b 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java
@@ -39,7 +39,8 @@
import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceProvider;
import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceReferencer;
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DefaultManagedDependentResourceContext;
-import io.javaoperatorsdk.operator.processing.dependent.workflow.ManagedWorkflow;
+import io.javaoperatorsdk.operator.health.ControllerHealthInfo;
+import io.javaoperatorsdk.operator.processing.dependent.workflow.Workflow;
import io.javaoperatorsdk.operator.processing.dependent.workflow.WorkflowCleanupResult;
import io.javaoperatorsdk.operator.processing.event.EventProcessor;
import io.javaoperatorsdk.operator.processing.event.EventSourceManager;
@@ -63,10 +64,11 @@ public class Controller
private final boolean contextInitializer;
private final boolean isCleaner;
private final Metrics metrics;
- private final ManagedWorkflow
managedWorkflow;
+ private final Workflow
managedWorkflow;
private final GroupVersionKind associatedGVK;
private final EventProcessor
eventProcessor;
+ private final ControllerHealthInfo controllerHealthInfo;
public Controller(Reconciler
reconciler,
ControllerConfiguration
configuration,
@@ -81,16 +83,18 @@ public Controller(Reconciler
reconciler,
this.metrics = Optional.ofNullable(configurationService.getMetrics()).orElse(Metrics.NOOP);
contextInitializer = reconciler instanceof ContextInitializer;
isCleaner = reconciler instanceof Cleaner;
- managedWorkflow = configurationService.getWorkflowFactory().workflowFor(configuration);
- managedWorkflow.resolve(kubernetesClient, configuration.getDependentResources());
+
+ final var managed = configurationService.getWorkflowFactory().workflowFor(configuration);
+ managedWorkflow = managed.resolve(kubernetesClient, configuration);
eventSourceManager = new EventSourceManager<>(this);
eventProcessor = new EventProcessor<>(eventSourceManager);
eventSourceManager.postProcessDefaultEventSourcesAfterProcessorInitializer();
-
+ controllerHealthInfo = new ControllerHealthInfo(eventSourceManager);
final var context = new EventSourceContext<>(
eventSourceManager.getControllerResourceEventSource(), configuration, kubernetesClient);
initAndRegisterEventSources(context);
+ ConfigurationServiceProvider.instance().getMetrics().controllerRegistered(this);
}
@Override
@@ -132,7 +136,7 @@ public Map metadata() {
@Override
public UpdateControl execute() throws Exception {
initContextIfNeeded(resource, context);
- if (!managedWorkflow.isEmptyWorkflow()) {
+ if (!managedWorkflow.isEmpty()) {
var res = managedWorkflow.reconcile(resource, context);
((DefaultManagedDependentResourceContext) context.managedDependentResourceContext())
.setWorkflowExecutionResult(res);
@@ -177,7 +181,7 @@ public Map metadata() {
public DeleteControl execute() {
initContextIfNeeded(resource, context);
WorkflowCleanupResult workflowCleanupResult = null;
- if (managedWorkflow.isCleaner()) {
+ if (managedWorkflow.hasCleaner()) {
workflowCleanupResult = managedWorkflow.cleanup(resource, context);
((DefaultManagedDependentResourceContext) context.managedDependentResourceContext())
.setWorkflowCleanupResult(workflowCleanupResult);
@@ -228,13 +232,13 @@ public void initAndRegisterEventSources(EventSourceContext context) {
final var dependentResourcesByName = managedWorkflow.getDependentResourcesByName();
final var size = dependentResourcesByName.size();
if (size > 0) {
- dependentResourcesByName.forEach((key, value) -> {
- if (value instanceof EventSourceProvider) {
- final var provider = (EventSourceProvider) value;
+ dependentResourcesByName.forEach((key, dependentResource) -> {
+ if (dependentResource instanceof EventSourceProvider) {
+ final var provider = (EventSourceProvider) dependentResource;
final var source = provider.initEventSource(context);
eventSourceManager.registerEventSource(key, source);
} else {
- Optional eventSource = value.eventSource(context);
+ Optional eventSource = dependentResource.eventSource(context);
eventSource.ifPresent(es -> eventSourceManager.registerEventSource(key, es));
}
});
@@ -289,6 +293,11 @@ public ControllerConfiguration getConfiguration() {
return configuration;
}
+ @Override
+ public ControllerHealthInfo getControllerHealthInfo() {
+ return controllerHealthInfo;
+ }
+
public KubernetesClient getClient() {
return kubernetesClient;
}
@@ -350,10 +359,13 @@ private void validateCRDWithLocalModelIfRequired(Class
resClass, String contr
}
public void changeNamespaces(Set namespaces) {
- if (namespaces.contains(Constants.WATCH_ALL_NAMESPACES)
- || namespaces.contains(WATCH_CURRENT_NAMESPACE)) {
+ if (namespaces.contains(WATCH_CURRENT_NAMESPACE)) {
throw new OperatorException("Unexpected value in target namespaces: " + namespaces);
}
+ if (namespaces.contains(Constants.WATCH_ALL_NAMESPACES) && namespaces.size() > 1) {
+ throw new OperatorException(
+ "Watching all namespaces, but additional specific namespace is present");
+ }
eventProcessor.stop();
eventSourceManager.changeNamespaces(namespaces);
eventProcessor.start();
@@ -406,7 +418,7 @@ public synchronized void stop() {
}
public boolean useFinalizer() {
- return isCleaner || managedWorkflow.isCleaner();
+ return isCleaner || managedWorkflow.hasCleaner();
}
public GroupVersionKind getAssociatedGroupVersionKind() {
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 dbfc34fe42..7a32938403 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
@@ -1,6 +1,6 @@
package io.javaoperatorsdk.operator.processing.dependent;
-import java.util.*;
+import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -9,6 +9,7 @@
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.Ignore;
import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult;
import io.javaoperatorsdk.operator.processing.dependent.Matcher.Result;
@@ -21,6 +22,7 @@ public abstract class AbstractDependentResource
private final boolean creatable = this instanceof Creator;
private final boolean updatable = this instanceof Updater;
+ private final boolean deletable = this instanceof Deleter;
protected Creator creator;
protected Updater updater;
@@ -172,4 +174,9 @@ protected boolean isCreatable() {
protected boolean isUpdatable() {
return updatable;
}
+
+ @Override
+ public boolean isDeletable() {
+ return deletable;
+ }
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentConverter.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentConverter.java
new file mode 100644
index 0000000000..a9a60f8e0a
--- /dev/null
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentConverter.java
@@ -0,0 +1,61 @@
+package io.javaoperatorsdk.operator.processing.dependent.kubernetes;
+
+import java.util.Arrays;
+import java.util.Set;
+
+import io.fabric8.kubernetes.api.model.HasMetadata;
+import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
+import io.javaoperatorsdk.operator.api.config.Utils;
+import io.javaoperatorsdk.operator.api.config.dependent.ConfigurationConverter;
+import io.javaoperatorsdk.operator.api.reconciler.Constants;
+import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator;
+import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter;
+import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter;
+import io.javaoperatorsdk.operator.processing.event.source.filter.OnDeleteFilter;
+import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter;
+
+public class KubernetesDependentConverter implements
+ ConfigurationConverter, KubernetesDependentResource> {
+
+ @Override
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public KubernetesDependentResourceConfig configFrom(KubernetesDependent configAnnotation,
+ ControllerConfiguration> parentConfiguration,
+ Class> originatingClass) {
+ var namespaces = parentConfiguration.getNamespaces();
+ var configuredNS = false;
+ String labelSelector = null;
+ OnAddFilter extends HasMetadata> onAddFilter = null;
+ OnUpdateFilter extends HasMetadata> onUpdateFilter = null;
+ OnDeleteFilter extends HasMetadata> onDeleteFilter = null;
+ GenericFilter extends HasMetadata> genericFilter = null;
+ ResourceDiscriminator, ?> resourceDiscriminator = null;
+ if (configAnnotation != null) {
+ if (!Arrays.equals(KubernetesDependent.DEFAULT_NAMESPACES, configAnnotation.namespaces())) {
+ namespaces = Set.of(configAnnotation.namespaces());
+ configuredNS = true;
+ }
+
+ final var fromAnnotation = configAnnotation.labelSelector();
+ labelSelector = Constants.NO_VALUE_SET.equals(fromAnnotation) ? null : fromAnnotation;
+
+ final var context =
+ Utils.contextFor(parentConfiguration, originatingClass,
+ configAnnotation.annotationType());
+ onAddFilter = Utils.instantiate(configAnnotation.onAddFilter(), OnAddFilter.class, context);
+ onUpdateFilter =
+ Utils.instantiate(configAnnotation.onUpdateFilter(), OnUpdateFilter.class, context);
+ onDeleteFilter =
+ Utils.instantiate(configAnnotation.onDeleteFilter(), OnDeleteFilter.class, context);
+ genericFilter =
+ Utils.instantiate(configAnnotation.genericFilter(), GenericFilter.class, context);
+
+ resourceDiscriminator =
+ Utils.instantiate(configAnnotation.resourceDiscriminator(), ResourceDiscriminator.class,
+ context);
+ }
+
+ return new KubernetesDependentResourceConfig(namespaces, labelSelector, configuredNS,
+ resourceDiscriminator, onAddFilter, onUpdateFilter, onDeleteFilter, genericFilter);
+ }
+}
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 5631536da5..a8bfcb798c 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
@@ -1,6 +1,5 @@
package io.javaoperatorsdk.operator.processing.dependent.kubernetes;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.Optional;
import java.util.Set;
@@ -12,35 +11,30 @@
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.dsl.Resource;
import io.javaoperatorsdk.operator.OperatorException;
-import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
-import io.javaoperatorsdk.operator.api.config.Utils;
+import io.javaoperatorsdk.operator.api.config.dependent.Configured;
import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.Constants;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext;
import io.javaoperatorsdk.operator.api.reconciler.Ignore;
-import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator;
import io.javaoperatorsdk.operator.api.reconciler.dependent.GarbageCollected;
-import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.AnnotationDependentResourceConfigurator;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DependentResourceConfigurator;
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.KubernetesClientAware;
import io.javaoperatorsdk.operator.processing.dependent.AbstractEventSourceHolderDependentResource;
import io.javaoperatorsdk.operator.processing.dependent.Matcher;
import io.javaoperatorsdk.operator.processing.dependent.Matcher.Result;
import io.javaoperatorsdk.operator.processing.event.ResourceID;
import io.javaoperatorsdk.operator.processing.event.source.SecondaryToPrimaryMapper;
-import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter;
-import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter;
-import io.javaoperatorsdk.operator.processing.event.source.filter.OnDeleteFilter;
-import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter;
import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource;
import io.javaoperatorsdk.operator.processing.event.source.informer.Mappers;
@Ignore
-@SuppressWarnings("rawtypes")
+@Configured(by = KubernetesDependent.class, with = KubernetesDependentResourceConfig.class,
+ converter = KubernetesDependentConverter.class)
public abstract class KubernetesDependentResource
extends AbstractEventSourceHolderDependentResource>
implements KubernetesClientAware,
- AnnotationDependentResourceConfigurator> {
+ DependentResourceConfigurator> {
private static final Logger log = LoggerFactory.getLogger(KubernetesDependentResource.class);
@@ -250,46 +244,6 @@ private void cleanupAfterEventFiltering(ResourceID resourceID) {
.cleanupOnCreateOrUpdateEventFiltering(resourceID);
}
- @Override
- @SuppressWarnings("unchecked")
- public KubernetesDependentResourceConfig configFrom(KubernetesDependent kubeDependent,
- ControllerConfiguration> parentConfiguration) {
- var namespaces = parentConfiguration.getNamespaces();
- var configuredNS = false;
- String labelSelector = null;
- OnAddFilter extends HasMetadata> onAddFilter = null;
- OnUpdateFilter extends HasMetadata> onUpdateFilter = null;
- OnDeleteFilter extends HasMetadata> onDeleteFilter = null;
- GenericFilter extends HasMetadata> genericFilter = null;
- ResourceDiscriminator, ?> resourceDiscriminator = null;
- if (kubeDependent != null) {
- if (!Arrays.equals(KubernetesDependent.DEFAULT_NAMESPACES, kubeDependent.namespaces())) {
- namespaces = Set.of(kubeDependent.namespaces());
- configuredNS = true;
- }
-
- final var fromAnnotation = kubeDependent.labelSelector();
- labelSelector = Constants.NO_VALUE_SET.equals(fromAnnotation) ? null : fromAnnotation;
-
- final var context =
- Utils.contextFor(parentConfiguration, getClass(), kubeDependent.annotationType());
- onAddFilter = Utils.instantiate(kubeDependent.onAddFilter(), OnAddFilter.class, context);
- onUpdateFilter =
- Utils.instantiate(kubeDependent.onUpdateFilter(), OnUpdateFilter.class, context);
- onDeleteFilter =
- Utils.instantiate(kubeDependent.onDeleteFilter(), OnDeleteFilter.class, context);
- genericFilter =
- Utils.instantiate(kubeDependent.genericFilter(), GenericFilter.class, context);
-
- resourceDiscriminator =
- Utils.instantiate(kubeDependent.resourceDiscriminator(), ResourceDiscriminator.class,
- context);
- }
-
- return new KubernetesDependentResourceConfig(namespaces, labelSelector, configuredNS,
- resourceDiscriminator, onAddFilter, onUpdateFilter, onDeleteFilter, genericFilter);
- }
-
@Override
public Optional> configuration() {
return Optional.ofNullable(kubernetesDependentResourceConfig);
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractDependentResourceNode.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractDependentResourceNode.java
deleted file mode 100644
index 22e368249a..0000000000
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractDependentResourceNode.java
+++ /dev/null
@@ -1,103 +0,0 @@
-package io.javaoperatorsdk.operator.processing.dependent.workflow;
-
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Optional;
-
-import io.fabric8.kubernetes.api.model.HasMetadata;
-import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
-
-@SuppressWarnings("rawtypes")
-abstract class AbstractDependentResourceNode
- implements DependentResourceNode {
-
- private final List dependsOn = new LinkedList<>();
- private final List parents = new LinkedList<>();
- private final String name;
- private Condition reconcilePrecondition;
- private Condition deletePostcondition;
- private Condition readyPostcondition;
- private DependentResource dependentResource;
-
- protected AbstractDependentResourceNode(String name) {
- this.name = name;
- }
-
- @Override
- public List extends DependentResourceNode> getDependsOn() {
- return dependsOn;
- }
-
- @Override
- public void addParent(DependentResourceNode parent) {
- parents.add(parent);
- }
-
- @Override
- public void addDependsOnRelation(DependentResourceNode node) {
- node.addParent(this);
- dependsOn.add(node);
- }
-
- @Override
- public List getParents() {
- return parents;
- }
-
- @Override
- public String getName() {
- return name;
- }
-
- @Override
- public Optional> getReconcilePrecondition() {
- return Optional.ofNullable(reconcilePrecondition);
- }
-
- @Override
- public Optional> getDeletePostcondition() {
- return Optional.ofNullable(deletePostcondition);
- }
-
- public void setReconcilePrecondition(Condition reconcilePrecondition) {
- this.reconcilePrecondition = reconcilePrecondition;
- }
-
- public void setDeletePostcondition(Condition cleanupCondition) {
- this.deletePostcondition = cleanupCondition;
- }
-
- @Override
- public Optional> getReadyPostcondition() {
- return Optional.ofNullable(readyPostcondition);
- }
-
- public void setReadyPostcondition(Condition readyPostcondition) {
- this.readyPostcondition = readyPostcondition;
- }
-
- public DependentResource getDependentResource() {
- return dependentResource;
- }
-
- public void setDependentResource(DependentResource dependentResource) {
- this.dependentResource = dependentResource;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- AbstractDependentResourceNode, ?> that = (AbstractDependentResourceNode, ?>) o;
- return name.equals(that.name);
- }
-
- @Override
- public int hashCode() {
- return name.hashCode();
- }
-}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutor.java
index 354975ebad..76db792468 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutor.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutor.java
@@ -87,7 +87,7 @@ protected boolean isInError(DependentResourceNode, P> dependentResourceNode) {
protected Map getErroredDependents() {
return exceptionsDuringExecution.entrySet().stream()
.collect(
- Collectors.toMap(e -> workflow.getDependentResourceFor(e.getKey()), Entry::getValue));
+ Collectors.toMap(e -> e.getKey().getDependentResource(), Entry::getValue));
}
protected synchronized void handleNodeExecutionFinish(
@@ -99,11 +99,6 @@ protected synchronized void handleNodeExecutionFinish(
}
}
- @SuppressWarnings("unchecked")
- protected