Skip to content

Commit 8d5b09f

Browse files
metacosmcsviri
authored andcommitted
refactor: isolate bulk dependent resource handling more (#1530)
1 parent 55c8014 commit 8d5b09f

File tree

7 files changed

+229
-73
lines changed

7 files changed

+229
-73
lines changed

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java

Lines changed: 23 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator;
1212
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
1313
import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult;
14+
import io.javaoperatorsdk.operator.processing.dependent.Matcher.Result;
1415
import io.javaoperatorsdk.operator.processing.event.ResourceID;
1516

1617
@Ignore
@@ -20,78 +21,50 @@ public abstract class AbstractDependentResource<R, P extends HasMetadata>
2021

2122
private final boolean creatable = this instanceof Creator;
2223
private final boolean updatable = this instanceof Updater;
23-
private final boolean bulk = this instanceof BulkDependentResource;
2424

2525
protected Creator<R, P> creator;
2626
protected Updater<R, P> updater;
27-
protected BulkDependentResource<R, P> bulkDependentResource;
2827
private ResourceDiscriminator<R, P> resourceDiscriminator;
28+
private final DependentResourceReconciler<R, P> dependentResourceReconciler;
2929

30-
@SuppressWarnings({"unchecked", "rawtypes"})
30+
@SuppressWarnings({"unchecked"})
3131
protected AbstractDependentResource() {
3232
creator = creatable ? (Creator<R, P>) this : null;
3333
updater = updatable ? (Updater<R, P>) this : null;
3434

35-
bulkDependentResource = bulk ? (BulkDependentResource) this : null;
35+
dependentResourceReconciler = this instanceof BulkDependentResource
36+
? new BulkDependentResourceReconciler<>((BulkDependentResource<R, P>) this)
37+
: new SingleDependentResourceReconciler<>(this);
3638
}
3739

38-
40+
/**
41+
* Overriding classes are strongly encouraged to call this implementation as part of their
42+
* implementation, as they otherwise risk breaking functionality.
43+
*
44+
* @param primary the primary resource for which we want to reconcile the dependent state
45+
* @param context {@link Context} providing useful contextual information
46+
* @return the reconciliation result
47+
*/
3948
@Override
4049
public ReconcileResult<R> reconcile(P primary, Context<P> context) {
41-
if (bulk) {
42-
final var targetResources = bulkDependentResource.desiredResources(primary, context);
43-
44-
Map<String, R> actualResources =
45-
bulkDependentResource.getSecondaryResources(primary, context);
46-
47-
deleteBulkResourcesIfRequired(targetResources.keySet(), actualResources, primary, context);
48-
final List<ReconcileResult<R>> results = new ArrayList<>(targetResources.size());
49-
50-
targetResources.forEach((key, resource) -> {
51-
results.add(reconcileIndexAware(primary, actualResources.get(key), resource, key, context));
52-
});
53-
54-
return ReconcileResult.aggregatedResult(results);
55-
} else {
56-
var actualResource = getSecondaryResource(primary, context);
57-
return reconcileIndexAware(primary, actualResource.orElse(null), null, null, context);
58-
}
50+
return dependentResourceReconciler.reconcile(primary, context);
5951
}
6052

61-
@SuppressWarnings({"rawtypes"})
62-
protected void deleteBulkResourcesIfRequired(Set targetKeys, Map<String, R> actualResources,
63-
P primary, Context<P> context) {
64-
actualResources.forEach((key, value) -> {
65-
if (!targetKeys.contains(key)) {
66-
bulkDependentResource.deleteBulkResource(primary, value, key, context);
67-
}
68-
});
69-
}
70-
71-
protected ReconcileResult<R> reconcileIndexAware(P primary, R actualResource, R desiredResource,
72-
String key,
73-
Context<P> context) {
53+
protected ReconcileResult<R> reconcile(P primary, R actualResource, Context<P> context) {
7454
if (creatable || updatable) {
7555
if (actualResource == null) {
7656
if (creatable) {
77-
var desired = bulkAwareDesired(primary, desiredResource, context);
57+
var desired = desired(primary, context);
7858
throwIfNull(desired, primary, "Desired");
7959
logForOperation("Creating", primary, desired);
8060
var createdResource = handleCreate(desired, primary, context);
8161
return ReconcileResult.resourceCreated(createdResource);
8262
}
8363
} else {
8464
if (updatable) {
85-
final Matcher.Result<R> match;
86-
if (bulk) {
87-
match =
88-
bulkDependentResource.match(actualResource, desiredResource, primary, key, context);
89-
} else {
90-
match = updater.match(actualResource, primary, context);
91-
}
65+
final Matcher.Result<R> match = match(actualResource, primary, context);
9266
if (!match.matched()) {
93-
final var desired =
94-
match.computedDesired().orElse(bulkAwareDesired(primary, desiredResource, context));
67+
final var desired = match.computedDesired().orElse(desired(primary, context));
9568
throwIfNull(desired, primary, "Desired");
9669
logForOperation("Updating", primary, desired);
9770
var updatedResource = handleUpdate(actualResource, desired, primary, context);
@@ -110,9 +83,8 @@ protected ReconcileResult<R> reconcileIndexAware(P primary, R actualResource, R
11083
return ReconcileResult.noOperation(actualResource);
11184
}
11285

113-
private R bulkAwareDesired(P primary, R alreadyComputedDesire, Context<P> context) {
114-
return bulk ? alreadyComputedDesire
115-
: desired(primary, context);
86+
public Result<R> match(R resource, P primary, Context<P> context) {
87+
return updater.match(resource, primary, context);
11688
}
11789

11890
@Override
@@ -179,12 +151,7 @@ protected R desired(P primary, Context<P> context) {
179151
}
180152

181153
public void delete(P primary, Context<P> context) {
182-
if (bulk) {
183-
var actualResources = bulkDependentResource.getSecondaryResources(primary, context);
184-
deleteBulkResourcesIfRequired(Collections.emptySet(), actualResources, primary, context);
185-
} else {
186-
handleDelete(primary, context);
187-
}
154+
dependentResourceReconciler.delete(primary, context);
188155
}
189156

190157
protected void handleDelete(P primary, Context<P> context) {
@@ -200,11 +167,8 @@ protected boolean isCreatable() {
200167
return creatable;
201168
}
202169

170+
@SuppressWarnings("unused")
203171
protected boolean isUpdatable() {
204172
return updatable;
205173
}
206-
207-
protected boolean isBulk() {
208-
return bulk;
209-
}
210174
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResource.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,27 @@ public interface BulkDependentResource<R, P extends HasMetadata>
1717
extends Creator<R, P>, Deleter<P> {
1818

1919
/**
20-
* @return number of resources to create
20+
* Retrieves a map of desired secondary resources associated with the specified primary resource,
21+
* identified by an arbitrary key.
22+
*
23+
* @param primary the primary resource with which we want to identify which secondary resources
24+
* are associated
25+
* @param context the {@link Context} associated with the current reconciliation
26+
* @return a Map associating desired secondary resources with the specified primary via arbitrary
27+
* identifiers
2128
*/
2229
Map<String, R> desiredResources(P primary, Context<P> context);
2330

31+
/**
32+
* Retrieves the actual secondary resources currently existing on the server and associated with
33+
* the specified primary resource.
34+
*
35+
* @param primary the primary resource for which we want to retrieve the associated secondary
36+
* resources
37+
* @param context the {@link Context} associated with the current reconciliation
38+
* @return a Map associating actual secondary resources with the specified primary via arbitrary
39+
* identifiers
40+
*/
2441
Map<String, R> getSecondaryResources(P primary, Context<P> context);
2542

2643
/**
@@ -39,14 +56,15 @@ public interface BulkDependentResource<R, P extends HasMetadata>
3956
* {@link Context}.
4057
*
4158
* @param actualResource the resource we want to determine whether it's matching the desired state
59+
* @param desired the resource's desired state
4260
* @param primary the primary resource from which the desired state is inferred
43-
* @param key key of the resource
4461
* @param context the context in which the resource is being matched
4562
* @return a {@link Result} encapsulating whether the resource matched its desired state and this
4663
* associated state if it was computed as part of the matching process. Use the static
4764
* convenience methods ({@link Result#nonComputed(boolean)} and
4865
* {@link Result#computed(boolean, Object)})
4966
*/
50-
Result<R> match(R actualResource, R desired, P primary, String key, Context<P> context);
51-
67+
default Result<R> match(R actualResource, R desired, P primary, Context<P> context) {
68+
return Matcher.Result.computed(desired.equals(actualResource), desired);
69+
}
5270
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package io.javaoperatorsdk.operator.processing.dependent;
2+
3+
import java.util.ArrayList;
4+
import java.util.Collections;
5+
import java.util.List;
6+
import java.util.Map;
7+
import java.util.Set;
8+
9+
import io.fabric8.kubernetes.api.model.HasMetadata;
10+
import io.javaoperatorsdk.operator.api.reconciler.Context;
11+
import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter;
12+
import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult;
13+
import io.javaoperatorsdk.operator.processing.dependent.Matcher.Result;
14+
import io.javaoperatorsdk.operator.processing.event.ResourceID;
15+
16+
class BulkDependentResourceReconciler<R, P extends HasMetadata>
17+
implements DependentResourceReconciler<R, P> {
18+
19+
private final BulkDependentResource<R, P> bulkDependentResource;
20+
21+
BulkDependentResourceReconciler(BulkDependentResource<R, P> bulkDependentResource) {
22+
this.bulkDependentResource = bulkDependentResource;
23+
}
24+
25+
@Override
26+
public ReconcileResult<R> reconcile(P primary, Context<P> context) {
27+
final var desiredResources = bulkDependentResource.desiredResources(primary, context);
28+
Map<String, R> actualResources = bulkDependentResource.getSecondaryResources(primary, context);
29+
30+
// remove existing resources that are not needed anymore according to the primary state
31+
deleteBulkResourcesIfRequired(desiredResources.keySet(), actualResources, primary, context);
32+
33+
final List<ReconcileResult<R>> results = new ArrayList<>(desiredResources.size());
34+
final var updatable = bulkDependentResource instanceof Updater;
35+
desiredResources.forEach((key, value) -> {
36+
final var instance =
37+
updatable ? new UpdatableBulkDependentResourceInstance<>(bulkDependentResource, value)
38+
: new BulkDependentResourceInstance<>(bulkDependentResource, value);
39+
results.add(instance.reconcile(primary, actualResources.get(key), context));
40+
});
41+
42+
return ReconcileResult.aggregatedResult(results);
43+
}
44+
45+
@Override
46+
public void delete(P primary, Context<P> context) {
47+
var actualResources = bulkDependentResource.getSecondaryResources(primary, context);
48+
deleteBulkResourcesIfRequired(Collections.emptySet(), actualResources, primary, context);
49+
}
50+
51+
private void deleteBulkResourcesIfRequired(Set<String> expectedKeys,
52+
Map<String, R> actualResources, P primary, Context<P> context) {
53+
actualResources.forEach((key, value) -> {
54+
if (!expectedKeys.contains(key)) {
55+
bulkDependentResource.deleteBulkResource(primary, value, key, context);
56+
}
57+
});
58+
}
59+
60+
/**
61+
* Exposes a dynamically-created instance of the bulk dependent resource precursor as an
62+
* AbstractDependentResource so that we can reuse its reconciliation logic.
63+
*
64+
* @param <R>
65+
* @param <P>
66+
*/
67+
private static class BulkDependentResourceInstance<R, P extends HasMetadata>
68+
extends AbstractDependentResource<R, P>
69+
implements Creator<R, P>, Deleter<P> {
70+
private final BulkDependentResource<R, P> bulkDependentResource;
71+
private final R desired;
72+
73+
private BulkDependentResourceInstance(BulkDependentResource<R, P> bulkDependentResource,
74+
R desired) {
75+
this.bulkDependentResource = bulkDependentResource;
76+
this.desired = desired;
77+
}
78+
79+
@SuppressWarnings("unchecked")
80+
private AbstractDependentResource<R, P> asAbstractDependentResource() {
81+
return (AbstractDependentResource<R, P>) bulkDependentResource;
82+
}
83+
84+
@Override
85+
protected R desired(P primary, Context<P> context) {
86+
return desired;
87+
}
88+
89+
@SuppressWarnings("unchecked")
90+
public R update(R actual, R desired, P primary, Context<P> context) {
91+
return ((Updater<R, P>) bulkDependentResource).update(actual, desired, primary, context);
92+
}
93+
94+
@Override
95+
public Result<R> match(R resource, P primary, Context<P> context) {
96+
return bulkDependentResource.match(resource, desired, primary, context);
97+
}
98+
99+
@Override
100+
protected void onCreated(ResourceID primaryResourceId, R created) {
101+
asAbstractDependentResource().onCreated(primaryResourceId, created);
102+
}
103+
104+
@Override
105+
protected void onUpdated(ResourceID primaryResourceId, R updated, R actual) {
106+
asAbstractDependentResource().onUpdated(primaryResourceId, updated, actual);
107+
}
108+
109+
@Override
110+
public Class<R> resourceType() {
111+
return asAbstractDependentResource().resourceType();
112+
}
113+
114+
@Override
115+
public R create(R desired, P primary, Context<P> context) {
116+
return bulkDependentResource.create(desired, primary, context);
117+
}
118+
}
119+
120+
/**
121+
* Makes sure that the instance implements Updater if its precursor does as well.
122+
*
123+
* @param <R>
124+
* @param <P>
125+
*/
126+
private static class UpdatableBulkDependentResourceInstance<R, P extends HasMetadata>
127+
extends BulkDependentResourceInstance<R, P> implements Updater<R, P> {
128+
129+
private UpdatableBulkDependentResourceInstance(
130+
BulkDependentResource<R, P> bulkDependentResource,
131+
R desired) {
132+
super(bulkDependentResource, desired);
133+
}
134+
}
135+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package io.javaoperatorsdk.operator.processing.dependent;
2+
3+
import io.fabric8.kubernetes.api.model.HasMetadata;
4+
import io.javaoperatorsdk.operator.api.reconciler.Context;
5+
import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult;
6+
7+
/**
8+
* An internal interface that abstracts away how to reconcile dependent resources, in particular
9+
* when they can be dynamically created based on the state provided by the primary resource (e.g.
10+
* the primary resource dictates which/how many secondary resources need to be created).
11+
*
12+
* @param <R> the type of the secondary resource to be reconciled
13+
* @param <P> the primary resource type
14+
*/
15+
interface DependentResourceReconciler<R, P extends HasMetadata> {
16+
17+
ReconcileResult<R> reconcile(P primary, Context<P> context);
18+
19+
void delete(P primary, Context<P> context);
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.javaoperatorsdk.operator.processing.dependent;
2+
3+
import io.fabric8.kubernetes.api.model.HasMetadata;
4+
import io.javaoperatorsdk.operator.api.reconciler.Context;
5+
import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult;
6+
7+
class SingleDependentResourceReconciler<R, P extends HasMetadata>
8+
implements DependentResourceReconciler<R, P> {
9+
10+
private final AbstractDependentResource<R, P> instance;
11+
12+
SingleDependentResourceReconciler(AbstractDependentResource<R, P> dependentResource) {
13+
this.instance = dependentResource;
14+
}
15+
16+
@Override
17+
public ReconcileResult<R> reconcile(P primary, Context<P> context) {
18+
final var maybeActual = instance.getSecondaryResource(primary, context);
19+
return instance.reconcile(primary, maybeActual.orElse(null), context);
20+
}
21+
22+
@Override
23+
public void delete(P primary, Context<P> context) {
24+
instance.handleDelete(primary, context);
25+
}
26+
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,8 @@ public Result<R> match(R actualResource, P primary, Context<P> context) {
139139
return matcher.match(actualResource, primary, context);
140140
}
141141

142-
public Result<R> match(R actualResource, R desired, P primary, String key, Context<P> context) {
142+
@SuppressWarnings("unused")
143+
public Result<R> match(R actualResource, R desired, P primary, Context<P> context) {
143144
return GenericKubernetesResourceMatcher.match(desired, actualResource, false);
144145
}
145146

@@ -148,6 +149,7 @@ protected void handleDelete(P primary, Context<P> context) {
148149
resource.ifPresent(r -> client.resource(r).delete());
149150
}
150151

152+
@SuppressWarnings("unused")
151153
public void deleteBulkResource(P primary, R resource, String key, Context<P> context) {
152154
client.resource(resource).delete();
153155
}

0 commit comments

Comments
 (0)