Skip to content

Commit 681dd59

Browse files
authored
Increase code coverage of SSABasedGenericKubernetesResourceMatcher (#2781)
* test: Add missing tests for SSABasedGenericKubernetesResourceMatcher Signed-off-by: David Sondermann <[email protected]> * test: Add missing tests for SSABasedGenericKubernetesResourceMatcher Signed-off-by: David Sondermann <[email protected]> --------- Signed-off-by: David Sondermann <[email protected]>
1 parent 5b673a4 commit 681dd59

File tree

10 files changed

+393
-213
lines changed

10 files changed

+393
-213
lines changed

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

+162-164
Large diffs are not rendered by default.

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

+168-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.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.ArgumentMatchers.any;
2628
import static org.mockito.Mockito.mock;
2729
import static org.mockito.Mockito.when;
@@ -47,6 +49,54 @@ void setup() {
4749
when(mockedContext.getControllerConfiguration()).thenReturn(controllerConfiguration);
4850
}
4951

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

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

120203
@Test
121-
@SuppressWarnings("unchecked")
122-
void sortListItemsTest() {
123-
var nestedMap1 = new HashMap<String, Object>();
124-
nestedMap1.put("z", 26);
125-
nestedMap1.put("y", 25);
126-
127-
var nestedMap2 = new HashMap<String, Object>();
128-
nestedMap2.put("b", 26);
129-
nestedMap2.put("c", 25);
130-
nestedMap2.put("a", 24);
131-
132-
var unsortedListItems = List.<Object>of(1, nestedMap1, nestedMap2);
133-
var sortedListItems = matcher.sortListItems(unsortedListItems);
134-
assertThat(sortedListItems).element(0).isEqualTo(1);
135-
136-
var sortedNestedMap1 = (Map<String, Object>) sortedListItems.get(1);
137-
assertThat(sortedNestedMap1.keySet()).containsExactly("y", "z");
204+
void withFinalizer() {
205+
var desired = loadResource("secret-with-finalizer-desired.yaml", Secret.class);
206+
var actual = loadResource("secret-with-finalizer.yaml", Secret.class);
138207

139-
var sortedNestedMap2 = (Map<String, Object>) sortedListItems.get(2);
140-
assertThat(sortedNestedMap2.keySet()).containsExactly("a", "b", "c");
141-
}
142-
143-
@Test
144-
@SuppressWarnings("unchecked")
145-
void testSortMapWithNestedMap() {
146-
var nestedMap = new HashMap<String, Object>();
147-
nestedMap.put("z", 26);
148-
nestedMap.put("y", 25);
149-
150-
var unsortedMap = new HashMap<String, Object>();
151-
unsortedMap.put("b", nestedMap);
152-
unsortedMap.put("a", 1);
153-
unsortedMap.put("c", 2);
154-
155-
var sortedMap = matcher.sortMap(unsortedMap);
156-
assertThat(sortedMap.keySet()).containsExactly("a", "b", "c");
157-
158-
var sortedNestedMap = (Map<String, Object>) sortedMap.get("b");
159-
assertThat(sortedNestedMap.keySet()).containsExactly("y", "z");
208+
assertThat(matcher.matches(actual, desired, mockedContext)).isTrue();
160209
}
161210

162211
@ParameterizedTest
@@ -205,6 +254,23 @@ void testSanitizeState_statefulSetWithResources_withMismatch() {
205254
assertThat(matcher.matches(actualStatefulSet, desiredStatefulSet, mockedContext)).isFalse();
206255
}
207256

257+
@Test
258+
void testSanitizeState_statefulSet_withResourceTypeMismatch() {
259+
var desiredReplicaSet = loadResource("sample-rs-resources-desired.yaml", ReplicaSet.class);
260+
var actualStatefulSet = loadResource("sample-sts-resources.yaml", StatefulSet.class);
261+
262+
assertThat(matcher.matches(actualStatefulSet, desiredReplicaSet, mockedContext)).isFalse();
263+
}
264+
265+
@Test
266+
void testSanitizeState_deployment_withResourceTypeMismatch() {
267+
var desiredReplicaSet = loadResource("sample-rs-resources-desired.yaml", ReplicaSet.class);
268+
var actualDeployment =
269+
loadResource("deployment-with-managed-fields-additional-controller.yaml", Deployment.class);
270+
271+
assertThat(matcher.matches(actualDeployment, desiredReplicaSet, mockedContext)).isFalse();
272+
}
273+
208274
@Test
209275
void testSanitizeState_replicaSetWithResources() {
210276
var desiredReplicaSet = loadResource("sample-rs-resources-desired.yaml", ReplicaSet.class);
@@ -222,6 +288,14 @@ void testSanitizeState_replicaSetWithResources_withMismatch() {
222288
assertThat(matcher.matches(actualReplicaSet, desiredReplicaSet, mockedContext)).isFalse();
223289
}
224290

291+
@Test
292+
void testSanitizeState_replicaSet_withResourceTypeMismatch() {
293+
var desiredDaemonSet = loadResource("sample-ds-resources-desired.yaml", DaemonSet.class);
294+
var actualReplicaSet = loadResource("sample-rs-resources.yaml", ReplicaSet.class);
295+
296+
assertThat(matcher.matches(actualReplicaSet, desiredDaemonSet, mockedContext)).isFalse();
297+
}
298+
225299
@Test
226300
void testSanitizeState_daemonSetWithResources() {
227301
var desiredDaemonSet = loadResource("sample-ds-resources-desired.yaml", DaemonSet.class);
@@ -238,6 +312,14 @@ void testSanitizeState_daemonSetWithResources_withMismatch() {
238312
assertThat(matcher.matches(actualDaemonSet, desiredDaemonSet, mockedContext)).isFalse();
239313
}
240314

315+
@Test
316+
void testSanitizeState_daemonSet_withResourceTypeMismatch() {
317+
var desiredReplicaSet = loadResource("sample-rs-resources-desired.yaml", ReplicaSet.class);
318+
var actualDaemonSet = loadResource("sample-ds-resources.yaml", DaemonSet.class);
319+
320+
assertThat(matcher.matches(actualDaemonSet, desiredReplicaSet, mockedContext)).isFalse();
321+
}
322+
241323
@ParameterizedTest
242324
@ValueSource(booleans = {true, false})
243325
void testCustomMatcher_returnsExpectedMatchBasedOnReadOnlyLabel(boolean readOnly) {
@@ -263,6 +345,52 @@ void testCustomMatcher_returnsExpectedMatchBasedOnReadOnlyLabel(boolean readOnly
263345
.isEqualTo(readOnly);
264346
}
265347

348+
@Test
349+
void keepOnlyManagedFields_withInvalidManagedFieldsKey() {
350+
assertThatThrownBy(
351+
() ->
352+
SSABasedGenericKubernetesResourceMatcher.keepOnlyManagedFields(
353+
Map.of(),
354+
Map.of(),
355+
Map.of("invalid", 1),
356+
mockedContext.getClient().getKubernetesSerialization())) //
357+
.isInstanceOf(IllegalStateException.class) //
358+
.hasMessage("Key: invalid has no prefix: f:");
359+
}
360+
361+
@Test
362+
@SuppressWarnings("unchecked")
363+
void testSortMap() {
364+
final var unsortedMap = Map.of("b", Map.of("z", 26, "y", 25), "a", List.of("w", "v"), "c", 2);
365+
366+
var sortedMap = SSABasedGenericKubernetesResourceMatcher.sortMap(unsortedMap);
367+
assertThat(sortedMap.keySet()).containsExactly("a", "b", "c");
368+
369+
var sortedNestedMap = (Map<String, Object>) sortedMap.get("b");
370+
assertThat(sortedNestedMap.keySet()).containsExactly("y", "z");
371+
}
372+
373+
@Test
374+
@SuppressWarnings("unchecked")
375+
void testSortListItems() {
376+
final var unsortedList =
377+
List.of(1, Map.of("z", 26, "y", 25), Map.of("b", 26, "c", 25, "a", 24), List.of("w", "v"));
378+
379+
var sortedListItems = SSABasedGenericKubernetesResourceMatcher.sortListItems(unsortedList);
380+
assertThat(sortedListItems).element(0).isEqualTo(1);
381+
382+
var sortedNestedMap1 = (Map<String, Object>) sortedListItems.get(1);
383+
assertThat(sortedNestedMap1.keySet()).containsExactly("y", "z");
384+
385+
var sortedNestedMap2 = (Map<String, Object>) sortedListItems.get(2);
386+
assertThat(sortedNestedMap2.keySet()).containsExactly("a", "b", "c");
387+
}
388+
389+
private static <R> R loadResource(String fileName, Class<R> clazz) {
390+
return ReconcilerUtils.loadYaml(
391+
clazz, SSABasedGenericKubernetesResourceMatcherTest.class, fileName);
392+
}
393+
266394
private static class ConfigMapDR extends KubernetesDependentResource<ConfigMap, ConfigMap> {
267395
public ConfigMapDR() {
268396
super(ConfigMap.class);
@@ -285,9 +413,4 @@ protected boolean matches(
285413
return actualMap.equals(desiredMap);
286414
}
287415
}
288-
289-
private static <R> R loadResource(String fileName, Class<R> clazz) {
290-
return ReconcilerUtils.loadYaml(
291-
clazz, SSABasedGenericKubernetesResourceMatcherTest.class, fileName);
292-
}
293416
}

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

-2
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

+1
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

+1-1
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

+1-1
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)