Skip to content

Commit 513a40a

Browse files
committed
feat: compute managed workflow graphs w/o requiring dependent resources (#1647)
This allows for workflows to be computed at build time, only to be resolved (i.e. associating workflow nodes to actual dependent resources) when needed. In the process, the configuration mechanism of managed dependent resource has been improved to make it possible to resolve the configuration solely from the dependent resource class instead of previously requiring the dependent to be instantiated. This is now done viw the DependentResourceConfigurationResolver class.
1 parent 28a86ff commit 513a40a

File tree

42 files changed

+1188
-722
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1188
-722
lines changed

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

+11-35
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
2121
import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
2222
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
23-
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.AnnotationDependentResourceConfigurator;
2423
import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition;
2524
import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
2625
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
@@ -121,19 +120,17 @@ public ResourceEventFilter<P> getEventFilter() {
121120
Class<ResourceEventFilter<P>>[] filterTypes =
122121
(Class<ResourceEventFilter<P>>[]) valueOrDefault(annotation,
123122
ControllerConfiguration::eventFilters, new Object[] {});
124-
if (filterTypes.length > 0) {
125-
for (var filterType : filterTypes) {
126-
try {
127-
ResourceEventFilter<P> filter = filterType.getConstructor().newInstance();
128-
129-
if (answer == null) {
130-
answer = filter;
131-
} else {
132-
answer = answer.and(filter);
133-
}
134-
} catch (Exception e) {
135-
throw new IllegalArgumentException(e);
123+
for (var filterType : filterTypes) {
124+
try {
125+
ResourceEventFilter<P> filter = filterType.getConstructor().newInstance();
126+
127+
if (answer == null) {
128+
answer = filter;
129+
} else {
130+
answer = answer.and(filter);
136131
}
132+
} catch (Exception e) {
133+
throw new IllegalArgumentException(e);
137134
}
138135
}
139136
return answer != null ? answer : ResourceEventFilters.passthrough();
@@ -177,22 +174,6 @@ private void configureFromAnnotatedReconciler(Object instance) {
177174
}
178175
}
179176

180-
@SuppressWarnings("unchecked")
181-
private void configureFromCustomAnnotation(Object instance) {
182-
if (instance instanceof AnnotationDependentResourceConfigurator) {
183-
AnnotationDependentResourceConfigurator configurator =
184-
(AnnotationDependentResourceConfigurator) instance;
185-
final Class<? extends Annotation> configurationClass =
186-
(Class<? extends Annotation>) Utils.getFirstTypeArgumentFromInterface(
187-
instance.getClass(), AnnotationDependentResourceConfigurator.class);
188-
final var configAnnotation = instance.getClass().getAnnotation(configurationClass);
189-
// always called even if the annotation is null so that implementations can provide default
190-
// values
191-
final var config = configurator.configFrom(configAnnotation, this);
192-
configurator.configureWith(config);
193-
}
194-
}
195-
196177
@Override
197178
@SuppressWarnings("unchecked")
198179
public Optional<OnAddFilter<P>> onAddFilter() {
@@ -239,15 +220,10 @@ public List<DependentResourceSpec> getDependentResources() {
239220
"A DependentResource named '" + name + "' already exists: " + spec);
240221
}
241222

242-
final var dependentResource = Utils.instantiateAndConfigureIfNeeded(dependentType,
243-
DependentResource.class,
244-
Utils.contextFor(this, dependentType, Dependent.class),
245-
this::configureFromCustomAnnotation);
246-
247223
var eventSourceName = dependent.useEventSourceWithName();
248224
eventSourceName = Constants.NO_VALUE_SET.equals(eventSourceName) ? null : eventSourceName;
249225
final var context = Utils.contextFor(this, dependentType, null);
250-
spec = new DependentResourceSpec(dependentResource, name,
226+
spec = new DependentResourceSpec(dependentType, name,
251227
Set.of(dependent.dependsOn()),
252228
Utils.instantiate(dependent.readyPostcondition(), Condition.class, context),
253229
Utils.instantiate(dependent.reconcilePrecondition(), Condition.class, context),

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,9 @@ default ObjectMapper getObjectMapper() {
145145
return Serialization.jsonMapper();
146146
}
147147

148-
@Deprecated(forRemoval = true)
148+
@SuppressWarnings("rawtypes")
149149
default DependentResourceFactory dependentResourceFactory() {
150-
return null;
150+
return DependentResourceFactory.DEFAULT;
151151
}
152152

153153
default Optional<LeaderElectionConfiguration> getLeaderElectionConfiguration() {

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

+14-34
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
package io.javaoperatorsdk.operator.api.config;
22

33
import java.time.Duration;
4+
import java.util.HashMap;
45
import java.util.HashSet;
5-
import java.util.LinkedHashMap;
66
import java.util.List;
7-
import java.util.Optional;
7+
import java.util.Map;
88
import java.util.Set;
99
import java.util.function.UnaryOperator;
10-
import java.util.stream.Collectors;
1110

1211
import io.fabric8.kubernetes.api.model.HasMetadata;
1312
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
14-
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DependentResourceConfigurator;
1513
import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
1614
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
1715
import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter;
@@ -34,12 +32,12 @@ public class ControllerConfigurationOverrider<R extends HasMetadata> {
3432
private ResourceEventFilter<R> customResourcePredicate;
3533
private final ControllerConfiguration<R> original;
3634
private Duration reconciliationMaxInterval;
37-
private final LinkedHashMap<String, DependentResourceSpec> namedDependentResourceSpecs;
3835
private OnAddFilter<R> onAddFilter;
3936
private OnUpdateFilter<R> onUpdateFilter;
4037
private GenericFilter<R> genericFilter;
4138
private RateLimiter rateLimiter;
4239
private UnaryOperator<R> cachePruneFunction;
40+
private Map<DependentResourceSpec, Object> configurations;
4341

4442
private ControllerConfigurationOverrider(ControllerConfiguration<R> original) {
4543
finalizer = original.getFinalizerName();
@@ -49,13 +47,9 @@ private ControllerConfigurationOverrider(ControllerConfiguration<R> original) {
4947
labelSelector = original.getLabelSelector();
5048
customResourcePredicate = original.getEventFilter();
5149
reconciliationMaxInterval = original.maxReconciliationInterval().orElse(null);
52-
// make the original specs modifiable
53-
final var dependentResources = original.getDependentResources();
54-
namedDependentResourceSpecs = new LinkedHashMap<>(dependentResources.size());
5550
this.onAddFilter = original.onAddFilter().orElse(null);
5651
this.onUpdateFilter = original.onUpdateFilter().orElse(null);
5752
this.genericFilter = original.genericFilter().orElse(null);
58-
dependentResources.forEach(drs -> namedDependentResourceSpecs.put(drs.getName(), drs));
5953
this.original = original;
6054
this.rateLimiter = original.getRateLimiter();
6155
this.cachePruneFunction = original.cachePruneFunction().orElse(null);
@@ -167,40 +161,24 @@ public ControllerConfigurationOverrider<R> withCachePruneFunction(
167161
return this;
168162
}
169163

170-
@SuppressWarnings("unchecked")
171164
public ControllerConfigurationOverrider<R> replacingNamedDependentResourceConfig(String name,
172165
Object dependentResourceConfig) {
173166

174-
var current = namedDependentResourceSpecs.get(name);
175-
if (current == null) {
176-
throw new IllegalArgumentException("Cannot find a DependentResource named: " + name);
177-
}
167+
final var specs = original.getDependentResources();
168+
final var spec = specs.stream()
169+
.filter(drs -> drs.getName().equals(name)).findFirst()
170+
.orElseThrow(
171+
() -> new IllegalArgumentException("Cannot find a DependentResource named: " + name));
178172

179-
var dependentResource = current.getDependentResource();
180-
if (dependentResource instanceof DependentResourceConfigurator) {
181-
var configurator = (DependentResourceConfigurator) dependentResource;
182-
configurator.configureWith(dependentResourceConfig);
173+
if (configurations == null) {
174+
configurations = new HashMap<>(specs.size());
183175
}
176+
configurations.put(spec, dependentResourceConfig);
184177

185178
return this;
186179
}
187180

188181
public ControllerConfiguration<R> build() {
189-
final var hasModifiedNamespaces = !original.getNamespaces().equals(namespaces);
190-
final var newDependentSpecs = namedDependentResourceSpecs.values().stream()
191-
.peek(spec -> {
192-
// if the dependent resource has a NamespaceChangeable config
193-
// update the namespaces if needed, otherwise, do nothing
194-
if (hasModifiedNamespaces) {
195-
final Optional<?> maybeConfig = spec.getDependentResourceConfiguration();
196-
maybeConfig
197-
.filter(NamespaceChangeable.class::isInstance)
198-
.map(NamespaceChangeable.class::cast)
199-
.filter(NamespaceChangeable::allowsNamespaceChanges)
200-
.ifPresent(nc -> nc.changeNamespaces(namespaces));
201-
}
202-
}).collect(Collectors.toList());
203-
204182
return new DefaultControllerConfiguration<>(
205183
original.getAssociatedReconcilerClassName(),
206184
original.getName(),
@@ -217,7 +195,9 @@ public ControllerConfiguration<R> build() {
217195
onUpdateFilter,
218196
genericFilter,
219197
rateLimiter,
220-
newDependentSpecs, cachePruneFunction);
198+
original.getDependentResources(),
199+
cachePruneFunction,
200+
configurations);
221201
}
222202

223203
public static <R extends HasMetadata> ControllerConfigurationOverrider<R> override(

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

+34-1
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
import java.time.Duration;
44
import java.util.Collections;
55
import java.util.List;
6+
import java.util.Map;
67
import java.util.Optional;
78
import java.util.Set;
89
import java.util.function.UnaryOperator;
910

1011
import io.fabric8.kubernetes.api.model.HasMetadata;
12+
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceConfigurationProvider;
1113
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
1214
import io.javaoperatorsdk.operator.processing.event.rate.LinearRateLimiter;
1315
import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
@@ -20,7 +22,7 @@
2022
@SuppressWarnings("rawtypes")
2123
public class DefaultControllerConfiguration<R extends HasMetadata>
2224
extends DefaultResourceConfiguration<R>
23-
implements ControllerConfiguration<R> {
25+
implements ControllerConfiguration<R>, DependentResourceConfigurationProvider {
2426

2527
private final String associatedControllerClassName;
2628
private final String name;
@@ -32,6 +34,7 @@ public class DefaultControllerConfiguration<R extends HasMetadata>
3234
private final List<DependentResourceSpec> dependents;
3335
private final Duration reconciliationMaxInterval;
3436
private final RateLimiter rateLimiter;
37+
private final Map<DependentResourceSpec, Object> configurations;
3538

3639
// NOSONAR constructor is meant to provide all information
3740
public DefaultControllerConfiguration(
@@ -52,6 +55,31 @@ public DefaultControllerConfiguration(
5255
RateLimiter rateLimiter,
5356
List<DependentResourceSpec> dependents,
5457
UnaryOperator<R> cachePruneFunction) {
58+
this(associatedControllerClassName, name, crdName, finalizer, generationAware, namespaces,
59+
retry, labelSelector, resourceEventFilter, resourceClass, reconciliationMaxInterval,
60+
onAddFilter, onUpdateFilter, genericFilter, rateLimiter, dependents, cachePruneFunction,
61+
null);
62+
}
63+
64+
DefaultControllerConfiguration(
65+
String associatedControllerClassName,
66+
String name,
67+
String crdName,
68+
String finalizer,
69+
boolean generationAware,
70+
Set<String> namespaces,
71+
Retry retry,
72+
String labelSelector,
73+
ResourceEventFilter<R> resourceEventFilter,
74+
Class<R> resourceClass,
75+
Duration reconciliationMaxInterval,
76+
OnAddFilter<R> onAddFilter,
77+
OnUpdateFilter<R> onUpdateFilter,
78+
GenericFilter<R> genericFilter,
79+
RateLimiter rateLimiter,
80+
List<DependentResourceSpec> dependents,
81+
UnaryOperator<R> cachePruneFunction,
82+
Map<DependentResourceSpec, Object> configurations) {
5583
super(labelSelector, resourceClass, onAddFilter, onUpdateFilter, genericFilter, namespaces,
5684
cachePruneFunction);
5785
this.associatedControllerClassName = associatedControllerClassName;
@@ -68,6 +96,7 @@ public DefaultControllerConfiguration(
6896
this.rateLimiter =
6997
rateLimiter != null ? rateLimiter : LinearRateLimiter.deactivatedRateLimiter();
7098
this.dependents = dependents != null ? dependents : Collections.emptyList();
99+
this.configurations = configurations != null ? configurations : Collections.emptyMap();
71100
}
72101

73102
@Override
@@ -120,4 +149,8 @@ public RateLimiter getRateLimiter() {
120149
return rateLimiter;
121150
}
122151

152+
@Override
153+
public Object getConfigurationFor(DependentResourceSpec spec) {
154+
return configurations.get(spec);
155+
}
123156
}

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

-1
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,6 @@ public static String contextFor(ControllerConfiguration<?> controllerConfigurati
237237
}
238238
context += "reconciler: " + controllerConfiguration.getName();
239239

240-
241240
return context;
242241
}
243242
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package io.javaoperatorsdk.operator.api.config.dependent;
2+
3+
import java.lang.annotation.Annotation;
4+
5+
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
6+
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DependentResourceConfigurator;
7+
8+
public interface ConfigurationConverter<A extends Annotation, C, D extends DependentResourceConfigurator<? extends C>> {
9+
10+
C configFrom(A configAnnotation, ControllerConfiguration<?> parentConfiguration,
11+
Class<D> originatingClass);
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.javaoperatorsdk.operator.api.config.dependent;
2+
3+
import java.lang.annotation.Annotation;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
7+
@Retention(RetentionPolicy.RUNTIME)
8+
public @interface Configured {
9+
10+
Class<? extends Annotation> by();
11+
12+
Class<?> with();
13+
14+
@SuppressWarnings("rawtypes")
15+
Class<? extends ConfigurationConverter> converter();
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package io.javaoperatorsdk.operator.api.config.dependent;
2+
3+
public interface DependentResourceConfigurationProvider {
4+
@SuppressWarnings("rawtypes")
5+
Object getConfigurationFor(DependentResourceSpec spec);
6+
}

0 commit comments

Comments
 (0)