Skip to content

Commit 114c431

Browse files
committed
test: complex workflow with dependent (#1581)
1 parent fb5f142 commit 114c431

16 files changed

+484
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package io.javaoperatorsdk.operator;
2+
3+
import java.time.Duration;
4+
5+
import org.junit.jupiter.api.Test;
6+
import org.junit.jupiter.api.extension.RegisterExtension;
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
9+
10+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
11+
import io.fabric8.kubernetes.api.model.Service;
12+
import io.fabric8.kubernetes.api.model.apps.StatefulSet;
13+
import io.fabric8.kubernetes.client.readiness.Readiness;
14+
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
15+
import io.javaoperatorsdk.operator.sample.complexdependent.ComplexDependentCustomResource;
16+
import io.javaoperatorsdk.operator.sample.complexdependent.ComplexDependentReconciler;
17+
import io.javaoperatorsdk.operator.sample.complexdependent.ComplexDependentSpec;
18+
import io.javaoperatorsdk.operator.sample.complexdependent.dependent.FirstService;
19+
import io.javaoperatorsdk.operator.sample.complexdependent.dependent.FirstStatefulSet;
20+
import io.javaoperatorsdk.operator.sample.complexdependent.dependent.SecondService;
21+
import io.javaoperatorsdk.operator.sample.complexdependent.dependent.SecondStatefulSet;
22+
23+
import static org.assertj.core.api.Assertions.assertThat;
24+
import static org.awaitility.Awaitility.await;
25+
26+
class ComplexDependentIT {
27+
28+
public static final String TEST_RESOURCE_NAME = "test1";
29+
30+
Logger log = LoggerFactory.getLogger(ComplexDependentIT.class);
31+
32+
@RegisterExtension
33+
LocallyRunOperatorExtension operator =
34+
LocallyRunOperatorExtension.builder()
35+
.withReconciler(new ComplexDependentReconciler())
36+
.build();
37+
38+
@Test
39+
void successfullyReconciles() {
40+
operator.create(testResource());
41+
42+
await().atMost(Duration.ofSeconds(90))
43+
.untilAsserted(() -> {
44+
var res = operator.get(ComplexDependentCustomResource.class, TEST_RESOURCE_NAME);
45+
assertThat(res.getStatus()).isNotNull();
46+
assertThat(res.getStatus().getStatus())
47+
.isEqualTo(ComplexDependentReconciler.RECONCILE_STATUS.READY);
48+
});
49+
50+
var firstStatefulSet = operator.get(StatefulSet.class, String.format("%s-%s",
51+
FirstStatefulSet.DISCRIMINATOR_PREFIX, TEST_RESOURCE_NAME));
52+
var secondStatefulSet = operator.get(StatefulSet.class, String.format("%s-%s",
53+
SecondStatefulSet.DISCRIMINATOR_PREFIX, TEST_RESOURCE_NAME));
54+
var firstService = operator.get(Service.class, String.format("%s-%s",
55+
FirstService.DISCRIMINATOR_PREFIX, TEST_RESOURCE_NAME));
56+
var secondService = operator.get(Service.class, String.format("%s-%s",
57+
SecondService.DISCRIMINATOR_PREFIX, TEST_RESOURCE_NAME));
58+
assertThat(firstService).isNotNull();
59+
assertThat(secondService).isNotNull();
60+
assertThat(firstStatefulSet).isNotNull();
61+
assertThat(secondStatefulSet).isNotNull();
62+
assertThat(Readiness.isStatefulSetReady(firstStatefulSet)).isTrue();
63+
assertThat(Readiness.isStatefulSetReady(secondStatefulSet)).isTrue();
64+
}
65+
66+
ComplexDependentCustomResource testResource() {
67+
var resource = new ComplexDependentCustomResource();
68+
resource.setMetadata(new ObjectMetaBuilder()
69+
.withName(TEST_RESOURCE_NAME)
70+
.build());
71+
resource.setSpec(new ComplexDependentSpec());
72+
resource.getSpec().setProjectId(TEST_RESOURCE_NAME);
73+
74+
return resource;
75+
}
76+
77+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package io.javaoperatorsdk.operator.sample.complexdependent;
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("cdc")
12+
public class ComplexDependentCustomResource
13+
extends CustomResource<ComplexDependentSpec, ComplexDependentStatus> implements Namespaced {
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package io.javaoperatorsdk.operator.sample.complexdependent;
2+
3+
import java.util.Map;
4+
import java.util.Objects;
5+
6+
import io.fabric8.kubernetes.api.model.Service;
7+
import io.fabric8.kubernetes.api.model.apps.StatefulSet;
8+
import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration;
9+
import io.javaoperatorsdk.operator.api.reconciler.*;
10+
import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
11+
import io.javaoperatorsdk.operator.processing.event.source.EventSource;
12+
import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource;
13+
import io.javaoperatorsdk.operator.sample.complexdependent.dependent.*;
14+
15+
import static io.javaoperatorsdk.operator.sample.complexdependent.ComplexDependentReconciler.SERVICE_EVENT_SOURCE_NAME;
16+
import static io.javaoperatorsdk.operator.sample.complexdependent.ComplexDependentReconciler.STATEFUL_SET_EVENT_SOURCE_NAME;
17+
18+
@ControllerConfiguration(
19+
name = "project-operator",
20+
dependents = {
21+
@Dependent(name = "first-svc", type = FirstService.class,
22+
useEventSourceWithName = SERVICE_EVENT_SOURCE_NAME),
23+
@Dependent(name = "second-svc", type = SecondService.class,
24+
useEventSourceWithName = SERVICE_EVENT_SOURCE_NAME),
25+
@Dependent(name = "first", type = FirstStatefulSet.class,
26+
useEventSourceWithName = STATEFUL_SET_EVENT_SOURCE_NAME,
27+
dependsOn = {"first-svc"},
28+
readyPostcondition = StatefulSetReadyCondition.class),
29+
@Dependent(name = "second",
30+
type = SecondStatefulSet.class,
31+
useEventSourceWithName = STATEFUL_SET_EVENT_SOURCE_NAME,
32+
dependsOn = {"second-svc", "first"},
33+
readyPostcondition = StatefulSetReadyCondition.class),
34+
})
35+
public class ComplexDependentReconciler implements Reconciler<ComplexDependentCustomResource>,
36+
EventSourceInitializer<ComplexDependentCustomResource> {
37+
38+
public static final String SERVICE_EVENT_SOURCE_NAME = "serviceEventSource";
39+
public static final String STATEFUL_SET_EVENT_SOURCE_NAME = "statefulSetEventSource";
40+
41+
@Override
42+
public UpdateControl<ComplexDependentCustomResource> reconcile(
43+
ComplexDependentCustomResource resource,
44+
Context<ComplexDependentCustomResource> context) throws Exception {
45+
var ready = context.managedDependentResourceContext().getWorkflowReconcileResult()
46+
.orElseThrow().allDependentResourcesReady();
47+
48+
var status = Objects.requireNonNullElseGet(resource.getStatus(), ComplexDependentStatus::new);
49+
status.setStatus(ready ? RECONCILE_STATUS.READY : RECONCILE_STATUS.NOT_READY);
50+
resource.setStatus(status);
51+
52+
return UpdateControl.updateStatus(resource);
53+
}
54+
55+
@Override
56+
public Map<String, EventSource> prepareEventSources(
57+
EventSourceContext<ComplexDependentCustomResource> context) {
58+
InformerEventSource<Service, ComplexDependentCustomResource> serviceEventSource =
59+
new InformerEventSource<>(InformerConfiguration.from(Service.class, context).build(),
60+
context);
61+
InformerEventSource<StatefulSet, ComplexDependentCustomResource> statefulSetEventSource =
62+
new InformerEventSource<>(InformerConfiguration.from(StatefulSet.class, context).build(),
63+
context);
64+
return Map.of(SERVICE_EVENT_SOURCE_NAME, serviceEventSource, STATEFUL_SET_EVENT_SOURCE_NAME,
65+
statefulSetEventSource);
66+
}
67+
68+
public enum RECONCILE_STATUS {
69+
READY, NOT_READY
70+
}
71+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package io.javaoperatorsdk.operator.sample.complexdependent;
2+
3+
public class ComplexDependentSpec {
4+
private String projectId;
5+
6+
public String getProjectId() {
7+
return projectId;
8+
}
9+
10+
public ComplexDependentSpec setProjectId(String projectId) {
11+
this.projectId = projectId;
12+
return this;
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.javaoperatorsdk.operator.sample.complexdependent;
2+
3+
4+
public class ComplexDependentStatus {
5+
private ComplexDependentReconciler.RECONCILE_STATUS status;
6+
7+
public ComplexDependentReconciler.RECONCILE_STATUS getStatus() {
8+
return status;
9+
}
10+
11+
public ComplexDependentStatus setStatus(ComplexDependentReconciler.RECONCILE_STATUS status) {
12+
this.status = status;
13+
return this;
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package io.javaoperatorsdk.operator.sample.complexdependent.dependent;
2+
3+
import io.fabric8.kubernetes.api.model.HasMetadata;
4+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
5+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
6+
import io.javaoperatorsdk.operator.sample.complexdependent.ComplexDependentCustomResource;
7+
8+
public abstract class BaseDependentResource<R extends HasMetadata>
9+
extends CRUDKubernetesDependentResource<R, ComplexDependentCustomResource> {
10+
11+
public static final String K8S_NAME = "app.kubernetes.io/name";
12+
protected final String component;
13+
14+
public BaseDependentResource(Class<R> resourceType, String component) {
15+
super(resourceType);
16+
this.component = component;
17+
}
18+
19+
protected String name(ComplexDependentCustomResource primary) {
20+
return String.format("%s-%s", component, primary.getSpec().getProjectId());
21+
}
22+
23+
protected ObjectMetaBuilder createMeta(ComplexDependentCustomResource primary) {
24+
String name = name(primary);
25+
return new ObjectMetaBuilder()
26+
.withName(name)
27+
.withNamespace(primary.getMetadata().getNamespace())
28+
.addToLabels(K8S_NAME, name);
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.javaoperatorsdk.operator.sample.complexdependent.dependent;
2+
3+
import java.util.Map;
4+
5+
import io.fabric8.kubernetes.api.model.Service;
6+
import io.fabric8.kubernetes.api.model.ServiceBuilder;
7+
import io.javaoperatorsdk.operator.ReconcilerUtils;
8+
import io.javaoperatorsdk.operator.api.reconciler.Context;
9+
import io.javaoperatorsdk.operator.sample.complexdependent.ComplexDependentCustomResource;
10+
11+
public abstract class BaseService extends BaseDependentResource<Service> {
12+
13+
public BaseService(String component) {
14+
super(Service.class, component);
15+
}
16+
17+
@Override
18+
protected Service desired(ComplexDependentCustomResource primary,
19+
Context<ComplexDependentCustomResource> context) {
20+
var template = ReconcilerUtils.loadYaml(Service.class, getClass(), "service.yaml");
21+
22+
return new ServiceBuilder(template)
23+
.withMetadata(createMeta(primary).build())
24+
.editOrNewSpec()
25+
.withSelector(Map.of(K8S_NAME, name(primary)))
26+
.endSpec()
27+
.build();
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package io.javaoperatorsdk.operator.sample.complexdependent.dependent;
2+
3+
import java.util.Map;
4+
5+
import io.fabric8.kubernetes.api.model.apps.StatefulSet;
6+
import io.fabric8.kubernetes.api.model.apps.StatefulSetBuilder;
7+
import io.javaoperatorsdk.operator.ReconcilerUtils;
8+
import io.javaoperatorsdk.operator.api.reconciler.Context;
9+
import io.javaoperatorsdk.operator.sample.complexdependent.ComplexDependentCustomResource;
10+
11+
public abstract class BaseStatefulSet extends BaseDependentResource<StatefulSet> {
12+
public BaseStatefulSet(String component) {
13+
super(StatefulSet.class, component);
14+
}
15+
16+
@Override
17+
protected StatefulSet desired(ComplexDependentCustomResource primary,
18+
Context<ComplexDependentCustomResource> context) {
19+
var template = ReconcilerUtils.loadYaml(StatefulSet.class, getClass(), "statefulset.yaml");
20+
var name = name(primary);
21+
var metadata = createMeta(primary).build();
22+
23+
return new StatefulSetBuilder(template)
24+
.withMetadata(metadata)
25+
.editSpec()
26+
.withServiceName(name)
27+
.editOrNewSelector()
28+
.withMatchLabels(Map.of(K8S_NAME, name))
29+
.endSelector()
30+
.editTemplate()
31+
.withMetadata(metadata)
32+
.endTemplate()
33+
.editFirstVolumeClaimTemplate()
34+
.editMetadata()
35+
.withLabels(Map.of(K8S_NAME, name))
36+
.endMetadata()
37+
.endVolumeClaimTemplate()
38+
.endSpec()
39+
.build();
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package io.javaoperatorsdk.operator.sample.complexdependent.dependent;
2+
3+
import io.fabric8.kubernetes.api.model.Service;
4+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
5+
6+
@KubernetesDependent(resourceDiscriminator = FirstService.Discriminator.class)
7+
public class FirstService extends BaseService {
8+
public static final String DISCRIMINATOR_PREFIX = "first";
9+
10+
public FirstService() {
11+
super(DISCRIMINATOR_PREFIX);
12+
}
13+
14+
public static class Discriminator extends NamePrefixResourceDiscriminator<Service> {
15+
protected Discriminator() {
16+
super(DISCRIMINATOR_PREFIX);
17+
}
18+
}
19+
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package io.javaoperatorsdk.operator.sample.complexdependent.dependent;
2+
3+
import io.fabric8.kubernetes.api.model.apps.StatefulSet;
4+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
5+
6+
@KubernetesDependent(resourceDiscriminator = FirstStatefulSet.Discriminator.class)
7+
public class FirstStatefulSet extends BaseStatefulSet {
8+
9+
public static final String DISCRIMINATOR_PREFIX = "first";
10+
11+
public FirstStatefulSet() {
12+
super(DISCRIMINATOR_PREFIX);
13+
}
14+
15+
16+
public static class Discriminator extends NamePrefixResourceDiscriminator<StatefulSet> {
17+
protected Discriminator() {
18+
super(DISCRIMINATOR_PREFIX);
19+
}
20+
}
21+
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package io.javaoperatorsdk.operator.sample.complexdependent.dependent;
2+
3+
import java.util.Optional;
4+
import java.util.stream.Collectors;
5+
6+
import io.fabric8.kubernetes.api.model.HasMetadata;
7+
import io.javaoperatorsdk.operator.api.reconciler.Context;
8+
import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator;
9+
import io.javaoperatorsdk.operator.sample.complexdependent.ComplexDependentCustomResource;
10+
11+
public abstract class NamePrefixResourceDiscriminator<R extends HasMetadata>
12+
implements ResourceDiscriminator<R, ComplexDependentCustomResource> {
13+
14+
private final String prefix;
15+
16+
protected NamePrefixResourceDiscriminator(String prefix) {
17+
this.prefix = prefix;
18+
}
19+
20+
@Override
21+
public Optional<R> distinguish(Class<R> resource, ComplexDependentCustomResource primary,
22+
Context<ComplexDependentCustomResource> context) {
23+
var resources = context.getSecondaryResources(resource);
24+
var filtered = resources.stream().filter(r -> r.getMetadata().getName().startsWith(prefix))
25+
.collect(Collectors.toList());
26+
if (filtered.size() > 1) {
27+
throw new IllegalStateException("More resources than expected for" + primary);
28+
}
29+
return filtered.isEmpty() ? Optional.empty() : Optional.of(filtered.get(0));
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package io.javaoperatorsdk.operator.sample.complexdependent.dependent;
2+
3+
import io.fabric8.kubernetes.api.model.Service;
4+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
5+
6+
@KubernetesDependent(resourceDiscriminator = SecondService.Discriminator.class)
7+
public class SecondService extends BaseService {
8+
9+
public static final String DISCRIMINATOR_PREFIX = "second";
10+
11+
public SecondService() {
12+
super(DISCRIMINATOR_PREFIX);
13+
}
14+
15+
public static class Discriminator extends NamePrefixResourceDiscriminator<Service> {
16+
protected Discriminator() {
17+
super(DISCRIMINATOR_PREFIX);
18+
}
19+
}
20+
}

0 commit comments

Comments
 (0)