Skip to content

test: complex workflow with dependent #1581

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
Oct 31, 2022
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package io.javaoperatorsdk.operator;

import java.time.Duration;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.apps.StatefulSet;
import io.fabric8.kubernetes.client.readiness.Readiness;
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
import io.javaoperatorsdk.operator.sample.complexdependent.ComplexDependentCustomResource;
import io.javaoperatorsdk.operator.sample.complexdependent.ComplexDependentReconciler;
import io.javaoperatorsdk.operator.sample.complexdependent.ComplexDependentSpec;
import io.javaoperatorsdk.operator.sample.complexdependent.dependent.FirstService;
import io.javaoperatorsdk.operator.sample.complexdependent.dependent.FirstStatefulSet;
import io.javaoperatorsdk.operator.sample.complexdependent.dependent.SecondService;
import io.javaoperatorsdk.operator.sample.complexdependent.dependent.SecondStatefulSet;

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

class ComplexDependentIT {

public static final String TEST_RESOURCE_NAME = "test1";

Logger log = LoggerFactory.getLogger(ComplexDependentIT.class);

@RegisterExtension
LocallyRunOperatorExtension operator =
LocallyRunOperatorExtension.builder()
.withReconciler(new ComplexDependentReconciler())
.build();

@Test
void successfullyReconciles() {
operator.create(testResource());

await().atMost(Duration.ofSeconds(90))
.untilAsserted(() -> {
var res = operator.get(ComplexDependentCustomResource.class, TEST_RESOURCE_NAME);
assertThat(res.getStatus()).isNotNull();
assertThat(res.getStatus().getStatus())
.isEqualTo(ComplexDependentReconciler.RECONCILE_STATUS.READY);
});

var firstStatefulSet = operator.get(StatefulSet.class, String.format("%s-%s",
FirstStatefulSet.DISCRIMINATOR_PREFIX, TEST_RESOURCE_NAME));
var secondStatefulSet = operator.get(StatefulSet.class, String.format("%s-%s",
SecondStatefulSet.DISCRIMINATOR_PREFIX, TEST_RESOURCE_NAME));
var firstService = operator.get(Service.class, String.format("%s-%s",
FirstService.DISCRIMINATOR_PREFIX, TEST_RESOURCE_NAME));
var secondService = operator.get(Service.class, String.format("%s-%s",
SecondService.DISCRIMINATOR_PREFIX, TEST_RESOURCE_NAME));
assertThat(firstService).isNotNull();
assertThat(secondService).isNotNull();
assertThat(firstStatefulSet).isNotNull();
assertThat(secondStatefulSet).isNotNull();
assertThat(Readiness.isStatefulSetReady(firstStatefulSet)).isTrue();
assertThat(Readiness.isStatefulSetReady(secondStatefulSet)).isTrue();
}

ComplexDependentCustomResource testResource() {
var resource = new ComplexDependentCustomResource();
resource.setMetadata(new ObjectMetaBuilder()
.withName(TEST_RESOURCE_NAME)
.build());
resource.setSpec(new ComplexDependentSpec());
resource.getSpec().setProjectId(TEST_RESOURCE_NAME);

return resource;
}

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

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("cdc")
public class ComplexDependentCustomResource
extends CustomResource<ComplexDependentSpec, ComplexDependentStatus> implements Namespaced {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package io.javaoperatorsdk.operator.sample.complexdependent;

import java.util.Map;
import java.util.Objects;

import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.apps.StatefulSet;
import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.*;
import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
import io.javaoperatorsdk.operator.processing.event.source.EventSource;
import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource;
import io.javaoperatorsdk.operator.sample.complexdependent.dependent.*;

import static io.javaoperatorsdk.operator.sample.complexdependent.ComplexDependentReconciler.SERVICE_EVENT_SOURCE_NAME;
import static io.javaoperatorsdk.operator.sample.complexdependent.ComplexDependentReconciler.STATEFUL_SET_EVENT_SOURCE_NAME;

@ControllerConfiguration(
name = "project-operator",
dependents = {
@Dependent(name = "first-svc", type = FirstService.class,
useEventSourceWithName = SERVICE_EVENT_SOURCE_NAME),
@Dependent(name = "second-svc", type = SecondService.class,
useEventSourceWithName = SERVICE_EVENT_SOURCE_NAME),
@Dependent(name = "first", type = FirstStatefulSet.class,
useEventSourceWithName = STATEFUL_SET_EVENT_SOURCE_NAME,
dependsOn = {"first-svc"},
readyPostcondition = StatefulSetReadyCondition.class),
@Dependent(name = "second",
type = SecondStatefulSet.class,
useEventSourceWithName = STATEFUL_SET_EVENT_SOURCE_NAME,
dependsOn = {"second-svc", "first"},
readyPostcondition = StatefulSetReadyCondition.class),
})
public class ComplexDependentReconciler implements Reconciler<ComplexDependentCustomResource>,
EventSourceInitializer<ComplexDependentCustomResource> {

public static final String SERVICE_EVENT_SOURCE_NAME = "serviceEventSource";
public static final String STATEFUL_SET_EVENT_SOURCE_NAME = "statefulSetEventSource";

@Override
public UpdateControl<ComplexDependentCustomResource> reconcile(
ComplexDependentCustomResource resource,
Context<ComplexDependentCustomResource> context) throws Exception {
var ready = context.managedDependentResourceContext().getWorkflowReconcileResult()
.orElseThrow().allDependentResourcesReady();

var status = Objects.requireNonNullElseGet(resource.getStatus(), ComplexDependentStatus::new);
status.setStatus(ready ? RECONCILE_STATUS.READY : RECONCILE_STATUS.NOT_READY);
resource.setStatus(status);

return UpdateControl.updateStatus(resource);
}

@Override
public Map<String, EventSource> prepareEventSources(
EventSourceContext<ComplexDependentCustomResource> context) {
InformerEventSource<Service, ComplexDependentCustomResource> serviceEventSource =
new InformerEventSource<>(InformerConfiguration.from(Service.class, context).build(),
context);
InformerEventSource<StatefulSet, ComplexDependentCustomResource> statefulSetEventSource =
new InformerEventSource<>(InformerConfiguration.from(StatefulSet.class, context).build(),
context);
return Map.of(SERVICE_EVENT_SOURCE_NAME, serviceEventSource, STATEFUL_SET_EVENT_SOURCE_NAME,
statefulSetEventSource);
}

public enum RECONCILE_STATUS {
READY, NOT_READY
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.javaoperatorsdk.operator.sample.complexdependent;

public class ComplexDependentSpec {
private String projectId;

public String getProjectId() {
return projectId;
}

public ComplexDependentSpec setProjectId(String projectId) {
this.projectId = projectId;
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.javaoperatorsdk.operator.sample.complexdependent;


public class ComplexDependentStatus {
private ComplexDependentReconciler.RECONCILE_STATUS status;

public ComplexDependentReconciler.RECONCILE_STATUS getStatus() {
return status;
}

public ComplexDependentStatus setStatus(ComplexDependentReconciler.RECONCILE_STATUS status) {
this.status = status;
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.javaoperatorsdk.operator.sample.complexdependent.dependent;

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
import io.javaoperatorsdk.operator.sample.complexdependent.ComplexDependentCustomResource;

public abstract class BaseDependentResource<R extends HasMetadata>
extends CRUDKubernetesDependentResource<R, ComplexDependentCustomResource> {

public static final String K8S_NAME = "app.kubernetes.io/name";
protected final String component;

public BaseDependentResource(Class<R> resourceType, String component) {
super(resourceType);
this.component = component;
}

protected String name(ComplexDependentCustomResource primary) {
return String.format("%s-%s", component, primary.getSpec().getProjectId());
}

protected ObjectMetaBuilder createMeta(ComplexDependentCustomResource primary) {
String name = name(primary);
return new ObjectMetaBuilder()
.withName(name)
.withNamespace(primary.getMetadata().getNamespace())
.addToLabels(K8S_NAME, name);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.javaoperatorsdk.operator.sample.complexdependent.dependent;

import java.util.Map;

import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.ServiceBuilder;
import io.javaoperatorsdk.operator.ReconcilerUtils;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.sample.complexdependent.ComplexDependentCustomResource;

public abstract class BaseService extends BaseDependentResource<Service> {

public BaseService(String component) {
super(Service.class, component);
}

@Override
protected Service desired(ComplexDependentCustomResource primary,
Context<ComplexDependentCustomResource> context) {
var template = ReconcilerUtils.loadYaml(Service.class, getClass(), "service.yaml");

return new ServiceBuilder(template)
.withMetadata(createMeta(primary).build())
.editOrNewSpec()
.withSelector(Map.of(K8S_NAME, name(primary)))
.endSpec()
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.javaoperatorsdk.operator.sample.complexdependent.dependent;

import java.util.Map;

import io.fabric8.kubernetes.api.model.apps.StatefulSet;
import io.fabric8.kubernetes.api.model.apps.StatefulSetBuilder;
import io.javaoperatorsdk.operator.ReconcilerUtils;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.sample.complexdependent.ComplexDependentCustomResource;

public abstract class BaseStatefulSet extends BaseDependentResource<StatefulSet> {
public BaseStatefulSet(String component) {
super(StatefulSet.class, component);
}

@Override
protected StatefulSet desired(ComplexDependentCustomResource primary,
Context<ComplexDependentCustomResource> context) {
var template = ReconcilerUtils.loadYaml(StatefulSet.class, getClass(), "statefulset.yaml");
var name = name(primary);
var metadata = createMeta(primary).build();

return new StatefulSetBuilder(template)
.withMetadata(metadata)
.editSpec()
.withServiceName(name)
.editOrNewSelector()
.withMatchLabels(Map.of(K8S_NAME, name))
.endSelector()
.editTemplate()
.withMetadata(metadata)
.endTemplate()
.editFirstVolumeClaimTemplate()
.editMetadata()
.withLabels(Map.of(K8S_NAME, name))
.endMetadata()
.endVolumeClaimTemplate()
.endSpec()
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.javaoperatorsdk.operator.sample.complexdependent.dependent;

import io.fabric8.kubernetes.api.model.Service;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;

@KubernetesDependent(resourceDiscriminator = FirstService.Discriminator.class)
public class FirstService extends BaseService {
public static final String DISCRIMINATOR_PREFIX = "first";

public FirstService() {
super(DISCRIMINATOR_PREFIX);
}

public static class Discriminator extends NamePrefixResourceDiscriminator<Service> {
protected Discriminator() {
super(DISCRIMINATOR_PREFIX);
}
}

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

import io.fabric8.kubernetes.api.model.apps.StatefulSet;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;

@KubernetesDependent(resourceDiscriminator = FirstStatefulSet.Discriminator.class)
public class FirstStatefulSet extends BaseStatefulSet {

public static final String DISCRIMINATOR_PREFIX = "first";

public FirstStatefulSet() {
super(DISCRIMINATOR_PREFIX);
}


public static class Discriminator extends NamePrefixResourceDiscriminator<StatefulSet> {
protected Discriminator() {
super(DISCRIMINATOR_PREFIX);
}
}

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

import java.util.Optional;
import java.util.stream.Collectors;

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator;
import io.javaoperatorsdk.operator.sample.complexdependent.ComplexDependentCustomResource;

public abstract class NamePrefixResourceDiscriminator<R extends HasMetadata>
implements ResourceDiscriminator<R, ComplexDependentCustomResource> {

private final String prefix;

protected NamePrefixResourceDiscriminator(String prefix) {
this.prefix = prefix;
}

@Override
public Optional<R> distinguish(Class<R> resource, ComplexDependentCustomResource primary,
Context<ComplexDependentCustomResource> context) {
var resources = context.getSecondaryResources(resource);
var filtered = resources.stream().filter(r -> r.getMetadata().getName().startsWith(prefix))
.collect(Collectors.toList());
if (filtered.size() > 1) {
throw new IllegalStateException("More resources than expected for" + primary);
}
return filtered.isEmpty() ? Optional.empty() : Optional.of(filtered.get(0));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.javaoperatorsdk.operator.sample.complexdependent.dependent;

import io.fabric8.kubernetes.api.model.Service;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;

@KubernetesDependent(resourceDiscriminator = SecondService.Discriminator.class)
public class SecondService extends BaseService {

public static final String DISCRIMINATOR_PREFIX = "second";

public SecondService() {
super(DISCRIMINATOR_PREFIX);
}

public static class Discriminator extends NamePrefixResourceDiscriminator<Service> {
protected Discriminator() {
super(DISCRIMINATOR_PREFIX);
}
}
}
Loading