From d275670893891435674cbb773d9a2460b50a1e0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Tue, 28 May 2024 12:22:16 +0200 Subject: [PATCH 1/8] feat: @ControllerConfiguration annotation is optional MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../api/config/BaseConfigurationService.java | 259 ++++++++---------- .../config/BaseConfigurationServiceTest.java | 8 +- 2 files changed, 125 insertions(+), 142 deletions(-) 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 243359a1d0..d50b242680 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 @@ -14,7 +14,6 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.informers.cache.ItemStore; -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.DependentResourceConfigurationResolver; @@ -33,7 +32,6 @@ import io.javaoperatorsdk.operator.processing.retry.Retry; import static io.javaoperatorsdk.operator.api.config.ControllerConfiguration.CONTROLLER_NAME_AS_FIELD_MANAGER; -import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_NAMESPACES_SET; public class BaseConfigurationService extends AbstractConfigurationService { @@ -57,88 +55,6 @@ public BaseConfigurationService() { this(Utils.VERSION); } - @SuppressWarnings({"unchecked", "rawtypes"}) - private static List dependentResources( - Workflow annotation, - ControllerConfiguration controllerConfiguration) { - final var dependents = annotation.dependents(); - - - if (dependents == null || dependents.length == 0) { - return Collections.emptyList(); - } - - final var specsMap = new LinkedHashMap(dependents.length); - for (Dependent dependent : dependents) { - final Class 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 = controllerConfiguration.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), - Utils.instantiate(dependent.activationCondition(), Condition.class, context), - eventSourceName); - - // extract potential configuration - DependentResourceConfigurationResolver.configureSpecFromConfigured(spec, - controllerConfiguration, - dependentType); - - specsMap.put(dependentName, spec); - } - return specsMap.values().stream().toList(); - } - - 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 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 configurable) { - final Class configurationClass = - (Class) Utils.getFirstTypeArgumentFromSuperClassOrInterface( - instance.getClass(), AnnotationConfigurable.class); - final var configAnnotation = reconciler.getClass().getAnnotation(configurationClass); - if (configAnnotation != null) { - configurable.initFrom(configAnnotation); - } - } - } @Override protected void logMissingReconcilerWarning(String reconcilerKey, String reconcilersNameMessage) { @@ -193,35 +109,68 @@ protected

ControllerConfiguration

configFor(Reconcile 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); + ResolvedControllerConfiguration

config = controllerConfiguration(reconciler, annotation); + + final var workflowAnnotation = reconciler.getClass().getAnnotation( + io.javaoperatorsdk.operator.api.reconciler.Workflow.class); + if (workflowAnnotation != null) { + final var specs = dependentResources(workflowAnnotation, config); + WorkflowSpec workflowSpec = new WorkflowSpec() { + @Override + public List getDependentResourceSpecs() { + return specs; + } + + @Override + public boolean isExplicitInvocation() { + return workflowAnnotation.explicitInvocation(); + } + + @Override + public boolean handleExceptionsInReconciler() { + return workflowAnnotation.handleExceptionsInReconciler(); + } + + }; + config.setWorkflowSpec(workflowSpec); } + + return config; + } + + @SuppressWarnings({"unchecked"}) + private

ResolvedControllerConfiguration

