Skip to content

Commit 821760a

Browse files
committed
feat: integration test showcasing how to use primary to secondary mapper with dependent resources
1 parent bdab2be commit 821760a

7 files changed

+250
-0
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package io.javaoperatorsdk.operator;
2+
3+
import java.time.Duration;
4+
import java.util.Map;
5+
6+
import org.junit.jupiter.api.Test;
7+
import org.junit.jupiter.api.extension.RegisterExtension;
8+
9+
import io.fabric8.kubernetes.api.model.ConfigMap;
10+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
11+
import io.fabric8.kubernetes.api.model.Secret;
12+
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
13+
import io.javaoperatorsdk.operator.sample.primarytosecondaydependent.PrimaryToSecondaryDependentCustomResource;
14+
import io.javaoperatorsdk.operator.sample.primarytosecondaydependent.PrimaryToSecondaryDependentReconciler;
15+
import io.javaoperatorsdk.operator.sample.primarytosecondaydependent.PrimaryToSecondaryDependentSpec;
16+
17+
import static io.javaoperatorsdk.operator.sample.primarytosecondaydependent.ConfigMapReconcilePrecondition.DO_NOT_RECONCILE;
18+
import static io.javaoperatorsdk.operator.sample.primarytosecondaydependent.PrimaryToSecondaryDependentReconciler.DATA_KEY;
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
import static org.awaitility.Awaitility.await;
21+
22+
class PrimaryToSecondaryDependentIT {
23+
24+
public static final String TEST_CONFIG_MAP_NAME = "testconfigmap";
25+
public static final String TEST_CR_NAME = "test1";
26+
public static final String TEST_DATA = "testData";
27+
public
28+
29+
@RegisterExtension LocallyRunOperatorExtension operator =
30+
LocallyRunOperatorExtension.builder()
31+
.withReconciler(new PrimaryToSecondaryDependentReconciler())
32+
.build();
33+
34+
@Test
35+
void testPrimaryToSecondaryInDependentResources() {
36+
var reconciler = operator.getReconcilerOfType(PrimaryToSecondaryDependentReconciler.class);
37+
var cm = operator.create(configMap(DO_NOT_RECONCILE));
38+
var cr = operator.create(testCustomResource());
39+
40+
await().pollDelay(Duration.ofMillis(250)).untilAsserted(() -> {
41+
assertThat(reconciler.getNumberOfExecutions()).isPositive();
42+
assertThat(operator.get(Secret.class, TEST_CR_NAME)).isNull();
43+
});
44+
45+
cm.setData(Map.of(DATA_KEY, TEST_DATA));
46+
operator.replace(cm);
47+
var executions = reconciler.getNumberOfExecutions();
48+
49+
await().pollDelay(Duration.ofMillis(250)).untilAsserted(() -> {
50+
assertThat(reconciler.getNumberOfExecutions()).isGreaterThan(executions);
51+
var secret = operator.get(Secret.class, TEST_CR_NAME);
52+
assertThat(secret).isNotNull();
53+
assertThat(secret.getData().get(DATA_KEY)).isEqualTo(TEST_DATA);
54+
});
55+
}
56+
57+
PrimaryToSecondaryDependentCustomResource testCustomResource() {
58+
var res = new PrimaryToSecondaryDependentCustomResource();
59+
res.setMetadata(new ObjectMetaBuilder()
60+
.withName(TEST_CR_NAME)
61+
.build());
62+
res.setSpec(new PrimaryToSecondaryDependentSpec());
63+
res.getSpec().setConfigMapName(TEST_CONFIG_MAP_NAME);
64+
return res;
65+
}
66+
67+
ConfigMap configMap(String data) {
68+
var cm = new ConfigMap();
69+
cm.setMetadata(new ObjectMetaBuilder()
70+
.withName(TEST_CONFIG_MAP_NAME)
71+
.build());
72+
cm.setData(Map.of(DATA_KEY, data));
73+
return cm;
74+
}
75+
76+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package io.javaoperatorsdk.operator.sample.primarytosecondaydependent;
2+
3+
import io.fabric8.kubernetes.api.model.ConfigMap;
4+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource;
5+
6+
public class ConfigMapDependent extends
7+
KubernetesDependentResource<ConfigMap, PrimaryToSecondaryDependentCustomResource> {
8+
9+
public ConfigMapDependent() {
10+
super(ConfigMap.class);
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.javaoperatorsdk.operator.sample.primarytosecondaydependent;
2+
3+
import io.fabric8.kubernetes.api.model.ConfigMap;
4+
import io.javaoperatorsdk.operator.api.reconciler.Context;
5+
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
6+
import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition;
7+
8+
import static io.javaoperatorsdk.operator.sample.primarytosecondaydependent.PrimaryToSecondaryDependentReconciler.DATA_KEY;
9+
10+
public class ConfigMapReconcilePrecondition
11+
implements Condition<ConfigMap, PrimaryToSecondaryDependentCustomResource> {
12+
13+
public static final String DO_NOT_RECONCILE = "doNotReconcile";
14+
15+
@Override
16+
public boolean isMet(
17+
DependentResource<ConfigMap, PrimaryToSecondaryDependentCustomResource> dependentResource,
18+
PrimaryToSecondaryDependentCustomResource primary,
19+
Context<PrimaryToSecondaryDependentCustomResource> context) {
20+
return dependentResource.getSecondaryResource(primary, context).map(cm -> {
21+
var data = cm.getData().get(DATA_KEY);
22+
return data != null && !data.equals(DO_NOT_RECONCILE);
23+
})
24+
.orElse(false);
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.javaoperatorsdk.operator.sample.primarytosecondaydependent;
2+
3+
import io.fabric8.kubernetes.api.model.Namespaced;
4+
import io.fabric8.kubernetes.client.CustomResource;
5+
import io.fabric8.kubernetes.model.annotation.Group;
6+
import io.fabric8.kubernetes.model.annotation.ShortNames;
7+
import io.fabric8.kubernetes.model.annotation.Version;
8+
9+
@Group("sample.javaoperatorsdk")
10+
@Version("v1")
11+
@ShortNames("ptsd")
12+
public class PrimaryToSecondaryDependentCustomResource
13+
extends CustomResource<PrimaryToSecondaryDependentSpec, Void>
14+
implements Namespaced {
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package io.javaoperatorsdk.operator.sample.primarytosecondaydependent;
2+
3+
import java.util.List;
4+
import java.util.Map;
5+
import java.util.Set;
6+
import java.util.concurrent.atomic.AtomicInteger;
7+
import java.util.stream.Collectors;
8+
9+
import io.fabric8.kubernetes.api.model.ConfigMap;
10+
import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration;
11+
import io.javaoperatorsdk.operator.api.reconciler.*;
12+
import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
13+
import io.javaoperatorsdk.operator.processing.event.ResourceID;
14+
import io.javaoperatorsdk.operator.processing.event.source.EventSource;
15+
import io.javaoperatorsdk.operator.processing.event.source.PrimaryToSecondaryMapper;
16+
import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource;
17+
import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider;
18+
19+
import static io.javaoperatorsdk.operator.sample.primarytosecondaydependent.PrimaryToSecondaryDependentReconciler.CONFIG_MAP;
20+
import static io.javaoperatorsdk.operator.sample.primarytosecondaydependent.PrimaryToSecondaryDependentReconciler.CONFIG_MAP_EVENT_SOURCE;
21+
22+
@ControllerConfiguration(dependents = {@Dependent(type = ConfigMapDependent.class,
23+
name = CONFIG_MAP,
24+
reconcilePrecondition = ConfigMapReconcilePrecondition.class,
25+
useEventSourceWithName = CONFIG_MAP_EVENT_SOURCE),
26+
@Dependent(type = SecretDependent.class, dependsOn = CONFIG_MAP)})
27+
public class PrimaryToSecondaryDependentReconciler
28+
implements Reconciler<PrimaryToSecondaryDependentCustomResource>, TestExecutionInfoProvider,
29+
EventSourceInitializer<PrimaryToSecondaryDependentCustomResource> {
30+
31+
public static final String DATA_KEY = "data";
32+
public static final String CONFIG_MAP = "ConfigMap";
33+
public static final String CONFIG_MAP_INDEX = "ConfigMapIndex";
34+
public static final String CONFIG_MAP_EVENT_SOURCE = "ConfigMapEventSource";
35+
36+
private final AtomicInteger numberOfExecutions = new AtomicInteger(0);
37+
38+
@Override
39+
public UpdateControl<PrimaryToSecondaryDependentCustomResource> reconcile(
40+
PrimaryToSecondaryDependentCustomResource resource,
41+
Context<PrimaryToSecondaryDependentCustomResource> context) {
42+
numberOfExecutions.addAndGet(1);
43+
return UpdateControl.noUpdate();
44+
}
45+
46+
public int getNumberOfExecutions() {
47+
return numberOfExecutions.get();
48+
}
49+
50+
@Override
51+
public Map<String, EventSource> prepareEventSources(
52+
EventSourceContext<PrimaryToSecondaryDependentCustomResource> context) {
53+
context.getPrimaryCache().addIndexer(CONFIG_MAP_INDEX, (primary -> List
54+
.of(indexKey(primary.getSpec().getConfigMapName(), primary.getMetadata().getNamespace()))));
55+
56+
var cmES = new InformerEventSource<>(InformerConfiguration
57+
.from(ConfigMap.class, context)
58+
.withPrimaryToSecondaryMapper(
59+
(PrimaryToSecondaryMapper<PrimaryToSecondaryDependentCustomResource>) p -> Set
60+
.of(new ResourceID(p.getSpec().getConfigMapName(), p.getMetadata().getNamespace())))
61+
.withSecondaryToPrimaryMapper(cm -> context.getPrimaryCache()
62+
.byIndex(CONFIG_MAP_INDEX, indexKey(cm.getMetadata().getName(),
63+
cm.getMetadata().getNamespace()))
64+
.stream().map(ResourceID::fromResource).collect(Collectors.toSet()))
65+
.build(),
66+
context);
67+
68+
return Map.of(CONFIG_MAP_EVENT_SOURCE, cmES);
69+
}
70+
71+
private String indexKey(String clusterName, String namespace) {
72+
return clusterName + "#" + namespace;
73+
}
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.javaoperatorsdk.operator.sample.primarytosecondaydependent;
2+
3+
public class PrimaryToSecondaryDependentSpec {
4+
5+
private String configMapName;
6+
7+
public String getConfigMapName() {
8+
return configMapName;
9+
}
10+
11+
public PrimaryToSecondaryDependentSpec setConfigMapName(String configMapName) {
12+
this.configMapName = configMapName;
13+
return this;
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package io.javaoperatorsdk.operator.sample.primarytosecondaydependent;
2+
3+
import java.util.Map;
4+
5+
import io.fabric8.kubernetes.api.model.ConfigMap;
6+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
7+
import io.fabric8.kubernetes.api.model.Secret;
8+
import io.javaoperatorsdk.operator.api.reconciler.Context;
9+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
10+
11+
import static io.javaoperatorsdk.operator.sample.primarytosecondaydependent.PrimaryToSecondaryDependentReconciler.DATA_KEY;
12+
13+
public class SecretDependent
14+
extends CRUDKubernetesDependentResource<Secret, PrimaryToSecondaryDependentCustomResource> {
15+
16+
public SecretDependent() {
17+
super(Secret.class);
18+
}
19+
20+
@Override
21+
protected Secret desired(PrimaryToSecondaryDependentCustomResource primary,
22+
Context<PrimaryToSecondaryDependentCustomResource> context) {
23+
Secret secret = new Secret();
24+
secret.setMetadata(new ObjectMetaBuilder()
25+
.withName(primary.getMetadata().getName())
26+
.withNamespace(primary.getMetadata().getNamespace())
27+
.build());
28+
secret.setData(Map.of(DATA_KEY, context.getSecondaryResource(ConfigMap.class)
29+
.orElseThrow().getData().get(DATA_KEY)));
30+
return secret;
31+
}
32+
}

0 commit comments

Comments
 (0)