Skip to content

Commit d45328f

Browse files
shawkinscsviri
authored andcommitted
various refinements to ssa logic (#2028)
- localizes sanitation to ssa matcher - further ensures values won't be logged Signed-off-by: Steven Hawkins <[email protected]>
1 parent b14f69e commit d45328f

File tree

4 files changed

+51
-77
lines changed

4 files changed

+51
-77
lines changed

Diff for: docs/documentation/dependent-resources.md

+2
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,8 @@ server. To bypass the matching feature completely, simply override the `match` m
370370
return `false`, thus telling JOSDK that the actual state never matches the desired one, making
371371
it always update the resources using SSA.
372372

373+
WARNING: Older versions of Kubernetes before 1.25 would create an additional resource version for every SSA update performed with certain resources - even though there were no actual changes in the stored resource - leading to infinite reconciliations. This behavior was seen with Secrets using `stringData`, Ingresses using empty string fields, and StatefulSets using volume claim templates. The operator framework has added built-in handling for the StatefulSet issue. If you encounter this issue on an older Kubernetes version, consider changing your desired state, turning off SSA for that resource, or even upgrading your Kubernetes version. If you encounter it on a newer Kubernetes version, please log an issue with the JOSDK and with upstream Kubernetes.
374+
373375
## Telling JOSDK how to find which secondary resources are associated with a given primary resource
374376

375377
[`KubernetesDependentResource`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java)

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

-47
This file was deleted.

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

+7-8
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,14 @@ public abstract class KubernetesDependentResource<R extends HasMetadata, P exten
4343
private final boolean garbageCollected = this instanceof GarbageCollected;
4444
private KubernetesDependentResourceConfig<R> kubernetesDependentResourceConfig;
4545

46+
private boolean usingCustomResourceUpdateMatcher;
47+
4648
@SuppressWarnings("unchecked")
4749
public KubernetesDependentResource(Class<R> resourceType) {
4850
super(resourceType);
4951

50-
updaterMatcher = this instanceof ResourceUpdaterMatcher
52+
usingCustomResourceUpdateMatcher = this instanceof ResourceUpdaterMatcher;
53+
updaterMatcher = usingCustomResourceUpdateMatcher
5154
? (ResourceUpdaterMatcher<R>) this
5255
: GenericResourceUpdaterMatcher.updaterMatcherFor(resourceType);
5356
}
@@ -114,7 +117,6 @@ public R create(R desired, P primary, Context<P> context) {
114117
}
115118
}
116119
addMetadata(false, null, desired, primary, context);
117-
sanitizeDesired(desired, null, primary, context);
118120
final var resource = prepare(desired, primary, "Creating");
119121
return useSSA(context)
120122
? resource
@@ -131,7 +133,6 @@ public R update(R actual, R desired, P primary, Context<P> context) {
131133
}
132134
R updatedResource;
133135
addMetadata(false, actual, desired, primary, context);
134-
sanitizeDesired(desired, actual, primary, context);
135136
if (useSSA(context)) {
136137
updatedResource = prepare(desired, primary, "Updating")
137138
.fieldManager(context.getControllerConfiguration().fieldManager())
@@ -148,7 +149,6 @@ public R update(R actual, R desired, P primary, Context<P> context) {
148149
@Override
149150
public Result<R> match(R actualResource, P primary, Context<P> context) {
150151
final var desired = desired(primary, context);
151-
sanitizeDesired(desired, actualResource, primary, context);
152152
return match(actualResource, desired, primary, updaterMatcher, context);
153153
}
154154

@@ -193,11 +193,10 @@ protected void addMetadata(boolean forMatch, R actualResource, final R target, P
193193
addReferenceHandlingMetadata(target, primary);
194194
}
195195

196-
protected void sanitizeDesired(R desired, R actual, P primary, Context<P> context) {
197-
DesiredResourceSanitizer.sanitizeDesired(desired, actual, primary, context, useSSA(context));
198-
}
199-
200196
protected boolean useSSA(Context<P> context) {
197+
if (usingCustomResourceUpdateMatcher) {
198+
return false;
199+
}
201200
Optional<Boolean> useSSAConfig =
202201
configuration().flatMap(KubernetesDependentResourceConfig::useSSA);
203202
var configService = context.getControllerConfiguration().getConfigurationService();

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

+42-22
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
import org.slf4j.Logger;
88
import org.slf4j.LoggerFactory;
99

10+
import io.fabric8.kubernetes.api.model.GenericKubernetesResource;
1011
import io.fabric8.kubernetes.api.model.HasMetadata;
1112
import io.fabric8.kubernetes.api.model.ManagedFieldsEntry;
13+
import io.fabric8.kubernetes.api.model.apps.StatefulSet;
1214
import io.fabric8.kubernetes.client.utils.KubernetesSerialization;
1315
import io.javaoperatorsdk.operator.OperatorException;
1416
import io.javaoperatorsdk.operator.api.reconciler.Context;
@@ -74,6 +76,9 @@ public boolean matches(R actual, R desired, Context<?> context) {
7476
var objectMapper = context.getClient().getKubernetesSerialization();
7577

7678
var actualMap = objectMapper.convertValue(actual, Map.class);
79+
80+
sanitizeState(actual, desired, actualMap);
81+
7782
var desiredMap = objectMapper.convertValue(desired, Map.class);
7883
if (LoggingUtils.isNotSensitiveResource(desired)) {
7984
log.trace("Original actual: \n {} \n original desired: \n {} ", actual, desiredMap);
@@ -92,6 +97,35 @@ public boolean matches(R actual, R desired, Context<?> context) {
9297
return prunedActual.equals(desiredMap);
9398
}
9499

100+
/**
101+
* Correct for known issue with SSA
102+
*/
103+
@SuppressWarnings("unchecked")
104+
private void sanitizeState(R actual, R desired, Map<String, Object> actualMap) {
105+
if (desired instanceof StatefulSet) {
106+
StatefulSet desiredStatefulSet = (StatefulSet) desired;
107+
StatefulSet actualStatefulSet = (StatefulSet) actual;
108+
int claims = desiredStatefulSet.getSpec().getVolumeClaimTemplates().size();
109+
if (claims == actualStatefulSet.getSpec().getVolumeClaimTemplates().size()) {
110+
for (int i = 0; i < claims; i++) {
111+
if (desiredStatefulSet.getSpec().getVolumeClaimTemplates().get(i).getSpec()
112+
.getVolumeMode() == null) {
113+
Optional
114+
.ofNullable(GenericKubernetesResource.get(actualMap, "spec", "volumeClaimTemplates",
115+
i, "spec"))
116+
.map(Map.class::cast).ifPresent(m -> m.remove("volumeMode"));
117+
}
118+
if (desiredStatefulSet.getSpec().getVolumeClaimTemplates().get(i).getStatus() == null) {
119+
Optional
120+
.ofNullable(
121+
GenericKubernetesResource.get(actualMap, "spec", "volumeClaimTemplates", i))
122+
.map(Map.class::cast).ifPresent(m -> m.remove("status"));
123+
}
124+
}
125+
}
126+
}
127+
}
128+
95129
@SuppressWarnings("unchecked")
96130
private static void removeIrrelevantValues(Map<String, Object> desiredMap) {
97131
var metadata = (Map<String, Object>) desiredMap.get(METADATA_KEY);
@@ -153,8 +187,7 @@ private static void fillResultsAndTraverseFurther(Map<String, Object> result,
153187
var emptyMapValue = new HashMap<String, Object>();
154188
result.put(keyInActual, emptyMapValue);
155189
var actualMapValue = actualMap.getOrDefault(keyInActual, Collections.emptyMap());
156-
log.trace("key: {} actual map value: {} managedFieldValue: {}", keyInActual,
157-
actualMapValue, managedFieldValue);
190+
log.debug("key: {} actual map value: managedFieldValue: {}", keyInActual, managedFieldValue);
158191

159192
keepOnlyManagedFields(emptyMapValue, (Map<String, Object>) actualMapValue,
160193
(Map<String, Object>) managedFields.get(key), objectMapper);
@@ -282,29 +315,16 @@ private static java.util.Map.Entry<Integer, Map<String, Object>> selectListEntry
282315
}
283316
if (possibleTargets.isEmpty()) {
284317
throw new IllegalStateException(
285-
"Cannot find list element for key:" + key + ", in map: " + values);
318+
"Cannot find list element for key:" + key + ", in map: "
319+
+ values.stream().map(Map::keySet).collect(Collectors.toList()));
286320
}
287321
if (possibleTargets.size() > 1) {
288322
throw new IllegalStateException(
289-
"More targets found in list element for key:" + key + ", in map: " + values);
323+
"More targets found in list element for key:" + key + ", in map: "
324+
+ values.stream().map(Map::keySet).collect(Collectors.toList()));
290325
}
291326
final var finalIndex = index;
292-
return new Map.Entry<>() {
293-
@Override
294-
public Integer getKey() {
295-
return finalIndex;
296-
}
297-
298-
@Override
299-
public Map<String, Object> getValue() {
300-
return possibleTargets.get(0);
301-
}
302-
303-
@Override
304-
public Map<String, Object> setValue(Map<String, Object> stringObjectMap) {
305-
throw new IllegalStateException("should not be called");
306-
}
307-
};
327+
return new AbstractMap.SimpleEntry<>(finalIndex, possibleTargets.get(0));
308328
}
309329

310330

@@ -318,14 +338,14 @@ private Optional<ManagedFieldsEntry> checkIfFieldManagerExists(R actual, String
318338
.collect(Collectors.toList());
319339
if (targetManagedFields.isEmpty()) {
320340
log.debug("No field manager exists for resource {} with name: {} and operation Apply ",
321-
actual, actual.getMetadata().getName());
341+
actual.getKind(), actual.getMetadata().getName());
322342
return Optional.empty();
323343
}
324344
// this should not happen in theory
325345
if (targetManagedFields.size() > 1) {
326346
throw new OperatorException(
327347
"More than one field manager exists with name: " + fieldManager + "in resource: " +
328-
actual + " with name: " + actual.getMetadata().getName());
348+
actual.getKind() + " with name: " + actual.getMetadata().getName());
329349
}
330350
return Optional.of(targetManagedFields.get(0));
331351
}

0 commit comments

Comments
 (0)