Skip to content

feat: activation condition #2105

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 24 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from 14 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
Expand Up @@ -230,6 +230,7 @@ private static List<DependentResourceSpec> dependentResources(
Utils.instantiate(dependent.readyPostcondition(), Condition.class, context),
Utils.instantiate(dependent.reconcilePrecondition(), Condition.class, context),
Utils.instantiate(dependent.deletePostcondition(), Condition.class, context),
Utils.instantiate(dependent.activationCondition(), Condition.class, context),
eventSourceName);
specsMap.put(dependentName, spec);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,21 @@ public class DependentResourceSpec<R, P extends HasMetadata> {

private final Condition<?, ?> deletePostCondition;

private final Condition<?, ?> activationCondition;

private final String useEventSourceWithName;

public DependentResourceSpec(Class<? extends DependentResource<R, P>> dependentResourceClass,
String name, Set<String> dependsOn, Condition<?, ?> readyCondition,
Condition<?, ?> reconcileCondition, Condition<?, ?> deletePostCondition,
String useEventSourceWithName) {
Condition<?, ?> activationCondition, String useEventSourceWithName) {
this.dependentResourceClass = dependentResourceClass;
this.name = name;
this.dependsOn = dependsOn;
this.readyCondition = readyCondition;
this.reconcileCondition = reconcileCondition;
this.deletePostCondition = deletePostCondition;
this.activationCondition = activationCondition;
this.useEventSourceWithName = useEventSourceWithName;
}

Expand Down Expand Up @@ -87,6 +90,11 @@ public Condition getDeletePostCondition() {
return deletePostCondition;
}

@SuppressWarnings("rawtypes")
public Condition getActivationCondition() {
return activationCondition;
}

public Optional<String> getUseEventSourceWithName() {
return Optional.ofNullable(useEventSourceWithName);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
*/
Class<? extends Condition> deletePostcondition() default Condition.class;

Class<? extends Condition> activationCondition() default Condition.class;

/**
* The list of named dependents that need to be reconciled before this one can be.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ public class Controller<P extends HasMetadata>
private final GroupVersionKind associatedGVK;
private final EventProcessor<P> eventProcessor;
private final ControllerHealthInfo controllerHealthInfo;
private final EventSourceContext<P> eventSourceContext;

public Controller(Reconciler<P> reconciler,
ControllerConfiguration<P> configuration,
Expand All @@ -98,9 +99,9 @@ public Controller(Reconciler<P> reconciler,
eventProcessor = new EventProcessor<>(eventSourceManager, configurationService);
eventSourceManager.postProcessDefaultEventSourcesAfterProcessorInitializer();
controllerHealthInfo = new ControllerHealthInfo(eventSourceManager);
final var context = new EventSourceContext<>(
eventSourceContext = new EventSourceContext<>(
eventSourceManager.getControllerResourceEventSource(), configuration, kubernetesClient);
initAndRegisterEventSources(context);
initAndRegisterEventSources(eventSourceContext);
configurationService.getMetrics().controllerRegistered(this);
}

Expand Down Expand Up @@ -236,7 +237,8 @@ public void initAndRegisterEventSources(EventSourceContext<P> context) {
}

// register created event sources
final var dependentResourcesByName = managedWorkflow.getDependentResourcesByName();
final var dependentResourcesByName =
managedWorkflow.getDependentResourcesByNameWithoutActivationCondition();
final var size = dependentResourcesByName.size();
if (size > 0) {
dependentResourcesByName.forEach((key, dependentResource) -> {
Expand Down Expand Up @@ -440,4 +442,8 @@ public EventProcessor<P> getEventProcessor() {
public ExecutorServiceManager getExecutorServiceManager() {
return getConfiguration().getConfigurationService().getExecutorServiceManager();
}

public EventSourceContext<P> eventSourceContext() {
return eventSourceContext;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@ public AbstractWorkflowExecutor(Workflow<P> workflow, P primary, Context<P> cont
protected synchronized void waitForScheduledExecutionsToRun() {
while (true) {
try {
this.wait();
if (noMoreExecutionsScheduled()) {
break;
} else {
logger().warn("Notified but still resources under execution. This should not happen.");
}
this.wait();
} catch (InterruptedException e) {
if (noMoreExecutionsScheduled()) {
logger().debug("interrupted, no more executions for: {}", primaryID);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public Workflow<P> resolve(KubernetesClient client,
spec.getReconcileCondition(),
spec.getDeletePostCondition(),
spec.getReadyCondition(),
spec.getActivationCondition(),
resolve(spec, client, configuration));
alreadyResolved.put(node.getName(), node);
spec.getDependsOn()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,15 @@ public Map<String, DependentResource> getDependentResourcesByName() {
.forEach((name, node) -> resources.put(name, node.getDependentResource()));
return resources;
}

public Map<String, DependentResource> getDependentResourcesByNameWithoutActivationCondition() {
final var resources = new HashMap<String, DependentResource>(dependentResourceNodes.size());
dependentResourceNodes
.forEach((name, node) -> {
if (node.getActivationCondition().isEmpty()) {
resources.put(name, node.getDependentResource());
}
});
return resources;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,21 @@ public class DependentResourceNode<R, P extends HasMetadata> {
private Condition<R, P> reconcilePrecondition;
private Condition<R, P> deletePostcondition;
private Condition<R, P> readyPostcondition;
private Condition<R, P> activationCondition;
private final DependentResource<R, P> dependentResource;

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

public DependentResourceNode(String name, Condition<R, P> reconcilePrecondition,
Condition<R, P> deletePostcondition, Condition<R, P> readyPostcondition,
DependentResource<R, P> dependentResource) {
Condition<R, P> activationCondition, DependentResource<R, P> dependentResource) {
this.name = name;
this.reconcilePrecondition = reconcilePrecondition;
this.deletePostcondition = deletePostcondition;
this.readyPostcondition = readyPostcondition;
this.activationCondition = activationCondition;
this.dependentResource = dependentResource;
}

Expand Down Expand Up @@ -63,6 +65,10 @@ public Optional<Condition<R, P>> getDeletePostcondition() {
return Optional.ofNullable(deletePostcondition);
}

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

void setReconcilePrecondition(Condition<R, P> reconcilePrecondition) {
this.reconcilePrecondition = reconcilePrecondition;
}
Expand All @@ -71,6 +77,10 @@ void setDeletePostcondition(Condition<R, P> cleanupCondition) {
this.deletePostcondition = cleanupCondition;
}

void setActivationCondition(Condition<R, P> activationCondition) {
this.activationCondition = activationCondition;
}

public Optional<Condition<R, P>> getReadyPostcondition() {
return Optional.ofNullable(readyPostcondition);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,8 @@ default boolean isEmpty() {
default Map<String, DependentResource> getDependentResourcesByName() {
return Collections.emptyMap();
}

default Map<String, DependentResource> getDependentResourcesByNameWithoutActivationCondition() {
return Collections.emptyMap();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ public WorkflowBuilder<P> withDeletePostcondition(Condition deletePostcondition)
return this;
}

public WorkflowBuilder<P> withActivationCondition(Condition activationCondition) {
currentNode.setActivationCondition(activationCondition);
return this;
}

DependentResourceNode getNodeByDependentResource(DependentResource<?, ?> dependentResource) {
// first check by name
final var node =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,20 @@ protected void doRun(DependentResourceNode<R, P> dependentResourceNode,
DependentResource<R, P> dependentResource) {
var deletePostCondition = dependentResourceNode.getDeletePostcondition();

if (dependentResource.isDeletable()) {
var active =
isConditionMet(dependentResourceNode.getActivationCondition(), dependentResource);

if (dependentResource.isDeletable() && active) {
((Deleter<P>) dependentResource).delete(primary, context);
deleteCalled.add(dependentResourceNode);
}
boolean deletePostConditionMet = isConditionMet(deletePostCondition, dependentResource);

boolean deletePostConditionMet;
if (active) {
deletePostConditionMet = isConditionMet(deletePostCondition, dependentResource);
} else {
deletePostConditionMet = true;
}
if (deletePostConditionMet) {
markAsVisited(dependentResourceNode);
handleDependentCleaned(dependentResourceNode);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,40 @@ private synchronized <R> void handleReconcile(DependentResourceNode<R, P> depend
return;
}

boolean reconcileConditionMet = isConditionMet(dependentResourceNode.getReconcilePrecondition(),
boolean activationConditionMet = isConditionMet(dependentResourceNode.getActivationCondition(),
dependentResourceNode.getDependentResource());
if (!reconcileConditionMet) {
handleReconcileConditionNotMet(dependentResourceNode);
registerOrDeregisterEventSourceBasedOnActivation(activationConditionMet, dependentResourceNode);

boolean reconcileConditionMet = true;
if (activationConditionMet) {
reconcileConditionMet = isConditionMet(dependentResourceNode.getReconcilePrecondition(),
dependentResourceNode.getDependentResource());
}
if (!reconcileConditionMet || !activationConditionMet) {
handleReconcileOrActivationConditionNotMet(dependentResourceNode, activationConditionMet);
} else {
submit(dependentResourceNode, new NodeReconcileExecutor<>(dependentResourceNode), RECONCILE);
}
}

private <R> void registerOrDeregisterEventSourceBasedOnActivation(boolean activationConditionMet,
DependentResourceNode<R, P> dependentResourceNode) {
if (dependentResourceNode.getActivationCondition().isPresent()) {
if (activationConditionMet) {
var eventSource =
dependentResourceNode.getDependentResource().eventSource(context.eventSourceRetriever()
.eventSourceContexForDynamicRegistration());
var es = eventSource.orElseThrow();
context.eventSourceRetriever()
.dynamicallyRegisterEventSource(dependentResourceNode.getName(), es);

} else {
context.eventSourceRetriever()
.dynamicallyDeRegisterEventSource(dependentResourceNode.getName());
}
}
}

private synchronized void handleDelete(DependentResourceNode dependentResourceNode) {
log.debug("Submitting for delete: {}", dependentResourceNode);

Expand All @@ -83,7 +108,8 @@ private synchronized void handleDelete(DependentResourceNode dependentResourceNo
return;
}

submit(dependentResourceNode, new NodeDeleteExecutor<>(dependentResourceNode), DELETE);
submit(dependentResourceNode,
new NodeDeleteExecutor<>(dependentResourceNode), DELETE);
}

private boolean allDependentsDeletedAlready(DependentResourceNode<?, P> dependentResourceNode) {
Expand Down Expand Up @@ -141,13 +167,21 @@ protected void doRun(DependentResourceNode<R, P> dependentResourceNode,
DependentResource<R, P> dependentResource) {
var deletePostCondition = dependentResourceNode.getDeletePostcondition();

// GarbageCollected status is irrelevant here, as this method is only called when a
// precondition does not hold,
// a deleter should be deleted even if it is otherwise garbage collected
if (dependentResource instanceof Deleter) {
((Deleter<P>) dependentResource).delete(primary, context);
var activationConditionMet = dependentResourceNode.getActivationCondition()
.map(c -> c.isMet(dependentResource, primary, context))
.orElse(true);

boolean deletePostConditionMet = true;
if (Boolean.TRUE.equals(activationConditionMet)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what's going on here. Why not use isConditionMet?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, by bad, changed it. thx!

// GarbageCollected status is irrelevant here, as this method is only called when a
// precondition does not hold,
// a deleter should be deleted even if it is otherwise garbage collected
if (dependentResource instanceof Deleter) {
((Deleter<P>) dependentResource).delete(primary, context);
}
deletePostConditionMet = isConditionMet(deletePostCondition, dependentResource);
}
boolean deletePostConditionMet = isConditionMet(deletePostCondition, dependentResource);

if (deletePostConditionMet) {
markAsVisited(dependentResourceNode);
handleDependentDeleted(dependentResourceNode);
Expand Down Expand Up @@ -180,20 +214,35 @@ private synchronized void handleDependentsReconcile(
}


private void handleReconcileConditionNotMet(DependentResourceNode<?, P> dependentResourceNode) {
private void handleReconcileOrActivationConditionNotMet(
DependentResourceNode<?, P> dependentResourceNode,
boolean activationConditionMet) {
Set<DependentResourceNode> bottomNodes = new HashSet<>();
markDependentsForDelete(dependentResourceNode, bottomNodes);
bottomNodes.forEach(this::handleDelete);
markDependentsForDelete(dependentResourceNode, bottomNodes, activationConditionMet);
bottomNodes.forEach(
dependentResourceNode1 -> handleDelete(dependentResourceNode1));
}

private void markDependentsForDelete(DependentResourceNode<?, P> dependentResourceNode,
Set<DependentResourceNode> bottomNodes) {
markedForDelete.add(dependentResourceNode);
Set<DependentResourceNode> bottomNodes, boolean activationConditionMet) {
// this is a check so the activation condition is not evaluated twice,
// so if the activation condition was false, this node is not meant to be deleted.
var dependents = dependentResourceNode.getParents();
if (dependents.isEmpty()) {
bottomNodes.add(dependentResourceNode);
if (activationConditionMet) {
markedForDelete.add(dependentResourceNode);
if (dependents.isEmpty()) {
bottomNodes.add(dependentResourceNode);
} else {
dependents.forEach(d -> markDependentsForDelete(d, bottomNodes, true));
}
} else {
dependents.forEach(d -> markDependentsForDelete(d, bottomNodes));
// this is for an edge case when there is only one resource but that is not active
markAsVisited(dependentResourceNode);
if (dependents.isEmpty()) {
handleNodeExecutionFinish(dependentResourceNode);
} else {
dependents.forEach(d -> markDependentsForDelete(d, bottomNodes, true));
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import io.javaoperatorsdk.operator.OperatorException;
import io.javaoperatorsdk.operator.api.config.ExecutorServiceManager;
import io.javaoperatorsdk.operator.api.config.NamespaceChangeable;
import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext;
import io.javaoperatorsdk.operator.api.reconciler.EventSourceInitializer;
import io.javaoperatorsdk.operator.processing.Controller;
import io.javaoperatorsdk.operator.processing.LifecycleAware;
Expand Down Expand Up @@ -231,6 +232,28 @@ public <R> List<ResourceEventSource<R, P>> getResourceEventSourcesFor(Class<R> d
return eventSources.getEventSources(dependentType);
}

@Override
public synchronized void dynamicallyRegisterEventSource(String name, EventSource eventSource) {
if (eventSources.existing(name, eventSource) != null) {
return;
}
registerEventSource(name, eventSource);
eventSource.start();
}

@Override
public synchronized void dynamicallyDeRegisterEventSource(String name) {
EventSource es = eventSources.remove(name);
if (es != null) {
es.stop();
}
}

@Override
public EventSourceContext<P> eventSourceContexForDynamicRegistration() {
return controller.eventSourceContext();
}

/**
* @deprecated Use {@link #getResourceEventSourceFor(Class)} instead
*
Expand Down
Loading