Skip to content

Commit e380488

Browse files
committed
refactor: clean up SSABasedGenericKubernetesResourceMatcher
Signed-off-by: David Sondermann <[email protected]>
1 parent 81d0773 commit e380488

File tree

1 file changed

+63
-81
lines changed

1 file changed

+63
-81
lines changed

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

+63-81
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@
22

33
import java.util.AbstractMap;
44
import java.util.ArrayList;
5-
import java.util.Arrays;
65
import java.util.Collections;
76
import java.util.HashMap;
87
import java.util.LinkedHashMap;
98
import java.util.List;
109
import java.util.Map;
1110
import java.util.Map.Entry;
11+
import java.util.Objects;
1212
import java.util.Optional;
1313
import java.util.Set;
14-
import java.util.SortedMap;
1514
import java.util.TreeMap;
1615

1716
import org.slf4j.Logger;
@@ -31,14 +30,14 @@
3130

3231
/**
3332
* Matches the actual state on the server vs the desired state. Based on the managedFields of SSA.
34-
*
3533
* <p>
36-
* The basis of algorithm is to extract the fields managed we convert resources to Map/List
34+
* The basis of the algorithm is to extract the managed fields by converting resources to a Map/List
3735
* composition. The actual resource (from the server) is pruned, all the fields which are not
38-
* mentioned in managedFields of the target manager is removed. Some irrelevant fields are also
39-
* removed from desired. And the two resulted Maps are compared for equality. The implementation is
40-
* a bit nasty since have to deal with some specific cases of managedFields format.
41-
* </p>
36+
* mentioned in managedFields of the target manager are removed. Some irrelevant fields are also
37+
* removed from the desired resource. Finally, the two resulting maps are compared for equality.
38+
* <p>
39+
* The implementation is a bit nasty since we have to deal with some specific cases of managedFields
40+
* formats.
4241
*
4342
* @param <R> matched resource type
4443
*/
@@ -48,15 +47,14 @@
4847
// see also: https://kubernetes.slack.com/archives/C0123CNN8F3/p1686141087220719
4948
public class SSABasedGenericKubernetesResourceMatcher<R extends HasMetadata> {
5049

51-
@SuppressWarnings("rawtypes")
52-
private static final SSABasedGenericKubernetesResourceMatcher INSTANCE =
53-
new SSABasedGenericKubernetesResourceMatcher<>();
5450
public static final String APPLY_OPERATION = "Apply";
5551
public static final String DOT_KEY = ".";
5652

53+
@SuppressWarnings("rawtypes")
54+
private static final SSABasedGenericKubernetesResourceMatcher INSTANCE =
55+
new SSABasedGenericKubernetesResourceMatcher<>();
5756
private static final List<String> IGNORED_METADATA =
58-
Arrays.asList("creationTimestamp", "deletionTimestamp",
59-
"generation", "selfLink", "uid");
57+
List.of("creationTimestamp", "deletionTimestamp", "generation", "selfLink", "uid");
6058

6159
@SuppressWarnings("unchecked")
6260
public static <L extends HasMetadata> SSABasedGenericKubernetesResourceMatcher<L> getInstance() {
@@ -92,31 +90,26 @@ public boolean matches(R actual, R desired, Context<?> context) {
9290
var objectMapper = context.getClient().getKubernetesSerialization();
9391

9492
var actualMap = objectMapper.convertValue(actual, Map.class);
95-
9693
sanitizeState(actual, desired, actualMap);
97-
98-
var desiredMap = objectMapper.convertValue(desired, Map.class);
99-
if (LoggingUtils.isNotSensitiveResource(desired)) {
100-
log.trace("Original actual: \n {} \n original desired: \n {} ", actual, desiredMap);
101-
}
102-
10394
var prunedActual = new HashMap<String, Object>(actualMap.size());
10495
keepOnlyManagedFields(prunedActual, actualMap,
10596
managedFieldsEntry.getFieldsV1().getAdditionalProperties(),
10697
objectMapper);
10798

99+
var desiredMap = objectMapper.convertValue(desired, Map.class);
100+
if (LoggingUtils.isNotSensitiveResource(desired)) {
101+
log.trace("Original actual:\n {}\n original desired:\n {}", actual, desiredMap);
102+
}
108103
removeIrrelevantValues(desiredMap);
109104

110105
var matches = prunedActual.equals(desiredMap);
111-
112106
if (!matches && log.isDebugEnabled() && LoggingUtils.isNotSensitiveResource(desired)) {
113107
var diff = getDiff(prunedActual, desiredMap, objectMapper);
114108
log.debug(
115-
"Diff between actual and desired state for resource: {} with name: {} in namespace: {} is: \n{}",
109+
"Diff between actual and desired state for resource: {} with name: {} in namespace: {} is:\n{}",
116110
actual.getKind(), actual.getMetadata().getName(), actual.getMetadata().getNamespace(),
117111
diff);
118112
}
119-
120113
return matches;
121114
}
122115

@@ -125,24 +118,23 @@ private String getDiff(Map<String, Object> prunedActualMap, Map<String, Object>
125118
var actualYaml = serialization.asYaml(sortMap(prunedActualMap));
126119
var desiredYaml = serialization.asYaml(sortMap(desiredMap));
127120
if (log.isTraceEnabled()) {
128-
log.trace("Pruned actual resource: \n {} \ndesired resource: \n {} ", actualYaml,
129-
desiredYaml);
121+
log.trace("Pruned actual resource:\n {} \ndesired resource:\n {} ", actualYaml, desiredYaml);
130122
}
131123

132124
var patch = DiffUtils.diff(actualYaml.lines().toList(), desiredYaml.lines().toList());
133-
List<String> unifiedDiff =
125+
var unifiedDiff =
134126
UnifiedDiffUtils.generateUnifiedDiff("", "", actualYaml.lines().toList(), patch, 1);
135127
return String.join("\n", unifiedDiff);
136128
}
137129

138130
@SuppressWarnings("unchecked")
139131
Map<String, Object> sortMap(Map<String, Object> map) {
140-
List<String> sortedKeys = new ArrayList<>(map.keySet());
132+
var sortedKeys = new ArrayList<>(map.keySet());
141133
Collections.sort(sortedKeys);
142134

143-
Map<String, Object> sortedMap = new LinkedHashMap<>();
144-
for (String key : sortedKeys) {
145-
Object value = map.get(key);
135+
var sortedMap = new LinkedHashMap<String, Object>();
136+
for (var key : sortedKeys) {
137+
var value = map.get(key);
146138
if (value instanceof Map) {
147139
sortedMap.put(key, sortMap((Map<String, Object>) value));
148140
} else if (value instanceof List) {
@@ -156,8 +148,8 @@ Map<String, Object> sortMap(Map<String, Object> map) {
156148

157149
@SuppressWarnings("unchecked")
158150
List<Object> sortListItems(List<Object> list) {
159-
List<Object> sortedList = new ArrayList<>();
160-
for (Object item : list) {
151+
var sortedList = new ArrayList<>();
152+
for (var item : list) {
161153
if (item instanceof Map) {
162154
sortedList.add(sortMap((Map<String, Object>) item));
163155
} else if (item instanceof List) {
@@ -173,21 +165,22 @@ List<Object> sortListItems(List<Object> list) {
173165
* Correct for known issue with SSA
174166
*/
175167
private void sanitizeState(R actual, R desired, Map<String, Object> actualMap) {
176-
if (desired instanceof StatefulSet desiredStatefulSet) {
177-
StatefulSet actualStatefulSet = (StatefulSet) actual;
178-
int claims = desiredStatefulSet.getSpec().getVolumeClaimTemplates().size();
179-
if (claims == actualStatefulSet.getSpec().getVolumeClaimTemplates().size()) {
168+
if (actual instanceof StatefulSet actualStatefulSet
169+
&& desired instanceof StatefulSet desiredStatefulSet) {
170+
var actualSpec = actualStatefulSet.getSpec();
171+
var desiredSpec = desiredStatefulSet.getSpec();
172+
int claims = desiredSpec.getVolumeClaimTemplates().size();
173+
if (claims == actualSpec.getVolumeClaimTemplates().size()) {
180174
for (int i = 0; i < claims; i++) {
181-
if (desiredStatefulSet.getSpec().getVolumeClaimTemplates().get(i).getSpec()
182-
.getVolumeMode() == null) {
175+
var claim = desiredSpec.getVolumeClaimTemplates().get(i);
176+
if (claim.getSpec().getVolumeMode() == null) {
183177
Optional.ofNullable(
184178
GenericKubernetesResource.get(actualMap, "spec", "volumeClaimTemplates", i, "spec"))
185179
.map(Map.class::cast).ifPresent(m -> m.remove("volumeMode"));
186180
}
187-
if (desiredStatefulSet.getSpec().getVolumeClaimTemplates().get(i).getStatus() == null) {
188-
Optional
189-
.ofNullable(
190-
GenericKubernetesResource.get(actualMap, "spec", "volumeClaimTemplates", i))
181+
if (claim.getStatus() == null) {
182+
Optional.ofNullable(
183+
GenericKubernetesResource.get(actualMap, "spec", "volumeClaimTemplates", i))
191184
.map(Map.class::cast).ifPresent(m -> m.remove("status"));
192185
}
193186
}
@@ -212,19 +205,17 @@ private static void removeIrrelevantValues(Map<String, Object> desiredMap) {
212205
private static void keepOnlyManagedFields(Map<String, Object> result,
213206
Map<String, Object> actualMap,
214207
Map<String, Object> managedFields, KubernetesSerialization objectMapper) {
215-
216208
if (managedFields.isEmpty()) {
217209
result.putAll(actualMap);
218210
return;
219211
}
220-
for (Map.Entry<String, Object> entry : managedFields.entrySet()) {
221-
String key = entry.getKey();
212+
for (var entry : managedFields.entrySet()) {
213+
var key = entry.getKey();
222214
if (key.startsWith(F_PREFIX)) {
223-
String keyInActual = keyWithoutPrefix(key);
215+
var keyInActual = keyWithoutPrefix(key);
224216
var managedFieldValue = (Map<String, Object>) entry.getValue();
225217
if (isNestedValue(managedFieldValue)) {
226218
var managedEntrySet = managedFieldValue.entrySet();
227-
228219
// two special cases "k:" and "v:" prefixes
229220
if (isListKeyEntrySet(managedEntrySet)) {
230221
handleListKeyEntrySet(result, actualMap, objectMapper, keyInActual, managedEntrySet);
@@ -260,7 +251,6 @@ private static void fillResultsAndTraverseFurther(Map<String, Object> result,
260251
result.put(keyInActual, emptyMapValue);
261252
var actualMapValue = actualMap.getOrDefault(keyInActual, Collections.emptyMap());
262253
log.debug("key: {} actual map value: managedFieldValue: {}", keyInActual, managedFieldValue);
263-
264254
keepOnlyManagedFields(emptyMapValue, (Map<String, Object>) actualMapValue,
265255
(Map<String, Object>) managedFields.get(key), objectMapper);
266256
}
@@ -288,10 +278,10 @@ private static void handleListKeyEntrySet(Map<String, Object> result,
288278
result.put(keyInActual, valueList);
289279
var actualValueList = (List<Map<String, Object>>) actualMap.get(keyInActual);
290280

291-
SortedMap<Integer, Map<String, Object>> targetValuesByIndex = new TreeMap<>();
292-
Map<Integer, Map<String, Object>> managedEntryByIndex = new HashMap<>();
281+
var targetValuesByIndex = new TreeMap<Integer, Map<String, Object>>();
282+
var managedEntryByIndex = new HashMap<Integer, Map<String, Object>>();
293283

294-
for (Map.Entry<String, Object> listEntry : managedEntrySet) {
284+
for (var listEntry : managedEntrySet) {
295285
if (DOT_KEY.equals(listEntry.getKey())) {
296286
continue;
297287
}
@@ -310,42 +300,35 @@ private static void handleListKeyEntrySet(Map<String, Object> result,
310300
}
311301

312302
/**
313-
* Set values, the "v:" prefix. Form in managed fields: "f:some-set":{"v:1":{}},"v:2":{},"v:3":{}}
303+
* Set values, the {@code "v:"} prefix. Form in managed fields:
304+
* {@code "f:some-set":{"v:1":{}},"v:2":{},"v:3":{}}.
305+
* <p>
314306
* Note that this should be just used in very rare cases, actually was not able to produce a
315307
* sample. Kubernetes developers who worked on this feature were not able to provide one either
316308
* when prompted. Basically this method just adds the values from {@code "v:<value>"} to the
317309
* result.
318310
*/
319-
@SuppressWarnings("rawtypes")
320311
private static void handleSetValues(Map<String, Object> result, Map<String, Object> actualMap,
321312
KubernetesSerialization objectMapper, String keyInActual,
322313
Set<Entry<String, Object>> managedEntrySet) {
323314
var valueList = new ArrayList<>();
324315
result.put(keyInActual, valueList);
325-
for (Map.Entry<String, Object> valueEntry : managedEntrySet) {
316+
for (var valueEntry : managedEntrySet) {
326317
// not clear if this can happen
327318
if (DOT_KEY.equals(valueEntry.getKey())) {
328319
continue;
329320
}
330-
Class<?> targetClass = null;
331-
List values = (List) actualMap.get(keyInActual);
332-
if (!(values.get(0) instanceof Map)) {
333-
targetClass = values.get(0).getClass();
334-
}
335-
321+
var values = (List<?>) actualMap.get(keyInActual);
322+
var targetClass = (values.get(0) instanceof Map) ? null : values.get(0).getClass();
336323
var value = parseKeyValue(keyWithoutPrefix(valueEntry.getKey()), targetClass, objectMapper);
337324
valueList.add(value);
338325
}
339326
}
340327

341328
public static Object parseKeyValue(String stringValue, Class<?> targetClass,
342329
KubernetesSerialization objectMapper) {
343-
stringValue = stringValue.trim();
344-
if (targetClass != null) {
345-
return objectMapper.unmarshal(stringValue, targetClass);
346-
} else {
347-
return objectMapper.unmarshal(stringValue, Map.class);
348-
}
330+
var type = Objects.requireNonNullElse(targetClass, Map.class);
331+
return objectMapper.unmarshal(stringValue.trim(), type);
349332
}
350333

351334
private static boolean isSetValueField(Set<Map.Entry<String, Object>> managedEntrySet) {
@@ -372,30 +355,29 @@ private static boolean isKeyPrefixedSkippingDotKey(Set<Map.Entry<String, Object>
372355
}
373356

374357
@SuppressWarnings("unchecked")
375-
private static java.util.Map.Entry<Integer, Map<String, Object>> selectListEntryBasedOnKey(
358+
private static Map.Entry<Integer, Map<String, Object>> selectListEntryBasedOnKey(
376359
String key,
377360
List<Map<String, Object>> values, KubernetesSerialization objectMapper) {
378361
Map<String, Object> ids = objectMapper.unmarshal(key, Map.class);
379-
List<Map<String, Object>> possibleTargets = new ArrayList<>(1);
380-
int index = -1;
362+
var possibleTargets = new ArrayList<Map<String, Object>>(1);
363+
int lastIndex = -1;
381364
for (int i = 0; i < values.size(); i++) {
382-
var v = values.get(i);
383-
if (v.entrySet().containsAll(ids.entrySet())) {
384-
possibleTargets.add(v);
385-
index = i;
365+
var value = values.get(i);
366+
if (value.entrySet().containsAll(ids.entrySet())) {
367+
possibleTargets.add(value);
368+
lastIndex = i;
386369
}
387370
}
388371
if (possibleTargets.isEmpty()) {
389-
throw new IllegalStateException("Cannot find list element for key:" + key + ", in map: "
372+
throw new IllegalStateException("Cannot find list element for key: " + key + " in map: "
390373
+ values.stream().map(Map::keySet).toList());
391374
}
392375
if (possibleTargets.size() > 1) {
393376
throw new IllegalStateException(
394-
"More targets found in list element for key:" + key + ", in map: "
377+
"More targets found in list element for key: " + key + " in map: "
395378
+ values.stream().map(Map::keySet).toList());
396379
}
397-
final var finalIndex = index;
398-
return new AbstractMap.SimpleEntry<>(finalIndex, possibleTargets.get(0));
380+
return new AbstractMap.SimpleEntry<>(lastIndex, possibleTargets.get(0));
399381
}
400382

401383
private Optional<ManagedFieldsEntry> checkIfFieldManagerExists(R actual, String fieldManager) {
@@ -407,21 +389,21 @@ private Optional<ManagedFieldsEntry> checkIfFieldManagerExists(R actual, String
407389
f -> f.getManager().equals(fieldManager) && f.getOperation().equals(APPLY_OPERATION))
408390
.toList();
409391
if (targetManagedFields.isEmpty()) {
410-
log.debug("No field manager exists for resource {} with name: {} and operation Apply ",
392+
log.debug("No field manager exists for resource: {} with name: {} and operation {}",
411393
actual.getKind(),
412-
actual.getMetadata().getName());
394+
actual.getMetadata().getName(),
395+
APPLY_OPERATION);
413396
return Optional.empty();
414397
}
415398
// this should not happen in theory
416399
if (targetManagedFields.size() > 1) {
417400
throw new OperatorException("More than one field manager exists with name: " + fieldManager
418-
+ "in resource: " + actual.getKind() + " with name: " + actual.getMetadata().getName());
401+
+ " in resource: " + actual.getKind() + " with name: " + actual.getMetadata().getName());
419402
}
420403
return Optional.of(targetManagedFields.get(0));
421404
}
422405

423406
private static String keyWithoutPrefix(String key) {
424407
return key.substring(2);
425408
}
426-
427409
}

0 commit comments

Comments
 (0)