Skip to content

Commit 5f71aaa

Browse files
Donnerbartcsviri
authored andcommitted
fix: infinite resource updates due to canonical format conversion of resource requirements (#2565)
* refactor: clean up SSABasedGenericKubernetesResourceMatcher Signed-off-by: David Sondermann <[email protected]> * test: add missing tests for StatefulSet with VolumeClaimTemplates for SSABasedGenericKubernetesResourceMatcher Signed-off-by: David Sondermann <[email protected]> * fix: Fix infinite resource updates due to canonical format conversion of resource requirements Signed-off-by: David Sondermann <[email protected]> * test: Add test cases with init containers to ResourceRequirementsSanitizerTest Signed-off-by: David Sondermann <[email protected]> --------- Signed-off-by: David Sondermann <[email protected]> Signed-off-by: Chris Laprun <[email protected]>
1 parent 7dce326 commit 5f71aaa

File tree

2 files changed

+70
-76
lines changed

2 files changed

+70
-76
lines changed

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

+28-31
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@
1212
import java.util.Optional;
1313
import java.util.Set;
1414
import java.util.TreeMap;
15-
import java.util.Optional;
16-
import java.util.Set;
17-
import java.util.SortedMap;
18-
import java.util.TreeMap;
1915

2016
import org.slf4j.Logger;
2117
import org.slf4j.LoggerFactory;
@@ -32,11 +28,11 @@
3228
import io.javaoperatorsdk.operator.api.reconciler.Context;
3329
import io.javaoperatorsdk.operator.processing.LoggingUtils;
3430

35-
import static io.javaoperatorsdk.operator.processing.dependent.kubernetes.ResourceRequirementsSanitizer.sanitizeResourceRequirements;
36-
3731
import com.github.difflib.DiffUtils;
3832
import com.github.difflib.UnifiedDiffUtils;
3933

34+
import static io.javaoperatorsdk.operator.processing.dependent.kubernetes.ResourceRequirementsSanitizer.sanitizeResourceRequirements;
35+
4036
/**
4137
* Matches the actual state on the server vs the desired state. Based on the managedFields of SSA.
4238
* <p>
@@ -112,15 +108,13 @@ public boolean matches(R actual, R desired, Context<?> context) {
112108
removeIrrelevantValues(desiredMap);
113109

114110
var matches = prunedActual.equals(desiredMap);
115-
116111
if (!matches && log.isDebugEnabled() && LoggingUtils.isNotSensitiveResource(desired)) {
117112
var diff = getDiff(prunedActual, desiredMap, objectMapper);
118113
log.debug(
119-
"Diff between actual and desired state for resource: {} with name: {} in namespace: {} is: \n{}",
114+
"Diff between actual and desired state for resource: {} with name: {} in namespace: {} is:\n{}",
120115
actual.getKind(), actual.getMetadata().getName(), actual.getMetadata().getNamespace(),
121116
diff);
122117
}
123-
124118
return matches;
125119
}
126120

@@ -129,24 +123,23 @@ private String getDiff(Map<String, Object> prunedActualMap, Map<String, Object>
129123
var actualYaml = serialization.asYaml(sortMap(prunedActualMap));
130124
var desiredYaml = serialization.asYaml(sortMap(desiredMap));
131125
if (log.isTraceEnabled()) {
132-
log.trace("Pruned actual resource: \n {} \ndesired resource: \n {} ", actualYaml,
133-
desiredYaml);
126+
log.trace("Pruned actual resource:\n {} \ndesired resource:\n {} ", actualYaml, desiredYaml);
134127
}
135128

136129
var patch = DiffUtils.diff(actualYaml.lines().toList(), desiredYaml.lines().toList());
137-
List<String> unifiedDiff =
130+
var unifiedDiff =
138131
UnifiedDiffUtils.generateUnifiedDiff("", "", actualYaml.lines().toList(), patch, 1);
139132
return String.join("\n", unifiedDiff);
140133
}
141134

142135
@SuppressWarnings("unchecked")
143136
Map<String, Object> sortMap(Map<String, Object> map) {
144-
List<String> sortedKeys = new ArrayList<>(map.keySet());
137+
var sortedKeys = new ArrayList<>(map.keySet());
145138
Collections.sort(sortedKeys);
146139

147-
Map<String, Object> sortedMap = new LinkedHashMap<>();
148-
for (String key : sortedKeys) {
149-
Object value = map.get(key);
140+
var sortedMap = new LinkedHashMap<String, Object>();
141+
for (var key : sortedKeys) {
142+
var value = map.get(key);
150143
if (value instanceof Map) {
151144
sortedMap.put(key, sortMap((Map<String, Object>) value));
152145
} else if (value instanceof List) {
@@ -160,8 +153,8 @@ Map<String, Object> sortMap(Map<String, Object> map) {
160153

161154
@SuppressWarnings("unchecked")
162155
List<Object> sortListItems(List<Object> list) {
163-
List<Object> sortedList = new ArrayList<>();
164-
for (Object item : list) {
156+
var sortedList = new ArrayList<>();
157+
for (var item : list) {
165158
if (item instanceof Map) {
166159
sortedList.add(sortMap((Map<String, Object>) item));
167160
} else if (item instanceof List) {
@@ -177,9 +170,10 @@ List<Object> sortListItems(List<Object> list) {
177170
* Correct for known issue with SSA
178171
*/
179172
private void sanitizeState(R actual, R desired, Map<String, Object> actualMap) {
180-
if (actual instanceof StatefulSet) {
181-
var actualSpec = (((StatefulSet) actual)).getSpec();
182-
var desiredSpec = (((StatefulSet) desired)).getSpec();
173+
if (actual instanceof StatefulSet actualStatefulSet
174+
&& desired instanceof StatefulSet desiredStatefulSet) {
175+
var actualSpec = actualStatefulSet.getSpec();
176+
var desiredSpec = desiredStatefulSet.getSpec();
183177
int claims = desiredSpec.getVolumeClaimTemplates().size();
184178
if (claims == actualSpec.getVolumeClaimTemplates().size()) {
185179
for (int i = 0; i < claims; i++) {
@@ -197,18 +191,21 @@ private void sanitizeState(R actual, R desired, Map<String, Object> actualMap) {
197191
}
198192
}
199193
sanitizeResourceRequirements(actualMap, actualSpec.getTemplate(), desiredSpec.getTemplate());
200-
} else if (actual instanceof Deployment) {
194+
} else if (actual instanceof Deployment actualDeployment
195+
&& desired instanceof Deployment desiredDeployment) {
201196
sanitizeResourceRequirements(actualMap,
202-
((Deployment) actual).getSpec().getTemplate(),
203-
((Deployment) desired).getSpec().getTemplate());
204-
} else if (actual instanceof ReplicaSet) {
197+
actualDeployment.getSpec().getTemplate(),
198+
desiredDeployment.getSpec().getTemplate());
199+
} else if (actual instanceof ReplicaSet actualReplicaSet
200+
&& desired instanceof ReplicaSet desiredReplicaSet) {
205201
sanitizeResourceRequirements(actualMap,
206-
((ReplicaSet) actual).getSpec().getTemplate(),
207-
((ReplicaSet) desired).getSpec().getTemplate());
208-
} else if (actual instanceof DaemonSet) {
202+
actualReplicaSet.getSpec().getTemplate(),
203+
desiredReplicaSet.getSpec().getTemplate());
204+
} else if (actual instanceof DaemonSet actualDaemonSet
205+
&& desired instanceof DaemonSet desiredDaemonSet) {
209206
sanitizeResourceRequirements(actualMap,
210-
((DaemonSet) actual).getSpec().getTemplate(),
211-
((DaemonSet) desired).getSpec().getTemplate());
207+
actualDaemonSet.getSpec().getTemplate(),
208+
desiredDaemonSet.getSpec().getTemplate());
212209
}
213210
}
214211