controllerConfiguration( + Reconciler

reconciler, + io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration annotation) { Class> reconcilerClass = (Class>) reconciler.getClass(); final var resourceClass = getResourceClassResolver().getPrimaryResourceClass(reconcilerClass); final var name = ReconcilerUtils.getNameFor(reconciler); - final var generationAware = valueOrDefault( + final var generationAware = valueOrDefaultFromAnnotation( annotation, io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::generationAwareEventProcessing, - true); + "generationAwareEventProcessing"); final var associatedReconcilerClass = ResolvedControllerConfiguration.getAssociatedReconcilerClassName(reconciler.getClass()); final var context = Utils.contextFor(name); - final Class retryClass = annotation.retry(); + final Class retryClass = + valueOrDefaultFromAnnotation(annotation, + io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::retry, + "retry"); final var retry = Utils.instantiateAndConfigureIfNeeded(retryClass, Retry.class, context, configuratorFor(Retry.class, reconciler)); - final Class rateLimiterClass = annotation.rateLimiter(); + + final Class rateLimiterClass = valueOrDefaultFromAnnotation(annotation, + io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::rateLimiter, + "rateLimiter"); final var rateLimiter = Utils.instantiateAndConfigureIfNeeded(rateLimiterClass, RateLimiter.class, context, configuratorFor(RateLimiter.class, reconciler)); - final var reconciliationInterval = annotation.maxReconciliationInterval(); + final var reconciliationInterval = valueOrDefaultFromAnnotation(annotation, + io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::maxReconciliationInterval, + "maxReconciliationInterval"); long interval = -1; TimeUnit timeUnit = null; if (reconciliationInterval != null && reconciliationInterval.interval() > 0) { @@ -229,60 +178,50 @@ protected

ControllerConfiguration

configFor(Reconcile timeUnit = reconciliationInterval.timeUnit(); } + var fieldManager = valueOrDefaultFromAnnotation(annotation, + io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::fieldManager, + "fieldManager"); final var dependentFieldManager = - annotation.fieldManager().equals(CONTROLLER_NAME_AS_FIELD_MANAGER) ? name - : annotation.fieldManager(); + fieldManager.equals(CONTROLLER_NAME_AS_FIELD_MANAGER) ? name + : fieldManager; + var informerListLimitValue = valueOrDefaultFromAnnotation(annotation, + io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::informerListLimit, + "informerListLimit"); final var informerListLimit = - annotation.informerListLimit() == Constants.NO_LONG_VALUE_SET ? null - : annotation.informerListLimit(); + informerListLimitValue == Constants.NO_LONG_VALUE_SET ? null + : informerListLimitValue; - final var config = new ResolvedControllerConfiguration

( + return new ResolvedControllerConfiguration

( resourceClass, name, generationAware, associatedReconcilerClass, retry, rateLimiter, ResolvedControllerConfiguration.getMaxReconciliationInterval(interval, timeUnit), - Utils.instantiate(annotation.onAddFilter(), OnAddFilter.class, context), - Utils.instantiate(annotation.onUpdateFilter(), OnUpdateFilter.class, context), - Utils.instantiate(annotation.genericFilter(), GenericFilter.class, context), - Set.of(valueOrDefault(annotation, + Utils.instantiate(valueOrDefaultFromAnnotation(annotation, + io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::onAddFilter, + "onAddFilter"), OnAddFilter.class, context), + Utils.instantiate(valueOrDefaultFromAnnotation(annotation, + io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::onUpdateFilter, + "onUpdateFilter"), OnUpdateFilter.class, context), + Utils.instantiate(valueOrDefaultFromAnnotation(annotation, + io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::genericFilter, + "genericFilter"), GenericFilter.class, context), + Set.of(valueOrDefaultFromAnnotation(annotation, io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::namespaces, - DEFAULT_NAMESPACES_SET.toArray(String[]::new))), - valueOrDefault(annotation, + "namespaces")), + valueOrDefaultFromAnnotation(annotation, io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::finalizerName, - Constants.NO_VALUE_SET), - valueOrDefault(annotation, + "finalizerName"), + valueOrDefaultFromAnnotation(annotation, io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::labelSelector, - Constants.NO_VALUE_SET), + "labelSelector"), null, - Utils.instantiate(annotation.itemStore(), ItemStore.class, context), dependentFieldManager, + Utils.instantiate( + valueOrDefaultFromAnnotation(annotation, + io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::itemStore, + "itemStore"), + ItemStore.class, context), + dependentFieldManager, this, informerListLimit); - - - final var workflowAnnotation = reconciler.getClass().getAnnotation( - io.javaoperatorsdk.operator.api.reconciler.Workflow.class); - if (workflowAnnotation != null) { - final var specs = dependentResources(workflowAnnotation, config); - WorkflowSpec workflowSpec = new WorkflowSpec() { - @Override - public List getDependentResourceSpecs() { - return specs; - } - - @Override - public boolean isExplicitInvocation() { - return workflowAnnotation.explicitInvocation(); - } - - @Override - public boolean handleExceptionsInReconciler() { - return workflowAnnotation.handleExceptionsInReconciler(); - } - - }; - config.setWorkflowSpec(workflowSpec); - } - - return config; } protected boolean createIfNeeded() { @@ -293,4 +232,48 @@ protected boolean createIfNeeded() { public boolean checkCRDAndValidateLocalModel() { return Utils.shouldCheckCRDAndValidateLocalModel(); } + + @SuppressWarnings("unchecked") + private static T valueOrDefaultFromAnnotation( + io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration controllerConfiguration, + Function mapper, + String defaultMethodName) { + try { + if (controllerConfiguration == null) { + return (T) io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration.class + .getDeclaredMethod(defaultMethodName).getDefaultValue(); + } else { + return mapper.apply(controllerConfiguration); + } + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + @SuppressWarnings("rawtypes") + private static String getName(String name, Class 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 configurable) { + final Class configurationClass = + (Class) Utils.getFirstTypeArgumentFromSuperClassOrInterface( + instance.getClass(), AnnotationConfigurable.class); + final var configAnnotation = reconciler.getClass().getAnnotation(configurationClass); + if (configAnnotation != null) { + configurable.initFrom(configAnnotation); + } + } + } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/BaseConfigurationServiceTest.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/BaseConfigurationServiceTest.java index d0c2d67b50..6e19c84777 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/BaseConfigurationServiceTest.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/BaseConfigurationServiceTest.java @@ -9,12 +9,10 @@ import java.util.Optional; import java.util.concurrent.TimeUnit; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.OperatorException; import io.javaoperatorsdk.operator.api.config.AnnotationConfigurable; import io.javaoperatorsdk.operator.api.config.BaseConfigurationService; import io.javaoperatorsdk.operator.api.config.dependent.ConfigurationConverter; @@ -104,9 +102,11 @@ void getDependentResources() { } @Test - void missingAnnotationThrowsException() { + void missingAnnotationCreatesDefaultConfig() { final var reconciler = new MissingAnnotationReconciler(); - Assertions.assertThrows(OperatorException.class, () -> configFor(reconciler)); + var config = configFor(reconciler); + // todo asserts + } @SuppressWarnings("rawtypes") From 7e7809dacf2404d5e4d2d2bc3f4634c004095b0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Tue, 28 May 2024 12:25:19 +0200 Subject: [PATCH 2/8] remove controller annotation test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../operator/sample/WebPageManagedDependentsReconciler.java | 1 - 1 file changed, 1 deletion(-) diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageManagedDependentsReconciler.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageManagedDependentsReconciler.java index df3cae354f..e59f7fe0fc 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageManagedDependentsReconciler.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageManagedDependentsReconciler.java @@ -18,7 +18,6 @@ @Dependent(type = IngressDependentResource.class, reconcilePrecondition = ExposedIngressCondition.class) }) -@ControllerConfiguration public class WebPageManagedDependentsReconciler implements Reconciler, Cleaner { From d0c2091683da6b5586659eaea783123e3478c9b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Wed, 29 May 2024 10:19:22 +0200 Subject: [PATCH 3/8] test + docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../config/BaseConfigurationServiceTest.java | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/BaseConfigurationServiceTest.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/BaseConfigurationServiceTest.java index 6e19c84777..4e004fec92 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/BaseConfigurationServiceTest.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/BaseConfigurationServiceTest.java @@ -9,6 +9,8 @@ import java.util.Optional; import java.util.concurrent.TimeUnit; +import io.javaoperatorsdk.operator.ReconcilerUtils; +import io.javaoperatorsdk.operator.api.reconciler.*; import org.junit.jupiter.api.Test; import io.fabric8.kubernetes.api.model.ConfigMap; @@ -18,12 +20,6 @@ import io.javaoperatorsdk.operator.api.config.dependent.ConfigurationConverter; import io.javaoperatorsdk.operator.api.config.dependent.Configured; import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec; -import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; -import io.javaoperatorsdk.operator.api.reconciler.MaxReconciliationInterval; -import io.javaoperatorsdk.operator.api.reconciler.Reconciler; -import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; -import io.javaoperatorsdk.operator.api.reconciler.Workflow; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; @@ -39,6 +35,8 @@ import io.javaoperatorsdk.operator.processing.retry.RetryExecution; import io.javaoperatorsdk.operator.sample.readonly.ReadOnlyDependent; +import static io.javaoperatorsdk.operator.api.reconciler.MaxReconciliationInterval.DEFAULT_INTERVAL; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; class BaseConfigurationServiceTest { @@ -105,8 +103,20 @@ void getDependentResources() { void missingAnnotationCreatesDefaultConfig() { final var reconciler = new MissingAnnotationReconciler(); var config = configFor(reconciler); - // todo asserts + assertThat(config.getName()).isEqualTo(ReconcilerUtils.getNameFor(reconciler)); + assertThat(config.getLabelSelector()).isEmpty(); + assertThat(config.getRetry()).isInstanceOf(GenericRetry.class); + assertThat(config.getRateLimiter()).isInstanceOf(LinearRateLimiter.class); + assertThat(config.maxReconciliationInterval()).hasValue(Duration.ofHours(DEFAULT_INTERVAL)); + assertThat(config.fieldManager()).isEqualTo(config.getName()); + assertThat(config.getInformerListLimit()).isEmpty(); + assertThat(config.onAddFilter()).isEmpty(); + assertThat(config.onUpdateFilter()).isEmpty(); + assertThat(config.genericFilter()).isEmpty(); + assertThat(config.getNamespaces()).isEqualTo(Constants.DEFAULT_NAMESPACES_SET); + assertThat(config.getFinalizerName()).isEqualTo(ReconcilerUtils.getDefaultFinalizerName(config.getResourceClass())); + assertThat(config.getItemStore()).isEmpty(); } @SuppressWarnings("rawtypes") From 50e38c3550688872132e8ec350ac9e871745174e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Wed, 29 May 2024 10:24:58 +0200 Subject: [PATCH 4/8] format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../operator/config/BaseConfigurationServiceTest.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/BaseConfigurationServiceTest.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/BaseConfigurationServiceTest.java index 4e004fec92..d69b3092cf 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/BaseConfigurationServiceTest.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/BaseConfigurationServiceTest.java @@ -9,17 +9,17 @@ import java.util.Optional; import java.util.concurrent.TimeUnit; -import io.javaoperatorsdk.operator.ReconcilerUtils; -import io.javaoperatorsdk.operator.api.reconciler.*; import org.junit.jupiter.api.Test; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.ReconcilerUtils; import io.javaoperatorsdk.operator.api.config.AnnotationConfigurable; import io.javaoperatorsdk.operator.api.config.BaseConfigurationService; import io.javaoperatorsdk.operator.api.config.dependent.ConfigurationConverter; import io.javaoperatorsdk.operator.api.config.dependent.Configured; import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec; +import io.javaoperatorsdk.operator.api.reconciler.*; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; @@ -115,7 +115,8 @@ void missingAnnotationCreatesDefaultConfig() { assertThat(config.onUpdateFilter()).isEmpty(); assertThat(config.genericFilter()).isEmpty(); assertThat(config.getNamespaces()).isEqualTo(Constants.DEFAULT_NAMESPACES_SET); - assertThat(config.getFinalizerName()).isEqualTo(ReconcilerUtils.getDefaultFinalizerName(config.getResourceClass())); + assertThat(config.getFinalizerName()) + .isEqualTo(ReconcilerUtils.getDefaultFinalizerName(config.getResourceClass())); assertThat(config.getItemStore()).isEmpty(); } From 0b7804778ac70ff52afe5806b7b4417bf7f0e60b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 21 Jun 2024 15:32:57 +0200 Subject: [PATCH 5/8] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../api/config/BaseConfigurationService.java | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) 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 d50b242680..ec8a86f9ff 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 @@ -16,7 +16,6 @@ import io.fabric8.kubernetes.client.informers.cache.ItemStore; import io.javaoperatorsdk.operator.ReconcilerUtils; import io.javaoperatorsdk.operator.api.config.Utils.Configurator; -import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceConfigurationResolver; import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec; import io.javaoperatorsdk.operator.api.config.workflow.WorkflowSpec; import io.javaoperatorsdk.operator.api.reconciler.Constants; @@ -224,6 +223,45 @@ private

ResolvedControllerConfiguration

controllerCon this, informerListLimit); } + @SuppressWarnings({"unchecked", "rawtypes"}) + private static List dependentResources( + Workflow annotation, + ControllerConfiguration parent) { + final var dependents = annotation.dependents(); + + + if (dependents == null || dependents.length == 0) { + return Collections.emptyList(); + } + + final var specsMap = new LinkedHashMap(dependents.length); + for (Dependent dependent : dependents) { + final Class 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), + Utils.instantiate(dependent.activationCondition(), Condition.class, context), + eventSourceName); + specsMap.put(dependentName, spec); + } + return specsMap.values().stream().toList(); + } + protected boolean createIfNeeded() { return true; } From 9d7b67d5a6e235759161a9df8f883e41a5581a9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 21 Jun 2024 15:42:28 +0200 Subject: [PATCH 6/8] fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../api/config/BaseConfigurationService.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) 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 ec8a86f9ff..48ed98ffb5 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 @@ -16,6 +16,7 @@ import io.fabric8.kubernetes.client.informers.cache.ItemStore; import io.javaoperatorsdk.operator.ReconcilerUtils; import io.javaoperatorsdk.operator.api.config.Utils.Configurator; +import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceConfigurationResolver; import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec; import io.javaoperatorsdk.operator.api.config.workflow.WorkflowSpec; import io.javaoperatorsdk.operator.api.reconciler.Constants; @@ -226,7 +227,7 @@ private

ResolvedControllerConfiguration

controllerCon @SuppressWarnings({"unchecked", "rawtypes"}) private static List dependentResources( Workflow annotation, - ControllerConfiguration parent) { + ControllerConfiguration controllerConfiguration) { final var dependents = annotation.dependents(); @@ -245,7 +246,7 @@ private static List dependentResources( "A DependentResource named '" + dependentName + "' already exists: " + spec); } - final var name = parent.getName(); + final var name = controllerConfiguration.getName(); var eventSourceName = dependent.useEventSourceWithName(); eventSourceName = Constants.NO_VALUE_SET.equals(eventSourceName) ? null : eventSourceName; @@ -258,7 +259,15 @@ private static List dependentResources( Utils.instantiate(dependent.activationCondition(), Condition.class, context), eventSourceName); specsMap.put(dependentName, spec); + + // extract potential configuration + DependentResourceConfigurationResolver.configureSpecFromConfigured(spec, + controllerConfiguration, + dependentType); + + specsMap.put(dependentName, spec); } + return specsMap.values().stream().toList(); } From be8ee7314fcdfaf3366deef12001df0992affe25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 21 Jun 2024 15:49:46 +0200 Subject: [PATCH 7/8] reorder methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../api/config/BaseConfigurationService.java | 178 +++++++++--------- 1 file changed, 90 insertions(+), 88 deletions(-) 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 48ed98ffb5..854dc44d34 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 @@ -55,6 +55,96 @@ public BaseConfigurationService() { this(Utils.VERSION); } + @SuppressWarnings({"unchecked", "rawtypes"}) + private static List dependentResources( + Workflow annotation, + ControllerConfiguration controllerConfiguration) { + final var dependents = annotation.dependents(); + + + if (dependents == null || dependents.length == 0) { + return Collections.emptyList(); + } + + final var specsMap = new LinkedHashMap(dependents.length); + for (Dependent dependent : dependents) { + final Class 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 = controllerConfiguration.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), + Utils.instantiate(dependent.activationCondition(), Condition.class, context), + eventSourceName); + specsMap.put(dependentName, spec); + + // extract potential configuration + DependentResourceConfigurationResolver.configureSpecFromConfigured(spec, + controllerConfiguration, + dependentType); + + specsMap.put(dependentName, spec); + } + + return specsMap.values().stream().toList(); + } + + @SuppressWarnings("unchecked") + private static T valueOrDefaultFromAnnotation( + io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration controllerConfiguration, + Function mapper, + String defaultMethodName) { + try { + if (controllerConfiguration == null) { + return (T) io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration.class + .getDeclaredMethod(defaultMethodName).getDefaultValue(); + } else { + return mapper.apply(controllerConfiguration); + } + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + @SuppressWarnings("rawtypes") + private static String getName(String name, Class 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 configurable) { + final Class configurationClass = + (Class) Utils.getFirstTypeArgumentFromSuperClassOrInterface( + instance.getClass(), AnnotationConfigurable.class); + final var configAnnotation = reconciler.getClass().getAnnotation(configurationClass); + if (configAnnotation != null) { + configurable.initFrom(configAnnotation); + } + } + } @Override protected void logMissingReconcilerWarning(String reconcilerKey, String reconcilersNameMessage) { @@ -224,52 +314,6 @@ private

ResolvedControllerConfiguration

controllerCon this, informerListLimit); } - @SuppressWarnings({"unchecked", "rawtypes"}) - private static List dependentResources( - Workflow annotation, - ControllerConfiguration controllerConfiguration) { - final var dependents = annotation.dependents(); - - - if (dependents == null || dependents.length == 0) { - return Collections.emptyList(); - } - - final var specsMap = new LinkedHashMap(dependents.length); - for (Dependent dependent : dependents) { - final Class 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 = controllerConfiguration.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), - Utils.instantiate(dependent.activationCondition(), Condition.class, context), - eventSourceName); - specsMap.put(dependentName, spec); - - // extract potential configuration - DependentResourceConfigurationResolver.configureSpecFromConfigured(spec, - controllerConfiguration, - dependentType); - - specsMap.put(dependentName, spec); - } - - return specsMap.values().stream().toList(); - } protected boolean createIfNeeded() { return true; @@ -280,47 +324,5 @@ public boolean checkCRDAndValidateLocalModel() { return Utils.shouldCheckCRDAndValidateLocalModel(); } - @SuppressWarnings("unchecked") - private static T valueOrDefaultFromAnnotation( - io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration controllerConfiguration, - Function mapper, - String defaultMethodName) { - try { - if (controllerConfiguration == null) { - return (T) io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration.class - .getDeclaredMethod(defaultMethodName).getDefaultValue(); - } else { - return mapper.apply(controllerConfiguration); - } - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } - } - - @SuppressWarnings("rawtypes") - private static String getName(String name, Class 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 configurable) { - final Class configurationClass = - (Class) Utils.getFirstTypeArgumentFromSuperClassOrInterface( - instance.getClass(), AnnotationConfigurable.class); - final var configAnnotation = reconciler.getClass().getAnnotation(configurationClass); - if (configAnnotation != null) { - configurable.initFrom(configAnnotation); - } - } - } } From a22faa67e2df147b74edb2a4e72e62a9df31a16d Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Fri, 21 Jun 2024 16:23:50 +0200 Subject: [PATCH 8/8] refactor: simplify --- .../api/config/BaseConfigurationService.java | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) 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 854dc44d34..3383bde416 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 @@ -129,17 +129,18 @@ private static String getName(String name, Class de @SuppressWarnings("unused") private static Configurator configuratorFor(Class instanceType, - Reconciler reconciler) { - return instance -> configureFromAnnotatedReconciler(instance, reconciler); + Class> reconcilerClass) { + return instance -> configureFromAnnotatedReconciler(instance, reconcilerClass); } @SuppressWarnings({"unchecked", "rawtypes"}) - private static void configureFromAnnotatedReconciler(Object instance, Reconciler reconciler) { + private static void configureFromAnnotatedReconciler(Object instance, + Class> reconcilerClass) { if (instance instanceof AnnotationConfigurable configurable) { final Class configurationClass = (Class) Utils.getFirstTypeArgumentFromSuperClassOrInterface( instance.getClass(), AnnotationConfigurable.class); - final var configAnnotation = reconciler.getClass().getAnnotation(configurationClass); + final var configAnnotation = reconcilerClass.getAnnotation(configurationClass); if (configAnnotation != null) { configurable.initFrom(configAnnotation); } @@ -196,12 +197,15 @@ protected ResourceClassResolver getResourceClassResolver() { @SuppressWarnings({"unchecked", "rawtypes"}) protected

