Skip to content
Draft
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
Expand Up @@ -304,12 +304,15 @@ private <P extends HasMetadata> ResolvedControllerConfiguration<P> controllerCon
final var dependentFieldManager =
fieldManager.equals(CONTROLLER_NAME_AS_FIELD_MANAGER) ? name : fieldManager;

var triggerReconcilerOnAllEvent =
annotation != null && annotation.triggerReconcilerOnAllEvent();

InformerConfiguration<P> informerConfig =
InformerConfiguration.builder(resourceClass)
.initFromAnnotation(annotation != null ? annotation.informer() : null, context)
.buildForController();

return new ResolvedControllerConfiguration<P>(
return new ResolvedControllerConfiguration<>(
name,
generationAware,
associatedReconcilerClass,
Expand All @@ -323,7 +326,8 @@ private <P extends HasMetadata> ResolvedControllerConfiguration<P> controllerCon
null,
dependentFieldManager,
this,
informerConfig);
informerConfig,
triggerReconcilerOnAllEvent);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,8 @@ default String fieldManager() {
}

<C> C getConfigurationFor(DependentResourceSpec<?, P, C> spec);

default boolean triggerReconcilerOnAllEvent() {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class ControllerConfigurationOverrider<R extends HasMetadata> {
private Duration reconciliationMaxInterval;
private Map<DependentResourceSpec, Object> configurations;
private final InformerConfiguration<R>.Builder config;
private boolean triggerReconcilerOnAllEvent;

private ControllerConfigurationOverrider(ControllerConfiguration<R> original) {
this.finalizer = original.getFinalizerName();
Expand All @@ -42,6 +43,7 @@ private ControllerConfigurationOverrider(ControllerConfiguration<R> original) {
this.rateLimiter = original.getRateLimiter();
this.name = original.getName();
this.fieldManager = original.fieldManager();
this.triggerReconcilerOnAllEvent = original.triggerReconcilerOnAllEvent();
}

public ControllerConfigurationOverrider<R> withFinalizer(String finalizer) {
Expand Down Expand Up @@ -154,6 +156,12 @@ public ControllerConfigurationOverrider<R> withFieldManager(String dependentFiel
return this;
}

public ControllerConfigurationOverrider<R> withTriggerReconcilerOnAllEvent(
boolean triggerReconcilerOnAllEvent) {
this.triggerReconcilerOnAllEvent = triggerReconcilerOnAllEvent;
return this;
}

/**
* Sets a max page size limit when starting the informer. This will result in pagination while
* populating the cache. This means that longer lists will take multiple requests to fetch. See
Expand Down Expand Up @@ -198,6 +206,7 @@ public ControllerConfiguration<R> build() {
fieldManager,
original.getConfigurationService(),
config.buildForController(),
triggerReconcilerOnAllEvent,
original.getWorkflowSpec().orElse(null));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public class ResolvedControllerConfiguration<P extends HasMetadata>
private final Map<DependentResourceSpec, Object> configurations;
private final ConfigurationService configurationService;
private final String fieldManager;
private final boolean triggerReconcilerOnAllEvent;
private WorkflowSpec workflowSpec;

public ResolvedControllerConfiguration(ControllerConfiguration<P> other) {
Expand All @@ -44,6 +45,7 @@ public ResolvedControllerConfiguration(ControllerConfiguration<P> other) {
other.fieldManager(),
other.getConfigurationService(),
other.getInformerConfig(),
other.triggerReconcilerOnAllEvent(),
other.getWorkflowSpec().orElse(null));
}

Expand All @@ -59,6 +61,7 @@ public ResolvedControllerConfiguration(
String fieldManager,
ConfigurationService configurationService,
InformerConfiguration<P> informerConfig,
boolean triggerReconcilerOnAllEvent,
WorkflowSpec workflowSpec) {
this(
name,
Expand All @@ -71,7 +74,8 @@ public ResolvedControllerConfiguration(
configurations,
fieldManager,
configurationService,
informerConfig);
informerConfig,
triggerReconcilerOnAllEvent);
setWorkflowSpec(workflowSpec);
}

Expand All @@ -86,7 +90,8 @@ protected ResolvedControllerConfiguration(
Map<DependentResourceSpec, Object> configurations,
String fieldManager,
ConfigurationService configurationService,
InformerConfiguration<P> informerConfig) {
InformerConfiguration<P> informerConfig,
boolean triggerReconcilerOnAllEvent) {
this.informerConfig = informerConfig;
this.configurationService = configurationService;
this.name = ControllerConfiguration.ensureValidName(name, associatedReconcilerClassName);
Expand All @@ -99,6 +104,7 @@ protected ResolvedControllerConfiguration(
this.finalizer =
ControllerConfiguration.ensureValidFinalizerName(finalizer, getResourceTypeName());
this.fieldManager = fieldManager;
this.triggerReconcilerOnAllEvent = triggerReconcilerOnAllEvent;
}

protected ResolvedControllerConfiguration(
Expand All @@ -117,7 +123,8 @@ protected ResolvedControllerConfiguration(
null,
null,
configurationService,
InformerConfiguration.builder(resourceClass).buildForController());
InformerConfiguration.builder(resourceClass).buildForController(),
false);
}

@Override
Expand Down Expand Up @@ -207,4 +214,9 @@ public <C> C getConfigurationFor(DependentResourceSpec<?, P, C> spec) {
public String fieldManager() {
return fieldManager;
}

@Override
public boolean triggerReconcilerOnAllEvent() {
return triggerReconcilerOnAllEvent;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,8 @@ default <R> Stream<R> getSecondaryResourcesAsStream(Class<R> expectedType) {
* @return {@code true} is another reconciliation is already scheduled, {@code false} otherwise
*/
boolean isNextReconciliationImminent();

boolean isPrimaryResourceDeleted();

boolean isPrimaryResourceFinalStateUnknown();
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,6 @@ MaxReconciliationInterval maxReconciliationInterval() default
* @return the name used as field manager for SSA operations
*/
String fieldManager() default CONTROLLER_NAME_AS_FIELD_MANAGER;

boolean triggerReconcilerOnAllEvent() default false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,21 @@ public class DefaultContext<P extends HasMetadata> implements Context<P> {
private final ControllerConfiguration<P> controllerConfiguration;
private final DefaultManagedWorkflowAndDependentResourceContext<P>
defaultManagedDependentResourceContext;

public DefaultContext(RetryInfo retryInfo, Controller<P> controller, P primaryResource) {
private final boolean primaryResourceDeleted;
private final boolean primaryResourceFinalStateUnknown;

public DefaultContext(
RetryInfo retryInfo,
Controller<P> controller,
P primaryResource,
boolean primaryResourceDeleted,
boolean primaryResourceFinalStateUnknown) {
this.retryInfo = retryInfo;
this.controller = controller;
this.primaryResource = primaryResource;
this.controllerConfiguration = controller.getConfiguration();
this.primaryResourceDeleted = primaryResourceDeleted;
this.primaryResourceFinalStateUnknown = primaryResourceFinalStateUnknown;
this.defaultManagedDependentResourceContext =
new DefaultManagedWorkflowAndDependentResourceContext<>(controller, primaryResource, this);
}
Expand Down Expand Up @@ -119,6 +128,16 @@ public boolean isNextReconciliationImminent() {
.isNextReconciliationImminent(ResourceID.fromResource(primaryResource));
}

@Override
public boolean isPrimaryResourceDeleted() {
return primaryResourceDeleted;
}

@Override
public boolean isPrimaryResourceFinalStateUnknown() {
return primaryResourceFinalStateUnknown;
}

public DefaultContext<P> setRetryInfo(RetryInfo retryInfo) {
this.retryInfo = retryInfo;
return this;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.javaoperatorsdk.operator.api.reconciler;

import java.lang.reflect.InvocationTargetException;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.function.UnaryOperator;
Expand All @@ -8,12 +9,17 @@
import org.slf4j.LoggerFactory;

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.dsl.base.PatchContext;
import io.fabric8.kubernetes.client.dsl.base.PatchType;
import io.javaoperatorsdk.operator.OperatorException;
import io.javaoperatorsdk.operator.processing.event.ResourceID;

import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getUID;
import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getVersion;

/**
* Utility methods to patch the primary resource state and store it to the related cache, to make
* sure that the latest version of the resource is present for the next reconciliation. The main use
Expand Down Expand Up @@ -229,4 +235,165 @@ private static <P extends HasMetadata> P pollLocalCache(
throw new OperatorException(e);
}
}

/** Adds finalizer using JSON Patch. Retries conflicts and unprocessable content (HTTP 422) */
@SuppressWarnings("unchecked")
public static <P extends HasMetadata> P addFinalizer(
Context<P> context, P resource, String finalizerName) {
if (log.isDebugEnabled()) {
log.debug("Conflict retrying update for: {}", ResourceID.fromResource(resource));
}
int retryIndex = 0;
while (true) {
try {
if (resource.hasFinalizer(finalizerName)) {
return resource;
}
return context
.getClient()
.resource(resource)
.edit(
r -> {
r.addFinalizer(finalizerName);
return r;
});
} catch (KubernetesClientException e) {
log.trace("Exception during patch for resource: {}", resource);
retryIndex++;
// only retry on conflict (409) and unprocessable content (422) which
// can happen if JSON Patch is not a valid request since there was
// a concurrent request which already removed another finalizer:
// List element removal from a list is by index in JSON Patch
// so if addressing a second finalizer but first is meanwhile removed
// it is a wrong request.
if (e.getCode() != 409 && e.getCode() != 422) {
throw e;
}
if (retryIndex >= DEFAULT_MAX_RETRY) {
throw new OperatorException(
"Exceeded maximum ("
+ DEFAULT_MAX_RETRY
+ ") retry attempts to patch resource: "
+ ResourceID.fromResource(resource));
}
log.debug(
"Retrying patch for resource name: {}, namespace: {}; HTTP code: {}",
resource.getMetadata().getName(),
resource.getMetadata().getNamespace(),
e.getCode());
var operation = context.getClient().resources(resource.getClass());
if (resource.getMetadata().getNamespace() != null) {
resource =
(P)
operation
.inNamespace(resource.getMetadata().getNamespace())
.withName(resource.getMetadata().getName())
.get();
} else {
resource = (P) operation.withName(resource.getMetadata().getName()).get();
}
}
}
}

/** Adds finalizer using Server-Side Apply. */
public static <P extends HasMetadata> P addFinalizerWithSSA(
Context<P> context, P originalResource, String finalizerName) {
return addFinalizerWithSSA(
context.getClient(),
originalResource,
finalizerName,
context.getControllerConfiguration().fieldManager());
}

/** Adds finalizer using Server-Side Apply. */
public static <P extends HasMetadata> P addFinalizerWithSSA(
KubernetesClient client, P originalResource, String finalizerName, String fieldManager) {
log.debug(
"Adding finalizer (using SSA) for resource: {} version: {}",
getUID(originalResource),
getVersion(originalResource));
try {
P resource = (P) originalResource.getClass().getConstructor().newInstance();
ObjectMeta objectMeta = new ObjectMeta();
objectMeta.setName(originalResource.getMetadata().getName());
objectMeta.setNamespace(originalResource.getMetadata().getNamespace());
resource.setMetadata(objectMeta);
resource.addFinalizer(finalizerName);
return client
.resource(resource)
.patch(
new PatchContext.Builder()
.withFieldManager(fieldManager)
.withForce(true)
.withPatchType(PatchType.SERVER_SIDE_APPLY)
.build());
} catch (InstantiationException
| IllegalAccessException
| InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(
"Issue with creating custom resource instance with reflection."
+ " Custom Resources must provide a no-arg constructor. Class: "
+ originalResource.getClass().getName(),
e);
}
}

// todo
public static <P extends HasMetadata> P removeFinalizer() {
return null;
}

/**
* Experimental. Patches finalizer. For retry uses informer cache to get the fresh resources.
* Therefore makes less Kubernetes API Calls.
*/
public static <P extends HasMetadata> P addFinalizer(
P resource, String finalizer, Context<P> context) {

if (resource.hasFinalizer(finalizer)) {
log.debug("Skipping adding finalizer, since already present.");
return resource;
}

return updateAndCacheResource(
resource,
context,
r -> r,
r ->
context
.getClient()
.resource(r)
.edit(
res -> {
res.addFinalizer(finalizer);
return res;
}));
}

/**
* Experimental. Removes finalizer, for retry uses informer cache to get the fresh resources.
* Therefore makes less Kubernetes API Calls.
*/
public static <P extends HasMetadata> P removeFinalizer(
P resource, String finalizer, Context<P> context) {
if (!resource.hasFinalizer(finalizer)) {
log.debug("Skipping removing finalizer, since not present.");
return resource;
}
return updateAndCacheResource(
resource,
context,
r -> r,
r ->
context
.getClient()
.resource(r)
.edit(
res -> {
res.removeFinalizer(finalizer);
return res;
}));
}
}
Loading