Skip to content

Commit 002b259

Browse files
csvirimetacosm
authored andcommitted
feat: integration test to show multiple dependents with activation condition (#2454)
Signed-off-by: Attila Mészáros <[email protected]>
1 parent 3296880 commit 002b259

9 files changed

+267
-3
lines changed

Diff for: docs/content/en/docs/workflows/_index.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,11 @@ reconciliation process.
4848
with the dependent's resource type is not present on the cluster.
4949
See related [integration test](https://github.com/operator-framework/java-operator-sdk/blob/ba5e33527bf9e3ea0bd33025ccb35e677f9d44b4/operator-framework/src/test/java/io/javaoperatorsdk/operator/CRDPresentActivationConditionIT.java).
5050

51-
Activation condition is semi-experimental at the moment, and it has its limitations.
52-
For example event sources cannot be shared between multiple managed dependent resources which use activation condition.
53-
The intention is to further improve and explore the possibilities with this approach.
51+
To have multiple resources of same type with an activation condition is a bit tricky, since you
52+
don't want to have multiple `InformerEvetnSource` for the same type, you have to explicitly
53+
name the informer for the Dependent Resource (`@KubernetesDependent(informerConfig = @InformerConfig(name = "configMapInformer"))`)
54+
for all resource of same type with activation condition. This will make sure that only one is registered.
55+
See details at [low level api](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceRetriever.java#L20-L52).
5456

5557
## Defining Workflows
5658

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package io.javaoperatorsdk.operator;
2+
3+
import org.junit.jupiter.api.Test;
4+
import org.junit.jupiter.api.extension.RegisterExtension;
5+
6+
import io.fabric8.kubernetes.api.model.ConfigMap;
7+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
8+
import io.fabric8.kubernetes.api.model.Secret;
9+
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
10+
import io.javaoperatorsdk.operator.sample.multipledependentwithactivation.*;
11+
12+
import static org.assertj.core.api.Assertions.assertThat;
13+
import static org.awaitility.Awaitility.await;
14+
15+
public class MultipleDependentWithActivationIT {
16+
17+
public static final String INITIAL_VALUE = "initial_value";
18+
public static final String CHANGED_VALUE = "changed_value";
19+
public static final String TEST_RESOURCE_NAME = "test1";
20+
21+
@RegisterExtension
22+
LocallyRunOperatorExtension extension =
23+
LocallyRunOperatorExtension.builder()
24+
.withReconciler(new MultipleDependentActivationReconciler())
25+
.build();
26+
27+
@Test
28+
void bothDependentsWithActivationAreHandled() {
29+
var resource = extension.create(testResource());
30+
31+
await().untilAsserted(() -> {
32+
var cm1 =
33+
extension.get(ConfigMap.class, TEST_RESOURCE_NAME + ConfigMapDependentResource1.SUFFIX);
34+
var cm2 =
35+
extension.get(ConfigMap.class, TEST_RESOURCE_NAME + ConfigMapDependentResource2.SUFFIX);
36+
var secret = extension.get(Secret.class, TEST_RESOURCE_NAME);
37+
assertThat(secret).isNotNull();
38+
assertThat(cm1).isNull();
39+
assertThat(cm2).isNull();
40+
});
41+
42+
ActivationCondition.MET = true;
43+
resource.getSpec().setValue(CHANGED_VALUE);
44+
extension.replace(resource);
45+
46+
await().untilAsserted(() -> {
47+
var cm1 =
48+
extension.get(ConfigMap.class, TEST_RESOURCE_NAME + ConfigMapDependentResource1.SUFFIX);
49+
var cm2 =
50+
extension.get(ConfigMap.class, TEST_RESOURCE_NAME + ConfigMapDependentResource2.SUFFIX);
51+
var secret = extension.get(Secret.class, TEST_RESOURCE_NAME);
52+
53+
assertThat(secret).isNotNull();
54+
assertThat(cm1).isNotNull();
55+
assertThat(cm2).isNotNull();
56+
assertThat(cm1.getData()).containsEntry(ConfigMapDependentResource1.DATA_KEY,
57+
CHANGED_VALUE + ConfigMapDependentResource1.SUFFIX);
58+
assertThat(cm2.getData()).containsEntry(ConfigMapDependentResource2.DATA_KEY,
59+
CHANGED_VALUE + ConfigMapDependentResource2.SUFFIX);
60+
});
61+
62+
}
63+
64+
MultipleDependentActivationCustomResource testResource() {
65+
var res = new MultipleDependentActivationCustomResource();
66+
res.setMetadata(new ObjectMetaBuilder()
67+
.withName(TEST_RESOURCE_NAME)
68+
.build());
69+
res.setSpec(new MultipleDependentActivationSpec());
70+
res.getSpec().setValue(INITIAL_VALUE);
71+
72+
return res;
73+
}
74+
75+
76+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package io.javaoperatorsdk.operator.sample.multipledependentwithactivation;
2+
3+
import io.fabric8.openshift.api.model.Route;
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+
public class ActivationCondition
9+
implements Condition<Route, MultipleDependentActivationCustomResource> {
10+
11+
public static volatile boolean MET = false;
12+
13+
@Override
14+
public boolean isMet(
15+
DependentResource<Route, MultipleDependentActivationCustomResource> dependentResource,
16+
MultipleDependentActivationCustomResource primary,
17+
Context<MultipleDependentActivationCustomResource> context) {
18+
return MET;
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package io.javaoperatorsdk.operator.sample.multipledependentwithactivation;
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.javaoperatorsdk.operator.api.reconciler.Context;
8+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDNoGCKubernetesDependentResource;
9+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.InformerConfig;
10+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
11+
12+
@KubernetesDependent(informerConfig = @InformerConfig(name = "configMapInformer"))
13+
public class ConfigMapDependentResource1
14+
extends
15+
CRUDNoGCKubernetesDependentResource<ConfigMap, MultipleDependentActivationCustomResource> {
16+
17+
public static final String DATA_KEY = "data";
18+
public static final String SUFFIX = "1";
19+
20+
public ConfigMapDependentResource1() {
21+
super(ConfigMap.class);
22+
}
23+
24+
@Override
25+
protected ConfigMap desired(MultipleDependentActivationCustomResource primary,
26+
Context<MultipleDependentActivationCustomResource> context) {
27+
ConfigMap configMap = new ConfigMap();
28+
configMap.setMetadata(new ObjectMetaBuilder()
29+
.withName(primary.getMetadata().getName() + SUFFIX)
30+
.withNamespace(primary.getMetadata().getNamespace())
31+
.build());
32+
configMap.setData(Map.of(DATA_KEY, primary.getSpec().getValue() + SUFFIX));
33+
return configMap;
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package io.javaoperatorsdk.operator.sample.multipledependentwithactivation;
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.javaoperatorsdk.operator.api.reconciler.Context;
8+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDNoGCKubernetesDependentResource;
9+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.InformerConfig;
10+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
11+
12+
@KubernetesDependent(informerConfig = @InformerConfig(name = "configMapInformer"))
13+
public class ConfigMapDependentResource2
14+
extends
15+
CRUDNoGCKubernetesDependentResource<ConfigMap, MultipleDependentActivationCustomResource> {
16+
17+
public static final String DATA_KEY = "data";
18+
public static final String SUFFIX = "2";
19+
20+
public ConfigMapDependentResource2() {
21+
super(ConfigMap.class);
22+
}
23+
24+
@Override
25+
protected ConfigMap desired(MultipleDependentActivationCustomResource primary,
26+
Context<MultipleDependentActivationCustomResource> context) {
27+
ConfigMap configMap = new ConfigMap();
28+
configMap.setMetadata(new ObjectMetaBuilder()
29+
.withName(primary.getMetadata().getName() + SUFFIX)
30+
.withNamespace(primary.getMetadata().getNamespace())
31+
.build());
32+
configMap.setData(Map.of(DATA_KEY, primary.getSpec().getValue() + SUFFIX));
33+
return configMap;
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package io.javaoperatorsdk.operator.sample.multipledependentwithactivation;
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("mdar")
12+
public class MultipleDependentActivationCustomResource
13+
extends CustomResource<MultipleDependentActivationSpec, Void>
14+
implements Namespaced {
15+
16+
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package io.javaoperatorsdk.operator.sample.multipledependentwithactivation;
2+
3+
import java.util.concurrent.atomic.AtomicInteger;
4+
5+
import io.javaoperatorsdk.operator.api.reconciler.*;
6+
import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
7+
8+
@Workflow(dependents = {
9+
@Dependent(type = ConfigMapDependentResource1.class,
10+
activationCondition = ActivationCondition.class),
11+
@Dependent(type = ConfigMapDependentResource2.class,
12+
activationCondition = ActivationCondition.class),
13+
@Dependent(type = SecretDependentResource.class)
14+
})
15+
@ControllerConfiguration
16+
public class MultipleDependentActivationReconciler
17+
implements Reconciler<MultipleDependentActivationCustomResource> {
18+
19+
private final AtomicInteger numberOfReconciliationExecution = new AtomicInteger(0);
20+
21+
@Override
22+
public UpdateControl<MultipleDependentActivationCustomResource> reconcile(
23+
MultipleDependentActivationCustomResource resource,
24+
Context<MultipleDependentActivationCustomResource> context) {
25+
26+
numberOfReconciliationExecution.incrementAndGet();
27+
28+
return UpdateControl.noUpdate();
29+
}
30+
31+
public int getNumberOfReconciliationExecution() {
32+
return numberOfReconciliationExecution.get();
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package io.javaoperatorsdk.operator.sample.multipledependentwithactivation;
2+
3+
public class MultipleDependentActivationSpec {
4+
5+
private String value;
6+
7+
public String getValue() {
8+
return value;
9+
}
10+
11+
public void setValue(String value) {
12+
this.value = value;
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package io.javaoperatorsdk.operator.sample.multipledependentwithactivation;
2+
3+
import java.util.Base64;
4+
import java.util.Map;
5+
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+
public class SecretDependentResource
12+
extends CRUDKubernetesDependentResource<Secret, MultipleDependentActivationCustomResource> {
13+
14+
public SecretDependentResource() {
15+
super(Secret.class);
16+
}
17+
18+
@Override
19+
protected Secret desired(MultipleDependentActivationCustomResource primary,
20+
Context<MultipleDependentActivationCustomResource> context) {
21+
// basically does not matter since this should not be called
22+
Secret secret = new Secret();
23+
secret.setMetadata(new ObjectMetaBuilder()
24+
.withName(primary.getMetadata().getName())
25+
.withNamespace(primary.getMetadata().getNamespace())
26+
.build());
27+
secret.setData(Map.of("data",
28+
Base64.getEncoder().encodeToString(primary.getSpec().getValue().getBytes())));
29+
return secret;
30+
}
31+
}

0 commit comments

Comments
 (0)