Skip to content

Commit c063eb1

Browse files
csvirimetacosm
authored andcommitted
improve: non-SSA resource matching and updating (#1963)
1 parent 84c0034 commit c063eb1

20 files changed

+232
-275
lines changed

Diff for: docs/documentation/v4-4-migration.md

+8-4
Original file line numberDiff line numberDiff line change
@@ -62,19 +62,23 @@ default [Dependent Resources](https://javaoperatorsdk.io/docs/dependent-resource
6262
[Server Side Apply (SSA)](https://kubernetes.io/docs/reference/using-api/server-side-apply/) to
6363
create and
6464
update Kubernetes resources. A
65-
new [default matching](https://github.com/java-operator-sdk/java-operator-sdk/blob/e95f9c8a8b8a8561c9a735e60fc5d82b7758df8e/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java#L163-L163)
65+
new [default matching](https://github.com/java-operator-sdk/java-operator-sdk/blob/2cc3bb7710adb8fca14767fbff8d93533dd05ef0/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java#L157-L157)
6666
algorithm is provided for `KubernetesDependentResource` that is based on `managedFields` of SSA. For
6767
details
68-
see [SSABasedGenericKubernetesResourceMatcher](https://github.com/java-operator-sdk/java-operator-sdk/blob/e95f9c8a8b8a8561c9a735e60fc5d82b7758df8e/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java)
68+
see [SSABasedGenericKubernetesResourceMatcher](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java)
6969

7070
Since those features are hard to completely test, we provided feature flags to revert to the
7171
legacy behavior if needed,
72-
see those
73-
in [ConfigurationService](https://github.com/java-operator-sdk/java-operator-sdk/blob/e95f9c8a8b8a8561c9a735e60fc5d82b7758df8e/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java#L268-L289)
72+
see
73+
in [ConfigurationService](https://github.com/java-operator-sdk/java-operator-sdk/blob/2cc3bb7710adb8fca14767fbff8d93533dd05ef0/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java#L332-L347)
7474

7575
Note that it is possible to override the related methods/behavior on class level when extending
7676
the `KubernetesDependentResource`.
7777

78+
The SSA based create/update can be combined with the legacy matcher, simply override the `match` method
79+
and use the [GenericKubernetesResourceMatcher](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java#L19-L19)
80+
directly.
81+
7882
### Migration from plain Update/Create to SSA Based Patch
7983

8084
Migration to SSA might not be trivial based on the uses cases and the type of managed resources.

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

+6-15
Original file line numberDiff line numberDiff line change
@@ -330,27 +330,18 @@ default ExecutorServiceManager getExecutorServiceManager() {
330330
}
331331

332332
/**
333-
* Allows to revert to the 4.3 behavior when it comes to creating or updating Kubernetes Dependent
334-
* Resources when set to {@code false}. The default approach how these resources are
335-
* created/updated was change to use
333+
* Allows to revert to the 4.3 behavior when it comes to creating, updating and matching
334+
* Kubernetes Dependent Resources when set to {@code false}. The default approach how these
335+
* resources are created/updated and match was change to use
336336
* <a href="https://kubernetes.io/docs/reference/using-api/server-side-apply/">Server-Side
337337
* Apply</a> (SSA) by default.
338338
*
339-
* @since 4.4.0
340-
*/
341-
default boolean ssaBasedCreateUpdateForDependentResources() {
342-
return true;
343-
}
344-
345-
/**
346-
* Allows to revert to the 4.3 generic matching algorithm for Kubernetes Dependent Resources when
347-
* set to {@code false}. Version 4.4 introduced a new generic matching algorithm for Kubernetes
348-
* Dependent Resources which is quite complex. As a consequence, we introduced this setting to
349-
* allow folks to revert to the previous matching algorithm if needed.
339+
* SSA based create/update can be still used with the legacy matching, just overriding the match
340+
* method of Kubernetes Dependent Resource.
350341
*
351342
* @since 4.4.0
352343
*/
353-
default boolean ssaBasedDefaultMatchingForDependentResources() {
344+
default boolean ssaBasedCreateUpdateMatchForDependentResources() {
354345
return true;
355346
}
356347

Diff for: operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverrider.java

+7-21
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@ public class ConfigurationServiceOverrider {
3434
private Boolean stopOnInformerErrorDuringStartup;
3535
private Duration cacheSyncTimeout;
3636
private ResourceClassResolver resourceClassResolver;
37-
private Boolean ssaBasedCreateUpdateForDependentResources;
38-
private Boolean ssaBasedDefaultMatchingForDependentResources;
37+
private Boolean ssaBasedCreateUpdateMatchForDependentResources;
3938

4039
ConfigurationServiceOverrider(ConfigurationService original) {
4140
this.original = original;
@@ -145,15 +144,9 @@ public ConfigurationServiceOverrider withResourceClassResolver(
145144
return this;
146145
}
147146

148-
public ConfigurationServiceOverrider withSSABasedCreateUpdateForDependentResources(
147+
public ConfigurationServiceOverrider withSSABasedCreateUpdateMatchForDependentResources(
149148
boolean value) {
150-
this.ssaBasedCreateUpdateForDependentResources = value;
151-
return this;
152-
}
153-
154-
public ConfigurationServiceOverrider withSSABasedDefaultMatchingForDependentResources(
155-
boolean value) {
156-
this.ssaBasedDefaultMatchingForDependentResources = value;
149+
this.ssaBasedCreateUpdateMatchForDependentResources = value;
157150
return this;
158151
}
159152

@@ -258,17 +251,10 @@ public ResourceClassResolver getResourceClassResolver() {
258251
}
259252

260253
@Override
261-
public boolean ssaBasedCreateUpdateForDependentResources() {
262-
return ssaBasedCreateUpdateForDependentResources != null
263-
? ssaBasedCreateUpdateForDependentResources
264-
: super.ssaBasedCreateUpdateForDependentResources();
265-
}
266-
267-
@Override
268-
public boolean ssaBasedDefaultMatchingForDependentResources() {
269-
return ssaBasedDefaultMatchingForDependentResources != null
270-
? ssaBasedDefaultMatchingForDependentResources
271-
: super.ssaBasedDefaultMatchingForDependentResources();
254+
public boolean ssaBasedCreateUpdateMatchForDependentResources() {
255+
return ssaBasedCreateUpdateMatchForDependentResources != null
256+
? ssaBasedCreateUpdateMatchForDependentResources
257+
: super.ssaBasedCreateUpdateMatchForDependentResources();
272258
}
273259
};
274260
}

Diff for: operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java

+42-7
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,13 @@
1313
import io.fabric8.zjsonpatch.JsonDiff;
1414
import io.javaoperatorsdk.operator.api.reconciler.Context;
1515
import io.javaoperatorsdk.operator.processing.dependent.Matcher;
16-
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.processors.GenericResourceUpdatePreProcessor;
1716

1817
import com.fasterxml.jackson.databind.JsonNode;
1918

2019
public class GenericKubernetesResourceMatcher<R extends HasMetadata, P extends HasMetadata>
2120
implements Matcher<R, P> {
2221

23-
22+
private static String SPEC = "/spec";
2423
private static final String ADD = "add";
2524
private static final String OP = "op";
2625
public static final String METADATA_LABELS = "/metadata/labels";
@@ -171,7 +170,6 @@ public static <R extends HasMetadata, P extends HasMetadata> Result<R> match(
171170
labelsAndAnnotationsEquality, specEquality, context);
172171
}
173172

174-
@SuppressWarnings("unchecked")
175173
public static <R extends HasMetadata, P extends HasMetadata> Result<R> match(R desired,
176174
R actualResource,
177175
boolean considerMetadata, boolean labelsAndAnnotationsEquality, boolean specEquality,
@@ -194,13 +192,50 @@ public static <R extends HasMetadata, P extends HasMetadata> Result<R> match(R d
194192
}
195193
}
196194

197-
final ResourceUpdatePreProcessor<R> processor =
198-
GenericResourceUpdatePreProcessor.processorFor((Class<R>) desired.getClass());
199-
final var matched =
200-
processor.matches(actualResource, desired, specEquality, context, ignoredPaths);
195+
final var matched = matchSpec(actualResource, desired, specEquality, context, ignoredPaths);
201196
return Result.computed(matched, desired);
202197
}
203198

199+
private static <R extends HasMetadata> boolean matchSpec(R actual, R desired, boolean equality,
200+
Context<?> context,
201+
String[] ignoredPaths) {
202+
203+
final var kubernetesSerialization = context.getClient().getKubernetesSerialization();
204+
var desiredNode = kubernetesSerialization.convertValue(desired, JsonNode.class);
205+
var actualNode = kubernetesSerialization.convertValue(actual, JsonNode.class);
206+
var wholeDiffJsonPatch = JsonDiff.asJson(desiredNode, actualNode);
207+
208+
final List<String> ignoreList =
209+
ignoredPaths != null && ignoredPaths.length > 0 ? Arrays.asList(ignoredPaths)
210+
: Collections.emptyList();
211+
// reflection will be replaced by this:
212+
// https://github.com/fabric8io/kubernetes-client/issues/3816
213+
var specDiffJsonPatch = getDiffsImpactingPathsWithPrefixes(wholeDiffJsonPatch, SPEC);
214+
// In case of equality is set to true, no diffs are allowed, so we return early if diffs exist
215+
// On contrary (if equality is false), "add" is allowed for cases when for some
216+
// resources Kubernetes fills-in values into spec.
217+
if (equality && !specDiffJsonPatch.isEmpty()) {
218+
return false;
219+
}
220+
if (!equality && !ignoreList.isEmpty()) {
221+
if (!allDiffsOnIgnoreList(specDiffJsonPatch, ignoreList)) {
222+
return false;
223+
}
224+
} else {
225+
if (!allDiffsAreAddOps(specDiffJsonPatch)) {
226+
return false;
227+
}
228+
}
229+
return true;
230+
}
231+
232+
private static boolean allDiffsOnIgnoreList(List<JsonNode> metadataJSonDiffs,
233+
List<String> ignoreList) {
234+
if (metadataJSonDiffs.isEmpty()) {
235+
return false;
236+
}
237+
return metadataJSonDiffs.stream().allMatch(n -> nodeIsChildOf(n, ignoreList));
238+
}
204239

205240
private static <R extends HasMetadata, P extends HasMetadata> Optional<Result<R>> matchMetadata(
206241
R desired,

Diff for: operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericResourceUpdatePreProcessor.java

-20
This file was deleted.

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

+36-35
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.KubernetesClientAware;
2424
import io.javaoperatorsdk.operator.processing.dependent.AbstractEventSourceHolderDependentResource;
2525
import io.javaoperatorsdk.operator.processing.dependent.Matcher.Result;
26-
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.processors.GenericResourceUpdatePreProcessor;
26+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.updatermatcher.GenericResourceUpdaterMatcher;
2727
import io.javaoperatorsdk.operator.processing.event.ResourceID;
2828
import io.javaoperatorsdk.operator.processing.event.source.SecondaryToPrimaryMapper;
2929
import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource;
@@ -40,18 +40,17 @@ public abstract class KubernetesDependentResource<R extends HasMetadata, P exten
4040
private static final Logger log = LoggerFactory.getLogger(KubernetesDependentResource.class);
4141

4242
protected KubernetesClient client;
43-
private final ResourceUpdatePreProcessor<R> processor;
43+
private final ResourceUpdaterMatcher<R> updaterMatcher;
4444
private final boolean garbageCollected = this instanceof GarbageCollected;
4545
private KubernetesDependentResourceConfig<R> kubernetesDependentResourceConfig;
4646

47-
4847
@SuppressWarnings("unchecked")
4948
public KubernetesDependentResource(Class<R> resourceType) {
5049
super(resourceType);
5150

52-
processor = this instanceof ResourceUpdatePreProcessor
53-
? (ResourceUpdatePreProcessor<R>) this
54-
: GenericResourceUpdatePreProcessor.processorFor(resourceType);
51+
updaterMatcher = this instanceof ResourceUpdaterMatcher
52+
? (ResourceUpdaterMatcher<R>) this
53+
: GenericResourceUpdaterMatcher.updaterMatcherFor(resourceType);
5554
}
5655

5756
@SuppressWarnings("unchecked")
@@ -129,57 +128,59 @@ protected R handleUpdate(R actual, R desired, P primary, Context<P> context) {
129128

130129
@SuppressWarnings("unused")
131130
public R create(R target, P primary, Context<P> context) {
132-
if (!context.getControllerConfiguration().getConfigurationService()
133-
.ssaBasedCreateUpdateForDependentResources()) {
134-
return prepare(target, primary, "Creating").create();
135-
} else {
136-
return prepare(target, primary, "Creating")
137-
.fieldManager(context.getControllerConfiguration().fieldManager())
138-
.forceConflicts()
139-
.serverSideApply();
140-
}
131+
final var resource = prepare(target, primary, "Creating");
132+
return useSSA(context)
133+
? resource
134+
.fieldManager(context.getControllerConfiguration().fieldManager())
135+
.forceConflicts()
136+
.serverSideApply()
137+
: resource.create();
141138
}
142139

143140
public R update(R actual, R target, P primary, Context<P> context) {
144-
if (!context.getControllerConfiguration().getConfigurationService()
145-
.ssaBasedCreateUpdateForDependentResources()) {
146-
var updatedActual = processor.replaceSpecOnActual(actual, target, context);
147-
return prepare(updatedActual, primary, "Updating").replace();
148-
} else {
141+
if (useSSA(context)) {
149142
target.getMetadata().setResourceVersion(actual.getMetadata().getResourceVersion());
150143
return prepare(target, primary, "Updating")
151144
.fieldManager(context.getControllerConfiguration().fieldManager())
152145
.forceConflicts().serverSideApply();
146+
} else {
147+
var updatedActual = updaterMatcher.updateResource(actual, target, context);
148+
return prepare(updatedActual, primary, "Updating").replace();
153149
}
154150
}
155151

156152
public Result<R> match(R actualResource, P primary, Context<P> context) {
157-
if (!context.getControllerConfiguration().getConfigurationService()
158-
.ssaBasedDefaultMatchingForDependentResources()) {
159-
return GenericKubernetesResourceMatcher.match(this, actualResource, primary, context, false);
160-
} else {
161-
final var desired = desired(primary, context);
153+
final var desired = desired(primary, context);
154+
final boolean matches;
155+
if (useSSA(context)) {
162156
addReferenceHandlingMetadata(desired, primary);
163-
var matches = SSABasedGenericKubernetesResourceMatcher.getInstance().matches(actualResource,
164-
desired, context);
165-
return Result.computed(matches, desired);
157+
matches = SSABasedGenericKubernetesResourceMatcher.getInstance()
158+
.matches(actualResource, desired, context);
159+
} else {
160+
matches = updaterMatcher.matches(actualResource, desired, context);
166161
}
162+
return Result.computed(matches, desired);
167163
}
168164

169165
@SuppressWarnings("unused")
170166
public Result<R> match(R actualResource, R desired, P primary, Context<P> context) {
171-
if (!context.getControllerConfiguration().getConfigurationService()
172-
.ssaBasedDefaultMatchingForDependentResources()) {
173-
return GenericKubernetesResourceMatcher.match(desired, actualResource, false,
174-
false, false, context);
175-
} else {
167+
if (useSSA(context)) {
176168
addReferenceHandlingMetadata(desired, primary);
177-
var matches = SSABasedGenericKubernetesResourceMatcher.getInstance().matches(actualResource,
178-
desired, context);
169+
var matches = SSABasedGenericKubernetesResourceMatcher.getInstance()
170+
.matches(actualResource, desired, context);
179171
return Result.computed(matches, desired);
172+
} else {
173+
return GenericKubernetesResourceMatcher
174+
.match(desired, actualResource, true,
175+
false, false, context);
180176
}
181177
}
182178

179+
private boolean useSSA(Context<P> context) {
180+
return context.getControllerConfiguration().getConfigurationService()
181+
.ssaBasedCreateUpdateMatchForDependentResources();
182+
}
183+
183184
protected void handleDelete(P primary, R secondary, Context<P> context) {
184185
if (secondary != null) {
185186
client.resource(secondary).delete();

Diff for: operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/ResourceUpdatePreProcessor.java

-63
This file was deleted.

0 commit comments

Comments
 (0)