Skip to content

Commit

Permalink
feat: SSA based dependent resource matching and create/update (#1928)
Browse files Browse the repository at this point in the history
  • Loading branch information
csviri authored Jun 15, 2023
1 parent 6b95603 commit 2b71e23
Show file tree
Hide file tree
Showing 35 changed files with 1,552 additions and 25 deletions.
14 changes: 14 additions & 0 deletions cm.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: test1
namespace: default
ownerReferences:
- apiVersion: v1
kind: ConfigMap
name: kube-root-ca.crt
uid: 1ef74cb4-dbbd-45ef-9caf-aa76186594ea
data:
key1: "val1"
# key2: "val2"

9 changes: 9 additions & 0 deletions cm2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: test1
namespace: default
data:
key3: "val3"


Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

import com.fasterxml.jackson.databind.ObjectMapper;

import static io.javaoperatorsdk.operator.api.config.ControllerConfiguration.CONTROLLER_NAME_AS_FIELD_MANAGER;
import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_NAMESPACES_SET;

public class BaseConfigurationService extends AbstractConfigurationService {
Expand Down Expand Up @@ -135,6 +136,10 @@ protected <P extends HasMetadata> ControllerConfiguration<P> configFor(Reconcile
timeUnit = reconciliationInterval.timeUnit();
}

final var dependentFieldManager =
annotation.fieldManager().equals(CONTROLLER_NAME_AS_FIELD_MANAGER) ? name
: annotation.fieldManager();

final var config = new ResolvedControllerConfiguration<P>(
resourceClass, name, generationAware,
associatedReconcilerClass, retry, rateLimiter,
Expand All @@ -152,7 +157,8 @@ protected <P extends HasMetadata> ControllerConfiguration<P> configFor(Reconcile
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::labelSelector,
Constants.NO_VALUE_SET),
null,
Utils.instantiate(annotation.itemStore(), ItemStore.class, context), this);
Utils.instantiate(annotation.itemStore(), ItemStore.class, context), dependentFieldManager,
this);

ResourceEventFilter<P> answer = deprecatedEventFilter(annotation);
config.setEventFilter(answer != null ? answer : ResourceEventFilters.passthrough());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,4 +264,28 @@ static ConfigurationService newOverriddenConfigurationService(
default ExecutorServiceManager getExecutorServiceManager() {
return new ExecutorServiceManager(this);
}

/**
* Allows to revert to the 4.3 behavior when it comes to creating or updating Kubernetes Dependent
* Resources when set to {@code false}. The default approach how these resources are
* created/updated was change to use
* <a href="https://kubernetes.io/docs/reference/using-api/server-side-apply/">Server-Side
* Apply</a> (SSA) by default. Note that the legacy approach, and this setting, might be removed
* in the future.
*/
default boolean ssaBasedCreateUpdateForDependentResources() {
return true;
}

/**
* Allows to revert to the 4.3 generic matching algorithm for Kubernetes Dependent Resources when
* set to {@code false}. Version 4.4 introduced a new generic matching algorithm for Kubernetes
* Dependent Resources which is quite complex. As a consequence, we introduced this setting to
* allow folks to revert to the previous matching algorithm if needed. Note, however, that the
* legacy algorithm, and this setting, might be removed in the future.
*/
default boolean ssaBasedDefaultMatchingForDependentResources() {
return true;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public class ConfigurationServiceOverrider {
private Boolean stopOnInformerErrorDuringStartup;
private Duration cacheSyncTimeout;
private ResourceClassResolver resourceClassResolver;
private Boolean ssaBasedCreateUpdateForDependentResources;
private Boolean ssaBasedDefaultMatchingForDependentResources;

ConfigurationServiceOverrider(ConfigurationService original) {
this.original = original;
Expand Down Expand Up @@ -139,6 +141,18 @@ public ConfigurationServiceOverrider withResourceClassResolver(
return this;
}

public ConfigurationServiceOverrider withSSABasedCreateUpdateForDependentResources(
boolean value) {
this.ssaBasedCreateUpdateForDependentResources = value;
return this;
}

public ConfigurationServiceOverrider withSSABasedDefaultMatchingForDependentResources(
boolean value) {
this.ssaBasedDefaultMatchingForDependentResources = value;
return this;
}

public ConfigurationService build() {
return new BaseConfigurationService(original.getVersion(), cloner, objectMapper) {
@Override
Expand Down Expand Up @@ -248,6 +262,20 @@ public ResourceClassResolver getResourceClassResolver() {
return resourceClassResolver != null ? resourceClassResolver
: super.getResourceClassResolver();
}

@Override
public boolean ssaBasedCreateUpdateForDependentResources() {
return ssaBasedCreateUpdateForDependentResources != null
? ssaBasedCreateUpdateForDependentResources
: super.ssaBasedCreateUpdateForDependentResources();
}

@Override
public boolean ssaBasedDefaultMatchingForDependentResources() {
return ssaBasedDefaultMatchingForDependentResources != null
? ssaBasedDefaultMatchingForDependentResources
: super.ssaBasedDefaultMatchingForDependentResources();
}
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ public interface ControllerConfiguration<P extends HasMetadata> extends Resource

@SuppressWarnings("rawtypes")
RateLimiter DEFAULT_RATE_LIMITER = LinearRateLimiter.deactivatedRateLimiter();
/**
* Will use the controller name as fieldManager if set.
*/
String CONTROLLER_NAME_AS_FIELD_MANAGER = "use_controller_name";

default String getName() {
return ensureValidName(null, getAssociatedReconcilerClassName());
Expand Down Expand Up @@ -124,4 +128,16 @@ default Class<P> getResourceClass() {
default Set<String> getEffectiveNamespaces() {
return ResourceConfiguration.super.getEffectiveNamespaces(getConfigurationService());
}

/**
* Retrieves the name used to assign as field manager for
* <a href="https://kubernetes.io/docs/reference/using-api/server-side-apply/">Server-Side
* Apply</a> (SSA) operations. If unset, the sanitized controller name will be used.
*
* @return the name used as field manager for SSA operations
*/
default String fieldManager() {
return getName();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public class ControllerConfigurationOverrider<R extends HasMetadata> {
private Map<DependentResourceSpec, Object> configurations;
private ItemStore<R> itemStore;
private String name;
private String fieldManager;

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

public ControllerConfigurationOverrider<R> withFinalizer(String finalizer) {
Expand Down Expand Up @@ -168,6 +170,12 @@ public ControllerConfigurationOverrider<R> withName(String name) {
return this;
}

public ControllerConfigurationOverrider<R> withFieldManager(
String dependentFieldManager) {
this.fieldManager = dependentFieldManager;
return this;
}

public ControllerConfigurationOverrider<R> replacingNamedDependentResourceConfig(String name,
Object dependentResourceConfig) {

Expand All @@ -190,7 +198,7 @@ public ControllerConfiguration<R> build() {
generationAware, original.getAssociatedReconcilerClassName(), retry, rateLimiter,
reconciliationMaxInterval, onAddFilter, onUpdateFilter, genericFilter,
original.getDependentResources(),
namespaces, finalizer, labelSelector, configurations, itemStore,
namespaces, finalizer, labelSelector, configurations, itemStore, fieldManager,
original.getConfigurationService());
overridden.setEventFilter(customResourcePredicate);
return overridden;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class ResolvedControllerConfiguration<P extends HasMetadata>
private final Map<DependentResourceSpec, Object> configurations;
private final ItemStore<P> itemStore;
private final ConfigurationService configurationService;
private final String fieldManager;

private ResourceEventFilter<P> eventFilter;
private List<DependentResourceSpec> dependentResources;
Expand All @@ -44,7 +45,8 @@ public ResolvedControllerConfiguration(Class<P> resourceClass, ControllerConfigu
other.genericFilter().orElse(null),
other.getDependentResources(), other.getNamespaces(),
other.getFinalizerName(), other.getLabelSelector(), Collections.emptyMap(),
other.getItemStore().orElse(null), other.getConfigurationService());
other.getItemStore().orElse(null), other.fieldManager(),
other.getConfigurationService());
}

public static Duration getMaxReconciliationInterval(long interval, TimeUnit timeUnit) {
Expand Down Expand Up @@ -72,10 +74,12 @@ public ResolvedControllerConfiguration(Class<P> resourceClass, String name,
List<DependentResourceSpec> dependentResources,
Set<String> namespaces, String finalizer, String labelSelector,
Map<DependentResourceSpec, Object> configurations, ItemStore<P> itemStore,
String fieldManager,
ConfigurationService configurationService) {
this(resourceClass, name, generationAware, associatedReconcilerClassName, retry, rateLimiter,
maxReconciliationInterval, onAddFilter, onUpdateFilter, genericFilter,
namespaces, finalizer, labelSelector, configurations, itemStore, configurationService);
namespaces, finalizer, labelSelector, configurations, itemStore, fieldManager,
configurationService);
setDependentResources(dependentResources);
}

Expand All @@ -86,6 +90,7 @@ protected ResolvedControllerConfiguration(Class<P> resourceClass, String name,
GenericFilter<? super P> genericFilter,
Set<String> namespaces, String finalizer, String labelSelector,
Map<DependentResourceSpec, Object> configurations, ItemStore<P> itemStore,
String fieldManager,
ConfigurationService configurationService) {
super(resourceClass, namespaces, labelSelector, onAddFilter, onUpdateFilter, genericFilter,
itemStore);
Expand All @@ -100,13 +105,14 @@ protected ResolvedControllerConfiguration(Class<P> resourceClass, String name,
this.itemStore = itemStore;
this.finalizer =
ControllerConfiguration.ensureValidFinalizerName(finalizer, getResourceTypeName());
this.fieldManager = fieldManager;
}

protected ResolvedControllerConfiguration(Class<P> resourceClass, String name,
Class<? extends Reconciler> reconcilerClas, ConfigurationService configurationService) {
this(resourceClass, name, false, getAssociatedReconcilerClassName(reconcilerClas), null, null,
null, null, null, null, null,
null, null, null, null, configurationService);
null, null, null, null, null, configurationService);
}

@Override
Expand Down Expand Up @@ -183,4 +189,9 @@ public Object getConfigurationFor(DependentResourceSpec spec) {
public Optional<ItemStore<P>> getItemStore() {
return Optional.ofNullable(itemStore);
}

@Override
public String fieldManager() {
return fieldManager;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import io.javaoperatorsdk.operator.processing.retry.GenericRetry;
import io.javaoperatorsdk.operator.processing.retry.Retry;

import static io.javaoperatorsdk.operator.api.config.ControllerConfiguration.CONTROLLER_NAME_AS_FIELD_MANAGER;

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
Expand Down Expand Up @@ -47,7 +49,7 @@
* Specified which namespaces this Controller monitors for custom resources events. If no
* namespace is specified then the controller will monitor all namespaces by default.
*
* @return the list of namespaces this controller monitors
* @return the array of namespaces this controller monitors
*/
String[] namespaces() default Constants.WATCH_ALL_NAMESPACES;

Expand Down Expand Up @@ -108,7 +110,7 @@ MaxReconciliationInterval maxReconciliationInterval() default @MaxReconciliation
* Optional list of {@link Dependent} configurations which associate a resource type to a
* {@link io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource} implementation
*
* @return the list of {@link Dependent} configurations
* @return the array of {@link Dependent} configurations
*/
Dependent[] dependents() default {};

Expand All @@ -129,4 +131,13 @@ MaxReconciliationInterval maxReconciliationInterval() default @MaxReconciliation
Class<? extends RateLimiter> rateLimiter() default LinearRateLimiter.class;

Class<? extends ItemStore> itemStore() default ItemStore.class;

/**
* Retrieves the name used to assign as field manager for
* <a href="https://kubernetes.io/docs/reference/using-api/server-side-apply/">Server-Side
* Apply</a> (SSA) operations. If unset, the sanitized controller name will be used.
*
* @return the name used as field manager for SSA operations
*/
String fieldManager() default CONTROLLER_NAME_AS_FIELD_MANAGER;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ public class GenericKubernetesResourceMatcher<R extends HasMetadata, P extends H

private static final String ADD = "add";
private static final String OP = "op";
private static final String METADATA_LABELS = "/metadata/labels";
private static final String METADATA_ANNOTATIONS = "/metadata/annotations";
public static final String METADATA_LABELS = "/metadata/labels";
public static final String METADATA_ANNOTATIONS = "/metadata/annotations";

private static final String PATH = "path";
private static final String[] EMPTY_ARRAY = {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public abstract class KubernetesDependentResource<R extends HasMetadata, P exten
private final boolean garbageCollected = this instanceof GarbageCollected;
private KubernetesDependentResourceConfig<R> kubernetesDependentResourceConfig;


@SuppressWarnings("unchecked")
public KubernetesDependentResource(Class<R> resourceType) {
super(resourceType);
Expand Down Expand Up @@ -128,16 +129,41 @@ protected R handleUpdate(R actual, R desired, P primary, Context<P> context) {

@SuppressWarnings("unused")
public R create(R target, P primary, Context<P> context) {
return prepare(target, primary, "Creating").create();
if (!context.getControllerConfiguration().getConfigurationService()
.ssaBasedCreateUpdateForDependentResources()) {
return prepare(target, primary, "Creating").create();
} else {
return prepare(target, primary, "Creating")
.fieldManager(context.getControllerConfiguration().fieldManager())
.forceConflicts()
.serverSideApply();
}
}

public R update(R actual, R target, P primary, Context<P> context) {
var updatedActual = processor.replaceSpecOnActual(actual, target, context);
return prepare(updatedActual, primary, "Updating").replace();
if (!context.getControllerConfiguration().getConfigurationService()
.ssaBasedCreateUpdateForDependentResources()) {
var updatedActual = processor.replaceSpecOnActual(actual, target, context);
return prepare(updatedActual, primary, "Updating").replace();
} else {
target.getMetadata().setResourceVersion(actual.getMetadata().getResourceVersion());
return prepare(target, primary, "Updating")
.fieldManager(context.getControllerConfiguration().fieldManager())
.forceConflicts().serverSideApply();
}
}

public Result<R> match(R actualResource, P primary, Context<P> context) {
return GenericKubernetesResourceMatcher.match(this, actualResource, primary, context, false);
if (!context.getControllerConfiguration().getConfigurationService()
.ssaBasedDefaultMatchingForDependentResources()) {
return GenericKubernetesResourceMatcher.match(this, actualResource, primary, context, false);
} else {
final var desired = desired(primary, context);
addReferenceHandlingMetadata(desired, primary);
var matches = SSABasedGenericKubernetesResourceMatcher.getInstance().matches(actualResource,
desired, context);
return Result.computed(matches, desired);
}
}

@SuppressWarnings("unused")
Expand All @@ -164,11 +190,7 @@ protected Resource<R> prepare(R desired, P primary, String actionName) {
desired.getClass(),
ResourceID.fromResource(desired));

if (addOwnerReference()) {
desired.addOwnerReference(primary);
} else if (useDefaultAnnotationsToIdentifyPrimary()) {
addDefaultSecondaryToPrimaryMapperAnnotations(desired, primary);
}
addReferenceHandlingMetadata(desired, primary);

if (desired instanceof Namespaced) {
return client.resource(desired).inNamespace(desired.getMetadata().getNamespace());
Expand All @@ -177,6 +199,14 @@ protected Resource<R> prepare(R desired, P primary, String actionName) {
}
}

protected void addReferenceHandlingMetadata(R desired, P primary) {
if (addOwnerReference()) {
desired.addOwnerReference(primary);
} else if (useDefaultAnnotationsToIdentifyPrimary()) {
addDefaultSecondaryToPrimaryMapperAnnotations(desired, primary);
}
}

@Override
@SuppressWarnings("unchecked")
protected InformerEventSource<R, P> createEventSource(EventSourceContext<P> context) {
Expand Down
Loading

0 comments on commit 2b71e23

Please sign in to comment.