Skip to content

Commit d5d1f81

Browse files
csvirimetacosm
andcommitted
feat: @ControllerConfiguration annotation is optional (#2412)
Signed-off-by: Attila Mészáros <csviri@gmail.com> Co-authored-by: Chris Laprun <claprun@redhat.com>
1 parent c21cfa5 commit d5d1f81

File tree

3 files changed

+128
-83
lines changed

3 files changed

+128
-83
lines changed

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java

+107-72
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import io.fabric8.kubernetes.api.model.HasMetadata;
1515
import io.fabric8.kubernetes.client.KubernetesClient;
1616
import io.fabric8.kubernetes.client.informers.cache.ItemStore;
17-
import io.javaoperatorsdk.operator.OperatorException;
1817
import io.javaoperatorsdk.operator.ReconcilerUtils;
1918
import io.javaoperatorsdk.operator.api.config.Utils.Configurator;
2019
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceConfigurationResolver;
@@ -33,7 +32,6 @@
3332
import io.javaoperatorsdk.operator.processing.retry.Retry;
3433

3534
import static io.javaoperatorsdk.operator.api.config.ControllerConfiguration.CONTROLLER_NAME_AS_FIELD_MANAGER;
36-
import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_NAMESPACES_SET;
3735

3836
public class BaseConfigurationService extends AbstractConfigurationService {
3937

@@ -91,6 +89,7 @@ private static List<DependentResourceSpec> dependentResources(
9189
Utils.instantiate(dependent.deletePostcondition(), Condition.class, context),
9290
Utils.instantiate(dependent.activationCondition(), Condition.class, context),
9391
eventSourceName);
92+
specsMap.put(dependentName, spec);
9493

9594
// extract potential configuration
9695
DependentResourceConfigurationResolver.configureSpecFromConfigured(spec,
@@ -99,17 +98,24 @@ private static List<DependentResourceSpec> dependentResources(
9998

10099
specsMap.put(dependentName, spec);
101100
}
101+
102102
return specsMap.values().stream().toList();
103103
}
104104

105-
private static <T> T valueOrDefault(
105+
@SuppressWarnings("unchecked")
106+
private static <T> T valueOrDefaultFromAnnotation(
106107
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration controllerConfiguration,
107108
Function<io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration, T> mapper,
108-
T defaultValue) {
109-
if (controllerConfiguration == null) {
110-
return defaultValue;
111-
} else {
112-
return mapper.apply(controllerConfiguration);
109+
String defaultMethodName) {
110+
try {
111+
if (controllerConfiguration == null) {
112+
return (T) io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration.class
113+
.getDeclaredMethod(defaultMethodName).getDefaultValue();
114+
} else {
115+
return mapper.apply(controllerConfiguration);
116+
}
117+
} catch (NoSuchMethodException e) {
118+
throw new RuntimeException(e);
113119
}
114120
}
115121

@@ -123,17 +129,18 @@ private static String getName(String name, Class<? extends DependentResource> de
123129

124130
@SuppressWarnings("unused")
125131
private static <T> Configurator<T> configuratorFor(Class<T> instanceType,
126-
Reconciler<?> reconciler) {
127-
return instance -> configureFromAnnotatedReconciler(instance, reconciler);
132+
Class<? extends Reconciler<?>> reconcilerClass) {
133+
return instance -> configureFromAnnotatedReconciler(instance, reconcilerClass);
128134
}
129135

130136
@SuppressWarnings({"unchecked", "rawtypes"})
131-
private static void configureFromAnnotatedReconciler(Object instance, Reconciler<?> reconciler) {
137+
private static void configureFromAnnotatedReconciler(Object instance,
138+
Class<? extends Reconciler<?>> reconcilerClass) {
132139
if (instance instanceof AnnotationConfigurable configurable) {
133140
final Class<? extends Annotation> configurationClass =
134141
(Class<? extends Annotation>) Utils.getFirstTypeArgumentFromSuperClassOrInterface(
135142
instance.getClass(), AnnotationConfigurable.class);
136-
final var configAnnotation = reconciler.getClass().getAnnotation(configurationClass);
143+
final var configAnnotation = reconcilerClass.getAnnotation(configurationClass);
137144
if (configAnnotation != null) {
138145
configurable.initFrom(configAnnotation);
139146
}
@@ -190,101 +197,127 @@ protected ResourceClassResolver getResourceClassResolver() {
190197

191198
@SuppressWarnings({"unchecked", "rawtypes"})
192199
protected <P extends HasMetadata> ControllerConfiguration<P> configFor(Reconciler<P> reconciler) {
193-
final var annotation = reconciler.getClass().getAnnotation(
200+
final Class<? extends Reconciler<P>> reconcilerClass =
201+
(Class<? extends Reconciler<P>>) reconciler.getClass();
202+
final var controllerAnnotation = reconcilerClass.getAnnotation(
194203
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration.class);
195204

196-
if (annotation == null) {
197-
throw new OperatorException(
198-
"Missing mandatory @"
199-
+ io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration.class
200-
.getSimpleName()
201-
+
202-
" annotation for reconciler: " + reconciler);
205+
ResolvedControllerConfiguration<P> config =
206+
controllerConfiguration(reconcilerClass, controllerAnnotation);
207+
208+
final var workflowAnnotation = reconcilerClass.getAnnotation(
209+
io.javaoperatorsdk.operator.api.reconciler.Workflow.class);
210+
if (workflowAnnotation != null) {
211+
final var specs = dependentResources(workflowAnnotation, config);
212+
WorkflowSpec workflowSpec = new WorkflowSpec() {
213+
@Override
214+
public List<DependentResourceSpec> getDependentResourceSpecs() {
215+
return specs;
216+
}
217+
218+
@Override
219+
public boolean isExplicitInvocation() {
220+
return workflowAnnotation.explicitInvocation();
221+
}
222+
223+
@Override
224+
public boolean handleExceptionsInReconciler() {
225+
return workflowAnnotation.handleExceptionsInReconciler();
226+
}
227+
228+
};
229+
config.setWorkflowSpec(workflowSpec);
203230
}
204-
Class<Reconciler<P>> reconcilerClass = (Class<Reconciler<P>>) reconciler.getClass();
231+
232+
return config;
233+
}
234+
235+
@SuppressWarnings({"unchecked"})
236+
private <P extends HasMetadata> ResolvedControllerConfiguration<P> controllerConfiguration(
237+
Class<? extends Reconciler<P>> reconcilerClass,
238+
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration annotation) {
205239
final var resourceClass = getResourceClassResolver().getPrimaryResourceClass(reconcilerClass);
206240

207-
final var name = ReconcilerUtils.getNameFor(reconciler);
208-
final var generationAware = valueOrDefault(
241+
final var name = ReconcilerUtils.getNameFor(reconcilerClass);
242+
final var generationAware = valueOrDefaultFromAnnotation(
209243
annotation,
210244
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::generationAwareEventProcessing,
211-
true);
245+
"generationAwareEventProcessing");
212246
final var associatedReconcilerClass =
213-
ResolvedControllerConfiguration.getAssociatedReconcilerClassName(reconciler.getClass());
247+
ResolvedControllerConfiguration.getAssociatedReconcilerClassName(reconcilerClass);
214248

215249
final var context = Utils.contextFor(name);
216-
final Class<? extends Retry> retryClass = annotation.retry();
250+
final Class<? extends Retry> retryClass =
251+
valueOrDefaultFromAnnotation(annotation,
252+
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::retry,
253+
"retry");
217254
final var retry = Utils.instantiateAndConfigureIfNeeded(retryClass, Retry.class,
218-
context, configuratorFor(Retry.class, reconciler));
255+
context, configuratorFor(Retry.class, reconcilerClass));
219256

220-
final Class<? extends RateLimiter> rateLimiterClass = annotation.rateLimiter();
257+
@SuppressWarnings("rawtypes")
258+
final Class<? extends RateLimiter> rateLimiterClass = valueOrDefaultFromAnnotation(annotation,
259+
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::rateLimiter,
260+
"rateLimiter");
221261
final var rateLimiter = Utils.instantiateAndConfigureIfNeeded(rateLimiterClass,
222-
RateLimiter.class, context, configuratorFor(RateLimiter.class, reconciler));
262+
RateLimiter.class, context, configuratorFor(RateLimiter.class, reconcilerClass));
223263

224-
final var reconciliationInterval = annotation.maxReconciliationInterval();
264+
final var reconciliationInterval = valueOrDefaultFromAnnotation(annotation,
265+
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::maxReconciliationInterval,
266+
"maxReconciliationInterval");
225267
long interval = -1;
226268
TimeUnit timeUnit = null;
227269
if (reconciliationInterval != null && reconciliationInterval.interval() > 0) {
228270
interval = reconciliationInterval.interval();
229271
timeUnit = reconciliationInterval.timeUnit();
230272
}
231273

274+
var fieldManager = valueOrDefaultFromAnnotation(annotation,
275+
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::fieldManager,
276+
"fieldManager");
232277
final var dependentFieldManager =
233-
annotation.fieldManager().equals(CONTROLLER_NAME_AS_FIELD_MANAGER) ? name
234-
: annotation.fieldManager();
278+
fieldManager.equals(CONTROLLER_NAME_AS_FIELD_MANAGER) ? name
279+
: fieldManager;
235280

281+
var informerListLimitValue = valueOrDefaultFromAnnotation(annotation,
282+
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::informerListLimit,
283+
"informerListLimit");
236284
final var informerListLimit =
237-
annotation.informerListLimit() == Constants.NO_LONG_VALUE_SET ? null
238-
: annotation.informerListLimit();
285+
informerListLimitValue == Constants.NO_LONG_VALUE_SET ? null
286+
: informerListLimitValue;
239287

240-
final var config = new ResolvedControllerConfiguration<P>(
288+
return new ResolvedControllerConfiguration<P>(
241289
resourceClass, name, generationAware,
242290
associatedReconcilerClass, retry, rateLimiter,
243291
ResolvedControllerConfiguration.getMaxReconciliationInterval(interval, timeUnit),
244-
Utils.instantiate(annotation.onAddFilter(), OnAddFilter.class, context),
245-
Utils.instantiate(annotation.onUpdateFilter(), OnUpdateFilter.class, context),
246-
Utils.instantiate(annotation.genericFilter(), GenericFilter.class, context),
247-
Set.of(valueOrDefault(annotation,
292+
Utils.instantiate(valueOrDefaultFromAnnotation(annotation,
293+
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::onAddFilter,
294+
"onAddFilter"), OnAddFilter.class, context),
295+
Utils.instantiate(valueOrDefaultFromAnnotation(annotation,
296+
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::onUpdateFilter,
297+
"onUpdateFilter"), OnUpdateFilter.class, context),
298+
Utils.instantiate(valueOrDefaultFromAnnotation(annotation,
299+
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::genericFilter,
300+
"genericFilter"), GenericFilter.class, context),
301+
Set.of(valueOrDefaultFromAnnotation(annotation,
248302
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::namespaces,
249-
DEFAULT_NAMESPACES_SET.toArray(String[]::new))),
250-
valueOrDefault(annotation,
303+
"namespaces")),
304+
valueOrDefaultFromAnnotation(annotation,
251305
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::finalizerName,
252-
Constants.NO_VALUE_SET),
253-
valueOrDefault(annotation,
306+
"finalizerName"),
307+
valueOrDefaultFromAnnotation(annotation,
254308
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::labelSelector,
255-
Constants.NO_VALUE_SET),
309+
"labelSelector"),
256310
null,
257-
Utils.instantiate(annotation.itemStore(), ItemStore.class, context), dependentFieldManager,
311+
Utils.instantiate(
312+
valueOrDefaultFromAnnotation(annotation,
313+
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::itemStore,
314+
"itemStore"),
315+
ItemStore.class, context),
316+
dependentFieldManager,
258317
this, informerListLimit);
259-
260-
261-
final var workflowAnnotation = reconciler.getClass().getAnnotation(
262-
io.javaoperatorsdk.operator.api.reconciler.Workflow.class);
263-
if (workflowAnnotation != null) {
264-
final var specs = dependentResources(workflowAnnotation, config);
265-
WorkflowSpec workflowSpec = new WorkflowSpec() {
266-
@Override
267-
public List<DependentResourceSpec> getDependentResourceSpecs() {
268-
return specs;
269-
}
270-
271-
@Override
272-
public boolean isExplicitInvocation() {
273-
return workflowAnnotation.explicitInvocation();
274-
}
275-
276-
@Override
277-
public boolean handleExceptionsInReconciler() {
278-
return workflowAnnotation.handleExceptionsInReconciler();
279-
}
280-
281-
};
282-
config.setWorkflowSpec(workflowSpec);
283-
}
284-
285-
return config;
286318
}
287319

320+
288321
protected boolean createIfNeeded() {
289322
return true;
290323
}
@@ -293,4 +326,6 @@ protected boolean createIfNeeded() {
293326
public boolean checkCRDAndValidateLocalModel() {
294327
return Utils.shouldCheckCRDAndValidateLocalModel();
295328
}
329+
330+
296331
}

operator-framework/src/test/java/io/javaoperatorsdk/operator/config/BaseConfigurationServiceTest.java

+21-10
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,18 @@
99
import java.util.Optional;
1010
import java.util.concurrent.TimeUnit;
1111

12-
import org.junit.jupiter.api.Assertions;
1312
import org.junit.jupiter.api.Test;
1413

1514
import io.fabric8.kubernetes.api.model.ConfigMap;
1615
import io.fabric8.kubernetes.api.model.HasMetadata;
1716
import io.fabric8.kubernetes.api.model.Service;
18-
import io.javaoperatorsdk.operator.OperatorException;
17+
import io.javaoperatorsdk.operator.ReconcilerUtils;
1918
import io.javaoperatorsdk.operator.api.config.AnnotationConfigurable;
2019
import io.javaoperatorsdk.operator.api.config.BaseConfigurationService;
2120
import io.javaoperatorsdk.operator.api.config.dependent.ConfigurationConverter;
2221
import io.javaoperatorsdk.operator.api.config.dependent.Configured;
2322
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
24-
import io.javaoperatorsdk.operator.api.reconciler.Context;
25-
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
26-
import io.javaoperatorsdk.operator.api.reconciler.MaxReconciliationInterval;
27-
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
28-
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
29-
import io.javaoperatorsdk.operator.api.reconciler.Workflow;
23+
import io.javaoperatorsdk.operator.api.reconciler.*;
3024
import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
3125
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
3226
import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult;
@@ -45,6 +39,8 @@
4539
import io.javaoperatorsdk.operator.sample.readonly.ConfigMapReader;
4640
import io.javaoperatorsdk.operator.sample.readonly.ReadOnlyDependent;
4741

42+
import static io.javaoperatorsdk.operator.api.reconciler.MaxReconciliationInterval.DEFAULT_INTERVAL;
43+
import static org.assertj.core.api.Assertions.assertThat;
4844
import static org.junit.jupiter.api.Assertions.*;
4945

5046
class BaseConfigurationServiceTest {
@@ -108,9 +104,24 @@ void getDependentResources() {
108104
}
109105

110106
@Test
111-
void missingAnnotationThrowsException() {
107+
void missingAnnotationCreatesDefaultConfig() {
112108
final var reconciler = new MissingAnnotationReconciler();
113-
Assertions.assertThrows(OperatorException.class, () -> configFor(reconciler));
109+
var config = configFor(reconciler);
110+
111+
assertThat(config.getName()).isEqualTo(ReconcilerUtils.getNameFor(reconciler));
112+
assertThat(config.getLabelSelector()).isEmpty();
113+
assertThat(config.getRetry()).isInstanceOf(GenericRetry.class);
114+
assertThat(config.getRateLimiter()).isInstanceOf(LinearRateLimiter.class);
115+
assertThat(config.maxReconciliationInterval()).hasValue(Duration.ofHours(DEFAULT_INTERVAL));
116+
assertThat(config.fieldManager()).isEqualTo(config.getName());
117+
assertThat(config.getInformerListLimit()).isEmpty();
118+
assertThat(config.onAddFilter()).isEmpty();
119+
assertThat(config.onUpdateFilter()).isEmpty();
120+
assertThat(config.genericFilter()).isEmpty();
121+
assertThat(config.getNamespaces()).isEqualTo(Constants.DEFAULT_NAMESPACES_SET);
122+
assertThat(config.getFinalizerName())
123+
.isEqualTo(ReconcilerUtils.getDefaultFinalizerName(config.getResourceClass()));
124+
assertThat(config.getItemStore()).isEmpty();
114125
}
115126

116127
@SuppressWarnings("rawtypes")

sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageManagedDependentsReconciler.java

-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
@Dependent(type = IngressDependentResource.class,
1919
reconcilePrecondition = ExposedIngressCondition.class)
2020
})
21-
@ControllerConfiguration
2221
public class WebPageManagedDependentsReconciler
2322
implements Reconciler<WebPage>, Cleaner<WebPage> {
2423

0 commit comments

Comments
 (0)