Skip to content

Commit 1abaea2

Browse files
committed
test: Add missing tests for SSABasedGenericKubernetesResourceMatcher
Signed-off-by: David Sondermann <david.sondermann@hivemq.com>
1 parent 9b6ab87 commit 1abaea2

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.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() {
@@ -117,6 +200,14 @@ void addedLabelInDesiredMakesMatchFail() {
117200
assertThat(matcher.matches(actualConfigMap, desiredConfigMap, mockedContext)).isFalse();
118201
}
119202

203+
@Test
204+
void withFinalizer() {
205+
var desired = loadResource("secret-with-finalizer-desired.yaml", Secret.class);
206+
var actual = loadResource("secret-with-finalizer.yaml", Secret.class);
207+
208+
assertThat(matcher.matches(actual, desired, mockedContext)).isTrue();
209+
}
210+
120211
@ParameterizedTest
121212
@ValueSource(
122213
strings = {
@@ -163,6 +254,23 @@ void testSanitizeState_statefulSetWithResources_withMismatch() {
163254
assertThat(matcher.matches(actualStatefulSet, desiredStatefulSet, mockedContext)).isFalse();
164255
}
165256

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+
166274
@Test
167275
void testSanitizeState_replicaSetWithResources() {
168276
var desiredReplicaSet = loadResource("sample-rs-resources-desired.yaml", ReplicaSet.class);
@@ -180,6 +288,14 @@ void testSanitizeState_replicaSetWithResources_withMismatch() {
180288
assertThat(matcher.matches(actualReplicaSet, desiredReplicaSet, mockedContext)).isFalse();
181289
}
182290

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+
183299
@Test
184300
void testSanitizeState_daemonSetWithResources() {
185301
var desiredDaemonSet = loadResource("sample-ds-resources-desired.yaml", DaemonSet.class);
@@ -196,6 +312,14 @@ void testSanitizeState_daemonSetWithResources_withMismatch() {
196312
assertThat(matcher.matches(actualDaemonSet, desiredDaemonSet, mockedContext)).isFalse();
197313
}
198314

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+
199323
@ParameterizedTest
200324
@ValueSource(booleans = {true, false})
201325
void testCustomMatcher_returnsExpectedMatchBasedOnReadOnlyLabel(boolean readOnly) {
@@ -222,16 +346,22 @@ void testCustomMatcher_returnsExpectedMatchBasedOnReadOnlyLabel(boolean readOnly
222346
}
223347

224348
@Test
225-
@SuppressWarnings("unchecked")
226-
void testSortMapWithNestedMap() {
227-
var nestedMap = new HashMap<String, Object>();
228-
nestedMap.put("z", 26);
229-
nestedMap.put("y", 25);
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+
}
230360

231-
var unsortedMap = new HashMap<String, Object>();
232-
unsortedMap.put("b", nestedMap);
233-
unsortedMap.put("a", 1);
234-
unsortedMap.put("c", 2);
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);
235365

236366
var sortedMap = SSABasedGenericKubernetesResourceMatcher.sortMap(unsortedMap);
237367
assertThat(sortedMap.keySet()).containsExactly("a", "b", "c");
@@ -242,18 +372,11 @@ void testSortMapWithNestedMap() {
242372

243373
@Test
244374
@SuppressWarnings("unchecked")
245-
void sortListItemsTest() {
246-
var nestedMap1 = new HashMap<String, Object>();
247-
nestedMap1.put("z", 26);
248-
nestedMap1.put("y", 25);
249-
250-
var nestedMap2 = new HashMap<String, Object>();
251-
nestedMap2.put("b", 26);
252-
nestedMap2.put("c", 25);
253-
nestedMap2.put("a", 24);
254-
255-
var unsortedListItems = List.<Object>of(1, nestedMap1, nestedMap2);
256-
var sortedListItems = SSABasedGenericKubernetesResourceMatcher.sortListItems(unsortedListItems);
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);
257380
assertThat(sortedListItems).element(0).isEqualTo(1);
258381

259382
var sortedNestedMap1 = (Map<String, Object>) sortedListItems.get(1);
@@ -265,7 +388,7 @@ void sortListItemsTest() {
265388

266389
private static <R> R loadResource(String fileName, Class<R> clazz) {
267390
return ReconcilerUtils.loadYaml(
268-
clazz, SSABasedGenericKubernetesResourceMatcherTest.class, fileName);
391+
clazz, SSABasedGenericKubernetesResourceMatcherTest.class, fileName);
269392
}
270393

271394
private static class ConfigMapDR extends KubernetesDependentResource<ConfigMap, ConfigMap> {

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)