@@ -393,7 +390,7 @@ private static Map.Entry<Integer, Map<String, Object>> selectListEntryBasedOnKey
393390
}
394391
}
395392
if (possibleTargets.isEmpty()) {
396-
throw new IllegalStateException("Cannot find list element for key: " + key + ", in map: "
393+
throw new IllegalStateException("Cannot find list element for key: " + key + " in map: "
397394
+ values.stream().map(Map::keySet).toList());
398395
}
399396
if (possibleTargets.size() > 1) {

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcherTest.java

+42-45
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.javaoperatorsdk.operator.processing.dependent.kubernetes;
22

3-
import java.util.Arrays;
43
import java.util.HashMap;
54
import java.util.List;
65
import java.util.Map;
@@ -123,6 +122,48 @@ void addedLabelInDesiredMakesMatchFail() {
123122
assertThat(matcher.matches(actualConfigMap, desiredConfigMap, mockedContext)).isFalse();
124123
}
125124

125+
@Test
126+
@SuppressWarnings("unchecked")
127+
void sortListItemsTest() {
128+
var nestedMap1 = new HashMap<String, Object>();
129+
nestedMap1.put("z", 26);
130+
nestedMap1.put("y", 25);
131+
132+
var nestedMap2 = new HashMap<String, Object>();
133+
nestedMap2.put("b", 26);
134+
nestedMap2.put("c", 25);
135+
nestedMap2.put("a", 24);
136+
137+
var unsortedListItems = List.<Object>of(1, nestedMap1, nestedMap2);
138+
var sortedListItems = matcher.sortListItems(unsortedListItems);
139+
assertThat(sortedListItems).element(0).isEqualTo(1);
140+
141+
var sortedNestedMap1 = (Map<String, Object>) sortedListItems.get(1);
142+
assertThat(sortedNestedMap1.keySet()).containsExactly("y", "z");
143+
144+
var sortedNestedMap2 = (Map<String, Object>) sortedListItems.get(2);
145+
assertThat(sortedNestedMap2.keySet()).containsExactly("a", "b", "c");
146+
}
147+
148+
@Test
149+
@SuppressWarnings("unchecked")
150+
void testSortMapWithNestedMap() {
151+
var nestedMap = new HashMap<String, Object>();
152+
nestedMap.put("z", 26);
153+
nestedMap.put("y", 25);
154+
155+
var unsortedMap = new HashMap<String, Object>();
156+
unsortedMap.put("b", nestedMap);
157+
unsortedMap.put("a", 1);
158+
unsortedMap.put("c", 2);
159+
160+
var sortedMap = matcher.sortMap(unsortedMap);
161+
assertThat(sortedMap.keySet()).containsExactly("a", "b", "c");
162+
163+
var sortedNestedMap = (Map<String, Object>) sortedMap.get("b");
164+
assertThat(sortedNestedMap.keySet()).containsExactly("y", "z");
165+
}
166+
126167
@ParameterizedTest
127168
@ValueSource(strings = {"sample-sts-volumeclaimtemplates-desired.yaml",
128169
"sample-sts-volumeclaimtemplates-desired-with-status.yaml",
@@ -209,48 +250,4 @@ private static <R> R loadResource(String fileName, Class<R> clazz) {
209250
return ReconcilerUtils.loadYaml(clazz, SSABasedGenericKubernetesResourceMatcherTest.class,
210251
fileName);
211252
}
212-
213-
@Test
214-
@SuppressWarnings("unchecked")
215-
void sortListItemsTest() {
216-
Map<String, Object> nestedMap1 = new HashMap<>();
217-
nestedMap1.put("z", 26);
218-
nestedMap1.put("y", 25);
219-
220-
Map<String, Object> nestedMap2 = new HashMap<>();
221-
nestedMap2.put("b", 26);
222-
nestedMap2.put("c", 25);
223-
nestedMap2.put("a", 24);
224-
225-
List<Object> unsortedListItems = Arrays.asList(1, nestedMap1, nestedMap2);
226-
List<Object> sortedListItems = matcher.sortListItems(unsortedListItems);
227-
228-
assertThat(sortedListItems).element(0).isEqualTo(1);
229-
230-
Map<String, Object> sortedNestedMap1 = (Map<String, Object>) sortedListItems.get(1);
231-
assertThat(sortedNestedMap1.keySet()).containsExactly("y", "z");
232-
233-
Map<String, Object> sortedNestedMap2 = (Map<String, Object>) sortedListItems.get(2);
234-
assertThat(sortedNestedMap2.keySet()).containsExactly("a", "b", "c");
235-
}
236-
237-
@Test
238-
@SuppressWarnings("unchecked")
239-
void testSortMapWithNestedMap() {
240-
Map<String, Object> nestedMap = new HashMap<>();
241-
nestedMap.put("z", 26);
242-
nestedMap.put("y", 25);
243-
244-
Map<String, Object> unsortedMap = new HashMap<>();
245-
unsortedMap.put("b", nestedMap);
246-
unsortedMap.put("a", 1);
247-
unsortedMap.put("c", 2);
248-
249-
Map<String, Object> sortedMap = matcher.sortMap(unsortedMap);
250-
251-
assertThat(sortedMap.keySet()).containsExactly("a", "b", "c");
252-
253-
Map<String, Object> sortedNestedMap = (Map<String, Object>) sortedMap.get("b");
254-
assertThat(sortedNestedMap.keySet()).containsExactly("y", "z");
255-
}
256253
}

0 commit comments

Comments
 (0)