Skip to content

Workflow engine implementation #1153

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 51 commits into from
May 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
6c0edb8
feat: garbage collected interface (#1164)
csviri May 5, 2022
5b8a925
fix: build
csviri May 9, 2022
dc50630
Using Annotations to Identify primary for a secondary object if no ow…
csviri May 11, 2022
889baa9
fix: build
csviri May 16, 2022
8a72f35
fix: format
csviri May 16, 2022
8021568
fix: e2e test issue
csviri May 19, 2022
1183f38
fix: format
csviri May 19, 2022
0fc4493
feat: workflow
csviri Apr 6, 2022
4b2b8cb
wip
csviri Apr 10, 2022
408b501
wip
csviri Apr 10, 2022
1aa332c
wip
csviri Apr 11, 2022
c959d4b
wip
csviri Apr 11, 2022
903edf4
wip
csviri Apr 11, 2022
ff322fe
fix: basic depends on works
csviri Apr 12, 2022
0bb4843
fix: tests
csviri Apr 12, 2022
aadd3a4
tests for exception handling
csviri Apr 12, 2022
f2bb236
some smell fixes
csviri Apr 12, 2022
ee88a6b
fix: smells
csviri Apr 12, 2022
e2aba17
wip
csviri Apr 12, 2022
362f077
feat: reconcile condition
csviri Apr 13, 2022
b10bc39
fix: tests
csviri Apr 13, 2022
3de8f8d
fix: ready impl no tests
csviri Apr 13, 2022
def8cab
tests for ready condition
csviri Apr 19, 2022
c55add1
smell remove
csviri Apr 19, 2022
2f830d4
addtional test
csviri Apr 19, 2022
8bd1256
fix: bug with the ready
csviri Apr 19, 2022
d2f9110
execution results
csviri Apr 19, 2022
2b4c265
comment
csviri Apr 19, 2022
67f6ec9
test refactor, bottom resources
csviri Apr 25, 2022
90535c1
wip
csviri Apr 25, 2022
44e1ef1
wip on cleanup
csviri May 4, 2022
e5e939c
cleanup wip
csviri May 4, 2022
6c9d3c8
wip
csviri May 4, 2022
ca3ab56
fix: test
csviri May 16, 2022
540e22a
cleanup workflow process
csviri May 16, 2022
0475c2f
Only one Condition type
csviri May 16, 2022
a7f4fa0
unused var
csviri May 16, 2022
9cf0bd8
progress on delete during reconcile
csviri May 17, 2022
820bd65
fix tests
csviri May 19, 2022
9d2bdcf
format
csviri May 19, 2022
15e5137
test fix
csviri May 19, 2022
df897eb
unit tests
csviri May 19, 2022
c107eb0
fixes
csviri May 19, 2022
9f8dc31
cleanup result
csviri May 19, 2022
e96ade0
sample webpage reconciler
csviri May 19, 2022
1460a01
improved assertions
csviri May 23, 2022
13c823c
wip
csviri May 23, 2022
088665a
reconcile result
csviri May 24, 2022
e45d304
fix test
csviri May 24, 2022
7511b4d
refactor: remove need to maintain external list of parents
metacosm May 24, 2022
dff7f39
refactor: show parents and dependents in String representation
metacosm May 24, 2022
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
Expand Up @@ -3,15 +3,15 @@
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.CustomResource;

public class UpdateControl<T extends HasMetadata> extends BaseControl<UpdateControl<T>> {
public class UpdateControl<P extends HasMetadata> extends BaseControl<UpdateControl<P>> {

private final T resource;
private final P resource;
private final boolean updateStatus;
private final boolean updateResource;
private final boolean patch;

private UpdateControl(
T resource, boolean updateStatus, boolean updateResource, boolean patch) {
P resource, boolean updateStatus, boolean updateResource, boolean patch) {
if ((updateResource || updateStatus) && resource == null) {
throw new IllegalArgumentException("CustomResource cannot be null in case of update");
}
Expand Down Expand Up @@ -92,7 +92,7 @@ public static <T extends HasMetadata> UpdateControl<T> noUpdate() {
return new UpdateControl<>(null, false, false, false);
}

public T getResource() {
public P getResource() {
return resource;
}

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

import io.fabric8.kubernetes.api.model.HasMetadata;

/**
* Should be implemented by {@link DependentResource} implementations that are explicitly deleted
* during reconciliation but which should also benefit from Kubernetes' automated garbage collection
* during the cleanup phase.
* <p>
* See <a href="https://github.com/java-operator-sdk/java-operator-sdk/issues/1127">this issue</a>
* for more details.
*/
public interface GarbageCollected<P extends HasMetadata> extends Deleter<P> {

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceProvider;
import io.javaoperatorsdk.operator.api.reconciler.dependent.GarbageCollected;
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DependentResourceConfigurator;
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.KubernetesClientAware;
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.ManagedDependentResourceException;
Expand Down Expand Up @@ -62,15 +63,16 @@ public Controller(Reconciler<P> reconciler,

final var hasDeleterHolder = new boolean[] {false};
final var specs = configuration.getDependentResources();
final var size = specs.size();
if (size == 0) {
final var specsSize = specs.size();
if (specsSize == 0) {
dependents = new LinkedHashMap<>();
} else {
final Map<String, DependentResource> dependentsHolder = new LinkedHashMap<>(size);
final Map<String, DependentResource> dependentsHolder = new LinkedHashMap<>(specsSize);
specs.forEach(drs -> {
final var dependent = createAndConfigureFrom(drs, kubernetesClient);
// check if dependent implements Deleter to record that fact
if (!hasDeleterHolder[0] && dependent instanceof Deleter) {
if (!hasDeleterHolder[0] && dependent instanceof Deleter
&& !(dependent instanceof GarbageCollected)) {
hasDeleterHolder[0] = true;
}
dependentsHolder.put(drs.getName(), dependent);
Expand Down Expand Up @@ -131,7 +133,7 @@ public DeleteControl execute() {
initContextIfNeeded(resource, context);
if (hasDeleterDependents) {
dependents.values().stream()
.filter(d -> d instanceof Deleter)
.filter(d -> d instanceof Deleter && !(d instanceof GarbageCollected))
.map(Deleter.class::cast)
.forEach(deleter -> deleter.delete(resource, context));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult;
import io.javaoperatorsdk.operator.processing.event.ResourceID;
Expand All @@ -16,7 +15,7 @@ public abstract class AbstractDependentResource<R, P extends HasMetadata>

protected final boolean creatable = this instanceof Creator;
protected final boolean updatable = this instanceof Updater;
protected final boolean deletable = this instanceof Deleter;

protected Creator<R, P> creator;
protected Updater<R, P> updater;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
package io.javaoperatorsdk.operator.processing.dependent.kubernetes;

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter;
import io.javaoperatorsdk.operator.api.reconciler.dependent.GarbageCollected;
import io.javaoperatorsdk.operator.processing.dependent.Creator;
import io.javaoperatorsdk.operator.processing.dependent.Updater;

/**
* Adaptor Class for standalone mode for resources that manages Create, Read, Update and Delete
* Adaptor class for standalone mode for resources that manage Create, Read and Update operations
* and that should be automatically garbage-collected by Kubernetes when the associated primary
* resource is destroyed.
*
* @param <R> Managed resource
* @param <P> Primary Resource
* @param <R> the type of the managed dependent resource
* @param <P> the type of the associated primary resource
*/
public abstract class CRUDKubernetesDependentResource<R extends HasMetadata, P extends HasMetadata>
extends
KubernetesDependentResource<R, P> implements Creator<R, P>, Updater<R, P>, Deleter<P> {
KubernetesDependentResource<R, P>
implements Creator<R, P>, Updater<R, P>, GarbageCollected<P> {

public CRUDKubernetesDependentResource(Class<R> resourceType) {
super(resourceType);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.javaoperatorsdk.operator.processing.dependent.kubernetes;

import java.util.HashMap;
import java.util.Optional;
import java.util.Set;

Expand All @@ -11,10 +12,12 @@
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation;
import io.fabric8.kubernetes.client.dsl.Resource;
import io.javaoperatorsdk.operator.OperatorException;
import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.Constants;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext;
import io.javaoperatorsdk.operator.api.reconciler.dependent.GarbageCollected;
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DependentResourceConfigurator;
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.KubernetesClientAware;
import io.javaoperatorsdk.operator.processing.dependent.AbstractEventSourceHolderDependentResource;
Expand All @@ -36,6 +39,7 @@ public abstract class KubernetesDependentResource<R extends HasMetadata, P exten
private final Matcher<R, P> matcher;
private final ResourceUpdatePreProcessor<R> processor;
private final Class<R> resourceType;
private final boolean garbageCollected = this instanceof GarbageCollected;
private KubernetesDependentResourceConfig kubernetesDependentResourceConfig;

@SuppressWarnings("unchecked")
Expand All @@ -62,18 +66,29 @@ private void configureWith(String labelSelector, Set<String> namespaces,
namespaces = context.getControllerConfiguration().getNamespaces();
}

final SecondaryToPrimaryMapper<R> primaryResourcesRetriever =
(this instanceof SecondaryToPrimaryMapper) ? (SecondaryToPrimaryMapper<R>) this
: Mappers.fromOwnerReference();
var ic = InformerConfiguration.from(resourceType())
.withLabelSelector(labelSelector)
.withSecondaryToPrimaryMapper(primaryResourcesRetriever)
.withSecondaryToPrimaryMapper(getSecondaryToPrimaryMapper())
.withNamespaces(namespaces, inheritNamespacesOnChange)
.build();

configureWith(new InformerEventSource<>(ic, client));
}

@SuppressWarnings("unchecked")
private SecondaryToPrimaryMapper<R> getSecondaryToPrimaryMapper() {
if (this instanceof SecondaryToPrimaryMapper) {
return (SecondaryToPrimaryMapper<R>) this;
} else if (garbageCollected) {
return Mappers.fromOwnerReference();
} else if (useDefaultAnnotationsToIdentifyPrimary()) {
return Mappers.fromDefaultAnnotations();
} else {
throw new OperatorException("Provide a SecondaryToPrimaryMapper to associate " +
"this resource with the primary resource. DependentResource: " + getClass().getName());
}
}

/**
* Use to share informers between event more resources.
*
Expand Down Expand Up @@ -121,10 +136,8 @@ public Result<R> match(R actualResource, P primary, Context<P> context) {
}

public void delete(P primary, Context<P> context) {
if (!addOwnerReference()) {
var resource = getSecondaryResource(primary);
resource.ifPresent(r -> client.resource(r).delete());
}
var resource = getSecondaryResource(primary);
resource.ifPresent(r -> client.resource(r).delete());
}

@SuppressWarnings("unchecked")
Expand All @@ -136,6 +149,8 @@ protected NonNamespaceOperation<R, KubernetesResourceList<R>, Resource<R>> prepa
ResourceID.fromResource(desired));
if (addOwnerReference()) {
desired.addOwnerReference(primary);
} else if (useDefaultAnnotationsToIdentifyPrimary()) {
addDefaultSecondaryToPrimaryMapperAnnotations(desired, primary);
}
Class<R> targetClass = (Class<R>) desired.getClass();
return client.resources(targetClass).inNamespace(desired.getMetadata().getNamespace());
Expand All @@ -157,8 +172,26 @@ protected InformerEventSource<R, P> createEventSource(EventSourceContext<P> cont
return eventSource();
}

private boolean useDefaultAnnotationsToIdentifyPrimary() {
return !(this instanceof SecondaryToPrimaryMapper) && !garbageCollected && creatable;
}

private void addDefaultSecondaryToPrimaryMapperAnnotations(R desired, P primary) {
var annotations = desired.getMetadata().getAnnotations();
if (annotations == null) {
annotations = new HashMap<>();
desired.getMetadata().setAnnotations(annotations);
}
annotations.put(Mappers.DEFAULT_ANNOTATION_FOR_NAME, primary.getMetadata().getName());
var primaryNamespaces = primary.getMetadata().getNamespace();
if (primaryNamespaces != null) {
annotations.put(
Mappers.DEFAULT_ANNOTATION_FOR_NAMESPACE, primary.getMetadata().getNamespace());
}
}

protected boolean addOwnerReference() {
return creatable && !deletable;
return garbageCollected;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public KubernetesDependentResourceConfig(Set<String> namespaces, String labelSel
boolean configuredNS) {
this.namespaces = namespaces;
this.labelSelector = labelSelector;
namespacesWereConfigured = configuredNS;
this.namespacesWereConfigured = configuredNS;
}

public KubernetesDependentResourceConfig(Set<String> namespaces, String labelSelector) {
Expand Down Expand Up @@ -48,4 +48,5 @@ public String labelSelector() {
public boolean wereNamespacesConfigured() {
return namespacesWereConfigured;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.javaoperatorsdk.operator.processing.dependent.workflow;

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;

public interface Condition<R, P extends HasMetadata> {

boolean isMet(DependentResource<R, P> dependentResource, P primary, Context<P> context);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package io.javaoperatorsdk.operator.processing.dependent.workflow;

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

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;

@SuppressWarnings("rawtypes")
public class DependentResourceNode<R, P extends HasMetadata> {

private final DependentResource<R, P> dependentResource;
private Condition reconcileCondition;
private Condition deletePostCondition;
private Condition readyCondition;
private final List<DependentResourceNode> dependsOn = new LinkedList<>();
private final List<DependentResourceNode> parents = new LinkedList<>();

public DependentResourceNode(DependentResource<R, P> dependentResource) {
this(dependentResource, null, null);
}

public DependentResourceNode(DependentResource<R, P> dependentResource,
Condition reconcileCondition) {
this(dependentResource, reconcileCondition, null);
}

public DependentResourceNode(DependentResource<R, P> dependentResource,
Condition reconcileCondition, Condition deletePostCondition) {
this.dependentResource = dependentResource;
this.reconcileCondition = reconcileCondition;
this.deletePostCondition = deletePostCondition;
}

public DependentResource<R, P> getDependentResource() {
return dependentResource;
}

public Optional<Condition> getReconcileCondition() {
return Optional.ofNullable(reconcileCondition);
}

public Optional<Condition> getDeletePostCondition() {
return Optional.ofNullable(deletePostCondition);
}

public List<DependentResourceNode> getDependsOn() {
return dependsOn;
}

@SuppressWarnings("unchecked")
public void addDependsOnRelation(DependentResourceNode node) {
node.parents.add(this);
dependsOn.add(node);
}

@Override
public String toString() {
return "{"
+ parents.stream().map(p -> p.dependentResource.toString())
.collect(Collectors.joining(", ", "[", "]->"))
+ "(" + dependentResource + ")"
+ dependsOn.stream().map(d -> d.dependentResource.toString())
.collect(Collectors.joining(", ", "->[", "]"))
+ '}';
}

public DependentResourceNode<R, P> setReconcileCondition(
Condition reconcileCondition) {
this.reconcileCondition = reconcileCondition;
return this;
}

public DependentResourceNode<R, P> setDeletePostCondition(Condition cleanupCondition) {
this.deletePostCondition = cleanupCondition;
return this;
}

public Optional<Condition<R, P>> getReadyCondition() {
return Optional.ofNullable(readyCondition);
}

public DependentResourceNode<R, P> setReadyCondition(Condition readyCondition) {
this.readyCondition = readyCondition;
return this;
}

public List<DependentResourceNode> getParents() {
return parents;
}
}
Loading