ControllerConfiguration

configFor(Reconciler

reconciler) { - final var annotation = reconciler.getClass().getAnnotation( + final Class> reconcilerClass = + (Class>) reconciler.getClass(); + final var controllerAnnotation = reconcilerClass.getAnnotation( io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration.class); - ResolvedControllerConfiguration

config = controllerConfiguration(reconciler, annotation); + ResolvedControllerConfiguration

config = + controllerConfiguration(reconcilerClass, controllerAnnotation); - final var workflowAnnotation = reconciler.getClass().getAnnotation( + final var workflowAnnotation = reconcilerClass.getAnnotation( io.javaoperatorsdk.operator.api.reconciler.Workflow.class); if (workflowAnnotation != null) { final var specs = dependentResources(workflowAnnotation, config); @@ -230,18 +234,17 @@ public boolean handleExceptionsInReconciler() { @SuppressWarnings({"unchecked"}) private

ResolvedControllerConfiguration

controllerConfiguration( - Reconciler

reconciler, + Class> reconcilerClass, io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration annotation) { - Class> reconcilerClass = (Class>) reconciler.getClass(); final var resourceClass = getResourceClassResolver().getPrimaryResourceClass(reconcilerClass); - final var name = ReconcilerUtils.getNameFor(reconciler); + final var name = ReconcilerUtils.getNameFor(reconcilerClass); final var generationAware = valueOrDefaultFromAnnotation( annotation, io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::generationAwareEventProcessing, "generationAwareEventProcessing"); final var associatedReconcilerClass = - ResolvedControllerConfiguration.getAssociatedReconcilerClassName(reconciler.getClass()); + ResolvedControllerConfiguration.getAssociatedReconcilerClassName(reconcilerClass); final var context = Utils.contextFor(name); final Class retryClass = @@ -249,14 +252,14 @@ private

ResolvedControllerConfiguration

controllerCon io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::retry, "retry"); final var retry = Utils.instantiateAndConfigureIfNeeded(retryClass, Retry.class, - context, configuratorFor(Retry.class, reconciler)); - + context, configuratorFor(Retry.class, reconcilerClass)); + @SuppressWarnings("rawtypes") final Class rateLimiterClass = valueOrDefaultFromAnnotation(annotation, io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::rateLimiter, "rateLimiter"); final var rateLimiter = Utils.instantiateAndConfigureIfNeeded(rateLimiterClass, - RateLimiter.class, context, configuratorFor(RateLimiter.class, reconciler)); + RateLimiter.class, context, configuratorFor(RateLimiter.class, reconcilerClass)); final var reconciliationInterval = valueOrDefaultFromAnnotation(annotation, io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::maxReconciliationInterval,