Skip to content
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

feat: SSA based dependent resource matching and create/update #1928

Merged
merged 25 commits into from
Jun 15, 2023
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
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 @@ -85,6 +89,7 @@ protected ResolvedControllerConfiguration(Class<P> resourceClass, String name,
OnAddFilter<P> onAddFilter, OnUpdateFilter<P> onUpdateFilter, GenericFilter<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 @@ -99,13 +104,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 @@ -182,4 +188,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