Skip to content

Commit 33f1f4e

Browse files
committed
test: Add missing tests for SSABasedGenericKubernetesResourceMatcher
Signed-off-by: David Sondermann <david.sondermann@hivemq.com>
1 parent 91ecdfc commit 33f1f4e

File tree

10 files changed

+211
-29
lines changed

10 files changed

+211
-29
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ private void sanitizeState(R actual, R desired, Map<String, Object> actualMap) {
226226
}
227227

228228
@SuppressWarnings("unchecked")
229-
private static void keepOnlyManagedFields(
229+
static void keepOnlyManagedFields(
230230
Map<String, Object> result,
231231
Map<String, Object> actualMap,
232232
Map<String, Object> managedFields,

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

Lines changed: 147 additions & 24 deletions
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.HashMap;
43
import java.util.List;
54
import java.util.Map;
65

@@ -11,17 +10,20 @@
1110

1211
import io.fabric8.kubernetes.api.model.ConfigMap;
1312
import io.fabric8.kubernetes.api.model.HasMetadata;
13+
import io.fabric8.kubernetes.api.model.Secret;
1414
import io.fabric8.kubernetes.api.model.apps.DaemonSet;
1515
import io.fabric8.kubernetes.api.model.apps.Deployment;
1616
import io.fabric8.kubernetes.api.model.apps.ReplicaSet;
1717
import io.fabric8.kubernetes.api.model.apps.StatefulSet;
1818
import io.javaoperatorsdk.operator.MockKubernetesClient;
19+
import io.javaoperatorsdk.operator.OperatorException;
1920
import io.javaoperatorsdk.operator.ReconcilerUtils;
2021
import io.javaoperatorsdk.operator.api.config.ConfigurationService;
2122
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
2223
import io.javaoperatorsdk.operator.api.reconciler.Context;
2324

2425
import static org.assertj.core.api.Assertions.assertThat;
26+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2527
import static org.mockito.Mockito.mock;
2628
import static org.mockito.Mockito.when;
2729

@@ -45,6 +47,54 @@ void setup() {
4547
when(mockedContext.getControllerConfiguration()).thenReturn(controllerConfiguration);
4648
}
4749

50+
@Test
51+
void noMatchWhenNoMatchingController() {
52+
var desired = loadResource("nginx-deployment.yaml", Deployment.class);
53+
var actual =
54+
loadResource("deployment-with-managed-fields-additional-controller.yaml", Deployment.class);
55+
actual
56+
.getMetadata()
57+
.getManagedFields()
58+
.removeIf(managedFieldsEntry -> managedFieldsEntry.getManager().equals("controller"));
59+
60+
assertThat(matcher.matches(actual, desired, mockedContext)).isFalse();
61+
}
62+
63+
@Test
64+
void exceptionWhenDuplicateController() {
65+
var desired = loadResource("nginx-deployment.yaml", Deployment.class);
66+
var actual =
67+
loadResource("deployment-with-managed-fields-additional-controller.yaml", Deployment.class);
68+
actual.getMetadata().getManagedFields().stream()
69+
.filter(managedFieldsEntry -> managedFieldsEntry.getManager().equals("controller"))
70+
.findFirst()
71+
.ifPresent(
72+
managedFieldsEntry -> actual.getMetadata().getManagedFields().add(managedFieldsEntry));
73+
74+
assertThatThrownBy(() -> matcher.matches(actual, desired, mockedContext))
75+
.isInstanceOf(OperatorException.class)
76+
.hasMessage(
77+
"More than one field manager exists with name: controller in resource: Deployment with"
78+
+ " name: test");
79+
}
80+
81+
@Test
82+
void matchWithSensitiveResource() {
83+
var desired = loadResource("secret-desired.yaml", Secret.class);
84+
var actual = loadResource("secret.yaml", Secret.class);
85+
86+
assertThat(matcher.matches(actual, desired, mockedContext)).isTrue();
87+
}
88+
89+
@Test
90+
void noMatchWithSensitiveResource() {
91+
var desired = loadResource("secret-desired.yaml", Secret.class);
92+
var actual = loadResource("secret.yaml", Secret.class);
93+
actual.getData().put("key1", "dmFsMg==");
94+
95+
assertThat(matcher.matches(actual, desired, mockedContext)).isFalse();
96+
}
97+
4898
@Test
4999
void checksIfAddsNotAddedByController() {
50100
var desired = loadResource("nginx-deployment.yaml", Deployment.class);
@@ -54,7 +104,40 @@ void checksIfAddsNotAddedByController() {
54104
assertThat(matcher.matches(actual, desired, mockedContext)).isTrue();
55105
}
56106

57-
// In the example the owner reference in a list is referenced by "k:", while all the fields are
107+
@Test
108+
void throwExceptionWhenManagedListEntryNotFound() {
109+
var desired = loadResource("nginx-deployment.yaml", Deployment.class);
110+
var actual =
111+
loadResource("deployment-with-managed-fields-additional-controller.yaml", Deployment.class);
112+
final var container = actual.getSpec().getTemplate().getSpec().getContainers().get(0);
113+
container.setName("foobar");
114+
115+
assertThatThrownBy(() -> matcher.matches(actual, desired, mockedContext))
116+
.isInstanceOf(IllegalStateException.class)
117+
.hasMessage(
118+
"Cannot find list element for key: {\"name\":\"nginx\"} in map: [[image,"
119+
+ " imagePullPolicy, name, ports, resources, terminationMessagePath,"
120+
+ " terminationMessagePolicy]]");
121+
}
122+
123+
@Test
124+
void throwExceptionWhenDuplicateManagedListEntryFound() {
125+
var desired = loadResource("nginx-deployment.yaml", Deployment.class);
126+
var actual =
127+
loadResource("deployment-with-managed-fields-additional-controller.yaml", Deployment.class);
128+
final var container = actual.getSpec().getTemplate().getSpec().getContainers().get(0);
129+
actual.getSpec().getTemplate().getSpec().getContainers().add(container);
130+
131+
assertThatThrownBy(() -> matcher.matches(actual, desired, mockedContext))
132+
.isInstanceOf(IllegalStateException.class)
133+
.hasMessage(
134+
"More targets found in list element for key: {\"name\":\"nginx\"} in map: [[image,"
135+
+ " imagePullPolicy, name, ports, resources, terminationMessagePath,"
136+
+ " terminationMessagePolicy], [image, imagePullPolicy, name, ports, resources,"
137+
+ " terminationMessagePath, terminationMessagePolicy]]");
138+
}
139+
140+
// in the example the owner reference in a list is referenced by "k:", while all the fields are
58141
// managed but not listed
59142
@Test
60143
void emptyListElementMatchesAllFields() {
@@ -115,6 +198,14 @@ void addedLabelInDesiredMakesMatchFail() {
115198
assertThat(matcher.matches(actualConfigMap, desiredConfigMap, mockedContext)).isFalse();
116199
}
117200

201+
@Test
202+
void withFinalizer() {
203+
var desired = loadResource("secret-with-finalizer-desired.yaml", Secret.class);
204+
var actual = loadResource("secret-with-finalizer.yaml", Secret.class);
205+
206+
assertThat(matcher.matches(actual, desired, mockedContext)).isTrue();
207+
}
208+
118209
@ParameterizedTest
119210
@ValueSource(
120211
strings = {
@@ -161,6 +252,23 @@ void testSanitizeState_statefulSetWithResources_withMismatch() {
161252
assertThat(matcher.matches(actualStatefulSet, desiredStatefulSet, mockedContext)).isFalse();
162253
}
163254

255+
@Test
256+
void testSanitizeState_statefulSet_withResourceTypeMismatch() {
257+
var desiredReplicaSet = loadResource("sample-rs-resources-desired.yaml", ReplicaSet.class);
258+
var actualStatefulSet = loadResource("sample-sts-resources.yaml", StatefulSet.class);
259+
260+
assertThat(matcher.matches(actualStatefulSet, desiredReplicaSet, mockedContext)).isFalse();
261+
}
262+
263+
@Test
264+
void testSanitizeState_deployment_withResourceTypeMismatch() {
265+
var desiredReplicaSet = loadResource("sample-rs-resources-desired.yaml", ReplicaSet.class);
266+
var actualDeployment =
267+
loadResource("deployment-with-managed-fields-additional-controller.yaml", Deployment.class);
268+
269+
assertThat(matcher.matches(actualDeployment, desiredReplicaSet, mockedContext)).isFalse();
270+
}
271+
164272
@Test
165273
void testSanitizeState_replicaSetWithResources() {
166274
var desiredReplicaSet = loadResource("sample-rs-resources-desired.yaml", ReplicaSet.class);
@@ -178,6 +286,14 @@ void testSanitizeState_replicaSetWithResources_withMismatch() {
178286
assertThat(matcher.matches(actualReplicaSet, desiredReplicaSet, mockedContext)).isFalse();
179287
}
180288

289+
@Test
290+
void testSanitizeState_replicaSet_withResourceTypeMismatch() {
291+
var desiredDaemonSet = loadResource("sample-ds-resources-desired.yaml", DaemonSet.class);
292+
var actualReplicaSet = loadResource("sample-rs-resources.yaml", ReplicaSet.class);
293+
294+
assertThat(matcher.matches(actualReplicaSet, desiredDaemonSet, mockedContext)).isFalse();
295+
}
296+
181297
@Test
182298
void testSanitizeState_daemonSetWithResources() {
183299
var desiredDaemonSet = loadResource("sample-ds-resources-desired.yaml", DaemonSet.class);
@@ -194,6 +310,14 @@ void testSanitizeState_daemonSetWithResources_withMismatch() {
194310
assertThat(matcher.matches(actualDaemonSet, desiredDaemonSet, mockedContext)).isFalse();
195311
}
196312

313+
@Test
314+
void testSanitizeState_daemonSet_withResourceTypeMismatch() {
315+
var desiredReplicaSet = loadResource("sample-rs-resources-desired.yaml", ReplicaSet.class);
316+
var actualDaemonSet = loadResource("sample-ds-resources.yaml", DaemonSet.class);
317+
318+
assertThat(matcher.matches(actualDaemonSet, desiredReplicaSet, mockedContext)).isFalse();
319+
}
320+
197321
@ParameterizedTest
198322
@ValueSource(booleans = {true, false})
199323
void testCustomMatcher_returnsExpectedMatchBasedOnReadOnlyLabel(boolean readOnly) {
@@ -209,16 +333,22 @@ void testCustomMatcher_returnsExpectedMatchBasedOnReadOnlyLabel(boolean readOnly
209333
}
210334

211335
@Test
212-
@SuppressWarnings("unchecked")
213-
void testSortMapWithNestedMap() {
214-
var nestedMap = new HashMap<String, Object>();
215-
nestedMap.put("z", 26);
216-
nestedMap.put("y", 25);
336+
void keepOnlyManagedFields_withInvalidManagedFieldsKey() {
337+
assertThatThrownBy(
338+
() ->
339+
SSABasedGenericKubernetesResourceMatcher.keepOnlyManagedFields(
340+
Map.of(),
341+
Map.of(),
342+
Map.of("invalid", 1),
343+
mockedContext.getClient().getKubernetesSerialization())) //
344+
.isInstanceOf(IllegalStateException.class) //
345+
.hasMessage("Key: invalid has no prefix: f:");
346+
}
217347

218-
var unsortedMap = new HashMap<String, Object>();
219-
unsortedMap.put("b", nestedMap);
220-
unsortedMap.put("a", 1);
221-
unsortedMap.put("c", 2);
348+
@Test
349+
@SuppressWarnings("unchecked")
350+
void testSortMap() {
351+
final var unsortedMap = Map.of("b", Map.of("z", 26, "y", 25), "a", List.of("w", "v"), "c", 2);
222352

223353
var sortedMap = SSABasedGenericKubernetesResourceMatcher.sortMap(unsortedMap);
224354
assertThat(sortedMap.keySet()).containsExactly("a", "b", "c");
@@ -229,18 +359,11 @@ void testSortMapWithNestedMap() {
229359

230360
@Test
231361
@SuppressWarnings("unchecked")
232-
void sortListItemsTest() {
233-
var nestedMap1 = new HashMap<String, Object>();
234-
nestedMap1.put("z", 26);
235-
nestedMap1.put("y", 25);
236-
237-
var nestedMap2 = new HashMap<String, Object>();
238-
nestedMap2.put("b", 26);
239-
nestedMap2.put("c", 25);
240-
nestedMap2.put("a", 24);
241-
242-
var unsortedListItems = List.<Object>of(1, nestedMap1, nestedMap2);
243-
var sortedListItems = SSABasedGenericKubernetesResourceMatcher.sortListItems(unsortedListItems);
362+
void testSortListItems() {
363+
final var unsortedList =
364+
List.of(1, Map.of("z", 26, "y", 25), Map.of("b", 26, "c", 25, "a", 24), List.of("w", "v"));
365+
366+
var sortedListItems = SSABasedGenericKubernetesResourceMatcher.sortListItems(unsortedList);
244367
assertThat(sortedListItems).element(0).isEqualTo(1);
245368

246369
var sortedNestedMap1 = (Map<String, Object>) sortedListItems.get(1);
@@ -252,7 +375,7 @@ void sortListItemsTest() {
252375

253376
private static <R> R loadResource(String fileName, Class<R> clazz) {
254377
return ReconcilerUtils.loadYaml(
255-
clazz, SSABasedGenericKubernetesResourceMatcherTest.class, fileName);
378+
clazz, SSABasedGenericKubernetesResourceMatcherTest.class, fileName);
256379
}
257380

258381
private static class ReadOnlyAwareMatcher<T extends HasMetadata>

operator-framework-core/src/test/resources/io/javaoperatorsdk/operator/processing/dependent/kubernetes/configmap.empty-owner-reference-desired.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,3 @@ metadata:
1010
uid: 1ef74cb4-dbbd-45ef-9caf-aa76186594ea
1111
data:
1212
key1: "val1"
13-
14-

operator-framework-core/src/test/resources/io/javaoperatorsdk/operator/processing/dependent/kubernetes/deployment-with-managed-fields-additional-controller.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ metadata:
2525
f:image: {}
2626
f:name: {}
2727
f:ports:
28+
.: {}
2829
k:{"containerPort":80,"protocol":"TCP"}:
2930
.: {}
3031
f:containerPort: {}

operator-framework-core/src/test/resources/io/javaoperatorsdk/operator/processing/dependent/kubernetes/multi-container-pod-desired.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ spec:
1818
- name: shared-data
1919
mountPath: /data
2020
command: ["/bin/sh"]
21-
args: ["-c", "echo Level Up Blue Team! > /data/index.html"]
21+
args: ["-c", "echo Level Up Blue Team! > /data/index.html"]

operator-framework-core/src/test/resources/io/javaoperatorsdk/operator/processing/dependent/kubernetes/multi-container-pod.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,4 +211,4 @@ status:
211211
podIPs:
212212
- ip: 10.244.0.3
213213
qosClass: BestEffort
214-
startTime: "2023-06-08T11:50:59Z"
214+
startTime: "2023-06-08T11:50:59Z"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
apiVersion: v1
2+
kind: Secret
3+
metadata:
4+
name: test1
5+
namespace: default
6+
data:
7+
key1: "dmFsMQ=="
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
apiVersion: v1
2+
kind: Secret
3+
metadata:
4+
finalizers:
5+
- test-finalizer
6+
name: test1
7+
namespace: default
8+
data:
9+
key1: "dmFsMQ=="
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
apiVersion: v1
2+
data:
3+
key1: "dmFsMQ=="
4+
kind: Secret
5+
metadata:
6+
creationTimestamp: "2023-06-07T11:08:34Z"
7+
finalizers:
8+
- test-finalizer
9+
managedFields:
10+
- apiVersion: v1
11+
fieldsType: FieldsV1
12+
fieldsV1:
13+
f:data:
14+
f:key1: {}
15+
f:metadata:
16+
f:finalizers:
17+
.: {}
18+
v:"test-finalizer": {}
19+
manager: controller
20+
operation: Apply
21+
time: "2023-06-07T11:08:34Z"
22+
name: test1
23+
namespace: default
24+
resourceVersion: "400"
25+
uid: 1d47f98f-ff1e-46d8-bbb5-6658ec488ae2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
apiVersion: v1
2+
data:
3+
key1: "dmFsMQ=="
4+
kind: Secret
5+
metadata:
6+
creationTimestamp: "2023-06-07T11:08:34Z"
7+
managedFields:
8+
- apiVersion: v1
9+
fieldsType: FieldsV1
10+
fieldsV1:
11+
f:data:
12+
f:key1: {}
13+
manager: controller
14+
operation: Apply
15+
time: "2023-06-07T11:08:34Z"
16+
name: test1
17+
namespace: default
18+
resourceVersion: "400"
19+
uid: 1d47f98f-ff1e-46d8-bbb5-6658ec488ae2

0 commit comments

Comments
 (0)