Skip to content

feat: integration test to show multiple dependents with activation condition #2454

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions docs/content/en/docs/workflows/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@ reconciliation process.
with the dependent's resource type is not present on the cluster.
See related [integration test](https://github.com/operator-framework/java-operator-sdk/blob/ba5e33527bf9e3ea0bd33025ccb35e677f9d44b4/operator-framework/src/test/java/io/javaoperatorsdk/operator/CRDPresentActivationConditionIT.java).

Activation condition is semi-experimental at the moment, and it has its limitations.
For example event sources cannot be shared between multiple managed dependent resources which use activation condition.
The intention is to further improve and explore the possibilities with this approach.
To have multiple resources of same type with an activation condition is a bit tricky, since you
don't want to have multiple `InformerEvetnSource` for the same type, you have to explicitly
name the informer for the Dependent Resource (`@KubernetesDependent(informerConfig = @InformerConfig(name = "configMapInformer"))`)
for all resource of same type with activation condition. This will make sure that only one is registered.
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).

## Defining Workflows

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package io.javaoperatorsdk.operator;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.fabric8.kubernetes.api.model.Secret;
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
import io.javaoperatorsdk.operator.sample.multipledependentwithactivation.*;

import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;

public class MultipleDependentWithActivationIT {

public static final String INITIAL_VALUE = "initial_value";
public static final String CHANGED_VALUE = "changed_value";
public static final String TEST_RESOURCE_NAME = "test1";

@RegisterExtension
LocallyRunOperatorExtension extension =
LocallyRunOperatorExtension.builder()
.withReconciler(new MultipleDependentActivationReconciler())
.build();

@Test
void bothDependentsWithActivationAreHandled() {
var resource = extension.create(testResource());

await().untilAsserted(() -> {
var cm1 =
extension.get(ConfigMap.class, TEST_RESOURCE_NAME + ConfigMapDependentResource1.SUFFIX);
var cm2 =
extension.get(ConfigMap.class, TEST_RESOURCE_NAME + ConfigMapDependentResource2.SUFFIX);
var secret = extension.get(Secret.class, TEST_RESOURCE_NAME);
assertThat(secret).isNotNull();
assertThat(cm1).isNull();
assertThat(cm2).isNull();
});

ActivationCondition.MET = true;
resource.getSpec().setValue(CHANGED_VALUE);
extension.replace(resource);

await().untilAsserted(() -> {
var cm1 =
extension.get(ConfigMap.class, TEST_RESOURCE_NAME + ConfigMapDependentResource1.SUFFIX);
var cm2 =
extension.get(ConfigMap.class, TEST_RESOURCE_NAME + ConfigMapDependentResource2.SUFFIX);
var secret = extension.get(Secret.class, TEST_RESOURCE_NAME);

assertThat(secret).isNotNull();
assertThat(cm1).isNotNull();
assertThat(cm2).isNotNull();
assertThat(cm1.getData()).containsEntry(ConfigMapDependentResource1.DATA_KEY,
CHANGED_VALUE + ConfigMapDependentResource1.SUFFIX);
assertThat(cm2.getData()).containsEntry(ConfigMapDependentResource2.DATA_KEY,
CHANGED_VALUE + ConfigMapDependentResource2.SUFFIX);
});

}

MultipleDependentActivationCustomResource testResource() {
var res = new MultipleDependentActivationCustomResource();
res.setMetadata(new ObjectMetaBuilder()
.withName(TEST_RESOURCE_NAME)
.build());
res.setSpec(new MultipleDependentActivationSpec());
res.getSpec().setValue(INITIAL_VALUE);

return res;
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.javaoperatorsdk.operator.sample.multipledependentwithactivation;

import io.fabric8.openshift.api.model.Route;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition;

public class ActivationCondition
implements Condition<Route, MultipleDependentActivationCustomResource> {

public static volatile boolean MET = false;

@Override
public boolean isMet(
DependentResource<Route, MultipleDependentActivationCustomResource> dependentResource,
MultipleDependentActivationCustomResource primary,
Context<MultipleDependentActivationCustomResource> context) {
return MET;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.javaoperatorsdk.operator.sample.multipledependentwithactivation;

import java.util.Map;

import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDNoGCKubernetesDependentResource;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.InformerConfig;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;

@KubernetesDependent(informerConfig = @InformerConfig(name = "configMapInformer"))
public class ConfigMapDependentResource1
extends
CRUDNoGCKubernetesDependentResource<ConfigMap, MultipleDependentActivationCustomResource> {

public static final String DATA_KEY = "data";
public static final String SUFFIX = "1";

public ConfigMapDependentResource1() {
super(ConfigMap.class);
}

@Override
protected ConfigMap desired(MultipleDependentActivationCustomResource primary,
Context<MultipleDependentActivationCustomResource> context) {
ConfigMap configMap = new ConfigMap();
configMap.setMetadata(new ObjectMetaBuilder()
.withName(primary.getMetadata().getName() + SUFFIX)
.withNamespace(primary.getMetadata().getNamespace())
.build());
configMap.setData(Map.of(DATA_KEY, primary.getSpec().getValue() + SUFFIX));
return configMap;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.javaoperatorsdk.operator.sample.multipledependentwithactivation;

import java.util.Map;

import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDNoGCKubernetesDependentResource;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.InformerConfig;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;

@KubernetesDependent(informerConfig = @InformerConfig(name = "configMapInformer"))
public class ConfigMapDependentResource2
extends
CRUDNoGCKubernetesDependentResource<ConfigMap, MultipleDependentActivationCustomResource> {

public static final String DATA_KEY = "data";
public static final String SUFFIX = "2";

public ConfigMapDependentResource2() {
super(ConfigMap.class);
}

@Override
protected ConfigMap desired(MultipleDependentActivationCustomResource primary,
Context<MultipleDependentActivationCustomResource> context) {
ConfigMap configMap = new ConfigMap();
configMap.setMetadata(new ObjectMetaBuilder()
.withName(primary.getMetadata().getName() + SUFFIX)
.withNamespace(primary.getMetadata().getNamespace())
.build());
configMap.setData(Map.of(DATA_KEY, primary.getSpec().getValue() + SUFFIX));
return configMap;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.javaoperatorsdk.operator.sample.multipledependentwithactivation;

import io.fabric8.kubernetes.api.model.Namespaced;
import io.fabric8.kubernetes.client.CustomResource;
import io.fabric8.kubernetes.model.annotation.Group;
import io.fabric8.kubernetes.model.annotation.ShortNames;
import io.fabric8.kubernetes.model.annotation.Version;

@Group("sample.javaoperatorsdk")
@Version("v1")
@ShortNames("mdar")
public class MultipleDependentActivationCustomResource
extends CustomResource<MultipleDependentActivationSpec, Void>
implements Namespaced {


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.javaoperatorsdk.operator.sample.multipledependentwithactivation;

import java.util.concurrent.atomic.AtomicInteger;

import io.javaoperatorsdk.operator.api.reconciler.*;
import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;

@Workflow(dependents = {
@Dependent(type = ConfigMapDependentResource1.class,
activationCondition = ActivationCondition.class),
@Dependent(type = ConfigMapDependentResource2.class,
activationCondition = ActivationCondition.class),
@Dependent(type = SecretDependentResource.class)
})
@ControllerConfiguration
public class MultipleDependentActivationReconciler
implements Reconciler<MultipleDependentActivationCustomResource> {

private final AtomicInteger numberOfReconciliationExecution = new AtomicInteger(0);

@Override
public UpdateControl<MultipleDependentActivationCustomResource> reconcile(
MultipleDependentActivationCustomResource resource,
Context<MultipleDependentActivationCustomResource> context) {

numberOfReconciliationExecution.incrementAndGet();

return UpdateControl.noUpdate();
}

public int getNumberOfReconciliationExecution() {
return numberOfReconciliationExecution.get();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.javaoperatorsdk.operator.sample.multipledependentwithactivation;

public class MultipleDependentActivationSpec {

private String value;

public String getValue() {
return value;
}

public void setValue(String value) {
this.value = value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.javaoperatorsdk.operator.sample.multipledependentwithactivation;

import java.util.Base64;
import java.util.Map;

import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.fabric8.kubernetes.api.model.Secret;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;

public class SecretDependentResource
extends CRUDKubernetesDependentResource<Secret, MultipleDependentActivationCustomResource> {

public SecretDependentResource() {
super(Secret.class);
}

@Override
protected Secret desired(MultipleDependentActivationCustomResource primary,
Context<MultipleDependentActivationCustomResource> context) {
// basically does not matter since this should not be called
Secret secret = new Secret();
secret.setMetadata(new ObjectMetaBuilder()
.withName(primary.getMetadata().getName())
.withNamespace(primary.getMetadata().getNamespace())
.build());
secret.setData(Map.of("data",
Base64.getEncoder().encodeToString(primary.getSpec().getValue().getBytes())));
return secret;
}
}
Loading